Maison > Article > interface Web > J'ai écrit un bundler de modules. notes, etc.
J'ai construit un simple bundler JavaScript et il s'est avéré beaucoup plus simple que prévu. Je partagerai tout ce que j'ai appris dans cet article.
Lors de l'écriture de grandes applications, il est recommandé de diviser notre code source JavaScript en fichiers js distincts. Cependant, l'ajout de ces fichiers à votre document HTML à l'aide de plusieurs balises de script introduit de nouveaux problèmes tels que
pollution de l'espace de noms global.
conditions de course.
Les bundlers de modules combinent notre code source de différents fichiers en un seul gros fichier, nous aidant ainsi à profiter des avantages des abstractions tout en évitant les inconvénients.
Les bundlers de modules procèdent généralement à cette opération en deux étapes.
Comme mentionné précédemment, nous voici
Voici comment procéder (code JavaScript à venir)
Créez un fichier bundler.js dans votre éditeur de texte et ajoutez le code suivant :
const bundler = (entry)=>{ const graph = createDependencyGraph(entry) const bundle = createBundle(graph) return bundle }
La fonction bundler est l'entrée principale de notre bundler. Il prend le chemin d'accès à un fichier (fichier d'entrée) et renvoie une chaîne (le bundle). À l'intérieur de celui-ci, il génère un graphe de dépendances à l'aide de la fonction createDependencyGraph.
const createDependencyGraph = (path)=>{ const entryModule = createModule(path) /* other code */ }
La fonction createDependencyGraph prend le chemin d'accès au fichier d'entrée. Il utilise la fonction createModule pour générer une représentation de module de ce fichier.
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 } }
La fonction createAsset prend le chemin d'accès à un fichier et lit son contenu dans une chaîne. Cette chaîne est ensuite analysée dans un arbre syntaxique abstrait. Un arbre de syntaxe abstraite est une représentation arborescente du contenu d'un code source. Il peut être assimilé à l’arborescence DOM d’un document HTML. Cela facilite l'exécution de certaines fonctionnalités sur le code telles que la recherche, etc.
Nous créons un ast à partir du module en utilisant l'analyseur babylon.
Ensuite, à l'aide du transpilateur Babel Core, nous convertissons le contenu du code en une syntaxe pré-es2015 pour une compatibilité entre navigateurs.
Ensuite, l'ast est parcouru à l'aide d'une fonction spéciale de babel pour trouver chaque déclaration d'importation de notre fichier source (dépendances).
Nous poussons ensuite ces dépendances (qui sont des chaînes de texte de chemins de fichiers relatifs) dans un tableau de dépendances.
Nous créons également un identifiant pour identifier de manière unique ce module et
Enfin nous renvoyons un objet représentant ce module. Ce module contient un identifiant, le contenu de notre fichier sous forme de chaîne, un tableau de dépendances et le chemin absolu du fichier.
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 }
De retour dans notre fonction createDependencyGraph, nous pouvons maintenant commencer le processus de génération de notre graphique. Notre graphique est un tableau d'objets, chaque objet représentant chaque fichier source utilisé dans notre application.
Nous initialisons notre graphique avec le module d'entrée puis le bouclons. Bien qu'il ne contienne qu'un seul élément, nous ajoutons des éléments à la fin du tableau en accédant au tableau de dépendances du module d'entrée (et aux autres modules que nous ajouterons).
Le tableau des dépendances contient les chemins de fichiers relatifs de toutes les dépendances d'un module. Le tableau est bouclé et pour chaque chemin de fichier relatif, le chemin absolu est d'abord résolu et utilisé pour créer un nouveau module. Ce module enfant est poussé à la fin du graphique et le processus recommence jusqu'à ce que toutes les dépendances aient été converties en modules.
De plus, chaque module donne un objet de mappage qui mappe simplement chaque chemin relatif de dépendance à l'identifiant du module enfant.
Une vérification si un module existe déjà est effectuée sur chaque dépendance pour éviter la duplication de modules et les dépendances circulaires infinies.
Enfin nous renvoyons notre graphe qui contient désormais tous les modules de notre application.
Une fois le graphique de dépendance terminé, la génération d'un bundle impliquera deux étapes
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.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!