Maison  >  Article  >  interface Web  >  J'ai écrit un bundler de modules. notes, etc.

J'ai écrit un bundler de modules. notes, etc.

王林
王林original
2024-07-25 03:10:43427parcourir

I wrote a module bundler. 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.

  1. Recherche de tous les fichiers sources JavaScript, en commençant par le fichier d'entrée. C'est ce qu'on appelle la résolution des dépendances et la carte générée est appelée un graphe de dépendances.
  2. Utiliser le graphe de dépendances pour générer un bundle : une grande chaîne de code source JavaScript pouvant s'exécuter dans un navigateur. Cela pourrait être écrit dans un fichier et ajouté au document HTML à l'aide d'une balise de script.

RÉSOLUTION DE DÉPENDANCE

Comme mentionné précédemment, nous voici

  • prendre un dossier d'entrée,
  • lire et analyser son contenu,
  • Ajoutez-le à un tableau de modules
  • trouver toutes ses dépendances (autres fichiers qu'il importe),
  • Lire et analyser le contenu des dépendances
  • Ajouter des dépendances au tableau
  • Trouver les dépendances des dépendances et ainsi de suite jusqu'à arriver au dernier module

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.

GROUPEMENT

Une fois le graphique de dépendance terminé, la génération d'un bundle impliquera deux étapes

  1. Wrapping each module in a function. This creates the idea of each module having its own scope
  2. Wrapping the module in a runtime.

Wrapping each module

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.

Creating the runtime

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn