Heim > Artikel > Web-Frontend > Ich habe einen Modul-Bundler geschrieben. Notizen usw
Ich habe einen einfachen JavaScript-Bundler erstellt und es stellte sich heraus, dass er viel einfacher war, als ich erwartet hatte. Ich werde alles, was ich gelernt habe, in diesem Beitrag teilen.
Beim Schreiben großer Anwendungen empfiehlt es sich, unseren JavaScript-Quellcode in separate JS-Dateien aufzuteilen. Das Hinzufügen dieser Dateien zu Ihrem HTML-Dokument mithilfe mehrerer Skript-Tags führt jedoch zu neuen Problemen wie
Verschmutzung des globalen Namensraums.
Rennbedingungen.
Modul-Bundler kombinieren unseren Quellcode aus verschiedenen Dateien in einer großen Datei und helfen uns so, die Vorteile von Abstraktionen zu nutzen und gleichzeitig die Nachteile zu vermeiden.
Modul-Bundler erledigen dies im Allgemeinen in zwei Schritten.
Wie bereits erwähnt, hier
So würden wir das machen (JavaScript-Code voraus)
Erstellen Sie eine bundler.js-Datei in Ihrem Texteditor und fügen Sie den folgenden Code hinzu:
const bundler = (entry)=>{ const graph = createDependencyGraph(entry) const bundle = createBundle(graph) return bundle }
Die Bundler-Funktion ist der Haupteintrag unseres Bundlers. Es nimmt den Pfad zu einer Datei (Eintragsdatei) und gibt eine Zeichenfolge (das Bundle) zurück. Darin wird mithilfe der Funktion „createDependencyGraph“ ein Abhängigkeitsdiagramm generiert.
const createDependencyGraph = (path)=>{ const entryModule = createModule(path) /* other code */ }
Die Funktion createDependencyGraph übernimmt den Pfad zur Eintragsdatei. Es verwendet die Funktion „createModule“, um eine Moduldarstellung dieser Datei zu generieren.
let ID = 0 const createModule = (filename)=>{ const content = fs.readFileSync(filename) const ast = babylon.parse(content, {sourceType: “module”}) const {code} = babel.transformFromAst(ast, null, { presets: ['env'] }) const dependencies = [ ] const id = ID++ traverse(ast, { ImportDeclaration: ({node})=>{ dependencies.push(node.source.value) } } return { id, filename, code, dependencies } }
Die Funktion „createAsset“ nimmt den Pfad zu einer Datei und liest deren Inhalt in einen String. Diese Zeichenfolge wird dann in einen abstrakten Syntaxbaum analysiert. Ein abstrakter Syntaxbaum ist eine Baumdarstellung des Inhalts eines Quellcodes. Es kann mit dem DOM-Baum eines HTML-Dokuments verglichen werden. Dadurch ist es einfacher, einige Funktionen des Codes auszuführen, z. B. das Durchsuchen usw.
Wir erstellen einen Ast aus dem Modul mit dem Babylon-Parser.
Als nächstes konvertieren wir mit Hilfe des Babel-Core-Transpilers den Codeinhalt in eine Syntax vor ES2015 für browserübergreifende Kompatibilität.
Anschließend wird der ast mit einer speziellen Funktion von babel durchlaufen, um jede Importdeklaration unserer Quelldatei (Abhängigkeiten) zu finden.
Wir verschieben diese Abhängigkeiten (bei denen es sich um Zeichenfolgen mit relativen Dateipfaden handelt) dann in ein Abhängigkeitsarray.
Außerdem erstellen wir eine ID, um dieses Modul eindeutig zu identifizieren und
Schließlich geben wir ein Objekt zurück, das dieses Modul darstellt. Dieses Modul enthält eine ID, den Inhalt unserer Datei im String-Format, ein Array von Abhängigkeiten und den absoluten Dateipfad.
const createDependencyGraph = (path)=>{ const entryModule = createModule(path) const graph = [ entryModule ] for ( const module of graph) { module.mapping = { } module.dependencies.forEach((dep)=>{ let absolutePath = path.join(dirname, dep); let child = graph.find(mod=> mod.filename == dep) if(!child){ child = createModule(dep) graph.push(child) } module.mapping[dep] = child.id }) } return graph }
Zurück in unserer Funktion createDependencyGraph können wir nun mit der Generierung unseres Diagramms beginnen. Unser Diagramm ist ein Array von Objekten, wobei jedes Objekt jede in unserer Anwendung verwendete Quelldatei darstellt.
Wir initialisieren unseren Graphen mit dem Eingabemodul und führen ihn dann in einer Schleife durch. Obwohl es nur ein Element enthält, fügen wir Elemente am Ende des Arrays hinzu, indem wir auf das Abhängigkeitsarray des Einstiegsmoduls (und anderer Module, die wir hinzufügen werden) zugreifen.
Das Abhängigkeitsarray enthält relative Dateipfade aller Abhängigkeiten eines Moduls. Das Array wird durchlaufen und für jeden relativen Dateipfad wird zunächst der absolute Pfad aufgelöst und zum Erstellen eines neuen Moduls verwendet. Dieses untergeordnete Modul wird an das Ende des Diagramms verschoben und der Prozess beginnt von vorne, bis alle Abhängigkeiten in Module konvertiert wurden.
Außerdem gibt jedes Modul ein Zuordnungsobjekt an, das einfach jeden relativen Abhängigkeitspfad der ID des untergeordneten Moduls zuordnet.
Für jede Abhängigkeit wird geprüft, ob bereits ein Modul vorhanden ist, um eine Duplizierung von Modulen und unendliche zirkuläre Abhängigkeiten zu verhindern.
Zum Schluss geben wir unseren Graphen zurück, der nun alle Module unserer Anwendung enthält.
Nachdem das Abhängigkeitsdiagramm erstellt wurde, umfasst die Erstellung eines Bundles zwei Schritte
We have to convert our module objects to strings so we can be able to write them into the bundle.js file. We do this by initializing moduleString as an empty string. Next we loop through our graph appending each module into the module string as key value pairs, with the id of a module being the key and an array containing two items: first, the module content wrapped in function (to give it scope as stated earlier) and second an object containing the mapping of its dependencies.
const wrapModules = (graph)=>{ let modules = ‘’ graph.forEach(mod => { modules += `${http://mod.id}: [ function (require, module, exports) { ${mod.code} }, ${JSON.stringify(mod.mapping)}, ],`; }); return modules }
Also to note, the function wrapping each module takes a require, export and module objects as arguments. This is because these don’t exist in the browser but since they appear in our code we will create them and pass them into these modules.
This is code that will run immediately the bundle is loaded, it will provide our modules with the require, module and module.exports objects.
const bundle = (graph)=>{ let modules = wrapModules(graph) const result = ` (function(modules) { function require(id) { const [fn, mapping] = modules[id]; function localRequire(name) { return require(mapping[name]); } const module = { exports : {} }; fn(localRequire, module, module.exports); return module.exports; } require(0); })({${modules}})`; return result; }
We use an immediately invoked function expression that takes our module object as an argument. Inside it we define our require function that gets a module from our module object using its id.
It constructs a localRequire function specific to a particular module to map file path string to id. And a module object with an empty exports property
It runs our module code, passing the localrequire, module and exports object as arguments and then returns module.exports just like a node js module would.
Finally we call require on our entry module (index 0).
To test our bundler, in the working directory of our bundler.js file create an index.js file and two directories: a src and a public directory.
In the public directory create an index.html file, and add the following code in the body tag:
<!DOCTYPE html> <html> <head> <title>Module bundler</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <body> <div id='root'></div> <script src= ‘./bundler.js> <script> </body> </html In the src directory create a name.js file and add the following code
const name = “David”
export default name
also create a hello.js file and add the following code
import name from ‘./name.js’
const hello = document.getElementById(“root”)
hello.innerHTML = “hello” + name
Lastly in the index.js file of the root directory import our bundler, bundle the files and write it to a bundle.js file in the public directory
const createBundle = require(“./bundler.js”)
const run = (output , input)=>{
let bundle = creatBundle(entry)
fs.writeFileSync(bundle, ‘utf-8’)
}
run(“./public/bundle.js”, “./src/hello.js”)
Open our index.html file in the browser to see the magic. In this post we have illustrated how a simple module bundler works. This is a minimal bundler meant for understanding how these technologies work behind the hood. please like if you found this insightful and comment any questions you may have.
Das obige ist der detaillierte Inhalt vonIch habe einen Modul-Bundler geschrieben. Notizen usw. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!