Das Werkzeugsystem hinter React

1. Übersicht über die
React Toolchain-Tag-Cloud:


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 mit [x] zeigt an, dass es zuvor verwendet wurde, aber kürzlich (Reaktion 16) nicht verwendet wird

Die einfache Klassifizierung lautet wie folgt:


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

Organisieren Sie den Quellcode gemäß dem ES-Modulmechanismus, ergänzt durch Typprüfungs- und Flusen- / Formatierungswerkzeuge, verwenden Sie Garn, um Modulabhängigkeiten zu verarbeiten, HUBOT-Prüfung PR; Rollup + Closure Compiler-Konstruktion, verwenden Sie den Fehlercode-Mechanismus, um die Fehlerverfolgung in der Produktionsumgebung zu implementieren, DevTools-Seite hilft bei der Bündelprüfung; Jest führt den Einzeltest durch und bestätigt durch Formatieren des Bundles, dass das Build-Ergebnis sauber genug ist. Schließlich wird das neue Paket über npm veröffentlicht

Der gesamte Prozess ist nicht sehr kompliziert, aber einige Details werden eingehend betrachtet, wie z. B. das Fehlercodesystem, die doppelte Versicherungsprüfung (Unterscheidung zwischen Entwicklungs- und Produktumgebung) und das Tooling des Freigabeprozesses

2. Entwicklungswerkzeuge


CommonJS Module + Haste -> ES Module

Versionen vor React 15 werden mit CommonJS-Modulen definiert, zum Beispiel:


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

Es gibt mehrere Gründe für den Wechsel zum ES-Modul:

Hilft, Probleme beim Import / Export von Modulen frühzeitig zu erkennen

Für das CommonJS-Modul ist leicht eine nicht vorhandene Methode erforderlich, und das Problem kann erst gefunden werden, wenn der Anruf gemeldet wird. Der statische Modulmechanismus des ES-Moduls erfordert, dass Import und Export nach Namen übereinstimmen müssen. Andernfalls wird beim Kompilieren und Erstellen ein Fehler gemeldet

Vorteile der Bündelgröße

Das ES-Modul kann das Bundle durch Baumschütteln sauberer machen. Der Hauptgrund dafür ist, dass module.exports ein Export auf Objektebene ist und der Export einen feinkörnigeren Export auf atomarer Ebene unterstützt. Auf der anderen Seite ermöglicht die Einführung nach Namen Tools wie Rollup, um die Module zu reduzieren, und das Komprimierungswerkzeug kann auf dieser Basis eine heftigere Verwechslung von Variablennamen durchführen, wodurch die Bündelgröße weiter reduziert wird

Nur der Quellcode wird auf das ES-Modul umgeschaltet, und der einzelne Testfall wird nicht umgeschaltet, da das CommonJS-Modul für einige Funktionen von Jest (z. B. resetModules) benutzerfreundlicher ist (selbst wenn Sie zum ES-Modul wechseln, müssen Sie in Szenarien, in denen eine Isolation des Modulstatus erforderlich ist, die Anforderung erforderlich verwenden Das Umschalten ist von geringer Bedeutung.

Haste ist ein Modulverarbeitungswerkzeug, das vom React-Team angepasst wurde, um das Problem langer relativer Pfade zu lösen, z.


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

Modulreferenzen unter dem Haste-Modulmechanismus müssen keinen eindeutigen relativen Pfad angeben, sondern werden automatisch über den eindeutigen Modulnamen auf Projektebene durchsucht, z. B.:


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

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

An der Oberfläche löst es das Problem der langen Pfadreferenzen (und löst nicht das grundlegende Problem der tiefen Verschachtelung der Projektstruktur). Die Verwendung eines nicht standardmäßigen Modulmechanismus weist mehrere typische Nachteile auf:

In Übereinstimmung mit dem Standard treten Anpassungsprobleme beim Zugriff auf die Tools in der Standardökologie auf

Der Quellcode ist schwer zu lesen und die Modulabhängigkeiten sind nicht leicht zu verstehen

In React 16 wird der größte Teil des benutzerdefinierten Modulmechanismus entfernt (es gibt einen kleinen Teil in ReactNative) und es werden relative Standardpfadreferenzen für Knoten verwendet. Das Problem langer Pfade wird durch Umgestaltung der Projektstruktur vollständig gelöst und es wird eine flache Verzeichnisstruktur verwendet (unter demselben Paket). Die tiefsten Level 2-Referenzen (Cross-Packages) werden nach der Garnverarbeitung durch den absoluten Pfad der obersten Ebene referenziert.

Flow + ES Lint
Flow ist dafür verantwortlich, Typfehler zu überprüfen und mögliche Probleme mit Typfehlanpassungen so früh wie möglich zu erkennen, z.


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

Neben der statischen Typdeklaration und -prüfung ist das größte Merkmal von Flow die umfassende Unterstützung für React-Komponenten und 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 Weitere Informationen zur React-Unterstützung von Flow finden Sie unter Noch bessere Unterstützung für React in Flow

Es gibt auch die Flow "Magie" der Exporttypprüfung, mit der überprüft wird, ob der Exporttyp des Scheinmoduls mit dem Quellmodul übereinstimmt:


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 wird verwendet, um Code für verschiedene Zwecke automatisch zu formatieren:

Formatieren Sie den alten Code in einen einheitlichen Stil

Formatieren Sie das geänderte Teil vor dem Senden

Arbeiten Sie mit der kontinuierlichen Integration zusammen, um sicherzustellen, dass der PR-Codestil vollständig konsistent ist (andernfalls schlägt der Build fehl und die Teile mit unterschiedlichen Stilen werden ausgegeben).

In IDE integrieren, einmal täglich formatieren

Das Formatieren der Build-Ergebnisse verbessert einerseits die Lesbarkeit des Dev-Bundles und hilft auch, redundanten Code im Prod-Bundle zu finden

Ein einheitlicher Codestil ist natürlich förderlich für die Zusammenarbeit. Darüber hinaus treten bei Open Source-Projekten häufig unterschiedliche PR-Stile auf. Wenn Sie strenge Formatierungsprüfungen als obligatorische Verknüpfung bei der kontinuierlichen Integration betrachten, können Sie das Problem der Unterschiede im Codestil vollständig lösen und Open Source vereinfachen Arbeitsplätze

PS Die obligatorische einheitliche Formatierung des gesamten Projekts scheint etwas extrem zu sein, es ist ein mutiger Versuch, aber es wird gesagt, dass der Effekt nicht schlecht ist:


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

Yarn Arbeitsplatz
Yarn Arbeitsbereich Funktion wird verwendet , um die Paketabhängigkeit von monorepo (ähnlich lerna Bootstrap) zu lösen, und „betrügen“ , um den Knoten Modulmechanismus durch einen Softlink unter node_modules Gründung


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.

Konfigurieren Sie Garnarbeitsbereiche über package.json / workspaces:


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

Hinweis: Die tatsächliche Verarbeitung von Garn ähnelt der von Lerna, die über Softlinks implementiert wird. Es ist jedoch sinnvoller, auf Paketmanagerebene Monorepo-Paketunterstützung bereitzustellen. Weitere Informationen finden Sie unter Arbeitsbereiche im Garn | Garn-Blog

Nach der Installation des Garns können Sie sich dann gerne paketübergreifend darauf beziehen:


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

PS Darüber hinaus können Garn und Lerna nahtlos kombiniert werden. Der Teil der Abhängigkeitsverarbeitung wird über die Option useWorkspaces an Garn übergeben. Weitere Informationen finden Sie unter Integrieren in Lerna

HUBOT
HUBOT bezieht sich auf den GitHub-Roboter, der normalerweise verwendet wird, um:

Verbinden Sie sich mit kontinuierlicher Integration, PR löst Build / Check aus

Probleme verwalten und inaktive Diskussionsbeiträge deaktivieren

Führen Sie hauptsächlich einige automatisierte Aktionen rund um PR und Issue durch, z. B. den (noch nicht abgeschlossenen) React-Teamplan, um auf die Auswirkungen von PR auf die Bundle-Größe zu reagieren und eine kontinuierliche Optimierung der Bundle-Größe zu fordern

Derzeit werden die Änderungen der Bundle-Größe für jeden Build in eine Datei ausgegeben, und die Änderungen werden von Git (übermittelt) verfolgt, zum Beispiel:


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

Man kann sich die Mängel vorstellen. Diese JSON-Datei ist häufig in Konflikt geraten. Entweder müssen Sie Energie verschwenden, um Konflikte zusammenzuführen, oder Sie müssen diese automatisch generierte problematische Datei nicht senden, wodurch die Version verzögert wird. Daher planen wir, diese Probleme über GitHub Bot zu beheben.

3. Vor dem Build-Tool-
Bundle-Formular
wurden zwei Bundle-Formulare bereitgestellt:

UMD-Einzeldatei, die als externe Abhängigkeit verwendet wird

CJS-Massendatei zur Unterstützung des selbst erstellten Bundles (unter Verwendung von React als Quellcode-Abhängigkeit)

Es gibt ein paar Probleme:

Die selbst erstellte Version ist inkonsistent: Die von verschiedenen Build-Umgebungen / Konfigurationen erstellten Bundles sind unterschiedlich

Es gibt Raum für die Optimierung der Bundle-Leistung: Es ist nicht angebracht, eine Klassenbibliothek durch Packen der App zu erstellen, und es gibt Raum für Leistungsverbesserungen

Experimentellen Optimierungsversuchen nicht förderlich: Optimierungsmethoden wie Packen und Komprimieren können nicht auf Bulk-Dateimodule angewendet werden

Reaktion 16 passte die Bündelform an:

CJS-Massendateien werden nicht mehr bereitgestellt. Von npm erhalten Sie das erstellte, einheitliche und optimierte Bundle

Bereitstellung einer UMD-Einzeldatei bzw. einer CJS-Einzeldatei für die Webumgebung und die Knotenumgebung (***)

In einer untrennbaren Klassenbibliothekshaltung werden alle Optimierungslinks verwendet, um die durch das Bundle-Formular verursachten Einschränkungen zu

beseitigen
. Gulp / Grunt + Browserify -> Das
vorherige Build-System von Rollup basiert auf einer Reihe von Tools von Gulp / Grunt + Browserify. Später Beschränkt auf Tools im Hinblick auf die Erweiterung, wie z.

Schlechte Leistung in der Knotenumgebung: Der häufige Zugriff auf process.env.NODE_ENV verlangsamt die *** Leistung, kann jedoch aus Sicht der Klassenbibliothek nicht gelöst werden, da Uglify darauf angewiesen ist, um nutzlosen Code zu entfernen

Daher haben die Best Practices für die React *** -Leistung im Allgemeinen das "Umpacken von React, remove process.env.NODE_ENV beim Erstellen" (React 16 muss dies natürlich nicht mehr tun, der Grund ist die Änderung der oben genannten Bundle-Form).

Überkomplizierte benutzerdefinierte Build-Tools werden verworfen und ein geeigneteres Rollup wird verwendet:


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

PS Ob Eile -> ES-Modul oder Gulp / Grunt + Browserify -> Rollup von einer nicht standardmäßigen kundenspezifischen Lösung auf eine offene Standardlösung umgestellt wird, wir sollten aus dem Aspekt "Handreiben" lernen, warum die branchenüblichen Dinge Gilt in unserem Szenario nicht, müssen wir es selbst machen?

Mock
Modulkonstruktion kann dynamische Abhängigkeits Szenarien stellen: verschiedene Bündel verlassen sich auf Module mit ähnlichen Funktionen , aber unterschiedlichen Implementierungen Zum Beispiel kann der Fehlerbenachrichtigungsmechanismus der ReactNative ist ein rotes Feld anzuzeigen, und die Web - Umgebung ist auf die Konsole.

Es gibt zwei allgemeine Lösungen:

Dynamische Abhängigkeit (Injection) zur Laufzeit: Fügen Sie beide in das Bundle ein und wählen Sie zur Laufzeit je nach Konfiguration oder Umgebung aus

Behandeln Sie Abhängigkeiten während des Builds: Erstellen Sie einige weitere Kopien, verschiedene Bundles enthalten ihre eigenen erforderlichen abhängigen Module

Offensichtlich ist der Prozess beim Erstellen sauberer, dh beim Erstellen eines Mock-Moduls. Sie müssen sich nicht um diesen Unterschied in der Entwicklung kümmern. Spezifische Abhängigkeiten werden automatisch je nach Umgebung während der Erstellung ausgewählt. Dies wird durch die einfache Handschrift einfacher Rollup-Plug-Ins erreicht: dynamische Abhängigkeitskonfiguration + Ersetzen von Abhängigkeiten während der Erstellung

Closure Compiler
Google / Closure-Compiler ist ein sehr leistungsfähiger Minifier mit 3 Optimierungsmodi (compilation_level):

WHITESPACE_ONLY: Entfernen Sie Kommentare, zusätzliche Satzzeichen und Leerzeichen, die logisch funktional dem Quellcode entsprechen

SIMPLE_OPTIMIZATIONS: Der Standardmodus. Auf der Grundlage von WHITESPACE_ONLY werden die Variablennamen (lokale Variablen und Funktionsparameter) weiter verkürzt, und die Logikfunktionen sind grundsätzlich äquivalent. Sonderfälle (wie eval ('localVar')) greifen auf lokale Variablen durch Namen zu und analysieren fn.toString ( ))außer

ADVANCED_OPTIMIZATIONS: Führen Sie auf der Grundlage von SIMPLE_OPTIMIZATIONS ein leistungsfähigeres Umbenennen (globale Variablennamen, Funktionsnamen und Attribute) durch, entfernen Sie nutzlosen Code (nicht erreichbar, unnötig), rufen Sie Inline-Methodenaufrufe und Konstanten auf (rufen Sie die Funktion auf, falls dies kostengünstig ist) Ersetzen Sie den Inhalt des Funktionskörpers und ersetzen Sie die Konstante durch ihren Wert.

PS Weitere Informationen zu compilation_level finden Sie unter Closure Compiler Compilation Levels

Der ADVANCED-Modus ist zu leistungsfähig:


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

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

PS kann online beim Closure Compiler Service getestet werden

Migration und Switching sind mit bestimmten Risiken verbunden, sodass React weiterhin den SIMPLE-Modus verwendet. Möglicherweise ist jedoch geplant, den ADVANCED-Modus in Zukunft zu öffnen, um den Closure Compiler zur Optimierung der Bundle-Größe vollständig nutzen zu können


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.

Kurz gesagt, die detaillierten Fehlerinformationen werden durch den entsprechenden Fehlercode im Produktpaket ersetzt. Die Produktionsumgebung erkennt den Laufzeitfehler, gibt den Fehlercode und die Kontextinformationen aus und sendet sie dann an den Fehlercode-Konvertierungsdienst, um die vollständige Fehlermeldung wiederherzustellen. Dies stellt nicht nur sicher, dass das Produktpaket so sauber wie möglich ist, sondern behält auch die gleichen detaillierten Fehlerberichterstattungsfunktionen wie die Entwicklungsumgebung bei

Zum Beispiel der unzulässige React Element-Fehler in der Produktionsumgebung:


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.

Sehr interessante Technik, die wirklich viel darüber nachgedacht hat, die Entwicklungserfahrung zu verbessern

Die
sogenannte Envifikation besteht darin, durch die Umgebung zu bauen, zum Beispiel:


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

Ersetzen Sie die häufig verwendete Methode process.env.NODE_ENV durch die Zeichenfolgenkonstante, die der Zielumgebung während der Erstellung entspricht, und der redundante Code wird im nachfolgenden Konstruktionsprozess (Verpackungswerkzeug / Komprimierungswerkzeug) entfernt.

Zusätzlich zur Paketeintragsdatei wird das gleiche Urteil auch als Doppelversicherung getroffen:


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

Darüber hinaus befürchte ich, dass Entwickler das Entwicklerpaket missbrauchen könnten, um online zu gehen. Deshalb habe ich React DevTools eine Erinnerung hinzugefügt:


This page is using the development build of React. 

Ich denke du magst

Origin blog.51cto.com/15080030/2592712
Empfohlen
Rangfolge