El sistema de herramientas detrás de React

1. Descripción general de la
nube de etiquetas de la cadena de herramientas React:


Rollup    Prettier    Closure Compiler
Yarn workspace    [x]Haste    [x]Gulp/Grunt+Browserify
ES Module    [x]CommonJS Module
Flow    Jest    ES Lint    React DevTools
Error Code System    HUBOT(GitHub Bot)    npm

PS con [x] indica que se usó antes, pero recientemente (React 16) no se usó

La clasificación simple es la siguiente:


开发:ES Module, Flow, ES Lint, Prettier, Yarn workspace, HUBOT
构建:Rollup, Closure Compiler, Error Code System, React DevTools
测试:Jest, Prettier
发布:npm

Organice el código fuente de acuerdo con el mecanismo del módulo ES, complementado con la verificación de tipo y las herramientas de Lint / formateo, use Yarn para procesar las dependencias del módulo, HUBOT verifique PR; Construcción del compilador Rollup + Closure, use el mecanismo de Código de error para implementar el seguimiento de errores en el entorno de producción, el lado de DevTools ayuda en la verificación del paquete; Jest impulsa la prueba única y también confirma que el resultado de la compilación es lo suficientemente limpio al formatear el paquete; finalmente, el nuevo paquete se lanza a través de npm

Todo el proceso no es muy complicado, pero algunos detalles se consideran en profundidad, como el sistema de código de error, la envificación de doble seguro (distinción de entorno dev / prod) y las herramientas del proceso de lanzamiento.

2. Herramientas de desarrollo


CommonJS Module + Haste -> ES Module

Las versiones anteriores a React 15 se definen con módulos CommonJS, por ejemplo:


var ReactChildren = require('ReactChildren');
module.exports = React;

Hay varias razones para cambiar al módulo ES:

Ayuda a detectar temprano problemas de importación / exportación de módulos

El módulo CommonJS es fácil de requerir un método inexistente y el problema no se puede encontrar hasta que se informe de la llamada. El mecanismo del módulo estático del módulo ES requiere que la importación y la exportación deben coincidir por nombre; de ​​lo contrario, se informará un error al compilar y compilar

Ventajas del tamaño del paquete

ES Module puede hacer que el paquete sea más limpio mediante la agitación del árbol. La razón fundamental es que module.exports es una exportación a nivel de objeto, y la exportación admite una exportación a nivel atómico más detallada. Por otro lado, la introducción por nombre habilita herramientas como el rollup para aplanar los módulos, y la herramienta de compresión puede realizar una confusión de nombre de variable más violenta sobre esta base, reduciendo aún más el tamaño del paquete.

Solo el código fuente se cambia al módulo ES, y el caso de prueba único no se cambia, porque el módulo CommonJS es más compatible con algunas funciones de Jest (como resetModules) (incluso si cambia al módulo ES, aún necesita usar require en escenarios donde se requiere aislamiento del estado del módulo, por lo que El cambio tiene poca importancia)

En cuanto a Haste, es una herramienta de procesamiento de módulos personalizada por el equipo de React para resolver el problema de las rutas relativas largas, como:


// ref: react-15.5.4
var ReactCurrentOwner = require('ReactCurrentOwner');
var warning = require('warning');
var canDefineProperty = require('canDefineProperty');
var hasOwnProperty = Object.prototype.hasOwnProperty;
var REACT_ELEMENT_TYPE = require('ReactElementSymbol');

Las referencias de módulo bajo el mecanismo del módulo Haste no necesitan proporcionar una ruta relativa clara, pero se buscan automáticamente a través del nombre de módulo único a nivel de proyecto, por ejemplo:


// 声明
/**
 * @providesModule ReactClass
 */

// 引用
var ReactClass = require('ReactClass');

En la superficie, resuelve el problema de las referencias de rutas largas (y no resuelve el problema fundamental del anidamiento profundo de la estructura del proyecto). Existen varias desventajas típicas de usar un mecanismo de módulo no estándar:

Inconsistente con el estándar, enfrentará problemas de adaptación al acceder a las herramientas en la ecología estándar

El código fuente es difícil de leer y no es fácil comprender las dependencias del módulo.

React 16 elimina la mayor parte del mecanismo del módulo personalizado (hay una pequeña parte en ReactNative) y usa referencias de ruta relativa estándar de Node. El problema de las rutas largas se resuelve por completo refactorizando la estructura del proyecto y usa una estructura de directorio plana (bajo el mismo paquete) Las referencias de nivel 2 más profundas, los paquetes cruzados son referenciadas por la ruta absoluta de nivel superior después del procesamiento del hilo)

Flow + ES Lint
Flow es responsable de verificar los errores de tipo y detectar problemas potenciales de discrepancia de tipos lo antes posible, como:


export type ReactElement = {
  $$typeof: any,
  type: any,
  key: any,
  ref: any,
  props: any,
  _owner: any, // ReactInstance or ReactFiber

  // __DEV__
  _store: {
    validated: boolean,
  },
  _self: React$Element<any>,
  _shadowChildren: any,
  _source: Source,
};

Además de la declaración y verificación de tipo estático, la característica más importante de Flow es su soporte profundo para componentes React y JSX:


type Props = {
  foo: number,
};
type State = {
  bar: number,
};
class MyComponent extends React.Component<Props, State> {
  state = {
    bar: 42,
  };

  render() {
    return this.props.foo + this.state.bar;
  }
}

PD: Para obtener más información sobre la compatibilidad con React de Flow, consulte Aún mejor compatibilidad para React in Flow

También existe la "magia" de Flow de la verificación del tipo de exportación, que se utiliza para verificar si el tipo de exportación del módulo simulado es coherente con el módulo fuente:


type Check<_X, Y: _X, X: Y = _X> = null;
(null: Check<FeatureFlagsShimType, FeatureFlagsType>);
ES Lint负责检查语法错误及约定编码风格错误,例如:

rules: {
  'no-unused-expressions': ERROR,
  'no-unused-vars': [ERROR, {args: 'none'}],
  // React & JSX
  // Our transforms set this automatically
  'react/jsx-boolean-value': [ERROR, 'always'],
  'react/jsx-no-undef': ERROR,
}

Prettier
Prettier se utiliza para formatear el código automáticamente, con varios propósitos:

Formatee el código antiguo en un estilo unificado

Formatee la parte cambiada antes de enviarla

Coopere con la integración continua para garantizar que el estilo del código de relaciones públicas sea completamente coherente (de lo contrario, la compilación fallará y se generarán las piezas con estilos diferentes)

Integrar en IDE, formatear una vez al día

Dar formato a los resultados de la compilación, por un lado, mejora la legibilidad del paquete de desarrollo y también ayuda a encontrar código redundante en el paquete de producción.

Un estilo de código unificado es, por supuesto, propicio para la colaboración. Además, para proyectos de código abierto, a menudo se enfrentan a diferentes estilos de relaciones públicas. Con respecto a las comprobaciones de formato estrictas como enlace obligatorio en la integración continua, puede resolver por completo el problema de las diferencias de estilo de código y ayudar a simplificar el código abierto trabajos

PD El formateo unificado forzado de todo el proyecto parece un poco extremo, es un intento audaz, pero se dice que el efecto no es malo:


Our experience with Prettier has been fantastic, and we recommend it to any team that writes JavaScript.

Espacio de trabajo de
Yarn La función de espacio de trabajo de Yarn se utiliza para resolver la dependencia del paquete de monorepo (similar a lerna bootstrap) y "engañar" al mecanismo del módulo Node estableciendo un enlace suave en node_modules


Yarn Workspaces is a feature that allows users to install dependencies from multiple package.json files in subfolders of a single root package.json file, all in one go.

Configure los espacios de trabajo de Yarn a través de package.json / workspaces:


// ref: react-16.2.0/package.json
"workspaces": [
  "packages/*"
],

Nota: El procesamiento real de Yarn es similar al de Lerna, que se implementa a través de enlaces suaves, pero es más razonable proporcionar soporte de paquetes monorepo a nivel del administrador de paquetes. Por razones específicas, consulte Espacios de trabajo en Yarn | Blog de Yarn

Luego, después de la instalación del hilo, puede hacer referencia a todos los paquetes:


import {enableUserTimingAPI} from 'shared/ReactFeatureFlags';
import getComponentName from 'shared/getComponentName';
import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';

PS Además, Yarn y Lerna se pueden combinar sin problemas. La parte de procesamiento de dependencia se entrega a Yarn a través de la opción useWorkspaces. Para obtener más información, consulte Integración con Lerna

HUBOT
HUBOT se refiere al robot GitHub, generalmente utilizado para:

Conéctese con integración continua, las relaciones públicas desencadenan la compilación / verificación

Administrar problemas y desactivar las publicaciones de discusión inactivas

Principalmente, haga algunas cosas automatizadas en torno a las relaciones públicas y problemas, como los robots del plan del equipo React (aún no hecho) para responder al impacto de las relaciones públicas en el tamaño del paquete, con el fin de impulsar la optimización continua del tamaño del paquete.

Actualmente, los cambios de tamaño del paquete se envían a un archivo para cada compilación, y Git (enviado) realiza un seguimiento de los cambios, por ejemplo:


// ref: react-16.2.0/scripts/rollup/results.json
{
  "bundleSizes": {
    "react.development.js (UMD_DEV)": {
      "size": 54742,
      "gzip": 14879
    },
    "react.production.min.js (UMD_PROD)": {
      "size": 6617,
      "gzip": 2819
    }
  }
}

Las deficiencias se pueden imaginar. Este archivo json a menudo entra en conflicto. O necesita gastar energía para fusionar conflictos, o no se molesta en enviar este archivo problemático generado automáticamente, lo que hace que la versión se retrase, por lo que planeamos solucionar este problema a través del GitHub Bot.

Tres. Antes del
formulario de paquete de herramientas de construcción
, se proporcionaron dos formularios de paquete:

Archivo único UMD, utilizado como dependencia externa

Archivo masivo CJS, utilizado para admitir el paquete de autoconstrucción (usando React como dependencia del código fuente)

Hay algunos problemas:

La versión autoconstruida es inconsistente: los paquetes construidos por diferentes entornos / configuraciones de construcción son diferentes

Hay espacio para la optimización del rendimiento del paquete: no es apropiado crear una biblioteca de clases empaquetando la aplicación, y hay espacio para mejorar el rendimiento.

No es propicio para intentos de optimización experimentales: los métodos de optimización como el empaquetado y la compresión no se pueden aplicar a módulos de archivos masivos

React 16 ajustó la forma del paquete:

Los archivos masivos CJS ya no se proporcionan, lo que obtiene de npm es el paquete integrado, unificado y optimizado

Proporcione un solo archivo UMD y un solo archivo CJS, respectivamente para el entorno web y el entorno de nodo (***)

En una postura de biblioteca de clases inseparable, todos los enlaces de optimización se toman para deshacerse de las restricciones traídas por el formulario de paquete.

Gulp / Grunt + Browserify -> El

sistema de compilación anterior de Rollup se basa en un conjunto de herramientas de Gulp / Grunt + Browserify. Más tarde Limitado a herramientas en términos de expansión, como:

Rendimiento deficiente en el entorno Node: el acceso frecuente a process.env.NODE_ENV ralentiza el rendimiento ***, pero no hay forma de resolverlo desde la perspectiva de la biblioteca de clases, porque Uglify se basa en esto para eliminar el código inútil

Por lo tanto, las mejores prácticas de rendimiento de React *** generalmente tienen un "reempaquetar React, eliminar process.env.NODE_ENV al compilar" (por supuesto, React 16 ya no necesita hacer esto, la razón es el cambio en la forma del paquete mencionado anteriormente)

Se descartan las herramientas de compilación personalizadas demasiado complicadas y se utiliza un resumen más adecuado:


It solves one problem well: how to combine multiple modules into a flat file with minimal junk code in between.

PD Ya sea Haste -> Módulo ES o Gulp / Grunt + Browserify -> Rollup se cambia de una solución personalizada no estándar a una solución abierta estándar, debemos aprender del aspecto de "frotar las manos", por qué las cosas estándar de la industria No se aplica en nuestro escenario, ¿tenemos que hacerlo nosotros mismos?

La
construcción de módulos simulados puede enfrentar escenarios de dependencia dinámica: diferentes paquetes dependen de módulos con funciones similares pero diferentes implementaciones. Por ejemplo, el mecanismo de notificación de errores de ReactNative es mostrar un cuadro rojo y el entorno web se envía a la consola

Hay dos soluciones generales:

Dependencia dinámica (inyección) en tiempo de ejecución: coloque ambos en el paquete y elija según la configuración o el entorno en tiempo de ejecución

Maneje las dependencias durante la compilación: cree algunas copias más, los diferentes paquetes contienen sus propios módulos dependientes requeridos

Obviamente, el proceso es más limpio durante la construcción, es decir, el módulo simulado. No es necesario que te preocupes por esta diferencia en el desarrollo. Las dependencias específicas se seleccionan automáticamente según el entorno durante la construcción. Esto se logra escribiendo a mano complementos simples de acumulación: configuración de dependencia dinámica + reemplazo de dependencia durante la compilación

Closure Compiler
google / closings-compiler es un minificador muy potente, con 3 modos de optimización (compilation_level):

WHITESPACE_ONLY: Elimina comentarios, puntuación adicional y caracteres en blanco, lógicamente funcionalmente equivalente al código fuente

SIMPLE_OPTIMIZATIONS: El modo predeterminado. Sobre la base de WHITESPACE_ONLY, los nombres de las variables (variables locales y parámetros de función) se acortan aún más, y las funciones lógicas son básicamente equivalentes. Casos especiales (como eval ('localVar')) acceden a las variables locales por nombre y analizan fn.toString ( ))excepto

ADVANCED_OPTIMIZATIONS: sobre la base de SIMPLE_OPTIMIZATIONS, realice un cambio de nombre más potente (nombres de variables globales, nombres de funciones y atributos), elimine el código inútil (inalcanzable, innecesario), llamadas de método en línea y constantes (si es rentable, llame a la función Reemplace el contenido del cuerpo de la función y reemplace la constante con su valor)

PS Para obtener más información sobre compilation_level, consulte Niveles de compilación del compilador de cierre

El modo AVANZADO es demasiado potente:


// 输入
function hello(name) {
  alert('Hello, ' + name);
}
hello('New user');

// 输出
alert("Hello, New user");

PS se puede probar en línea en Closure Compiler Service

La migración y el cambio tienen ciertos riesgos, por lo que React todavía usa el modo SIMPLE, pero puede haber planes para abrir el modo AVANZADO en el futuro para hacer un uso completo de Closure Compiler para optimizar el tamaño del paquete.


Error Code System
In order to make debugging in production easier, we’re introducing an Error Code System in 15.2.0. We developed a gulp script that collects all of our invariant error messages and folds them to a JSON file, and at build-time Babel uses the JSON to rewrite our invariant calls in production to reference the corresponding error IDs.

En resumen, la información de error detallada se reemplaza con el código de error correspondiente en el paquete prod. El entorno de producción detecta el error de tiempo de ejecución y arroja el código de error y la información de contexto, y luego lo envía al servicio de conversión de código de error para restaurar el mensaje de error completo. Esto no solo garantiza que el paquete de productos sea lo más limpio posible, sino que también conserva las mismas capacidades detalladas de informes de errores que el entorno de desarrollo.

Por ejemplo, el error ilegal de React Element en el entorno de producción:


Minified React error #109; visit https://reactjs.org/docs/error-decoder.html?invariant=109&args[]=Foo for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

Técnica muy interesante, realmente dediqué mucho a mejorar la experiencia de desarrollo

La
denominada envificación consiste en construir por entorno, por ejemplo:


// ref: react-16.2.0/build/packages/react/index.js
if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

Método de uso común, reemplace process.env.NODE_ENV con la constante de cadena correspondiente al entorno de destino durante la construcción, y el código redundante se eliminará en el proceso de construcción posterior (herramienta de empaquetado / herramienta de compresión)

Además del archivo de entrada del paquete, también se hace el mismo juicio en el interior como un seguro doble:


// ref: react-16.2.0/build/packages/react/cjs/react.development.js
if (process.env.NODE_ENV !== "production") {
  (function() {
    module.exports = react;
  })();
}

Además, me preocupa que los desarrolladores puedan hacer un mal uso del paquete de desarrollo para conectarse, así que también agregué un recordatorio a React DevTools:


This page is using the development build of React. 

Supongo que te gusta

Origin blog.51cto.com/15080030/2592712
Recomendado
Clasificación