Partage de technologie

Webpack : logique de packaging de trois produits Chunk

2024-07-08

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Aperçu

  • dans l'article précédent Webpack : Dependency Graph gère les dépendances entre modules , nous avons expliqué en détail comment la phase de "construction" commence à partir d'Entrée et lit et analyse progressivement de manière récursive le contenu du module, et construit enfin le graphe de dépendances du module - l'objet ModuleGraph. Dans cet article, nous continuons à expliquer comment organiser les Chunks basés sur le contenu ModuleGraph dans la prochaine étape « d'encapsulation », et à développer davantage le processus principal des objets de dépendance ChunkGroup et ChunkGraph.

En plus du processus principal, nous expliquerons également en détail plusieurs concepts vagues :

  • Que sont les objets Chunk, ChunkGroup et ChunGraph ? Quel type d’interaction y a-t-il entre eux ?
  • Règles de sous-package par défaut de Webpack et problèmes dans les règles.

Processus de création de ChunkGraph

devant Init, Créer, Sceller"Dans ", nous avons introduit que la logique de construction sous-jacente de Webpack peut être grossièrement divisée en : "Initialisation, construction, packaging"trois phases :

Insérer la description de l'image ici

dans,"Construction"La phase est chargée d'analyser les dépendances entre les modules et d'établir les Graphique de dépendance(ModuleGraph); puis, dans "Encapsulation", basée sur le graphe de dépendances, les modules sont encapsulés séparément dans plusieurs objets Chunk et les relations de dépendance parent-enfant entre les Chunks sont triées dans ChunkGraph et plusieurs objets ChunkGroup.

L'objectif le plus important de la phase « d'encapsulation » est de construire le graphe de relations ChunkGraph basé sur le graphe de relations ModuleGraph collecté lors de la phase de « construction ». La logique de ce processus est relativement complexe :

Veuillez ajouter une description de l'image

Analysons ici brièvement la logique de mise en œuvre de plusieurs étapes importantes.

La première étape est très critique : transfertseal() Après la fonction, parcourezentry Configuration, créez-en un vide pour chaque entréeChunk etPoint d'accès objet (un objet spécialChunkGroup), et configurez initialement la base ChunkGraph Relation structurelle, préparez-vous à l'étape suivante, code clé :

class Compilation {
  seal(callback) {
    // ...
    const chunkGraphInit = new Map();
    // 遍历入口模块列表
    for (const [name, { dependencies, includeDependencies, options }] of this
      .entries) {
      // 为每一个 entry 创建对应的 Chunk 对象
      const chunk = this.addChunk(name);
      // 为每一个 entry 创建对应的 ChunkGroup 对象
      const entrypoint = new Entrypoint(options);
      // 关联 Chunk 与 ChunkGroup
      connectChunkGroupAndChunk(entrypoint, chunk);

      // 遍历 entry Dependency 列表
      for (const dep of [...this.globalEntry.dependencies, ...dependencies]) {
        // 为每一个 EntryPoint 关联入口依赖对象,以便下一步从入口依赖开始遍历其它模块
        entrypoint.addOrigin(null, { name }, /** @type {any} */ (dep).request);

        const module = this.moduleGraph.getModule(dep);
        if (module) {
          // 在 ChunkGraph 中记录入口模块与 Chunk 关系
          chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
          // ...
        }
      }
    }
    // 调用 buildChunkGraph 方法,开始构建 ChunkGraph
    buildChunkGraph(this, chunkGraphInit);
    // 触发各种优化钩子
    // ...
  }
}

Une fois l’exécution terminée, la structure de données suivante est formée :
Insérer la description de l'image ici

Deuxièmement, si configuré à ce moment entry.runtime, Webpack fournira également le code d'exécution à ce stade créer Le Chunk correspondant et directementdistribuer Donnerentry correspondantChunkGroup objet.Appelé quand tout est prêtconstruireChunkGraph fonction, passez à l’étape suivante.

Deuxième étape : existerbuildChunkGraph dans la fonctiontransfert visitModules Fonction, parcourez le ModuleGraph et attribuez tous les modules à différents modules en fonction de leurs dépendances.Chunk Objet ; s'il est rencontré au cours de ce processus.module asynchrone, puis le module créernouveau ChunkGroup etChunk Objet, formant finalement la structure de données suivante :
Veuillez ajouter une description de l'image

troisième étape: existerbuildChunkGraph en fonctiontransfert connectChunkGroups méthode, établirChunkGroup entre,Chunk dépendances entre elles pour générer un ensemble completChunkGraph Objet, formant finalement la structure de données suivante :
Veuillez ajouter une description de l'image

la quatrième étape : existerbuildChunkGraph en fonctiontransfert cleanupUnconnectedGroups Méthode, le nettoyage n'est pas valideChunkGroup, joue principalement un rôle dans l’optimisation des performances.

Après avoir parcouru ces quatre étapes de haut en bas,ModuleGraph Les modules stockés dans seront attribués à trois objets Chunk différents : Entry, Async et Runtime selon la nature du module lui-même, et les dépendances entre Chunks seront stockées dans les collections ChunkGraph et ChunkGroup. Vous pouvez continuer en fonction de ces objets. Modifier ultérieurement les politiques de sous-traitance (par ex.SplitChunksPlugin), réaliser une optimisation de la sous-traitance en réorganisant et en attribuant la propriété des objets Module et Chunk.

Chunk contre ChunkGroup contre ChunkGraph

Le processus de construction ci-dessus implique trois objets clés : Chunk, ChunkGroup et ChunkGraph. Résumons d'abord leurs concepts et fonctions pour approfondir notre compréhension :

  • Chunk: Le module est utilisé pour lire le contenu du module, enregistrer les dépendances inter-modules, etc. ; tandis que Chunk fusionne plusieurs modules en fonction des dépendances des modules et les génère dans des fichiers d'actifs (la logique de fusion et de sortie des produits sera expliquée dans le chapitre suivant) :

Veuillez ajouter une description de l'image

  • ChunkGroup:un ChunkGroup contient un ou plusieursChunk objet;ChunkGroup etChunkGroup Une relation de dépendance parent-enfant se noue entre :

Veuillez ajouter une description de l'image

  • ChunkGraph: Enfin, Webpack stockera les dépendances entre Chunks et ChunkGroups dans compilation.chunkGraph Dans l'objet, les relations de type suivantes sont formées :
    Veuillez ajouter une description de l'image

Règles de sous-traitance par défaut

Ci-dessus ChunkGraph Le processus de construction organisera finalement le module en trois types différents de morceaux :

  • Morceau d'entrée : le même entry Les modules atteints par la touche inférieure sont organisés en un Chunk ;
  • Async Chunk : Le module asynchrone est organisé séparément en un Chunk ;
  • Bloc d'exécution :entry.runtime Lorsqu'il n'est pas vide, le module d'exécution sera organisé en un Chunk.

Ceci est intégré à Webpack, s'il n'est pas utilisé splitChunks Dans le cas d'autres plug-ins, les règles par défaut de mappage des entrées et sorties du module sont l'un des principes clés sous-jacents de Webpack, il est donc nécessaire d'introduire les règles spécifiques de chaque Chunk.

Partie d'entrée :

En commençant par le Entry Chunk, Webpack va d'abord entry créerChunk Objet, par exemple, la configuration suivante :

module.exports = {
  entry: {
    main: "./src/main",
    home: "./src/home",
  }
};

traverser entry propriétés de l'objet et créerchunk[main]chunk[home] Deux objets, à ce moment les deux Chunks contiennent respectivementmainhome Module:
Insérer la description de l'image ici

Une fois l'initialisation terminée, Webpack ModuleGraph les données de dépendance, serontentry Tous les modules touchés par ce qui suit sont insérés dans le Chunk (se produisant dansvisiterModules méthode), par exemple, pour les dépendances de fichiers suivantes :
Insérer la description de l'image ici

main.js Si les quatre fichiers a/b/c/d sont référencés directement ou indirectement de manière synchrone, Webpack va d'abordmain.js Le module crée des objets Chunk et EntryPoint, puis ajoute progressivement des modules a/b/c/d àchunk[main] , formant finalement :
Insérer la description de l'image ici

Morceau asynchrone :

Deuxièmement, Webpack compilera chaque instruction d'importation asynchrone (import(xxx) etrequire.ensure ) est traité comme un objet Chunk distinct et tous ses sous-modules sont ajoutés à ce Chunk - nous l'appelons Async Chunk. Par exemple, pour l'exemple suivant :

// index.js
import './sync-a.js'
import './sync-b.js'

import('./async-a.js')

// async-a.js
import './sync-c.js'

Dans le module d'entrée index.js Dans, sync-a et sync-b sont introduits de manière synchrone ; le module async-a est introduit de manière asynchrone, dans async-a, le module async-a est introduit de manière synchrone ;sync-c module, formant le diagramme de dépendances de module suivant :
Veuillez ajouter une description de l'image

À ce stade, Webpack sera le point d'entrée index.js, module asynchrone async-a.js Créez des sous-packages séparément pour former la structure Chunk suivante :
Insérer la description de l'image ici

et chunk[index] etchunk[async-a] Une dépendance à sens unique se forme entre eux, et Webpack enregistrera cette dépendance dansChunkGroup._parentsChunkGroup._children dans les propriétés.

Bloc d'exécution :

Enfin, sauf entry , En plus des modules asynchrones, Webpack5 prend également en charge l'extraction du code d'exécution séparément dans des morceaux. Le code d'exécution mentionné ici fait référence à une série de codes de structure de base injectés par Webpack pour garantir que le produit packagé peut fonctionner normalement. Par exemple, la structure courante du produit packagé Webpack est la suivante :
Veuillez ajouter une description de l'image
La grande section de code entourée dans la case rouge dans l'image ci-dessus est le code d'exécution généré dynamiquement par Webpack Lors de la compilation, Webpack décidera quel code d'exécution sortir en fonction du code métier (en fonction de).Dependency sous-classe), par exemple :

  • besoin __webpack_require__.f__webpack_require__.r et d'autres fonctions pour obtenir un support modulaire minimum ;
  • Si vous utilisez la fonctionnalité de chargement dynamique, vous devez écrire __webpack_require__.e fonction;
  • Si vous utilisez la fonctionnalité Module Federation, vous devez écrire __webpack_require__.o fonction;
  • etc.

Bien que chaque morceau de code d'exécution puisse être petit, à mesure que les fonctionnalités augmentent, le résultat final deviendra de plus en plus grand. Surtout pour les applications à entrées multiples, il est un peu inutile de regrouper à plusieurs reprises un runtime similaire à chaque entrée. entry.runtime Les éléments de configuration sont utilisés pour déclarer comment le code d'exécution est empaqueté.Pour l'utilisation, utilisez simplemententry Ajouter une forme de chaîne à l'élémentruntime valeur, par exemple :

module.exports = {
  entry: {
    index: { import: "./src/index", runtime: "solid-runtime" },
  }
};

exister compilation.seal Dans la fonction, Webpack est d'abordentry créerEntryPoint, alors juge entry La configuration contient-elleruntime propriétés, s'il y en a, créez-les avecruntime La valeur est le nom du Chunk. Par conséquent, l'exemple de configuration ci-dessus générera deux Chunks :chunk[index.js]chunk[solid-runtime], et sur cette base, deux fichiers sont finalement générés :

  • Correspondant à l'indice d'entrée index.js document;
  • Configuration d'exécution correspondant à solid-runtime.js document.

dans de nombreux entry Dans le scénario, juste pour chacunentry Tous réglés de la même manièreruntime valeur, le code d'exécution du Webpack sera fusionné et écrit dans le même Runtime Chunk, permettant ainsi d'optimiser les performances du produit. Par exemple, pour la configuration suivante :

module.exports = {
  entry: {
    index: { import: "./src/index", runtime: "solid-runtime" },
    home: { import: "./src/home", runtime: "solid-runtime" },
  }
};

Entrée indexhome partager la même choseruntime valeur, et enfin générer trois Chunks, respectivement :
Veuillez ajouter une description de l'image

Entrée à cette heure chunk[index]chunk[home] avec exécutionchunk[solid-runtime] Une relation de dépendance parent-enfant va également se nouer.

Problèmes avec les règles de sous-traitance

Le plus gros problème avec les règles de sous-packaging par défaut est qu'elles ne peuvent pas résoudre la duplication de module. Si plusieurs fragments contiennent le même module en même temps, alors ce module sera regroupé à plusieurs reprises dans ces fragments sans restriction. Par exemple, supposons que nous ayons deux entrées main/index qui dépendent toutes deux du même module :
Insérer la description de l'image ici

Par défaut, Webpack n'effectuera pas de traitement supplémentaire à ce sujet, mais emballera simplement le module c dans les deux morceaux principaux/index en même temps, formant finalement :

Insérer la description de l'image ici

peut être vu chunk sont isolés les uns des autres, le module c est emballé à plusieurs reprises, ce qui peut entraîner une perte de performances inutile pour le produit final !

Pour résoudre ce problème, Webpack 3 a introduit CommonChunkPlugin Le plugin tente d'extraire les dépendances communes entre les entrées dans des fichiers séparés.chunk,mais CommonChunkPlugin Il est essentiellement implémenté sur la base de la simple chaîne de relation parent-enfant entre Chunks. Il est difficile de déduire que le troisième package extrait doit être utilisé comme.entry pèrechunk Encore un enfantchunkCommonChunkPlugin traitement unifié en tant que parentchunk, dans certains cas, cela a un impact négatif considérable sur les performances.

Pour cette raison, des structures de données plus complexes ont été spécifiquement introduites après Webpack 4—— ChunkGroup Spécialisé dans la réalisation de la gestion de la chaîne relationnelle et de la coopérationSplitChunksPlugin peut être mis en œuvre de manière plus efficace et intelligenteSous-traitance heuristique.

Résumer

En résumé, la phase "Build" est chargée de construire le ModuleGraph en fonction de la relation de référence du module ; la phase "Encapsulation" est chargée de construire une série d'objets Chunk basés sur le ModuleGraph et d'organiser les dépendances entre les Chunks (références asynchrones, runtime ) dans ChunkGraph - Objet graphique Chunk Dependency. Semblable à ModuleGraph, l'introduction de la structure ChunkGraph peut également découpler la logique de gestion des dépendances entre les Chunks, rendant la logique globale de l'architecture plus raisonnable et plus facile à développer.

Cependant, même si cela semble très compliqué, l'objectif le plus important de l'étape "packaging" reste de déterminer le nombre de Chunks et quels modules sont inclus dans chaque Chunk - ce sont les facteurs clés qui affectent réellement le résultat final du packaging.

Pour ce point, nous devons comprendre les trois règles de sous-traitance intégrées à Webpack5 : Entry Chunk, Async Chunk et Runtime Chunk. Ce sont les logiques de sous-traitance les plus originales (comme les autres plug-ins). Plugin splitChunks) sont tous basés sur cela, avec l'aide de buildChunkGraph Divers hooks déclenchés plus tard divisent, fusionnent et optimisent la structure Chunk pour obtenir des effets de sous-traitance étendus.

pense Chunk Un seul fichier produit sera-t-il produit ? Pourquoi?mini-css-extract-pluginfile-loader Comment ce type de composant capable d'écrire des fichiers supplémentaires est-il implémenté en bas ?