In meinem Vortrag bei Y.Subbotnik Pro erinnerte ich mich daran, was und wie wir bei der Montage und Architektur des "Standard Modern Project" fertig waren und welche Ergebnisse wir erzielten.
- In den letzten anderthalb Jahren habe ich im Serp-Architektur-Team gearbeitet. Wir entwickeln dort die Laufzeit und Assemblierung von neuem Code in React und TypeScript.
Lassen Sie uns über unseren allgemeinen Schmerz sprechen, den dieser Vortrag ansprechen wird. Wenn Sie ein kleines Projekt in React erstellen möchten, müssen Sie nur einen Standardsatz von Tools verwenden, die als drei Buchstaben bezeichnet werden - CRA. Dies umfasst Build-Skripte, Skripte zum Ausführen von Tests, zum Einrichten einer Entwicklungsumgebung und alles wurde bereits für die Produktion erledigt. Alles wird sehr einfach über NPM-Skripte erledigt, und wahrscheinlich weiß jeder darüber Bescheid, der Erfahrung mit React hat.
Angenommen, das Projekt wird groß, es enthält viel Code, viele Entwickler und Produktionsfunktionen wie Übersetzungen, von denen die Create React App nichts weiß. Oder Sie haben eine komplexe CI / CD-Pipeline. Dann beginnen die Gedanken, einen Auswurf zu machen, um die Create React App als Basis zu verwenden und sie für Ihr eigenes Projekt anzupassen. Aber es ist absolut nicht klar, was dort hinter diesem Auswurf auf uns wartet. Denn wenn Sie einen Auswurf ausführen, heißt es, dass dies eine sehr gefährliche Operation ist. Es ist nicht möglich, ihn zurückzugeben, und so weiter, sehr beängstigend. Diejenigen, die auf Auswerfen geklickt haben, wissen, dass dort draußen viele Konfigurationen abgeworfen werden, die Sie verstehen müssen. Im Allgemeinen gibt es viele Risiken, und es ist nicht klar, was zu tun ist.
Ich werde dir sagen, wie es bei uns war. Zunächst zu unserem Projekt. Unser Front-End-Projekt sind die Serp-, Suchmaschinen-Ergebnisseiten und Yandex-Suchergebnisseiten, die jeder gesehen hat. Seit 2018 bewegen wir React und TypeScript nicht mehr. Im letzten Jahr wurden bereits etwa 12 Megabyte Code auf Serpa geschrieben. Es gibt einige Stile und viel TS- und SCSS-Code. Wie viele am Anfang, im Jahr 2018, es gab, habe ich nicht geschrieben, es gibt sehr wenig, es gab einen sehr scharfen Sprung.
Mal sehen, ob das viel Code ist oder nicht. Im Vergleich zum Webpack-4-Quellcode enthält Webpack-4 viel weniger Code. Sogar das TypeScript-Repository hat weniger Code.
Vs-Code enthält jedoch mehr Code, ein gutes Projekt mit bis zu 30 Megabyte TypeScript-Code. Ja, es ist auch in TypeScript geschrieben und die Sichel scheint kleiner zu sein. 2018 haben wir angefangen, 2019 waren es 12 Megabyte, und 70 unserer Entwickler haben gearbeitet und 100 Pulled-Pull-Anfragen pro Woche gestellt. Innerhalb eines Jahres verdreifachten sie diese Größe und erhielten genau 30 Megabyte. Ich habe diesen Monat Messungen durchgeführt, wir haben jetzt insgesamt 30 Megabyte Code, und das ist bereits mehr als bei vs-Code.
Ungefähr gleich, aber etwas mehr. Dies ist die Reihenfolge unseres Projekts.
Und wir haben gleich zu Beginn ausgeworfen, weil wir sofort wussten, dass wir viel Code haben würden und höchstwahrscheinlich die anfänglichen Konfigurationen in der Create React App für uns nicht funktionieren würden. Aber wir haben genauso mit der Create React App begonnen.
Darum geht es in der Geschichte. Wir möchten unsere Erfahrungen teilen und Ihnen sagen, was wir mit der Create React App zu tun hatten, damit Yandex Serp ordnungsgemäß daran arbeitet. Das heißt, wie wir schnell im Browser geladen und initialisiert wurden und wie wir versucht haben, den Build nicht zu verlangsamen, welche Einstellungen, Plugins und andere Dinge wir dafür verwendet haben. Und natürlich werden die Ergebnisse, die wir erzielt haben, am Ende kommen.
Wie haben wir argumentiert? Die ursprüngliche Idee war, dass unsere Sichel eine Seite ist, die sehr schnell gerendert werden muss, da es im Grunde genommen sehr einfache Textergebnisse gibt. Daher benötigen wir serverseitige Vorlagen, da dies der einzige Weg ist, um schnell zu rendern. Das heißt, wir müssen etwas zeichnen, bevor etwas auf dem Client initialisiert wird.
Gleichzeitig wollte ich die Mindestgröße der Statik festlegen, um nichts Überflüssiges zu laden, und die Initialisierung war auch schnell. Das heißt, wir möchten sowohl das erste Rendern als auch eine schnelle Initialisierung.
Was bietet uns die Create React App? Leider bietet es uns nichts über Server-Rendering.
Es heißt, dass das Server-Rendering in der Create React App nicht unterstützt wird. Darüber hinaus verfügt Create React App nur über einen Eintrag für die gesamte Anwendung. Das heißt, standardmäßig wird ein großes Bundle für alle Ihre großen Seitenvielfalt gesammelt. Das ist sehr viel. Es ist klar, dass von 30 Megabyte etwa die Hälfte TS-Typen sind, aber dennoch viel Code direkt in den Browser gelangt.
Gleichzeitig verfügt die Create React App über einige gute Einstellungen. Beispielsweise wird die Webpack-Laufzeit in einem separaten Block gespeichert. Es wird separat geladen und kann zwischengespeichert werden, da es sich nicht normal ändert.
Darüber hinaus werden Module von node_modules auch in separaten Blöcken gesammelt. Sie ändern sich auch selten und werden daher auch vom Browser zwischengespeichert. Das ist großartig, das muss gespeichert werden. Gleichzeitig sind Übersetzungen in der Create React App jedoch nicht von Bedeutung.
Stellen wir unsere Liste zusammen, wie in unserem Fall die Liste der Funktionen unserer Plattform aussehen sollte. Erstens möchten wir, wie gesagt, das Rendern im Norden, um ein schnelles Rendern durchzuführen. Zusätzlich möchten wir für jedes Suchergebnis eine eigene Eintragsdatei haben.
Wenn Serpa beispielsweise einen Taschenrechner hat, möchten wir, dass das Paket mit dem Taschenrechner geliefert wird und das Paket mit dem Übersetzer nicht schnell geliefert werden muss. Wenn all dies in einem großen Bündel gesammelt wird, wird immer alles gehen, auch wenn die Hälfte dieser Dinge nicht zu einem bestimmten Thema gehört.
Außerdem möchte ich gemeinsame Module in separaten Blöcken bereitstellen, um nicht das zu laden, was bereits geladen wurde.
Hier ist ein weiteres Beispiel mit der Sichel. Es hat einen Taschenrechner, es gibt ein Taschenrechner-Bundle. Es gibt gemeinsame Komponenten. Sie wurden an den Kunden geliefert. Dann erschien ein weiteres Feature - eine Karte. Fuhr das Kartenbündel und fuhr andere gängige Komponenten, abzüglich der bereits gelieferten.
Wenn die gemeinsamen Komponenten separat gesammelt werden, gibt es eine so große Möglichkeit zur Optimierung und nur das, was benötigt wird, wird geliefert, nur diff. Und die beliebtesten Module, die immer auf der Seite sind, zum Beispiel die Webpack-Laufzeit, die immer von dieser gesamten Infrastruktur benötigt wird, müssen immer geladen werden.
Daher ist es sinnvoll, in einem separaten Block zu sammeln. Das heißt, diese gemeinsamen Komponenten können auch in diejenigen Komponenten unterteilt werden, die nicht immer benötigt werden, und in Komponenten, die immer benötigt werden. Sie können in einer separaten Datei gesammelt und immer geladen und auch zwischengespeichert werden, da sich diese allgemeinen Komponenten, wie Schaltflächen / Links, nicht sehr oft ändern und im Allgemeinen einen Gewinn aus dem Caching ziehen.
Gleichzeitig müssen Sie eine Entscheidung über das Zusammenstellen von Übersetzungen treffen.
Hier ist alles klar genug. Wenn wir zu Turkish Serp gehen, möchten wir nur türkische Übersetzungen herunterladen und nicht alle anderen Übersetzungen, da dies ein zusätzlicher Code ist.
Was haben wir getan? Zunächst zum Servercode. In Bezug darauf werden wir zwei Richtungen haben - Bauen für die Produktion und Starten für Entwickler.
Im Allgemeinen müssen Sie zuerst eine solche separate Aussage zu TypeScript machen. Normalerweise verwenden Projekte, wie ich gehört habe, Babel. Wir haben uns jedoch sofort für den Standard-TypeScript-Compiler entschieden, da wir davon ausgegangen waren, dass neue TypeScript-Funktionen ihn schneller erreichen würden. Deshalb haben wir babel sofort aufgegeben und tsc verwendet.
Unsere aktuelle Codegröße, unsere 30 Megabyte, wird in drei Minuten auf einem Laptop kompiliert. Ziemlich viel. Wenn Sie die Typprüfung abbrechen und bei jeder Kompilierung einen tsc-Fork verwenden (TSC verfügt leider nicht über eine Einstellung, die die Typprüfung deaktiviert, Sie mussten einen Fork durchführen), können Sie die doppelte Zeit sparen. Die Kompilierung unseres Codes dauert nur eineinhalb Minuten.
Warum können wir die Typprüfung zur Kompilierungszeit nicht durchführen? Weil wir sie zum Beispiel in Pre-Commit-Hooks überprüfen können. Erstellen Sie einen Linter, der nur die Typprüfung ausführt, und die Montage selbst kann ohne Typprüfung durchgeführt werden. Wir haben diese Entscheidung getroffen.
Wie laufen wir in dev? Dev verwendet normalerweise auch ein Bündel Babel mit Webpack, aber wir verwenden ein Tool wie ts-node.
Dies ist ein sehr einfaches Werkzeug. Um es auszuführen, reicht es aus, eine solche Anforderung (ts-node) in die JavaScript-Eingabedatei zu schreiben, und später in diesem Prozess werden die Anforderungen des gesamten TS-Codes überschrieben. Und wenn unterwegs ein TS-Code in diesen Prozess geladen wird, wird er im laufenden Betrieb kompiliert. Eine sehr einfache Sache.
Natürlich ist ein geringer Aufwand damit verbunden, dass die Datei, wenn sie in diesem Prozess noch nicht geladen wurde, neu kompiliert werden muss. In Wirklichkeit ist dieser Aufwand jedoch minimal und allgemein akzeptabel.
Darüber hinaus enthält diese Auflistung einige weitere interessante Zeilen. Das erste ist, Stile zu ignorieren, da wir keine Stile für serverseitige Vorlagen benötigen. Wir brauchen nur HTML. Daher verwenden wir auch ein solches Modul - Ignorierstile. Außerdem deaktivieren wir die Typprüfung (nur Transpile) genau wie in TSC, um die Arbeit von ts-node zu beschleunigen.
Weiter zum Client-Code. Wie sammeln wir den Code im Webpack? Wir verwenden ts-loader und die Option transpileOnly, dh ungefähr dasselbe Bundle. Anstelle von Babel-Loader mehr oder weniger Standard-Tools für Ts-Loader und TranspileOnly.
Leider funktioniert der inkrementelle Build im ts-loader nicht. Das heißt, ts-loader ist kein Standardwerkzeug und wird nicht von denselben Leuten erstellt, die TypeScript ausführen. Daher werden dort nicht alle Compileroptionen unterstützt. Beispielsweise wird ein inkrementeller Build nicht unterstützt.
Ein inkrementeller Build ist eine Sache, die bei der Entwicklung sehr nützlich sein kann. Auf die gleiche Weise können Sie diese Caches zur Pipeline hinzufügen. Wenn Ihre Änderungen klein sind, können Sie im Allgemeinen nicht alles, alles TypeScript, vollständig neu kompilieren, sondern nur das, was sich geändert hat. Es funktioniert sehr effektiv.
Um auf einen inkrementellen Build zu verzichten, verwenden wir im Allgemeinen den Cache-Loader. Dies ist die Standardlösung von Webpack. Alles ist ganz klar. Wenn der TypeScript-Code während eines Webpack-Builds versucht, eine Verbindung herzustellen, wird er vom Compiler verarbeitet, dem Cache hinzugefügt. Wenn beim nächsten Mal keine Änderungen an den Quelldateien vorgenommen wurden, führt der Cache-Loader den ts-loader nicht aus und nimmt ihn aus dem Cache. Das heißt, hier ist alles ganz einfach.
Es kann für alles verwendet werden, aber speziell für TypeScript ist dies eine praktische Sache, da ts-loader ein ziemlich schwerer Loader ist, weshalb der Cache-Loader hier sehr gut geeignet ist.
Der Cache-Loader hat jedoch einen Nachteil: Er arbeitet mit der Änderungszeit der Datei. Hier ist ein Ausschnitt aus dem Quellcode. Und es hat bei uns nicht funktioniert.
Wir mussten den Caching-Algorithmus basierend auf dem Hash aus dem Dateiinhalt verzweigen und wiederholen, da er nicht für die Verwendung des Cache-Loaders in der Pipeline geeignet war.
Tatsache ist, dass dieser Mechanismus nicht funktioniert, wenn Sie die Build-Ergebnisse zwischen mehreren Pull-Anforderungen wiederverwenden möchten. Denn wenn die Montage zum Beispiel schon lange her ist. Anschließend versuchen Sie, eine neue Pull-Anforderung zu erstellen, die die beim vorherigen Mal gesammelten Dateien nicht ändert.
Aber ihre Zeit ist jünger. Dementsprechend wird der Cache-Loader denken, dass die Dateien aktualisiert wurden, aber tatsächlich nicht, da dies keine Änderungszeit, sondern eine Checkout-Zeit ist. Und wenn Sie es so machen, werden die Hashes aus dem Inhalt verglichen. Der Inhalt hat sich nicht geändert, das alte Ergebnis wird verwendet.
Hierbei ist zu beachten, dass bei Verwendung von babel der babel-loader standardmäßig über einen Caching-Mechanismus verfügt, der bereits für Hashes aus dem Inhalt und nicht für mtime erstellt wurde. Vielleicht denken wir deshalb etwas mehr nach und schauen zu Babel.
Nun zur Montage von Brocken.
Lassen Sie uns ein wenig darüber sprechen, was Webpack standardmäßig tut. Wenn wir eine Eingabeindexdatei haben, werden Komponenten damit verbunden. Sie haben auch Komponenten usw. Zusätzlich sind gemeinsame Module verbunden: React, React-dom und lodash zum Beispiel.
Standardmäßig sammelt Webpack, wie wahrscheinlich jeder weiß, aber nur für den Fall, ich wiederhole es, alle Abhängigkeiten in einem großen Bündel.
Gleichzeitig kann alles, was über node_modules verbunden ist, entweder als externes Element, das mit separaten Skripten geladen wird, oder in einem separaten Block zusammengestellt werden, indem im Webpack eine spezielle Einstellung für optimierung.splitChunks eingerichtet wird. Meiner Meinung nach werden diese Anbietermodule auch standardmäßig in einem separaten Block zusammengefasst. CRA hat eine leicht optimierte Version dieser splitChunks.
Erinnern wir uns, was runtimeChunks sind. Ich habe ihn erwähnt. Dies ist die Art von Code, die einen solchen „Header“ zum Laden von Skripten und Funktionen enthält, die den Betrieb des modularen Systems auf dem Client sicherstellen. Und dann ein Array (oder Cache), das tatsächlich die Module enthält.
Warum habe ich dir davon erzählt? Da Create React App immer noch eine Einstellung verwendet, die diese runtimeChunks in einer separaten Datei sammelt. Diese Datei wird nicht in das ursprüngliche fehlerfreie Bundle eingefügt, sondern in eine separate Datei. Es kann im Browser zwischengespeichert werden und so weiter.
Was funktioniert bei Create React App für uns nicht?
Diese splitChunks, die dort standardmäßig verwendet werden, sammeln nur node_modules in separaten Chunks. Tatsächlich gibt es jedoch gemeinsame Komponenten, gemeinsame Bibliotheken, die sich auf Projektebene befinden. Ich würde sie auch gerne in getrennten Stücken sammeln, weil sie sich vielleicht auch selten ändern. Warum beschränken wir uns nur auf das, was in node_modules enthalten ist?
Darüber hinaus können wir in Bezug auf runtimeChunks auch sagen, dass es großartig wäre, wie wir ursprünglich besprochen haben, neben der Laufzeit selbst auch Module in demselben Block zu sammeln, die immer benötigt werden. Gleiche Schaltflächen / Links. Es gibt immer Links auf Serp. Ich würde immer gerne Links sammeln. Das heißt, nicht nur die Webpack-Laufzeit, sondern auch einige sehr beliebte Komponenten.
Dies ist in der Create React App nicht vorhanden. Wie haben wir das mit uns gemacht?
Wir haben splitChunks so optimiert, dass wir alle Standardverhalten deaktiviert haben und darum gebeten haben, nicht nur die in node_modules enthaltenen, sondern auch die gemeinsamen Komponenten unseres Projekts und den Bibliothekscode unseres Projekts, was sich in src / lib befindet, in gemeinsamen Code zu sammeln , src / components enthält.
Darüber hinaus sammeln wir in separaten Blöcken, was über dynamische Importe verbunden ist und was normalerweise als asynchrone Blöcke bezeichnet wird.
Hier müssen Sie auf zwei Optionen achten. Einer ist erzwungen und der andere ist initial. Im Allgemeinen ist die Durchsetzung so bequem, dass komplexe Heuristiken in splitChunks deaktiviert werden.
Standardmäßig versucht splitChunks zu verstehen, wie viele Module nachgefragt werden, und berücksichtigt diese Statistiken bei der Aufteilung. Es ist jedoch schwierig, dem zu folgen, und die Nachfrage nach dem Modul kann sich von Zeit zu Zeit ändern, und das Modul "springt" zwischen Blöcken. Vom allgemeinen Teil bis zum Feature-Bundle und zurück. Das heißt, dies ist ein sehr unvorhersehbares Verhalten, daher deaktivieren wir es.
Das heißt, wir sagen immer alles, was die Bedingungen im Testfeld erfüllt, wir kommen in die gemeinsamen Stücke. Wir wollen keine Heuristiken.
Aber Chunks: Initial ist auch eine gute Sache, es geht um die Tatsache, dass diese synchronen Module, Module, die über dynamische Importe verbunden sind, an verschiedenen Orten auf unterschiedliche Weise verbunden werden können. Das heißt, Sie können dasselbe Modul entweder durch dynamischen Import oder durch regulären Import verbinden.
Mit dem Anfangswert kann dasselbe Modul auf zwei Arten erstellt werden. Das heißt, es wird sowohl asynchron als auch synchron zusammengesetzt, sodass es in beide Richtungen verwendet werden kann. Bequem genug. Dadurch wird die Größe der gesammelten Statik leicht erhöht, Sie können jedoch alle Importe verwenden.
Aus der Dokumentation ist dies übrigens ziemlich schwer zu verstehen. Ich habe kürzlich die Webpack-Dokumentation erneut gelesen und es wird nichts Normales über Initiale geschrieben.
Das haben wir mit splitChunks gemacht. Was haben wir nun mit runtimeChunks gemacht? Anstatt nur die Laufzeit in runtimeChunks zu erfassen, möchten wir dort weitere sehr beliebte Komponenten hinzufügen.
Also haben wir unser eigenes Plugin namens MainChunkPlugin geschrieben. Und es hat eine sehr triviale Einstellung. Es gibt nur eine Liste von Modulen, die dort gesammelt werden müssen, was wir als beliebt angesehen haben.
Mit unseren A / B-Testtools, verschiedenen Offline-Tools, haben wir festgestellt, welche Komponenten am häufigsten in den Suchergebnissen enthalten sind. Dort wurden sie nur in einer so flachen Liste geschrieben. Und am Ende sammelt unser Plugin diese Komponenten aus der Liste sowie Bibliotheken und eine Webpack-Laufzeit, die diese Standardoptimierung sammelt. SplitChunks.
Hier ist übrigens ein Stück Code, der die Laufzeit zusammenklebt. Auch nicht so trivial zu zeigen, dass es nicht so einfach ist, Plugins zu schreiben, aber dann wollen wir sehen, was es gab.
Es sollte auch beachtet werden, dass Webpack im Allgemeinen einen Standardmechanismus dafür hat, der DLLPlugin genannt wird. Außerdem können Sie einen separaten Block gemäß der Liste der Abhängigkeiten sammeln. Es hat jedoch eine Reihe von Nachteilen. Beispielsweise sind runtimeChunks nicht enthalten. Das heißt, bei runtimeChunks haben Sie immer einen separaten Block, und es wird einen von DLLPlugin zusammengestellten Block geben. Dies ist nicht sehr praktisch.
Auch DLLPlugin erfordert eine separate Assembly. Das heißt, wenn wir diesen separaten Block mit den perkussivsten Komponenten mithilfe von DLLPlugin erstellen möchten, müssten wir zwei Assemblys ausführen.
Das heißt, man hat diesen separaten Block mit der Manifestdatei zusammengestellt, und der Rest der Baugruppe würde alles andere sammeln, einfach durch Subtrahieren durch die Manifestdatei, würde nicht das sammeln, was bereits mit populären Komponenten in den Block eingegeben wurde. Dies verlangsamt den Build, da die DLLPlugin-Implementierung lokal sieben Sekunden dauerte. Das ist eine Menge. Und es kann nicht optimiert werden, da es eine strikte sequentielle Ausführung hat.
Außerdem mussten wir zu einem bestimmten Zeitpunkt unseren Hauptteil mit beliebten Komponenten ohne CSS erstellen, nur mit JS. DLLPlugin macht das nicht. Es sammelt immer alles, was über Bedarf über Importe verfügbar ist. Das heißt, wenn Sie CSS einschließen, trifft es immer auch. Es war unangenehm für uns. Aber wenn dies für Sie kein Problem ist und Sie keinen so kniffligen Code schreiben möchten, ist DLLPlugin eine ganz normale Lösung. Er löst das Hauptproblem. Das heißt, es liefert die beliebtesten Komponenten in einer separaten Datei. Es kann benutzt werden.
Also, was haben wir gemacht? Unsere Funktion kann sehr beliebte Komponenten aus unserem MainChunk verwenden, die von einem speziellen gleichnamigen Plugin zusammengestellt werden. Darüber hinaus gibt es allgemeine Chunks, die alle Arten von gemeinsamen Komponenten enthalten, und asynchrone Chunks, die durch dynamische Importe geladen werden.
Der Rest des Codes befindet sich in den Feature-Bundles. Im Prinzip ist dies Ihre Blockstruktur.
Über das Zusammenstellen von Übersetzungen. Unsere Übersetzungen sind nur ts-Dateien, die sich neben den Komponenten befinden, die Übersetzungen benötigen. Hier haben wir neun Sprachen, hier sind neun Dateien.
Die Übersetzungen sehen so aus. Es ist einfach ein Objekt, das eine Schlüsselphrase und die Bedeutung der übersetzten Phrase enthält.
Auf diese Weise werden Übersetzungen mit der Komponente verbunden und anschließend ein spezieller Helfer verwendet.
Wie könnten diese Übersetzungen gesammelt werden? Wir denken: Wir müssen Übersetzungen sammeln, im Internet schauen, was sie schreiben, wie wir das machen können.
Sie sagen im Internet: Verwenden Sie Multicompilation. Das heißt, anstatt einen Webpack-Build auszuführen, führen Sie einfach den Webpack-Build für jede Sprache aus. Sie sagen jedoch, dass alles in Ordnung sein wird, da es einen Cache-Loader gibt. All diese allgemeine Arbeit mit TypeScript oder was auch immer Sie haben, wird zwischengespeichert und wird daher nicht lange dauern.
Lassen Sie sich nicht entmutigen, denken Sie nicht, dass dies neun echte Webpack-Läufe sein werden. Es wird nicht so sein, es wird gut sein.
Das einzige, was korrigiert werden muss, ist das Hinzufügen des ReplacementPlugin-Moduls, das anstelle einer Indexdatei, die alle Sprachen verbindet, durch eine bestimmte Sprache ersetzt wird. Alles ist ziemlich trivial, und ja, die Ausgabe muss korrigiert werden. Nun müssen wir, wie sich herausstellt, für jede Sprache ein eigenes Bündel sammeln.
Das Diagramm für dieses Rezept lautet wie folgt. Es gab einen Übersetzer. Er verband Übersetzungen des Übersetzers. Er hat Sprachen verbunden, und anstatt diese eine Struktur zu sammeln, haben wir sie für jede Sprache repliziert, eine eigene erhalten und jede als separate Zusammenstellung gesammelt.
Leider funktioniert es nicht. Ich habe versucht, diese Multikompilierungsoption für unseren aktuellen 30-MB-Code auszuführen, habe anderthalb Stunden gewartet und diesen Fehler erhalten.
Es ist sehr lang und unmöglich. Was haben wir damit gemacht? Wir haben ein weiteres Plugin erstellt. Wir nehmen dieselbe Struktur und koppeln uns in die Arbeit von Webpack ein, wenn es darum geht, die Ausgabedateien auf der Festplatte zu speichern. Wir kopieren diese Struktur so oft wie es Sprachen gibt und kleben jeweils eine Sprache auf. Und erst dann erstellen wir die Dateien.
Gleichzeitig wird die Hauptarbeit, die Webpack zur Umgehung von Kompilierungsabhängigkeiten leistet, nicht wiederholt. Das heißt, wir klemmen uns in der allerletzten Phase ein und können daher hoffen, dass es schnell geht.
Der Plugin-Code erwies sich jedoch als kompliziert. Dies ist buchstäblich ein Achtel unseres Plugins. Ich zeige nur, wie schwierig es ist. Und dort finden wir regelmäßig kleine, unangenehme Käfer. Aber es war nicht einfacher, es umzusetzen. Aber es funktioniert sehr gut.
Das heißt, statt anderthalb Stunden mit einem Fehler erhalten wir fünf Minuten Montage mit diesem Plugin von uns.
Jetzt Lieferung und Initialisierung.
Lieferung und Initialisierung sind einfach. Was wir in separate Ressourcen laden, verwenden wir Preload, genau wie alle anderen, denke ich. Dann fügen wir CSS, JS und tatsächlich HTML für unsere Komponenten hinzu und laden diese unsere Ressourcen, jedoch ohne Asynchronität.
Wir haben experimentiert. Wenn wir Async verwenden, verzögert sich der Zeitpunkt des Einsetzens der Interaktivität, was wir nicht wollen würden. Verwenden Sie also einfach Preload und Load am Ende der Seite. Im Allgemeinen nichts Besonderes.
Gleichzeitig stellen wir alles andere inline. Das heißt, dies ist unser MainChunk, wir inline sein CSS. Allgemeine Komponenten, Stile, im Allgemeinen alles, was auf der Folie geschrieben ist, werden wir inline. Dies war auch eine Reihe von Experimenten, die zeigten, dass "Inline" das beste Ergebnis für das erste Rendern und den Beginn der Interaktivität liefert.
Und nun zu den Zahlen. Um über Zahlen zu sprechen, müssen Sie zwei Wörter über Metriken sagen.
Wir haben ein engagiertes Speed-Team, das dafür sorgt, dass der gesamte Front-End-Code effizient funktioniert. Dies betrifft im Allgemeinen das serverseitige Templating und Laden von Ressourcen sowie die Initialisierung auf dem Client.
Wir haben eine ganze Reihe von Metriken, die von der Produktion an unser spezielles Protokollierungssystem gesendet werden. Wir können dies in A / B-Experimenten kontrollieren. Wir haben Offline-Tools, im Allgemeinen verfolgen wir dies alles sehr aktiv.
Und wir haben diese Tools verwendet, als wir unseren neuen Code in React und TypeScript implementiert haben.
Lassen Sie uns jetzt mithilfe von Offline-Tools nachverfolgen (da ich kein ehrliches Online-Experiment zusammenstellen konnte, bei dem alle unsere Metriken verwendet werden). Mal sehen, was passiert, wenn wir von unserer aktuellen Lösung auf Create React App für diese Schlüsselmetriken zurückgreifen.
Das Tool funktioniert sehr einfach. Es wird ein Teil der Anforderungen übernommen. In diesem Fall wird eine Anforderung mit Funktionen in React ausgeführt, da noch nicht alle Serp in React neu geschrieben wurden. Dann werden unsere Vorlagen abgefeuert, Messungen gesammelt, in ein spezielles Dienstprogramm eingefügt, das diese Ergebnisse und Metriken vergleicht und findet. In diesem Fall bleiben nur statistisch signifikante Ergebnisse übrig. Im Allgemeinen ist dort alles vernünftig.
Mal sehen was passiert.
Das Deaktivieren von MultiPlugin, das tatsächlich alle Übersetzungen anstelle nur der erforderlichen Übersetzung sammelt, zeigte keine statistisch signifikanten Änderungen.
Zuerst war ich ein wenig verärgert, dann wurde mir klar, dass dies tatsächlich kein Problem ist, da wir jetzt nicht viele Funktionen haben, bei denen viele Übersetzungen in React übersetzt wurden. Wenn es mehr solche Funktionen gibt, werden diese signifikanten Änderungen definitiv auftreten. Es ist nur so, dass es jetzt Funktionen gibt, die hauptsächlich in Russland gezeigt werden und keine Übersetzungen haben. Und die Menge an Code, die in den Komponenten enthalten ist, übersteigt die Menge an Übersetzungen erheblich. Daher ist es nicht wahrnehmbar, dass alle Übersetzungen unterwegs sind.
Vielleicht würde es in ehrlicheren Experimenten auffallen, wenn ein ehrliches Experiment durchgeführt würde. Das Offline-Tool zeigte diese Änderungen jedoch nicht an.
Wenn wir MainChunkPlugin deaktivieren, verlangsamt sich die Zeit für den Beginn der Interaktivität, und das Laden von HTML verlangsamt sich ebenfalls erheblich. Daher ist die Sache durchaus notwendig.
Warum verlangsamt sich das Laden von HTML, weil der gesamte Code, der früher von einer separaten Ressource in diesen separaten Block geladen wurde, jetzt in HTML eingefügt ist. Es ist, als würden wir alles inline, aber die Interaktivität verlangsamt sich auch. Im Prinzip durchaus zu erwarten.
Und jetzt die Frage: Was würde passieren, wenn Sie alles in einem Paket zusammenfassen und keine Chunks mit gemeinsamen Komponenten verwenden? Es stellt sich heraus, dass dies überhaupt kein freudiges Bild ist.
Der erste Render verlangsamt sich dramatisch. Auch die Interaktivität hat sich fast verdoppelt. Dadurch wird der HTML-Code kleiner, da der gesamte Code in einer separaten Ressource bereitgestellt wird. Aber Interaktivität hilft nicht, wie Sie sehen können.
Und Montage. Letzte Folien.
Die Erstellungszeit der Create React App für das aktuelle Projekt beträgt auf einem Laptop drei Minuten. Und mit all unseren Schnickschnack - fünf Minuten. Lange?
Wenn Sie jedoch ein Bündel zusammenstellen, sind es tatsächlich drei Minuten. Das Erstellen ohne MultiPlugin ist noch schneller als das Erstellen einer React App. Wie ich in den vorherigen Folien gezeigt habe, können wir diese Änderungen an den ursprünglichen Build-Skripten nicht ablehnen, da ohne sie die Geschwindigkeitsmetriken sehr schlecht werden.
Lassen Sie uns nun untersuchen, was nützlich ist, um aus diesem Bericht zu lernen.
Babel ist nicht die einzige Möglichkeit, mit TypeScript zu arbeiten. TSC, ts-node und ts-loader können verwendet werden. Es funktioniert ganz gut.
TypeScript-Überprüfungen und Typprüfungen müssen jedoch nicht bei jedem Erstellen durchgeführt werden. Das verlangsamt sich sehr - wie Sie sich erinnern, zweimal. Daher ist es besser, solche Dinge in separaten Prüfungen zu platzieren, z. B. Pre-Commit-Hooks.
Es ist besser, häufig verwendete Komponenten in einem separaten Block zu sammeln. Es ist auch wünschenswert, gemeinsame Komponenten in getrennten Blöcken zu sammeln, da dies das Laden nur des benötigten, nur diff ermöglicht.
Das Wichtigste ist, dass Sie, wenn nicht der gesamte Code auf allen Seiten verwendet wird, ihn in separate Einträge aufteilen, separate Bundles sammeln und herunterladen müssen, wenn der Benutzer die entsprechenden Arten von Suchergebnissen sieht. Laden Sie nur die Dateien herunter, die Sie benötigen. Dies ergibt, wie Sie gesehen haben, das größte Ergebnis. Ziemlich offensichtliche Sache, aber ich bin mir nicht sicher, ob dies jeder tut, da er immer noch in der Create React App bleibt.
Multicompilation ist sehr lang. Glauben Sie nicht, wenn jemand sagt, dass Multicompilation in Ordnung ist und Caches irgendwo im Inneren all dies verarbeiten können. Die Verwendung von Vorspannung und Inline liefert ebenfalls Ergebnisse.
Ein paar Links zur Sichel:
- clck.ru/PdRdh und clck.ru/PdRjb - zwei Berichte, in denen es darum geht, Serp in React neu zu schreiben. Dies ist die erste Phase, in der es darum geht, wie wir dazu gekommen sind und warum wir damit begonnen haben. Der zweite Bericht handelt davon, wie wir all dies aus Managementsicht geplant und durchgeführt haben und wie die Phasen waren.
- clck.ru/PdRnr - Bericht über unsere Geschwindigkeitsmetriken. Es ist für diejenigen, die sich plötzlich gefragt haben, was es sonst noch gibt und wie Online-Tools funktionieren.
Danke an alle.