"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > J'ai écrit un bundler de modules. notes, etc.

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

Publié le 2024-08-05
Parcourir:407

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 bundles de modules combinent notre code source de différents fichiers en un seul gros fichier, nous aidant à 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. Utilisation du 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

  • prendre un fichier 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, 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. Envelopper chaque module dans une fonction. Cela crée l'idée que chaque module a sa propre portée
  2. Encapsulation du module dans un runtime.

Emballage de chaque module

Nous devons convertir nos objets de module en chaînes afin de pouvoir les écrire dans le fichier bundle.js. Nous faisons cela en initialisant moduleString comme une chaîne vide. Ensuite, nous parcourons notre graphique en ajoutant chaque module dans la chaîne du module sous forme de paires clé-valeur, l'identifiant d'un module étant la clé et un tableau contenant deux éléments : d'abord, le contenu du module enveloppé dans la fonction (pour lui donner la portée comme indiqué précédemment ) et d'autre part un objet contenant le mappage de ses dépendances.

const wrapModules = (graph)=>{
         let modules = ‘’
           graph.forEach(mod => {
    modules  = `${http://mod.id}: [
      function (require, module, exports) {
        ${mod.code}
      },
      ${JSON.stringify(mod.mapping)},
    ],`;
  });
return modules
}

À noter également que la fonction encapsulant chaque module prend comme arguments un objet require, export et module. En effet, ceux-ci n'existent pas dans le navigateur mais comme ils apparaissent dans notre code, nous allons les créer et les transmettre à ces modules.

Création du runtime

Il s'agit d'un code qui s'exécutera immédiatement lorsque le bundle sera chargé, il fournira à nos modules les objets require, module et module.exports.

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;
}

Nous utilisons une expression de fonction immédiatement invoquée qui prend notre objet module comme argument. À l'intérieur, nous définissons notre fonction require qui récupère un module de notre objet module en utilisant son identifiant.
Il construit une fonction localRequire spécifique à un module particulier pour mapper la chaîne du chemin du fichier à l'identifiant. Et un objet module avec une propriété d'exportation vide
Il exécute le code de notre module, en passant les objets localrequire, module et exports comme arguments, puis renvoie module.exports comme le ferait un module node js.
Enfin nous appelons require sur notre module d'entrée (index 0).

Pour tester notre bundler, dans le répertoire de travail de notre fichier bundler.js créez un fichier index.js et deux répertoires : un src et un répertoire public.

Dans le répertoire public, créez un fichier index.html et ajoutez le code suivant dans la balise body :


    
        Module bundler

const nom = « David »
exporter le nom par défaut

also create a hello.js file and add the following code

importer le nom depuis './name.js'
const bonjour = document.getElementById("root")
bonjour.innerHTML = « bonjour » nom

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 = (sortie, entrée) =>{
laissez bundle = creatBundle (entrée)
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.
Déclaration de sortie Cet article est reproduit sur : https://dev.to/frontendokeke/i-wrote-a-module-bundler-notes-etc-4ofa?1 En cas de violation, veuillez contacter [email protected] pour le supprimer.
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3