Le système d'outillage derrière React

1. Présentation du
nuage de balises de la chaîne d'outils 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 avec [x] indique qu'il a été utilisé auparavant, mais récemment (React 16) n'est pas utilisé

La classification simple est la suivante:


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

Organisez le code source en fonction du mécanisme du module ES, complété par la vérification de type et les outils Lint / formatage, utilisez Yarn pour traiter les dépendances de module, HUBOT check PR; Rollup + Closure Compiler construction, utilisez le mécanisme de code d'erreur pour implémenter le suivi des erreurs dans l'environnement de production, le côté DevTools aide à la vérification des lots; Jest pilote le test unique et confirme également que le résultat de la construction est suffisamment propre en formatant le bundle; enfin, le nouveau package est publié via npm

L'ensemble du processus n'est pas très compliqué, mais certains détails sont examinés en profondeur, tels que le système de code d'erreur, la double vision de l'assurance (distinction de l'environnement de développement / production) et l'outillage du processus de publication.

2. Outils de développement


CommonJS Module + Haste -> ES Module

Les versions antérieures à React 15 sont définies avec des modules CommonJS, par exemple:


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

Il existe plusieurs raisons pour passer au module ES:

Aide à détecter rapidement les problèmes d'importation / exportation de modules

CommonJS Module est facile d'exiger une méthode inexistante, et le problème ne peut être trouvé tant que l'appel n'est pas signalé. Le mécanisme du module statique du module ES nécessite que l'importation et l'exportation doivent correspondre par nom, sinon une erreur sera signalée lors de la compilation et de la construction

Avantages de la taille du paquet

ES Module peut rendre le bundle plus propre en secouant l'arborescence. La raison fondamentale est que module.exports est une exportation au niveau de l'objet, et l'exportation prend en charge une exportation au niveau atomique plus fine. D'autre part, l'introduction par le nom permet à des outils tels que le rollup d'aplatir les modules, et l'outil de compression peut effectuer une confusion de nom de variable plus violente sur cette base, réduisant encore la taille du bundle

Seul le code source est basculé vers le module ES, et le cas de test unique n'est pas commuté, car CommonJS Module est plus convivial pour certaines fonctionnalités de Jest (telles que resetModules) (même si vous passez au module ES, vous devez toujours utiliser require dans les scénarios où l'isolation de l'état du module est requise, donc La commutation est de peu d'importance)

Quant à Haste, il s'agit d'un outil de traitement de modules personnalisé par l'équipe React pour résoudre le problème des longs chemins relatifs, tels que:


// 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');

Les références de module sous le mécanisme du module Haste n'ont pas besoin de donner un chemin relatif clair, mais sont automatiquement recherchées via le nom de module unique au niveau du projet, par exemple:


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

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

En surface, cela résout le problème des références de chemin long (et ne résout pas le problème fondamental de l'imbrication profonde de la structure du projet). Il existe plusieurs inconvénients typiques à l'utilisation d'un mécanisme de module non standard:

En contradiction avec la norme, vous rencontrerez des problèmes d'adaptation lors de l'accès aux outils dans l'écologie standard

Le code source est difficile à lire, et il n'est pas facile de comprendre les dépendances du module

React 16 supprime la plupart du mécanisme de module personnalisé (il y a une petite partie dans ReactNative) et utilise les références de chemin relatif standard de Node. Le problème des chemins longs est complètement résolu en refactorisant la structure du projet et utilise une structure de répertoire plate (sous le même package) Les références de niveau 2 les plus profondes, les packages croisés sont référencés par le chemin absolu de niveau supérieur après le traitement de Yarn)

Flow + ES Lint
Flow est chargé de vérifier les erreurs de type et de détecter le plus tôt possible les problèmes potentiels de non-concordance de type, tels que:


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,
};

En plus de la déclaration et de la vérification de type statique, la plus grande fonctionnalité de Flow est sa prise en charge approfondie des composants React et 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;
  }
}

PS Pour plus d’informations sur le support React de Flow, veuillez consulter Even Better Support for React in Flow

Il y a aussi la "magie" du Flow de vérification du type d'exportation, qui permet de vérifier si le type d'exportation du module fictif est cohérent avec le module source:


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 est utilisé pour formater le code automatiquement, à plusieurs fins:

Formater l'ancien code dans un style unifié

Formater la pièce modifiée avant de la soumettre

Coopérez avec une intégration continue pour vous assurer que le style de code PR est complètement cohérent (sinon la construction échouera et les pièces avec des styles différents seront sorties)

Intégrer dans l'IDE, formater une fois par jour

Le formatage des résultats de compilation, d'une part, améliore la lisibilité du bundle de développement et aide également à trouver du code redondant dans le bundle prod

Un style de code unifié est bien sûr propice à la collaboration. De plus, pour les projets open source, vous êtes souvent confronté à différents styles de relations publiques. Les contrôles de mise en forme stricts en tant que lien obligatoire dans l'intégration continue peuvent résoudre complètement le problème des différences de style de code et contribuer à simplifier l'open source travaux

PS Le formatage unifié obligatoire de l'ensemble du projet semble un peu extrême, c'est une tentative audacieuse, mais on dit que l'effet n'est pas mauvais:


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

Espace de travail de
Yarn La fonction d'espace de travail de Yarn est utilisée pour résoudre la dépendance de package de monorepo (similaire à lerna bootstrap), et "tricher" le mécanisme du module Node en établissant un lien souple sous 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.

Configurez les espaces de travail Yarn via package.json / workspaces:


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

Remarque: Le traitement réel de Yarn est similaire à celui de Lerna, qui est implémenté via des liens souples, mais il est plus raisonnable de fournir une prise en charge des packages monorepo au niveau du gestionnaire de packages. Pour des raisons spécifiques, voir Espaces de travail dans Yarn | Blog Yarn

Ensuite, après l'installation de yarn, vous pouvez vous y référer avec plaisir à travers les packages:


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

PS De plus, Yarn et Lerna peuvent être parfaitement combinés. La partie de traitement des dépendances est transférée à Yarn via l'option useWorkspaces. Pour plus de détails, voir Intégration avec Lerna

HUBOT
HUBOT fait référence au robot GitHub, généralement utilisé pour:

Connectez-vous avec une intégration continue, la création / vérification des déclencheurs PR

Gérer les problèmes et désactiver les messages de discussion inactifs

Faites principalement des choses automatisées autour des relations publiques et des problèmes, telles que l'équipe React planifie (pas encore fait) les robots pour répondre à l'impact des relations publiques sur la taille du bundle, afin de pousser à l'optimisation continue de la taille du bundle

Actuellement, les changements de taille de bundle sont générés dans un fichier pour chaque build, et les changements sont suivis par Git (soumis), par exemple:


// 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
    }
  }
}

Les lacunes peuvent être imaginées. Ce fichier json est souvent en conflit. Soit vous devez gaspiller de l'énergie pour fusionner les conflits, soit vous ne vous souciez pas de soumettre ce fichier gênant généré automatiquement, entraînant un décalage de la version, nous prévoyons donc de résoudre ce problème via GitHub Bot.

Trois. Avant le
formulaire de regroupement d' outils de création
, deux formulaires de regroupement étaient fournis:

Fichier unique UMD, utilisé comme dépendance externe

Fichier en bloc CJS, utilisé pour prendre en charge le bundle d'auto-construction (en utilisant React comme dépendance du code source)

Il y a quelques problèmes:

La version auto-construite est incohérente: les bundles construits par différents environnements / configurations de build sont différents

Il y a place pour l'optimisation des performances du bundle: il n'est pas approprié de créer une bibliothèque de classes en empaquetant App, et il y a place à l'amélioration des performances

Non propice aux tentatives d'optimisation expérimentale: les méthodes d'optimisation telles que l'empaquetage et la compression ne peuvent pas être appliquées aux modules de fichiers en masse

React 16 a ajusté la forme du bundle:

Les fichiers en vrac CJS ne sont plus fournis, ce que vous obtenez de npm est le bundle construit, unifié et optimisé

Fournir un fichier unique UMD et un fichier unique CJS, respectivement pour l'environnement Web et l'environnement Node (***)

Dans une posture de bibliothèque de classes inséparable, tous les liens d'optimisation sont repris pour se débarrasser des restrictions apportées par le formulaire de bundle.

Gulp / Grunt + Browserify -> Le

système de construction précédent de Rollup est basé sur un ensemble d'outils de Gulp / Grunt + Browserify. Plus tard Limité aux outils en termes d'expansion, tels que:

Mauvaises performances dans l'environnement Node: un accès fréquent process.env.NODE_ENV ralentit les performances ***, mais il n'y a aucun moyen de le résoudre du point de vue de la bibliothèque de classes, car Uglify s'appuie sur cela pour supprimer le code inutile

Par conséquent, les meilleures pratiques de performance de React *** ont généralement un "reconditionner React, supprimer process.env.NODE_ENV lors de la construction" (bien sûr, React 16 n'a plus besoin de le faire, la raison en est le changement dans le formulaire de bundle mentionné ci-dessus)

Les outils de construction personnalisés trop compliqués sont supprimés et un Rollup plus approprié est utilisé:


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

PS Que Haste -> Module ES ou Gulp / Grunt + Browserify -> Rollup passe d'une solution personnalisée non standard à une solution ouverte standard, nous devrions apprendre de l'aspect «frottement à la main», pourquoi les choses standard de l'industrie Cela ne s'applique pas à notre scénario, devons-nous le faire nous-mêmes?

La
construction de modules fictifs peut être confrontée à des scénarios de dépendance dynamique: différents ensembles reposent sur des modules dotés de fonctions similaires mais d'implémentations différentes. Par exemple, le mécanisme de notification d'erreur de ReactNative consiste à afficher une boîte rouge et l'environnement Web est généré dans la console.

Il existe deux solutions générales:

Dépendance dynamique (injection) au moment de l'exécution: mettez les deux dans le bundle et choisissez en fonction de la configuration ou de l'environnement au moment de l'exécution

Gérez les dépendances lors de la construction: créez quelques copies supplémentaires, différents bundles contiennent leurs propres modules dépendants requis

De toute évidence, le processus est plus propre lors de la construction, c'est-à-dire un module simulé. Vous n'avez pas besoin de vous soucier de cette différence de développement. Les dépendances spécifiques sont automatiquement sélectionnées en fonction de l'environnement lors de la construction. Ceci est réalisé en écrivant à la main de simples plug-ins Rollup: configuration de dépendance dynamique + remplacement de dépendance pendant la construction

Closure Compiler
google / closing-compiler est un minificateur très puissant, avec 3 modes d'optimisation (compilation_level):

WHITESPACE_ONLY: Supprimez les commentaires, la ponctuation supplémentaire et les caractères vides, logiquement équivalents au code source

SIMPLE_OPTIMIZATIONS: Le mode par défaut. Sur la base de WHITESPACE_ONLY, les noms des variables (variables locales et paramètres de fonction) sont encore raccourcis et les fonctions logiques sont fondamentalement équivalentes. Des cas particuliers (comme eval ('localVar')) accèdent aux variables locales par leur nom et analysent fn.toString ( ))sauf

ADVANCED_OPTIMIZATIONS: Sur la base de SIMPLE_OPTIMIZATIONS, effectuez un changement de nom plus puissant (noms de variables globales, noms de fonctions et attributs), supprimez le code inutile (inaccessible, inutile), les appels de méthode en ligne et les constantes (si rentable, appelez la fonction Remplacez le contenu du corps de la fonction et remplacez la constante par sa valeur)

PS Pour plus d'informations sur compilation_level, consultez Closure Compiler Compilation Levels

Le mode ADVANCED est trop puissant:


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

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

PS peut être essayé en ligne sur Closure Compiler Service

La migration et la commutation comportent certains risques, donc React utilise toujours le mode SIMPLE, mais il peut être envisagé d'ouvrir le mode ADVANCED à l'avenir pour utiliser pleinement Closure Compiler pour optimiser la taille du bundle


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 bref, les informations d'erreur détaillées sont remplacées par le code d'erreur correspondant dans le bundle de produits. L'environnement de production détecte l'erreur d'exécution et renvoie le code d'erreur et les informations de contexte, puis les envoie au service de conversion de code d'erreur pour restaurer le message d'erreur complet. Cela garantit non seulement que l'ensemble de produits est aussi propre que possible, mais conserve également les mêmes capacités de rapport d'erreur détaillées que l'environnement de développement

Par exemple, l'erreur d'élément React illégale dans l'environnement de production:


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.

Technique très intéressante, a vraiment beaucoup réfléchi à l'amélioration de l'expérience de développement

La
soi-disant envification consiste à construire par environnement, par exemple:


// 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éthode couramment utilisée, remplacez process.env.NODE_ENV par la constante de chaîne correspondant à l'environnement cible pendant la construction, et le code redondant sera supprimé dans le processus de construction suivant (outil de packaging / outil de compression)

En plus du dossier de saisie du package, le même jugement est également porté à l'intérieur comme une double assurance:


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

De plus, je crains que les développeurs n'utilisent mal le bundle de développement pour aller en ligne, j'ai donc également ajouté un rappel à React DevTools:


This page is using the development build of React. 

Je suppose que tu aimes

Origine blog.51cto.com/15080030/2592712
conseillé
Classement