Compartir tecnología

Webpack: lógica de empaquetado de tres productos Chunk

2024-07-08

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

Descripción general

  • en el artículo anterior Webpack: Dependency Graph gestiona las dependencias entre módulos , hemos explicado en detalle cómo la fase de "construcción" comienza desde la entrada y gradualmente lee y analiza de forma recursiva el contenido del módulo, y finalmente construye el gráfico de dependencia del módulo: el objeto ModuleGraph. En este artículo, continuaremos explicando cómo organizar Chunks según el contenido de ModuleGraph en la siguiente etapa de "encapsulación" y desarrollaremos aún más el proceso principal de los objetos de dependencia ChunkGroup y ChunkGraph.

Además del proceso principal, también explicaremos en detalle varios conceptos vagos:

  • ¿Qué son los objetos Chunk, ChunkGroup y ChunGraph? ¿Qué tipo de interacción hay entre ellos?
  • Reglas de subempaquetado predeterminadas de Webpack y problemas en las reglas.

Proceso de construcción de ChunkGraph

Al frente Inicializar, hacer y sellar"En", hemos introducido que la lógica de construcción subyacente de Webpack se puede dividir aproximadamente en: "Inicialización, construcción, embalaje."tres fases:

Insertar descripción de la imagen aquí

en,"Construir"La fase se encarga de analizar las dependencias entre módulos y establecer las Gráfico de dependencia(ModuleGraph); luego, en "EncapsulaciónEn la etapa, según el gráfico de dependencia, los módulos se encapsulan por separado en varios objetos Chunk y las relaciones de dependencia padre-hijo entre Chunks se clasifican en ChunkGraph y varios objetos ChunkGroup.

El objetivo más importante de la fase de "encapsulación" es construir el gráfico de relaciones ChunkGraph basado en el gráfico de relaciones ModuleGraph recopilado en la fase de "construcción". La lógica de este proceso es relativamente compleja:

Por favor agregue la descripción de la imagen.

Analicemos brevemente la lógica de implementación de varios pasos importantes aquí.

El primer paso es muy crítico: transferirseal() Después de la función, atraviesaentry Configuración, crea uno vacío para cada entradaChunk yPunto de entrada objeto (un especialChunkGroup), y configurar inicialmente el básico ChunkGraph Relación estructural, prepárese para el siguiente paso, código clave:

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);
    // 触发各种优化钩子
    // ...
  }
}

Una vez completada la ejecución, se forma la siguiente estructura de datos:
Insertar descripción de la imagen aquí

En segundo lugar, si está configurado en este momento entry.runtimeWebpack también proporcionará código de tiempo de ejecución en esta etapa. crear El Chunk correspondiente y directamentedistribuir Darentry correspondienteChunkGroup objeto.Llamado cuando todo está listo.ConstruirChunkGraph función, vaya al siguiente paso.

Segundo paso: existirbuildChunkGraph dentro de la funcióntransferir visitModules Función, atraviesa ModuleGraph y asigna todos los módulos a diferentes módulos según sus dependencias.Chunk Objeto; si se encuentra durante este proceso;módulo asíncrono, entonces el módulo crearnuevo ChunkGroup yChunk Objeto, que finalmente forma la siguiente estructura de datos:
Por favor agregue la descripción de la imagen.

tercer paso: existirbuildChunkGraph en funcióntransferir connectChunkGroups método, construirChunkGroup entre,Chunk dependencias entre sí para generar una completaChunkGraph Objeto, que finalmente forma la siguiente estructura de datos:
Por favor agregue la descripción de la imagen.

el cuarto paso: existirbuildChunkGraph en funcióntransferir cleanupUnconnectedGroups Método, la limpieza no es válidaChunkGroup, juega principalmente un papel en la optimización del rendimiento.

Después de seguir estos cuatro pasos de arriba a abajo,ModuleGraph Los módulos almacenados se asignarán a tres objetos Chunk diferentes: Entry, Async y Runtime según la naturaleza del módulo en sí, y las dependencias entre Chunks se almacenarán en las colecciones ChunkGraph y ChunkGroup. Puede continuar en función de estos objetos. en el futuro modificar las políticas de subcontratación (p. ej.SplitChunksPlugin), logra la optimización de la subcontratación reorganizando y asignando la propiedad de los objetos Módulo y Chunk.

Fragmento frente a fragmentos de fragmentos frente a fragmentos de fragmentos

El proceso de construcción anterior involucra tres objetos clave: Chunk, ChunkGroup y ChunkGraph. Primero resumamos sus conceptos y funciones para profundizar nuestra comprensión:

  • Chunk: El módulo se utiliza para leer el contenido del módulo, registrar dependencias entre módulos, etc.; mientras que Chunk fusiona varios módulos en función de las dependencias del módulo y los genera en archivos de activos (la lógica de fusionar y generar productos se explicará en el siguiente capítulo):

Por favor agregue la descripción de la imagen.

  • ChunkGroup:uno ChunkGroup contiene uno o másChunk objeto;ChunkGroup yChunkGroup Se forma una relación de dependencia padre-hijo entre:

Por favor agregue la descripción de la imagen.

  • ChunkGraph: Finalmente, Webpack almacenará las dependencias entre Chunks y ChunkGroups en compilation.chunkGraph En el objeto se forman las siguientes relaciones de tipo:
    Por favor agregue la descripción de la imagen.

Reglas de subcontratación predeterminadas

Lo anterior ChunkGraph En última instancia, el proceso de construcción organizará el módulo en tres tipos diferentes de fragmentos:

  • Fragmento de entrada: lo mismo entry Los módulos alcanzados por el toque inferior se organizan en un Chunk;
  • Async Chunk: el módulo asincrónico se organiza por separado en un Chunk;
  • Fragmento de tiempo de ejecución:entry.runtime Cuando no está vacío, el módulo de ejecución se organizará en un fragmento.

Esto está integrado en Webpack, si no se usa splitChunks En el caso de otros complementos, las reglas predeterminadas para asignar la entrada del módulo a la salida son uno de los principios subyacentes clave de Webpack, por lo que es necesario introducir las reglas específicas de cada Chunk.

Fragmento de entrada:

Comenzando con Entry Chunk, Webpack primero entry crearChunk Objeto, por ejemplo, la siguiente configuración:

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

atravesar entry propiedades del objeto y crearchunk[main]chunk[home] Dos objetos, en este momento los dos trozos contienen respectivamentemainhome Módulo:
Insertar descripción de la imagen aquí

Una vez completada la inicialización, Webpack ModuleGraph datos de dependencia,entry Todos los módulos tocados por lo siguiente se insertan en el Chunk (que ocurre enVisitaMódulos método), por ejemplo, para las siguientes dependencias de archivos:
Insertar descripción de la imagen aquí

main.js Si se hace referencia directa o indirectamente a los cuatro archivos a/b/c/d de forma sincrónica, Webpack primeromain.js El módulo crea objetos Chunk y EntryPoint y luego agrega gradualmente módulos a/b/c/d achunk[main] , formando finalmente:
Insertar descripción de la imagen aquí

Fragmento asincrónico:

En segundo lugar, Webpack compilará cada declaración de importación asincrónica (import(xxx) yrequire.ensure ) se procesa como un objeto Chunk separado y todos sus submódulos se agregan a este Chunk; lo llamamos Async Chunk. Por ejemplo, para el siguiente ejemplo:

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

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

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

En el módulo de entrada index.js En, sync-a y sync-b se introducen de forma sincrónica; el módulo async-a se introduce de forma asincrónica; en async-a, el módulo async-a se introduce de forma sincrónica;sync-c módulo, formando el siguiente diagrama de dependencia del módulo:
Por favor agregue la descripción de la imagen.

En este punto, Webpack será el punto de entrada. index.js, módulo asíncrono async-a.js Cree subpaquetes por separado para formar la siguiente estructura de fragmentos:
Insertar descripción de la imagen aquí

y chunk[index] ychunk[async-a] Se forma una dependencia unidireccional entre ellos y Webpack guardará esta dependencia enChunkGroup._parentsChunkGroup._children en propiedades.

Fragmento de tiempo de ejecución:

Finalmente, excepto entry Además de los módulos asincrónicos, Webpack5 también admite la extracción de código de tiempo de ejecución por separado en fragmentos. El código de tiempo de ejecución mencionado aquí se refiere a una serie de códigos de marco básicos inyectados por Webpack para garantizar que el producto empaquetado pueda ejecutarse normalmente. Por ejemplo, la estructura común del producto empaquetado de Webpack es la siguiente:
Por favor agregue la descripción de la imagen.
La gran sección de código rodeada en el cuadro rojo en la imagen de arriba es el código de tiempo de ejecución generado dinámicamente por Webpack. Al compilar, Webpack decidirá qué código de tiempo de ejecución generar según el código comercial (según.Dependency subclase), por ejemplo:

  • necesidad __webpack_require__.f__webpack_require__.r y otras funciones para lograr un soporte modular mínimo;
  • Si utiliza la función de carga dinámica, debe escribir __webpack_require__.e función;
  • Si utiliza la función Federación de módulos, debe escribir __webpack_require__.o función;
  • etc.

Aunque cada fragmento de código de tiempo de ejecución puede ser pequeño, a medida que aumentan las funciones, el resultado final será cada vez más grande. Especialmente para aplicaciones de múltiples entradas, es un poco derrochador empaquetar repetidamente un tiempo de ejecución similar en cada entrada. entry.runtime Los elementos de configuración se utilizan para declarar cómo se empaqueta el código de ejecución.Para su uso, simplemente useentry Agregar forma de cadena al artículoruntime valor, por ejemplo:

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

existir compilation.seal En la función, Webpack primero esentry crearEntryPoint, entonces juzga entry ¿La configuración contieneruntime propiedades, si las hay, créalas conruntime El valor es el nombre del fragmento. Por lo tanto, la configuración del ejemplo anterior generará dos fragmentos:chunk[index.js]chunk[solid-runtime], y en base a esto, finalmente se generan dos archivos:

  • Correspondiente al índice de entrada index.js documento;
  • Configuración de tiempo de ejecución correspondiente a solid-runtime.js documento.

en muchos entry En el escenario, solo para cadaentry Todos configurados igualruntime valor, el código de tiempo de ejecución de Webpack se fusionará y escribirá en el mismo Runtime Chunk, logrando en última instancia la optimización del rendimiento del producto. Por ejemplo, para la siguiente configuración:

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

Entrada indexhome compartir el mismoruntime valor y finalmente generar tres fragmentos, respectivamente:
Por favor agregue la descripción de la imagen.

Entrada en este momento chunk[index]chunk[home] con tiempo de ejecuciónchunk[solid-runtime] También se formará una relación de dependencia entre padres e hijos.

Problemas con las reglas de subcontratación

El mayor problema con las reglas de subempaquetado predeterminadas es que no pueden resolver la duplicación de módulos. Si varios fragmentos contienen el mismo módulo al mismo tiempo, este módulo se empaquetará repetidamente en estos fragmentos sin restricciones. Por ejemplo, supongamos que tenemos dos entradas main/index y ambas dependen del mismo módulo:
Insertar descripción de la imagen aquí

De forma predeterminada, Webpack no realizará procesamiento adicional sobre esto, sino que simplemente empaquetará el módulo c en los fragmentos principal/índice al mismo tiempo, formando finalmente:

Insertar descripción de la imagen aquí

puede ser visto chunk están aislados entre sí, el módulo c se empaqueta repetidamente, lo que puede causar una pérdida innecesaria de rendimiento en el producto final.

Para resolver este problema, se introdujo Webpack 3. CommonChunkPlugin El complemento intenta extraer dependencias comunes entre entradas en separadochunk,pero CommonChunkPlugin Básicamente, se implementa en base a una simple cadena de relaciones padre-hijo entre fragmentos. Es difícil inferir que el tercer paquete extraído deba usarse como tal.entry padrechunk todavía un niñochunkCommonChunkPlugin procesamiento unificado como padrechunk, en algunos casos tiene un impacto negativo considerable en el rendimiento.

Por esta razón, se introdujeron específicamente estructuras de datos más complejas después de Webpack 4—— ChunkGroup Especializados en realizar gestión de cadenas de relaciones y cooperación.SplitChunksPlugin se puede implementar de manera más eficiente e inteligenteSubcontratación heurística.

Resumir

En resumen, la fase "Construir" es responsable de construir ModuleGraph según la relación de referencia del módulo; la fase "Encapsulación" es responsable de construir una serie de objetos Chunk basados ​​​​en ModuleGraph y organizar las dependencias entre Chunks (referencias asincrónicas, tiempo de ejecución). ) en ChunkGraph: objeto gráfico de dependencia de fragmentos. Al igual que ModuleGraph, la introducción de la estructura ChunkGraph también puede desacoplar la lógica de gestión de dependencias entre Chunks, lo que hace que la lógica de la arquitectura general sea más razonable y más fácil de expandir.

Sin embargo, aunque parezca muy complicado, el objetivo más importante de la etapa de "empaquetado" sigue siendo determinar cuántos fragmentos hay y qué módulos se incluyen en cada fragmento; estos son los factores clave que realmente afectan el resultado final del empaquetado.

Para este punto, debemos comprender las tres reglas de subempaquetado integradas de Webpack5: Entry Chunk, Async Chunk y Runtime Chunk. Estas son la lógica de subempaquetado más original. complemento splitChunks) se basan todos en esto, con la ayuda de buildChunkGraph Varios ganchos activados posteriormente dividieron, fusionaron y optimizaron aún más la estructura Chunk para lograr efectos de subcontratación ampliados.

pensar Chunk ¿Se producirá sólo un archivo de producto? ¿Por qué?mini-css-extract-pluginfile-loader ¿Cómo se implementa este tipo de componente que puede escribir archivos adicionales en la parte inferior?