JavaScript-Module Teil 2: Modulbündelung

In Teil I dieses Beitrags habe ich darüber gesprochen, was Module sind, warum Entwickler sie verwenden und wie sie auf verschiedene Weise in Ihre Programme integriert werden können.

In diesem zweiten Teil werde ich untersuchen, was es genau bedeutet, Module zu „bündeln“: Warum wir Module bündeln, welche Möglichkeiten dies gibt und welche Zukunft Module in der Webentwicklung haben.

Was ist Modulbündelung?

Auf hoher Ebene ist die Modulbündelung einfach der Vorgang des Zusammenfügens einer Gruppe von Modulen (und ihrer Abhängigkeiten) zu einer einzelnen Datei (oder Gruppe von Dateien) in der richtigen Reihenfolge.

Wie bei allen Aspekten der Webentwicklung steckt der Teufel im Detail. :) :)

Warum überhaupt Module bündeln?

Wenn Sie Ihr Programm in Module unterteilen, organisieren Sie diese Module normalerweise in verschiedene Dateien und Ordner. Möglicherweise verfügen Sie auch über eine Gruppe von Modulen für die von Ihnen verwendeten Bibliotheken, z. B. Unterstrich oder Reagieren.

Daher muss jede dieser Dateien in Ihrer Haupt-HTML-Datei in a enthalten sein pt> tag, das dann vom Browser geladen wird, wenn ein Benutzer Ihre Homepage besucht. Wenn für jede Datei separate & lt ; script> -Tags vorhanden sind, muss der Browser jede Datei einzeln laden: eins… nach… eins.

… Was für die Ladezeit der Seite eine schlechte Nachricht ist.

Um dieses Problem zu umgehen, bündeln oder "verketten" wir alle unsere Dateien zu einer großen Datei (oder gegebenenfalls zu einigen Dateien), um die Anzahl der Anforderungen zu verringern. Wenn Sie Entwickler über den „Build-Schritt“ oder den „Build-Prozess“ sprechen hören, ist dies das, worüber sie sprechen.

Ein weiterer gängiger Ansatz zur Beschleunigung des Bündelungsvorgangs besteht darin, den gebündelten Code zu „minimieren“. Beim Minimieren werden unnötige Zeichen aus dem Quellcode entfernt (z. B. Leerzeichen, Kommentare, neue Zeilenzeichen usw.), um die Gesamtgröße des Inhalts zu verringern, ohne die Funktionalität des Codes zu ändern.

Weniger Daten bedeuten weniger Bearbeitungszeit für den Browser, was wiederum die Zeit zum Herunterladen von Dateien verkürzt. Wenn Sie jemals eine Datei mit der Erweiterung "min" wie "underscore-min.js" gesehen haben, haben Sie wahrscheinlich bemerkt, dass die verkleinerte Version im Vergleich zur Vollversion ziemlich klein (und unlesbar) ist.

Task-Runner wie Gulp und Grunt vereinfachen die Verkettung und Minimierung für Entwickler und stellen sicher, dass für Menschen lesbarer Code für Entwickler verfügbar bleibt, während maschinenoptimierter Code für Browser gebündelt wird.

Welche Möglichkeiten gibt es, Module zu bündeln?

Das Verketten und Minimieren Ihrer Dateien funktioniert hervorragend, wenn Sie eines der Standardmodulmuster (im vorherigen Beitrag beschrieben) zum Definieren Ihrer Module verwenden. Alles, was Sie wirklich tun, ist, eine Menge einfachen Vanille-JavaScript-Codes zusammenzufügen.

Wenn Sie sich jedoch an nicht native Modulsysteme halten, die Browser nicht wie CommonJS oder AMD (oder sogar native ES6-Modulformate) interpretieren können , müssen Sie ein spezielles Tool verwenden, um Ihre Module in einen ordnungsgemäß geordneten Browser zu konvertieren -freundlicher Code. Hier kommen Browserify, RequireJS, Webpack und andere „Modulbündler“ oder „Modullader“ ins Spiel.

Zusätzlich zum Bündeln und / oder Laden Ihrer Module bieten Modulbündler eine Menge zusätzlicher Funktionen wie das automatische Neukompilieren von Code, wenn Sie Änderungen vornehmen oder Quellzuordnungen zum Debuggen erstellen.

Lassen Sie uns einige gängige Modulbündelungsmethoden durchgehen:

Bündelung von CommonJS

Wie Sie aus Teil 1 wissen, lädt CommonJS Module synchron, was in Ordnung wäre, außer dass es für Browser nicht praktisch ist. Ich erwähnte, dass es eine Problemumgehung gab - eine davon ist ein Modulbündler namens Browserify. Browserify ist ein Tool, das CommonJS-Module für den Browser kompiliert.

Angenommen, Sie haben diese Datei main.js, die ein Modul importiert, um den Durchschnitt eines Arrays von Zahlen zu berechnen:

In diesem Fall haben wir also eine Abhängigkeit (myDependency). Mit dem folgenden Befehl bündelt Browserify rekursiv alle erforderlichen Module ab main.js in einer einzigen Datei namens bundle.js:

Browserify springt dazu ein, um den AST für jeden erforderlichen Aufruf zu analysieren , um den gesamten Abhängigkeitsgraphen Ihres Projekts zu durchlaufen. Sobald herausgefunden wurde, wie Ihre Abhängigkeiten strukturiert sind, werden sie alle in der richtigen Reihenfolge in einer einzigen Datei zusammengefasst. An diesem Punkt müssen Sie nur noch eine einzelne einfügenpt> tagge mit deiner Datei "bund le.js" in deinem HTML, um sicherzustellen, dass dein gesamter Quellcode in einer HTTP-Anfrage heruntergeladen wird. Bam! Gebündelt, um zu gehen.

Wenn Sie mehrere Dateien mit mehreren Abhängigkeiten haben, teilen Sie Browserify einfach mit, was Ihre Eingabedatei ist, und lehnen Sie sich zurück, während sie ihre Magie ausübt.

Das Endprodukt: gebündelte Dateien vorbereitet und bereit für Tools wie Minify-JS, um den gebündelten Code zu minimieren.

AMD bündeln

Wenn Sie AMD verwenden, sollten Sie einen AMD- Loader wie RequireJS oder Curl verwenden. Ein Modullader (im Vergleich zu einem Bundler) lädt dynamisch Module, die Ihr Programm ausführen muss.

Zur Erinnerung: Einer der Hauptunterschiede von AMD gegenüber CommonJS besteht darin, dass Module asynchron geladen werden. In diesem Sinne benötigen Sie mit AMD technisch gesehen keinen Build-Schritt, bei dem Sie Ihre Module in einer Datei bündeln, da Sie Ihre Module asynchron laden. Dies bedeutet, dass Sie nach und nach nur die Dateien herunterladen, die für die Ausführung des Moduls unbedingt erforderlich sind Programm, anstatt alle Dateien auf einmal herunterzuladen, wenn der Benutzer die Seite zum ersten Mal besucht.

In der Realität macht der Overhead von Anfragen mit hohem Volumen im Laufe der Zeit für jede Benutzeraktion in der Produktion jedoch wenig Sinn. Die meisten Webentwickler verwenden immer noch Build-Tools, um ihre AMD-Module für eine optimale Leistung zu bündeln und zu minimieren, beispielsweise mithilfe von Tools wie RequireJS Optimizer, r.js.

Insgesamt besteht der Unterschied zwischen AMD und CommonJS beim Bündeln darin, dass AMD-Apps während der Entwicklung ohne Build-Schritt davonkommen können. Zumindest bis Sie den Code live übertragen, können Optimierer wie r.js eingreifen, um damit umzugehen.

Eine interessante Diskussion über CommonJS vs. AMD finden Sie in diesem Beitrag auf Tom Dales Blog :)

Webpack

Für Bundler ist Webpack das neue Kind auf dem Block. Es wurde entwickelt, um unabhängig von dem von Ihnen verwendeten Modulsystem zu sein, sodass Entwickler CommonJS, AMD oder ES6 verwenden können.

Sie fragen sich vielleicht, warum wir Webpack benötigen, wenn wir bereits andere Bundler wie Browserify und RequireJS haben, die die Arbeit erledigen und verdammt gute Arbeit leisten. Zum einen bietet Webpack einige nützliche Funktionen wie "Code-Splitting" - eine Möglichkeit, Ihre Codebasis in "Chunks" aufzuteilen, die bei Bedarf geladen werden.

Wenn Sie beispielsweise eine Web-App mit Codeblöcken haben, die nur unter bestimmten Umständen erforderlich sind, ist es möglicherweise nicht effizient, die gesamte Codebasis in einer einzigen massiven gebündelten Datei zusammenzufassen. In diesem Fall können Sie die Codeaufteilung verwenden, um Code in gebündelte Blöcke zu extrahieren, die bei Bedarf geladen werden können, um Probleme mit großen Nutzdaten im Voraus zu vermeiden, wenn die meisten Benutzer nur den Kern Ihrer Anwendung benötigen.

Das Aufteilen von Code ist nur eine von vielen überzeugenden Funktionen, die Webpack bietet, und das Internet ist voller starker Meinungen darüber, ob Webpack oder Browserify besser sind. Hier sind nur einige der besonneneren Diskussionen, die ich nützlich fand, um mich mit dem Thema zu beschäftigen:

  • //gist.github.com/substack/68f8d502be42d5cd4942
  • //mattdesl.svbtle.com/browserify-vs-webpack
  • //blog.namangoel.com/browserify-vs-webpack-js-drama

ES6-Module

Schon zurück? Gut! Denn als nächstes möchte ich über ES6-Module sprechen, die in gewisser Weise den Bedarf an Bundlern in Zukunft verringern könnten. (Sie werden gleich sehen, was ich meine.) Lassen Sie uns zunächst verstehen, wie ES6-Module geladen werden.

Der wichtigste Unterschied zwischen den aktuellen JS-Modulformaten (CommonJS, AMD) und ES6-Modulen besteht darin, dass ES6-Module unter Berücksichtigung der statischen Analyse entwickelt wurden. Dies bedeutet, dass beim Importieren von Modulen der Import zur Kompilierungszeit aufgelöst wird, dh bevor das Skript ausgeführt wird. Auf diese Weise können wir Exporte entfernen, die nicht von anderen Modulen verwendet werden, bevor wir das Programm ausführen. Das Entfernen nicht verwendeter Exporte kann zu erheblichen Platzersparnissen führen und den Browser entlasten.

Eine häufig gestellte Frage lautet: Wie unterscheidet sich dies von der Beseitigung von totem Code, die auftritt, wenn Sie Ihren Code mit UglifyJS minimieren? Die Antwort lautet wie immer: "Es kommt darauf an."

(HINWEIS: Die Beseitigung toten Codes ist ein Optimierungsschritt, bei dem nicht verwendeter Code und Variablen entfernt werden. Stellen Sie sich vor, Sie entfernen das Übergepäck, das Ihr gebündeltes Programm nicht ausführen muss, * nachdem * es gebündelt wurde.)

Manchmal funktioniert die Beseitigung von totem Code zwischen UglifyJS- und ES6-Modulen genauso und manchmal nicht. Es gibt ein cooles Beispiel in Rollups Wiki, wenn Sie es ausprobieren möchten.

Was ES6-Module unterscheidet, ist der unterschiedliche Ansatz zur Beseitigung von totem Code, der als „Tree Shaking“ bezeichnet wird. Das Schütteln von Bäumen ist im Wesentlichen umgekehrt. Es nur enthält Code , dass Ihr Bündel Bedürfnisse zu laufen, anstatt ohne Code Ihr Bündel nicht braucht. Schauen wir uns ein Beispiel für das Schütteln von Bäumen an:

Angenommen, wir haben eine utils.js-Datei mit den folgenden Funktionen, die wir jeweils mit der ES6-Syntax exportieren:

Nehmen wir als nächstes an, wir wissen nicht, welche Utils-Funktionen wir in unserem Programm verwenden möchten, und importieren alle Module in main.js wie folgt:

Und dann verwenden wir später nur noch die einzelnen Funktionen:

Die "Tree Shaken" -Version unserer Datei main.js würde nach dem Laden der Module folgendermaßen aussehen:

Beachten Sie, dass nur die Exporte enthalten sind, die wir verwenden: jeweils .

Wenn wir uns entscheiden, die Filterfunktion anstelle der einzelnen Funktionen zu verwenden, sehen wir uns in der Zwischenzeit Folgendes an:

Die baumgeschüttelte Version sieht aus wie:

Beachten Sie, wie diesmal sowohl jeder als auch der Filter enthalten sind. Dies liegt daran, dass Filter so definiert sind, dass sie jeweils verwendet werden. Daher benötigen wir beide Exporte, damit das Modul funktioniert.

Ziemlich schlau, oder?

Ich fordere Sie auf, in der Live-Demo und im Editor von Rollup.js herumzuspielen und das Baumschütteln zu erkunden.

Erstellen von ES6-Modulen

Ok, wir wissen also, dass ES6-Module anders geladen werden als andere Modulformate, aber wir haben noch nicht über den Erstellungsschritt für die Verwendung von ES6-Modulen gesprochen.

Leider erfordern ES6-Module noch einige zusätzliche Arbeit, da es noch keine native Implementierung für das Laden von ES6-Modulen durch Browser gibt.

Original text


Im Folgenden finden Sie einige Optionen zum Erstellen / Konvertieren von ES6-Modulen für die Arbeit im Browser, wobei # 1 heute der am häufigsten verwendete Ansatz ist:

  1. Verwenden Sie einen Transpiler (z. B. Babel oder Traceur), um Ihren ES6-Code in ES5-Code im CommonJS-, AMD- oder UMD-Format zu transpilieren. Leiten Sie dann den transpilierten Code durch einen Modulbündler wie Browserify oder Webpack, um eine oder mehrere gebündelte Dateien zu erstellen.
  2. Verwenden Sie Rollup.js, das Option 1 sehr ähnlich ist, mit der Ausnahme, dass Rollup die Leistung von ES6-Modulen nutzt, um Ihren ES6-Code und Ihre Abhängigkeiten vor dem Bündeln statisch zu analysieren. Es verwendet "Baumschütteln", um das Nötigste in Ihr Bündel aufzunehmen. Insgesamt besteht der Hauptvorteil von Rollup.js gegenüber Browserify oder Webpack bei der Verwendung von ES6-Modulen darin, dass durch das Schütteln von Bäumen Ihre Bundles kleiner werden können. Die Einschränkung besteht darin, dass Rollup verschiedene Formate zum Bündeln Ihres Codes bereitstellt, darunter ES6, CommonJS, AMD, UMD oder IIFE. Die IIFE- und UMD-Bundles funktionieren in Ihrem Browser unverändert. Wenn Sie sich jedoch für die Bündelung mit AMD, CommonJS oder ES6 entscheiden, müssen Sie andere Methoden finden, um diesen Code in ein Format zu konvertieren, das der Browser versteht (z. B. mithilfe von Browserify). Webpack, RequireJS usw.).

Durch Reifen springen

Als Webentwickler müssen wir durch viele Reifen springen. Es ist nicht immer einfach, unsere schönen ES6-Module in etwas zu konvertieren, das Browser interpretieren können.

Die Frage ist, wann ES6-Module ohne all diesen Aufwand im Browser ausgeführt werden.

Die Antwort zum Glück "früher als später".

ECMAScript verfügt derzeit über eine Spezifikation für eine Lösung namens ECMAScript 6 Module Loader API. Kurz gesagt, dies ist eine programmatische, Promise-basierte API, die Ihre Module dynamisch laden und zwischenspeichern soll, damit nachfolgende Importe keine neue Version des Moduls neu laden.

Es wird ungefähr so ​​aussehen:

myModule.js

main.js

Alternativ können Sie Module auch definieren, indem Sie "type = module" direkt im Skript-Tag angeben, wie folgt:

Wenn Sie das Repo für die Modul-Loader-API-Polyfüllung noch nicht ausgecheckt haben, empfehle ich Ihnen dringend, zumindest einen Blick darauf zu werfen.

Wenn Sie diesen Ansatz testen möchten, lesen Sie außerdem SystemJS, das auf der ES6 Module Loader-Polyfüllung basiert. SystemJS lädt dynamisch jedes Modulformat (ES6-Module, AMD, CommonJS und / oder globale Skripte) im Browser und im Knoten. Es verfolgt alle geladenen Module in einer „Modulregistrierung“, um zu vermeiden, dass zuvor geladene Module erneut geladen werden. Ganz zu schweigen davon, dass es auch automatisch ES6-Module transpiliert (wenn Sie einfach eine Option festlegen) und jeden Modultyp von jedem anderen Typ laden kann! Ziemlich ordentlich.

Benötigen wir jetzt noch Bundler, da wir native ES6-Module haben?

Die zunehmende Beliebtheit von ES6-Modulen hat einige interessante Konsequenzen:

Wird HTTP / 2 Modulbündler überflüssig machen?

Mit HTTP / 1 ist nur eine Anforderung pro TCP-Verbindung zulässig. Aus diesem Grund sind beim Laden mehrerer Ressourcen mehrere Anforderungen erforderlich. Mit HTTP / 2 ändert sich alles. HTTP / 2 ist vollständig gemultiplext, was bedeutet, dass mehrere Anforderungen und Antworten parallel erfolgen können. Infolgedessen können wir mehrere Anforderungen gleichzeitig mit einer einzigen Verbindung bearbeiten.

Da die Kosten pro HTTP-Anforderung erheblich niedriger sind als bei HTTP / 1, wird das Laden einer Reihe von Modulen auf lange Sicht kein großes Leistungsproblem darstellen. Einige argumentieren, dass dies bedeutet, dass eine Modulbündelung nicht mehr erforderlich sein wird. Es ist sicherlich möglich, aber es hängt wirklich von der Situation ab.

Zum einen bietet die Modulbündelung Vorteile, die HTTP / 2 nicht berücksichtigt, z. B. das Entfernen nicht verwendeter Exporte, um Platz zu sparen. Wenn Sie eine Website erstellen, bei der es auf die Leistung ankommt, kann die Bündelung auf lange Sicht zusätzliche Vorteile bringen. Wenn Ihre Leistungsanforderungen jedoch nicht so extrem sind, können Sie möglicherweise Zeit bei minimalen Kosten sparen, indem Sie den Erstellungsschritt insgesamt überspringen.

Insgesamt sind wir noch ziemlich weit davon entfernt, dass die meisten Websites ihren Code über HTTP / 2 bereitstellen. Ich bin geneigt vorherzusagen, dass der Build-Prozess zumindest kurzfristig anhält.

PS: Es gibt auch andere Unterschiede zu HTTP / 2, und wenn Sie neugierig sind, finden Sie hier eine großartige Ressource.

Werden CommonJS, AMD und UMD veraltet sein?

Sobald ES6 wird das Modul Standard, brauchen wir andere nicht-native Modulformate wirklich?

Ich bezweifle das.

Die Webentwicklung profitiert stark von der Befolgung einer einzigen standardisierten Methode zum Importieren und Exportieren von Modulen in JavaScript, ohne Zwischenschritte. Wie lange dauert es, bis der Punkt erreicht ist, an dem ES6 der Modulstandard ist?

Die Chancen stehen gut, eine Weile;)

Außerdem gibt es viele Menschen, die gerne „Aromen“ zur Auswahl haben, sodass der „einzig wahrheitsgemäße Ansatz“ möglicherweise nie Realität wird.

Fazit

Ich hoffe, dieser zweiteilige Beitrag hat dazu beigetragen, einige der Jargon-Entwickler zu klären, die bei der Diskussion über Module und Modulbündelung verwendet werden. Schauen Sie sich Teil I an, wenn Sie einen der oben genannten Begriffe verwirrend fanden.

Sprechen Sie wie immer in den Kommentaren mit mir und stellen Sie Fragen!

Viel Spaß beim Bündeln :)