Implementierung der CommonJS-Modularität in Browsern ohne Kompilierung/Server

Implementierung der CommonJS-Modularität in Browsern ohne Kompilierung/Server

Einführung

Ich besuche häufig Github. Neben einigen großen Projekten mit extrem hohen Sternen finde ich auf Github auch viele interessante kleine Projekte. Die Projekte oder Ideen sind sehr interessant oder weisen gute technische Aspekte auf und es lohnt sich, sie zu lesen. Daher plane ich, eine „Roaming Github“-Reihe daraus zusammenzustellen und dabei gelegentlich interessante Projekte, die ich auf Github finde, zu teilen und zu interpretieren. In dieser Reihe liegt der Schwerpunkt auf der Erklärung der Prinzipien, ohne auf die Details des Quellcodes einzugehen.

Okay, kommen wir zur Sache. Das in dieser Ausgabe vorgestellte Repository heißt one-click.js.

1. Was ist one-click.js

one-click.js ist eine sehr interessante Bibliothek. Github stellt es folgendermaßen vor:

Wir wissen, dass Sie normalerweise ein Build-/Verpackungstool wie Webpack, Rollup usw. benötigen, wenn Sie möchten, dass der modulare Commonjs-Code normal im Browser ausgeführt wird. Mit One-click.js können Sie das auf CommonJS basierende Modulsystem normal im Browser ausführen, ohne dass Sie diese Build-Tools benötigen.

Darüber hinaus müssen Sie nicht einmal einen Server starten. Sie können beispielsweise versuchen, das Projekt „one-click.js“ zu klonen und einfach auf die Datei „example/index.html“ zu doppelklicken (mit einem Browser öffnen), um es auszuführen.

Es gibt einen Satz im Repo, der seine Funktionen zusammenfasst:

Verwenden Sie CommonJS-Module direkt im Browser ohne Build-Schritt und ohne Webserver.

Zum Beispiel,

Nehmen wir an, dass wir im aktuellen Verzeichnis (demo/) jetzt drei „Modul“-Dateien haben:

demo/plus.js:

// plus.js
modul.exporte = Funktion plus(a, b) {
    gib a + b zurück;
}

demo/divide.js:

//teilen.js
module.exports = Funktion Dividiere(a, b) {
    Rückgabe a / b;
}

Und die Einstiegsmoduldatei demo/main.js:

// Haupt.js
const plus = erfordern('./plus.js');
const teilen = erfordern('./divide.js');
console.log(dividieren(12, addieren(1, 2)));
// Ausgabe: 4

Die übliche Verwendung besteht darin, den Eintrag anzugeben, ihn mit Webpack in ein Paket zu kompilieren und dann im Browser darauf zu verweisen. Mit One-click.js können Sie all dies verwerfen und einfach in HTML verwenden:

<!DOCTYPE html>
<html lang="de">
<Kopf>
    <meta charset="UTF-8">
    <title>Beispiel mit einem Klick</title>
</Kopf>
<Text>
    <script type="text/JavaScript" src="./one-click.js" data-main="./main.js"></script>
</body>
</html>

Beachten Sie die Verwendung des Skripttags, wobei data-main die Eintragsdatei angibt. Öffnen Sie zu diesem Zeitpunkt diese lokale HTML-Datei direkt mit einem Browser und das Ergebnis 7 kann normal ausgegeben werden.

2. Wie funktionieren Verpackungstools?

Im vorherigen Abschnitt wurde die Funktion von one-click.js vorgestellt – der Kern besteht darin, eine Front-End-Modularisierung ohne Verpackung/Erstellung zu erreichen.

Bevor wir die interne Implementierung vorstellen, wollen wir zunächst verstehen, was das Verpackungstool macht. Wie das Sprichwort sagt: „Wer sich selbst und seinen Feind kennt, kann hundert Schlachten schlagen, ohne Gefahr zu laufen, eine Niederlage zu erleiden.“

Nochmals unsere drei JavaScript-Dateien.

plus.js:

// plus.js
modul.exporte = Funktion plus(a, b) {
    gib a + b zurück;
}

teilen.js:

//teilen.js
module.exports = Funktion Dividiere(a, b) {
    Rückgabe a / b;
}

Und das Einstiegsmodul main.js:

// Haupt.js
const plus = erfordern('./plus.js');
const teilen = erfordern('./divide.js');
console.log(dividieren(12, addieren(1, 2)));
// Ausgabe: 4

Denken Sie daran, dass wir bei Verwendung von Webpack den Einstiegspunkt (main.js) angeben. webpack verpackt basierend auf dem Eintrag ein Paket (z. B. bundle.js). Schließlich können wir das verarbeitete bundle.js in die Seite importieren. Zu diesem Zeitpunkt hat bundle.js zusätzlich zum Quellcode viele „private Güter“ von webpack hinzugefügt.

Lassen Sie uns kurz die mit Webpack verbundene Arbeit klären:

  • Abhängigkeitsanalyse: Beim Verpacken ermittelt webpack zunächst die Modulabhängigkeiten basierend auf den Ergebnissen der Syntaxanalyse. Einfach ausgedrückt werden in CommonJS die Untermodule, von denen das aktuelle Modul abhängt, basierend auf der analysierten Anforderungssyntax abgerufen.
  • Bereichsisolierung und Variableninjektion: Webpack schließt jede Moduldatei in eine Funktion ein. Dadurch können nicht nur Variablen wie Module und Anforderungen eingefügt, sondern auch der Bereich isoliert werden, um eine globale Verschmutzung der Variablen zu verhindern.
  • Modullaufzeit bereitstellen: Um Anforderungen und Exporte effektiv auszuführen, ist schließlich auch ein Satz Laufzeitcode erforderlich, um das Laden, Ausführen, Exportieren und andere Funktionen des Moduls zu implementieren.

Wenn Sie die oben genannten Punkte 2 und 3 nicht verstehen, können Sie in diesem Artikel mehr über das Modullaufzeitdesign von webpack erfahren.

3. Herausforderungen, vor denen wir stehen

Ohne ein Build-Tool müssen Sie zum Ausführen eines Moduls mit CommonJS direkt im Browser eine Möglichkeit finden, die drei oben genannten Aufgaben zu erfüllen:

  • Abhängigkeitsanalyse
  • Bereichsisolierung und Variableneinfügung
  • Bereitstellen einer Modullaufzeit

Die Lösung dieser drei Probleme ist die Kernaufgabe von one-click.js. Sehen wir uns an, wie man sie nacheinander lösen kann.

3.1. Abhängigkeitsanalyse

Dies ist ein lästiges Problem. Um Module korrekt zu laden, müssen Sie die Abhängigkeiten zwischen den Modulen genau kennen. Beispielsweise hängen die drei oben genannten Moduldateien – main.js – von plus.js und divide.js ab. Wenn Sie den Code in main.js ausführen, müssen Sie daher sicherstellen, dass plus.js und divide.js in die Browserumgebung geladen wurden. Das Problem besteht jedoch darin, dass wir ohne Kompilierungstools die Abhängigkeiten zwischen Modulen natürlich nicht automatisch erkennen können.

Bei einer Modulbibliothek wie RequireJS deklariert es die Abhängigkeiten des aktuellen Moduls im Code und verwendet dann asynchrones Laden plus Rückruf. Offenbar verfügt die CommonJS-Spezifikation nicht über eine solche asynchrone API.

One-click.js verwendet eine knifflige, aber kostspielige Methode zur Analyse von Abhängigkeiten – das zweimalige Laden der Moduldatei. Wenn die Moduldatei zum ersten Mal geladen wird, wird eine simulierte require-Methode für die Moduldatei bereitgestellt. Wenn das Modul diese Methode aufruft, kann es in require erkennen, von welchen Untermodulen das aktuelle Modul abhängig ist.

// Haupt.js
const plus = erfordern('./plus.js');
const teilen = erfordern('./divide.js');
console.log(minus(12, add(1, 2)));

Beispielsweise können wir in main.js oben eine require-Methode wie die folgende bereitstellen:

const aufgezeichnetesFieldAccessesByRequireCall = {};
const erfordern = Funktion sammeln(modPath) {
    aufgezeichneteFeldzugriffeDurchRequireCall[modPath] = true;
    var Skript = Dokument.createElement('Skript');
    script.src = modPfad;
    Dokument.Body.AppendChild(Skript);
};

Nachdem main.js geladen wurde, werden zwei Dinge getan:

  • Notieren Sie die Untermodule, von denen das aktuelle Modul abhängt.
  • Laden Sie das Untermodul.

Auf diese Weise können wir die Abhängigkeiten des aktuellen Moduls in recordedFieldAccessesByRequireCall aufzeichnen und die Untermodule gleichzeitig laden. Untermodule können auch rekursiv bearbeitet werden, bis keine neuen Abhängigkeiten mehr auftreten. Schließlich ist die Integration des „recordedFieldAccessesByRequireCall“ jedes Moduls unsere Abhängigkeitsbeziehung.

Wenn wir darüber hinaus wissen möchten, welche Methoden in den Untermodulen tatsächlich von main.js aufgerufen werden, können wir mit Proxy ein Proxy-Objekt zurückgeben und weitere Abhängigkeiten zählen:

const erfordern = Funktion sammeln(modPath) {
    aufgezeichneteFeldzugriffeDurchAnforderungsaufruf[modPath] = [];
    var megaProxy = neuer Proxy(Funktion(){}, {
        get: Funktion(Ziel, Eigenschaft, Empfänger) {
            wenn(prop == Symbol.toPrimitive) {
                Rückgabefunktion () {0;};
            }
            gib MegaProxy zurück;
        }
    });
    var Datensatzfeldzugriff = neuer Proxy(Funktion(){}, {
        get: Funktion(Ziel, Eigenschaft, Empfänger) {
            window.aufgezeichneteFeldzugriffedurcherforderlichenAufruf[modPath].push(prop);
            gib MegaProxy zurück;
        }
    });
    // ...eine andere Verarbeitung return recordFieldAccess;
};

Der obige Code zeichnet die verwendeten Attribute auf, wenn Sie die Attribute des importierten Moduls abrufen.

Das Laden aller oben genannten Module ist der erste Durchgang dessen, was wir „zweimaliges Laden“ nennen und der Analyse von Abhängigkeiten dient. Beim zweiten Mal müssen Sie das Modul nur basierend auf der Abhängigkeit des Eingabemoduls „umgekehrt“ laden. Wenn main.js beispielsweise von plus.js und dividende.js abhängt, ist die tatsächliche Ladereihenfolge plus.js->divide.js->main.js.

Es ist erwähnenswert, dass beim ersten Laden aller Module diese Module bei der Ausführung grundsätzlich Fehler melden (weil die Ladereihenfolge der Abhängigkeiten falsch ist). Wir werden die Ausführungsfehler ignorieren und uns nur auf die Analyse der Abhängigkeiten konzentrieren. Nachdem Sie die Abhängigkeiten abgerufen haben, laden Sie alle Moduldateien in der richtigen Reihenfolge neu. Es gibt eine vollständigere Implementierung in one-click.js. Die Methode heißt scrapeModuleIdempotent. Den spezifischen Quellcode finden Sie hier.

An dieser Stelle denken Sie vielleicht: „Das ist Verschwendung, jede Datei zweimal zu laden.“

Tatsächlich ist dies auch der Kompromiss von one-click.js:

Damit dies offline funktioniert, muss One Click Ihre Module zweimal initialisieren, einmal im Hintergrund beim Laden der Seite, um das Abhängigkeitsdiagramm abzubilden, und dann ein weiteres Mal, um das Modul tatsächlich zu laden.

3.2. Bereichsisolation

Wir wissen, dass Module eine sehr wichtige Funktion haben – die Bereiche zwischen Modulen sind isoliert. Beispielsweise für das folgende gewöhnliche JavaScript-Skript:

// normales script.js
var foo = 123;

Wenn es in den Browser geladen wird, wird die Variable foo tatsächlich zu einer globalen Variable und kann über window.foo aufgerufen werden. Dies führt auch zu globaler Verschmutzung und Variablen und Methoden zwischen Modulen können in Konflikt geraten und sich gegenseitig überschreiben.

Aufgrund der Verwendung der CommonJS-Spezifikation ist in der NodeJS-Umgebung beim Importieren einer Moduldatei wie der oben stehenden der Gültigkeitsbereich der Variable foo nur auf das Quellmodul beschränkt und beeinträchtigt den globalen Gültigkeitsbereich nicht. In Bezug auf die Implementierung umschließt NodeJS den Code im Modul tatsächlich mit einer Wrap-Funktion. Wie wir alle wissen, bildet eine Funktion ihren eigenen Bereich und erreicht so eine Isolation.

NodeJS verpackt die Quellcodedateien bei Bedarf und Verpackungstools wie webpack schreiben die Quellcodedateien während der Kompilierung neu (auch ähnliche Verpackung). Da one-click.js kein Kompilierungstool hat, funktioniert das Umschreiben zur Kompilierungszeit definitiv nicht. Was sollen wir also tun? Hier sind zwei gängige Methoden:

3.2.1. Dynamische Codeausführung in JavaScript

Eine Möglichkeit besteht darin, den Textinhalt im Skript über eine Abrufanforderung abzurufen und dann die dynamische Codeausführung über eine neue Funktion oder Auswertung zu implementieren. Hier ist eine Einführung in die Verwendung der Funktion fetch+new:

Unter Verwendung des obigen Divisionsmoduls divide.js und einer kleinen Modifikation lautet der Quellcode wie folgt:

// Wenn diese Variable als Skript geladen wird, wird sie zur globalen Variable von window.outerVar, was zu einer Verschmutzung führt. var outerVar = 123;

modul.exporte = Funktion (a, b) {
    Rückgabe a / b;
}

Lassen Sie uns nun die Bereichsabschirmung implementieren:

const modMap = {};
Funktion erfordern(modPath) {
    wenn (modMap[modPath]) {
        gibt modMap[modPath].exports zurück;
    }
}

holen('./divide.js')
    .dann(res => res.text())
    .dann(Quelle => {
        const mod = neue Funktion('Exporte', 'erfordern', 'Modul', Quelle);
        const modObj = {
            ID: 1,
            Dateiname: './divide.js',
            Eltern: null,
            Kinder: [],
            Exporte: {}
        };

        mod(modObj.exports, erfordern, modObj);
        modMap['./divide.js'] = modObj;
        zurückkehren;
    })
    .then(() => {
        const teilen = erfordern('./divide.js')
        konsole.log(divide(10, 2)); // 5
        console.log(window.outerVar); // undefiniert
    });

Der Code ist sehr einfach. Der Kern besteht darin, den Quellcode durch Abrufen abzurufen, ihn über eine neue Funktion in eine Funktion zu konstruieren und beim Aufruf einige Modullaufzeitvariablen darin „einzufügen“. Um den reibungslosen Ablauf des Codes zu gewährleisten, wird auch eine einfache „Require“-Methode zum Implementieren der Modulreferenz bereitgestellt.

Natürlich ist das oben genannte eine Lösung, aber es funktioniert nicht im Rahmen des Ziels von one-click.js. Da one-click.js auch darauf abzielt, ohne Server (offline) ausgeführt zu werden, sind Abrufanforderungen ungültig.

Wie geht one-click.js damit um? Schauen wir uns Folgendes an:

3.2.2. Eine andere Möglichkeit, Bereiche zu isolieren

Generell ist die Notwendigkeit der Isolation der einer Sandbox sehr ähnlich, und eine gängige Methode zum Erstellen einer Sandbox auf dem Front-End ist die Verwendung eines Iframes. Der Einfachheit halber nennen wir das vom Benutzer tatsächlich verwendete Fenster das „Hauptfenster“ und das darin eingebettete Iframe das „Unterfenster“. Aufgrund der natürlichen Eigenschaften von Iframes verfügt jedes untergeordnete Fenster über sein eigenes Fensterobjekt und ist voneinander isoliert, sodass weder das Hauptfenster noch die anderen Fenster beeinträchtigt werden.

Im Folgenden wird noch das Laden des Moduls divide.js als Beispiel verwendet. Zuerst konstruieren wir ein Iframe, um das Skript zu laden:

var iframe = document.createElement("iframe");
iframe.style = "Anzeige: keine !wichtig";
Dokument.body.appendChild(iframe);
var doc = iframe.contentWindow.document;
var htmlStr = `
    <html><Kopf><Titel></Titel></Kopf></Körper>
    <script src="./divide.js"></script></body></html>
`;
doc.öffnen();
doc.write(htmlStr);
doc.schließen();

Auf diese Weise können Sie Modulskripte in einem „isolierten Bereich“ laden. Da es aber offensichtlich noch nicht richtig funktioniert, besteht der nächste Schritt darin, die Import- und Exportfunktionen des Moduls fertigzustellen. Das Problem, das beim Modulexport gelöst werden muss, besteht darin, dem Hauptfenster den Zugriff auf das Modulobjekt im Unterfenster zu ermöglichen. So können wir das Skript im Unterfenster in die Variable des Hauptfensters einbinden, nachdem es geladen und ausgeführt wurde.

Ändern Sie den obigen Code:

// ...wiederholten Code weglassen var htmlStr = `
    <html><Kopf><Titel></Titel></Kopf></Körper>
    <Skript>
        Fenster.erfordern = übergeordnetes Fenster.erfordern;
        window.exports = window.module.exports = nicht definiert;
    </Skript>
    <script src="./divide.js"></script>
    <Skript>
        wenn (window.module.exports !== undefiniert) {
            parent.window.modObj['./divide.js'] = Fenster.module.exports;
        }
    </Skript>
    </body></html>
`;
// ...wiederholten Code weglassen

Der Kern besteht darin, durch Methoden wie parent.window eine „Durchdringung“ zwischen dem Hauptfenster und dem untergeordneten Fenster zu erreichen:

  • Montieren Sie das Objekt des Unterfensters am Hauptfenster.
  • Es unterstützt auch die Funktion des Unterfensters, das die Methoden im Hauptfenster aufruft.

Das Obige ist nur eine grobe Implementierung des Prinzips. Wenn Sie an genaueren Implementierungsdetails interessiert sind, können Sie sich die Methode loadModuleForModuleData im Quellcode ansehen.

Erwähnenswert ist, dass in „3.1. Abhängigkeitsanalyse“ erwähnt wird, dass alle Module einmal geladen werden sollten, um Abhängigkeiten zu erhalten, und dass dieser Teil des Ladens auch im Iframe ausgeführt wird, wodurch auch „Verschmutzung“ verhindert werden muss.

3.3. Bereitstellung einer Modullaufzeit

Die Laufzeitversion eines Moduls umfasst das Erstellen eines Modulobjekts, das Speichern des Modulobjekts und das Bereitstellen einer Modulimportmethode (erforderlich). Die verschiedenen Implementierungen der Modullaufzeit sind im Allgemeinen ähnlich. Hier muss beachtet werden, dass, wenn die Isolationsmethode Iframe verwendet, einige Laufzeitmethoden und -objekte zwischen dem Hauptfenster und dem Unterfenster übergeben werden müssen.

Natürlich können die Details auch Unterstützung für die Modulpfadauflösung, die zirkuläre Abhängigkeitsverarbeitung, die Fehlerbehandlung usw. erfordern. Da die Implementierung dieses Teils vielen Bibliotheken ähnelt oder nicht besonders wichtig ist, wird sie hier nicht im Detail vorgestellt.

4. Fazit

Lassen Sie uns abschließend den allgemeinen Betriebsablauf zusammenfassen:

1. Holen Sie sich zuerst das Eingabemodul von der Seite. In one-click.js ist es document.querySelector("script[data-main]").dataset.main;

2. Laden Sie das Modul im Iframe und sammeln Sie Modulabhängigkeiten in der Require-Methode, bis keine neuen Abhängigkeiten mehr erscheinen.

3. Nachdem die Sammlung abgeschlossen ist, wird das vollständige Abhängigkeitsdiagramm erhalten.

4. Laden Sie gemäß dem Abhängigkeitsdiagramm die entsprechenden Moduldateien „rückwärts“, verwenden Sie Iframe, um den Bereich zu isolieren, und achten Sie darauf, die Modullaufzeit im Hauptfenster an jedes Unterfenster zu übergeben.

5. Wenn schließlich das Einstiegsskript geladen ist, stehen alle Abhängigkeiten bereit und können direkt ausgeführt werden.

Im Allgemeinen ist es ohne die Hilfe von Build-Tools und Servern schwierig, eine Abhängigkeitsanalyse und Bereichsisolierung zu implementieren. One-click.js löst diese Probleme mit den oben genannten technischen Mitteln.

Kann one-click.js also in Produktionsumgebungen verwendet werden? Offensichtlich nicht.

Verwenden Sie dies nicht in der Produktion. Der einzige Zweck dieses Dienstprogramms besteht darin, die lokale Entwicklung zu vereinfachen.

Achten Sie also darauf, dass der Autor auch sagte, dass der Zweck dieser Bibliothek nur darin besteht, die lokale Entwicklung zu erleichtern. Natürlich können wir einige dieser technischen Mittel auch als Lehrmaterial kennenlernen. Interessierte Freunde können das One-Click.js-Repository besuchen, um mehr zu erfahren.

Oben sind die Details zur Implementierung der CommonJS-Modularisierung in Browsern ohne Kompilierung/Server aufgeführt. Weitere Informationen zur Implementierung der CommonJS-Modularisierung ohne Kompilierung/Server finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM!

Das könnte Sie auch interessieren:
  • Verstehen der Unterschiede zwischen CommonJS- und ES-Modulen mithilfe von Webpack
  • Node-Modulspezifikation für CommonJS
  • Grundlegendes zum Laden von CommonJS-Modulen
  • Unterschiede zwischen der Modulbehandlung in ES6 und CommonJS
  • Detaillierte Diskussion der Unterschiede zwischen CommonJS-Modulen und ES6-Modulen
  • Tiefgreifendes Verständnis der Commonjs-Spezifikationen und der Node-Modulimplementierung
  • Verwenden Sie Browserify, um die CommonJS-Browser-Lademethode zu implementieren
  • Teilen Sie ein äußerst nützliches JavaScript-Verpackungs- und Komprimierungstool
  • Detaillierte Erklärung zur Verwendung von Webpack zum Verpacken von JS

<<:  Eine kurze Diskussion zur MySQL-Indexoptimierungsanalyse

>>:  Detaillierte Erklärung zur Identifizierung von Dateien mit gleichem Inhalt unter Linux

Artikel empfehlen

So installieren Sie eine virtuelle Maschine mit Windows-Diensten auf dem Mac

1. Laden Sie die virtuelle Maschine herunter Offi...

jQuery-Plugin zur Implementierung des Minesweeper-Spiels (1)

Dieser Artikel teilt den spezifischen Code des er...

So installieren und verwenden Sie Cockpit unter CentOS 8/RHEL 8

Cockpit ist ein webbasiertes Serververwaltungstoo...

Echarts-Tutorial zur Implementierung von Baumdiagrammen

Treemaps dienen vor allem der Visualisierung baum...

Einrichten der React-Native-Umgebung und grundlegende Einführung

Umgebungsvorbereitung 1. Umweltkonstruktion React...

Standard-CSS-Stil der XHTML-Sprache

html,Adresse, Blockzitat, Körper, dd, div, dl,dt,...

Detailliertes Tutorial zur Installation von VirtualBox 6.0 auf CentOS 8 / RHEL 8

VirtualBox ist ein kostenloses Open Source-Virtua...

Konkretes Beispiel einer MySQL-Mehrtabellenabfrage

1. Verwenden Sie die SELECT-Klausel, um mehrere T...

CSS steuert den Abstand zwischen Wörtern durch die Eigenschaft „letter-spacing“

Eigenschaft „letter-spacing“ : Vergrößern oder ve...

5 häufig verwendete Objekte in JavaScript

Inhaltsverzeichnis 1. JavaScript-Objekte 1).Array...