Compartilhamento de tecnologia

Webpack: Lógica de empacotamento de três produtos Chunk

2024-07-08

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

Visão geral

  • no artigo anterior Webpack: Dependency Graph gerencia dependências entre módulos , explicamos em detalhes como a fase de "construção" começa na entrada e gradualmente lê e analisa recursivamente o conteúdo do módulo e, finalmente, constrói o gráfico de dependência do módulo - o objeto ModuleGraph. Neste artigo, continuamos a explicar como organizar Chunks com base no conteúdo do ModuleGraph no próximo estágio de "encapsulamento" e construir ainda mais o processo principal dos objetos de dependência ChunkGroup e ChunkGraph.

Além do processo principal, também explicaremos detalhadamente vários conceitos vagos:

  • O que são os objetos Chunk, ChunkGroup e ChunGraph? Que tipo de interação existe entre eles?
  • Regras de subpacote padrão do Webpack e problemas nas regras.

Processo de construção do ChunkGraph

em frente Iniciar、Fazer、Selar"Em ", introduzimos que a lógica de construção subjacente do Webpack pode ser dividida em: "Inicialização, construção, embalagem"três fases:

Insira a descrição da imagem aqui

em,"Construir“A fase é responsável por analisar as dependências entre os módulos e estabelecer as Gráfico de dependência(MóduloGraph); então, em "encapsulamento”Estágio, com base no gráfico de dependência, os módulos são encapsulados separadamente em vários objetos Chunk, e os relacionamentos de dependência pai-filho entre Chunks são classificados em ChunkGraph e vários objetos ChunkGroup.

O objetivo mais importante da fase de "encapsulação" é construir o gráfico de relacionamento ChunkGraph com base no gráfico de relacionamento ModuleGraph coletado na fase de "construção". A lógica deste processo é relativamente complexa:

Adicione a descrição da imagem

Vamos analisar brevemente a lógica de implementação de várias etapas importantes aqui.

O primeiro passo é muito crítico: transferirseal() Após a função, percorraentry Configuração, crie uma vazia para cada entradaChunk ePonto de entrada objeto (um especialChunkGroup) e inicialmente configurei o básico ChunkGraph Relacionamento estrutural, prepare-se para a próxima etapa, código-chave:

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

Após a conclusão da execução, a seguinte estrutura de dados é formada:
Insira a descrição da imagem aqui

Em segundo lugar, se configurado neste momento entry.runtime, o Webpack também fornecerá código de tempo de execução nesta fase criar O pedaço correspondente e diretamentedistribuir Darentry correspondenteChunkGroup objeto.Chamado quando tudo estiver prontoconstruirChunkGraph função, vá para a próxima etapa.

Passo dois: existirbuildChunkGraph dentro da funçãotransferir visitModules Função, percorra o ModuleGraph e atribua todos os Módulos a módulos diferentes de acordo com suas dependências.Chunk Objeto; se encontrado durante este processomódulo assíncrono, então o módulo criarnovo ChunkGroup eChunk Objeto, formando em última análise a seguinte estrutura de dados:
Adicione a descrição da imagem

terceiro passo: existirbuildChunkGraph em funçãotransferir connectChunkGroups método, estabelecerChunkGroup entre,Chunk dependências entre si para gerar um completoChunkGraph Objeto, formando em última análise a seguinte estrutura de dados:
Adicione a descrição da imagem

o quarto passo: existirbuildChunkGraph em funçãotransferir cleanupUnconnectedGroups Método, a limpeza é inválidaChunkGroup, desempenha principalmente um papel na otimização do desempenho.

Depois de passar por essas quatro etapas de cima para baixo,ModuleGraph Os módulos armazenados serão atribuídos a três objetos Chunk diferentes: Entry, Async e Runtime de acordo com a natureza do próprio módulo, e as dependências entre os Chunks serão armazenadas nas coleções ChunkGraph e ChunkGroup. Você pode continuar com base nesses objetos. mais tarde. Modificar as políticas de subcontratação (por exemplo,SplitChunksPlugin), realize a otimização da subcontratação reorganizando e alocando a propriedade dos objetos Module e Chunk.

Bloco vs. Grupo de blocos vs. Gráfico de blocos

O processo de construção acima envolve três objetos principais: Chunk, ChunkGroup e ChunkGraph. Vamos primeiro resumir seus conceitos e funções para aprofundar nossa compreensão:

  • Chunk: o módulo é usado para ler o conteúdo do módulo, registrar dependências entre módulos, etc.; enquanto o Chunk mescla vários módulos com base nas dependências do módulo e os gera em arquivos de ativos (a lógica de mesclagem e saída de produtos será explicada no próximo capítulo):

Adicione a descrição da imagem

  • ChunkGroup:um ChunkGroup contém um ou maisChunk objeto;ChunkGroup eChunkGroup Uma relação de dependência pai-filho é formada entre:

Adicione a descrição da imagem

  • ChunkGraph: Finalmente, o Webpack armazenará as dependências entre Chunks e ChunkGroups em compilation.chunkGraph No objeto, os seguintes relacionamentos de tipo são formados:
    Adicione a descrição da imagem

Regras de subcontratação padrão

O de cima ChunkGraph O processo de construção organizará o Módulo em três tipos diferentes de Pedaços:

  • Pedaço de entrada: o mesmo entry Os módulos alcançados pelo toque inferior são organizados em um Chunk;
  • Async Chunk: O módulo assíncrono é organizado separadamente em um Chunk;
  • Bloco de tempo de execução:entry.runtime Quando não estiver vazio, o módulo de tempo de execução será organizado em um Chunk.

Isso está embutido no Webpack, se não for usado splitChunks No caso de outros plug-ins, as regras padrão para mapear a entrada do módulo para a saída são um dos principais princípios subjacentes do Webpack, por isso é necessário introduzir as regras específicas de cada Chunk.

Bloco de entrada:

Começando com o Entry Chunk, o Webpack irá primeiro entry criarChunk Objeto, por exemplo, a seguinte configuração:

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

Atravessar entry propriedades do objeto e criarchunk[main]chunk[home] Dois objetos, neste momento os dois Chunks contêm respectivamentemainhome Módulo:
Insira a descrição da imagem aqui

Após a conclusão da inicialização, o Webpack irá ModuleGraph dados de dependência, seráentry Todos os módulos tocados a seguir são inseridos no Chunk (ocorrendo emvisitMódulos método), por exemplo, para as seguintes dependências de arquivo:
Insira a descrição da imagem aqui

main.js Se os quatro arquivos a/b/c/d forem referenciados direta ou indiretamente de forma síncrona, o Webpack primeiromain.js O módulo cria objetos Chunk e EntryPoint e, em seguida, adiciona gradualmente módulos a/b/c/d achunk[main] , finalmente formando:
Insira a descrição da imagem aqui

Pedaço assíncrono:

Em segundo lugar, o Webpack irá compilar cada instrução de importação assíncrona (import(xxx) erequire.ensure ) é processado como um objeto Chunk separado e todos os seus submódulos são adicionados a esse Chunk - nós o chamamos de Async Chunk. Por exemplo, para o exemplo a seguir:

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

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

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

No módulo de entrada index.js Em, sync-a e sync-b são introduzidos de maneira síncrona; o módulo async-a é introduzido de maneira assíncrona; em async-a, o módulo async-a é introduzido de maneira síncrona;sync-c módulo, formando o seguinte diagrama de dependência do módulo:
Adicione a descrição da imagem

Neste ponto, o Webpack será o ponto de entrada index.js, módulo assíncrono async-a.js Crie subpacotes separadamente para formar a seguinte estrutura Chunk:
Insira a descrição da imagem aqui

e chunk[index] echunk[async-a] Uma dependência unidirecional é formada entre eles, e o Webpack salvará essa dependência emChunkGroup._parentsChunkGroup._children em propriedades.

Pedaço de tempo de execução:

Finalmente, exceto entry , Além dos módulos assíncronos, o Webpack5 também oferece suporte à extração de código de tempo de execução separadamente em Chunks. O código de tempo de execução mencionado aqui refere-se a uma série de códigos de estrutura básicos injetados pelo Webpack para garantir que o produto empacotado possa ser executado normalmente. Por exemplo, a estrutura comum do produto empacotado do Webpack é a seguinte:
Adicione a descrição da imagem
A grande seção de código circulada na caixa vermelha na imagem acima é o código de tempo de execução gerado dinamicamente pelo Webpack. Ao compilar, o Webpack decidirá qual código de tempo de execução será gerado com base no código de negócios (com base em).Dependency subclasse), por exemplo:

  • precisar __webpack_require__.f__webpack_require__.r e outras funções para obter suporte modular mínimo;
  • Se você usar o recurso de carregamento dinâmico, precisará escrever __webpack_require__.e função;
  • Se você usar o recurso Module Federation, será necessário escrever __webpack_require__.o função;
  • etc.

Embora cada parte do código de tempo de execução possa ser pequena, à medida que os recursos aumentam, o resultado final se tornará cada vez maior. Especialmente para aplicativos com várias entradas, é um desperdício empacotar repetidamente um tempo de execução semelhante em cada entrada. entry.runtime Os itens de configuração são usados ​​para declarar como o código de tempo de execução é empacotado.Para uso, basta usarentry Adicionar formato de string ao itemruntime valor, por exemplo:

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

existir compilation.seal Na função, Webpack primeiro éentry criarEntryPoint, então julgue entry A configuração contémruntime propriedades, se houver, crie-as comruntime O valor é o nome do Chunk. Portanto, a configuração do exemplo acima irá gerar dois Chunks:chunk[index.js]chunk[solid-runtime], e com base nisso, dois arquivos são finalmente gerados:

  • Correspondente ao índice de entrada index.js documento;
  • Configuração de tempo de execução correspondente a solid-runtime.js documento.

em muitos entry No cenário, apenas para cadaentry Tudo definido da mesma formaruntime valor, o código de tempo de execução do Webpack será mesclado e gravado no mesmo Runtime Chunk, alcançando, em última análise, a otimização do desempenho do produto. Por exemplo, para a seguinte configuração:

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

Entrada indexhome compartilhe o mesmoruntime valor e, finalmente, gerar três pedaços, respectivamente:
Adicione a descrição da imagem

Entrada neste horário chunk[index]chunk[home] com tempo de execuçãochunk[solid-runtime] Uma relação de dependência entre pais e filhos também será formada.

Problemas com regras de subcontratação

O maior problema com as regras de subempacotamento padrão é que elas não podem resolver a duplicação de módulos. Se vários pedaços contiverem o mesmo módulo ao mesmo tempo, então este módulo será repetidamente empacotado nesses pedaços sem restrição. Por exemplo, suponha que temos duas entradas main/index que dependem do mesmo módulo:
Insira a descrição da imagem aqui

Por padrão, o Webpack não fará processamento adicional nisso, mas simplesmente empacotará o módulo c nos dois pedaços principal/índice ao mesmo tempo, formando em última análise:

Insira a descrição da imagem aqui

pode ser visto chunk são isolados um do outro, o módulo c é embalado repetidamente, o que pode causar perda desnecessária de desempenho no produto final!

Para resolver este problema, o Webpack 3 introduziu CommonChunkPlugin O plugin tenta extrair dependências comuns entre entradas em separadochunk,mas CommonChunkPlugin É essencialmente implementado com base na simples cadeia de relacionamento pai-filho entre Chunks. É difícil inferir que o terceiro pacote extraído deva ser usado como.entry paichunk Ainda uma criançachunkCommonChunkPlugin processamento unificado como paichunk, em alguns casos tem um impacto negativo considerável no desempenho.

Por esta razão, estruturas de dados mais complexas foram introduzidas especificamente após o Webpack 4—— ChunkGroup Especializada em realizar gestão e cooperação da cadeia de relacionamentoSplitChunksPlugin pode ser implementado de forma mais eficiente e inteligenteSubcontratação heurística.

Resumir

Em resumo, a fase “Build” é responsável por construir o ModuleGraph com base no relacionamento de referência do módulo; a fase “Encapsulation” é responsável por construir uma série de objetos Chunk baseados no ModuleGraph e organizar as dependências entre os Chunks (referências assíncronas, tempo de execução). ) em ChunkGraph - objeto gráfico de dependência de Chunk. Semelhante ao ModuleGraph, a introdução da estrutura ChunkGraph também pode dissociar a lógica de gerenciamento de dependências entre os Chunks, tornando a lógica geral da arquitetura mais razoável e mais fácil de expandir.

No entanto, embora pareça muito complicado, o objetivo mais importante da etapa de “empacotamento” ainda é determinar quantos Chunks existem e quais Módulos estão incluídos em cada Chunk – esses são os fatores-chave que realmente afetam o resultado final do empacotamento.

Para este ponto, precisamos entender as três regras de subcontratação integradas do Webpack5: Entry Chunk, Async Chunk e Runtime Chunk. Essas são as lógicas de subcontratação mais originais. splitChunksPlugin) são todos baseados nisso, com a ajuda de buildChunkGraph Vários ganchos acionados posteriormente dividem, mesclam e otimizam a estrutura do Chunk para obter efeitos de subcontratação expandidos.

pensar Chunk Será produzido apenas um arquivo de produto? Por que?mini-css-extract-pluginfile-loader Como esse tipo de componente que pode gravar arquivos adicionais é implementado na parte inferior?