Condivisione della tecnologia

Webpack: logica di confezionamento di tre prodotti Chunk

2024-07-08

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

Panoramica

  • nell'articolo precedente Webpack: il grafico delle dipendenze gestisce le dipendenze tra i moduli , abbiamo spiegato in dettaglio come la fase di "costruzione" inizia da Entry e gradualmente legge e analizza in modo ricorsivo il contenuto del modulo e infine costruisce il grafico delle dipendenze del modulo: l'oggetto ModuleGraph. In questo articolo, continuiamo a spiegare come organizzare i blocchi in base al contenuto di ModuleGraph nella successiva fase di "incapsulamento" e a costruire ulteriormente il processo principale degli oggetti di dipendenza ChunkGroup e ChunkGraph.

Oltre al processo principale, spiegheremo in dettaglio anche alcuni concetti vaghi:

  • Cosa sono gli oggetti Chunk, ChunkGroup e ChunGraph? Che tipo di interazione c'è tra loro?
  • Regole predefinite del sottopacchetto di Webpack e problemi nelle regole.

Processo di costruzione di ChunkGraph

davanti Init, Crea, Sigilla"In ", abbiamo introdotto che la logica costruttiva sottostante a Webpack può essere grossolanamente suddivisa in: "Inizializzazione, costruzione, confezionamento"tre fasi:

Inserisci qui la descrizione dell'immagine

In,"Costruire"La fase è responsabile dell'analisi delle dipendenze tra i moduli e della definizione del file Grafico delle dipendenze(Grafico Modulo); quindi, in "Incapsulamento", in base al grafico delle dipendenze, i moduli vengono incapsulati separatamente in diversi oggetti Chunk e le relazioni di dipendenza padre-figlio tra Chunk vengono ordinate in ChunkGraph e diversi oggetti ChunkGroup.

L'obiettivo più importante della fase di "incapsulamento" è costruire il grafico delle relazioni ChunkGraph basato sul grafico delle relazioni ModuleGraph raccolto nella fase di "costruzione". La logica di questo processo è relativamente complessa:

Aggiungi la descrizione dell'immagine

Analizziamo qui brevemente la logica di implementazione di alcuni passaggi importanti.

Il primo passo è molto critico: trasferimentoseal() Dopo la funzione, traversaentry Configurazione, creane uno vuoto per ogni voceChunk EPunto d'entrata oggetto (uno specialeChunkGroup) e inizialmente impostare il file basic ChunkGraph Relazione strutturale, prepararsi per il passaggio successivo, codice chiave:

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

Al termine dell'esecuzione, viene formata la seguente struttura dati:
Inserisci qui la descrizione dell'immagine

In secondo luogo, se configurato in questo momento entry.runtime, Webpack fornirà anche il codice runtime in questa fase creare Il Chunk corrispondente e direttamentedistribuire Dareentry corrispondenteChunkGroup oggetto.Chiamato quando tutto è prontocostruireChunkGraph funzione, andare al passaggio successivo.

Passo due: esisterebuildChunkGraph all'interno della funzionetrasferimento visitModules Funzione, attraversa il ModuleGraph e assegna tutti i moduli a diversi moduli in base alle loro dipendenze.Chunk Oggetto; se incontrato durante questo processomodulo asincrono, quindi il modulo crearenuovo ChunkGroup EChunk Oggetto, che in definitiva forma la seguente struttura dati:
Aggiungi la descrizione dell'immagine

terzo passo: esisterebuildChunkGraph in funzionetrasferimento connectChunkGroups metodo, costruireChunkGroup fra,Chunk dipendenze tra loro per generare un file completoChunkGraph Oggetto, che in definitiva forma la seguente struttura dati:
Aggiungi la descrizione dell'immagine

il quarto passo: esisterebuildChunkGraph in funzionetrasferimento cleanupUnconnectedGroups Metodo, la pulizia non è validaChunkGroup, svolge principalmente un ruolo nell'ottimizzazione delle prestazioni.

Dopo aver eseguito questi quattro passaggi dall'alto verso il basso,ModuleGraph I moduli archiviati verranno assegnati a tre diversi oggetti Chunk: Entry, Async e Runtime in base alla natura del modulo stesso e le dipendenze tra Chunk verranno archiviate nelle raccolte ChunkGraph e ChunkGroup. È possibile continuare in base a questi oggetti in futuro modificare le politiche di subappalto (es.SplitChunksPlugin), realizzare l'ottimizzazione del conto lavoro riorganizzando e assegnando la proprietà degli oggetti Modulo e Blocco.

Chunk contro ChunkGroup contro ChunkGraph

Il processo di costruzione di cui sopra coinvolge tre oggetti chiave: Chunk, ChunkGroup e ChunkGraph Riassumiamo innanzitutto i loro concetti e funzioni per approfondire la nostra comprensione:

  • Chunk: Il modulo viene utilizzato per leggere il contenuto del modulo, registrare le dipendenze tra moduli, ecc.; mentre Chunk unisce più moduli in base alle dipendenze del modulo e li genera in file di risorse (la logica dell'unione e dell'output dei prodotti verrà spiegata nel capitolo successivo):

Aggiungi la descrizione dell'immagine

  • ChunkGroup:uno ChunkGroup ne contiene uno o piùChunk oggetto;ChunkGroup EChunkGroup Si forma una relazione di dipendenza genitore-figlio tra:

Aggiungi la descrizione dell'immagine

  • ChunkGraph: Infine, Webpack memorizzerà le dipendenze tra Chunks e ChunkGroups in compilation.chunkGraph Nell'oggetto si formano le seguenti relazioni di tipo:
    Aggiungi la descrizione dell'immagine

Regole di subappalto predefinite

Quanto sopra ChunkGraph Il processo di creazione organizzerà infine il modulo in tre diversi tipi di blocchi:

  • Blocco di ingresso: lo stesso entry I moduli raggiunti dal touch inferiore sono organizzati in Chunk;
  • Async Chunk: il modulo asincrono è organizzato separatamente in un Chunk;
  • Frammento di runtime:entry.runtime Quando non è vuoto, il modulo runtime sarà organizzato in un Chunk.

Questo è integrato in Webpack, se non utilizzato splitChunks Nel caso di altri plug-in, le regole predefinite per la mappatura dell'input del modulo all'output sono uno dei principi chiave alla base di Webpack, quindi è necessario introdurre le regole specifiche di ciascun Chunk.

Frammento di ingresso:

A partire da Entry Chunk, Webpack lo farà per primo entry creareChunk Oggetto, ad esempio, la seguente configurazione:

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

Attraversare entry proprietà dell'oggetto e crearechunk[main]chunk[home] Due oggetti, in questo momento i due Chunk contengono rispettivamentemainhome Modulo:
Inserisci qui la descrizione dell'immagine

Una volta completata l'inizializzazione, Webpack lo farà ModuleGraph dati sulla dipendenza, volontàentry Tutti i moduli toccati da quanto segue vengono inseriti nel Chunk (che si verifica invisitaModuli metodo), ad esempio, per le seguenti dipendenze file:
Inserisci qui la descrizione dell'immagine

main.js Se si fa riferimento direttamente o indirettamente ai quattro file a/b/c/d in modo sincrono, Webpack lo farà per primomain.js Il modulo crea oggetti Chunk e EntryPoint, quindi aggiunge gradualmente moduli a/b/c/dchunk[main] , formando infine:
Inserisci qui la descrizione dell'immagine

Blocco asincrono:

In secondo luogo, Webpack compilerà ogni istruzione di importazione asincrona (import(xxx) Erequire.ensure ) viene elaborato come un oggetto Chunk separato e tutti i suoi sottomoduli vengono aggiunti a questo Chunk: lo chiamiamo Async Chunk. Ad esempio, per il seguente esempio:

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

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

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

Nel modulo di inserimento index.js In, sync-a e sync-b vengono introdotti in modo sincrono; il modulo async-a viene introdotto in modo asincrono; allo stesso tempo, in async-a, il modulo async-a viene introdotto in modo sincrono;sync-c modulo, formando il seguente diagramma delle dipendenze del modulo:
Aggiungi la descrizione dell'immagine

A questo punto, Webpack sarà il punto di ingresso index.js, modulo asincrono async-a.js Crea sottopacchetti separatamente per formare la seguente struttura Chunk:
Inserisci qui la descrizione dell'immagine

E chunk[index] Echunk[async-a] Tra loro si forma una dipendenza unidirezionale e Webpack salverà questa dipendenzaChunkGroup._parentsChunkGroup._children nelle proprietà.

Frammento di runtime:

Infine, tranne entry , Oltre ai moduli asincroni, Webpack5 supporta anche l'estrazione del codice runtime separatamente in blocchi. Il codice runtime menzionato qui si riferisce a una serie di codici framework di base inseriti da Webpack per garantire che il prodotto in pacchetto possa essere eseguito normalmente. Ad esempio, la struttura comune del prodotto in pacchetto Webpack è la seguente:
Aggiungi la descrizione dell'immagine
La grande sezione di codice cerchiata nel riquadro rosso nell'immagine sopra è il codice runtime generato dinamicamente da Webpack Durante la compilazione, Webpack deciderà quale codice runtime generare in base al codice aziendale (basato suDependency sottoclasse), ad esempio:

  • Bisogno __webpack_require__.f__webpack_require__.r e altre funzioni per ottenere un supporto modulare minimo;
  • Se usi la funzione di caricamento dinamico, devi scrivere __webpack_require__.e funzione;
  • Se usi la funzionalità Module Federation, devi scrivere __webpack_require__.o funzione;
  • eccetera.

Anche se ogni pezzo di codice runtime può essere piccolo, man mano che le funzionalità aumentano, il risultato finale diventerà sempre più grande. Soprattutto per le applicazioni a più voci, è un po' uno spreco confezionare ripetutamente un runtime simile per ogni voce fornita da questo Webpack5 entry.runtime Gli elementi di configurazione vengono utilizzati per dichiarare la modalità di confezionamento del codice runtime.Per l'utilizzo, basta usareentry Aggiungi il formato stringa all'elementoruntime valore, ad esempio:

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

esistere compilation.seal Nella funzione, Webpack è innanzituttoentry creareEntryPoint, poi giudica entry La configurazione contieneruntime proprietà, se ce ne sono, creale conruntime Il valore è il nome del Chunk Pertanto, la configurazione dell'esempio precedente genererà due Chunk:chunk[index.js]chunk[solid-runtime]e in base a ciò vengono infine emessi due file:

  • Corrispondente all'indice d'ingresso index.js documento;
  • Configurazione runtime corrispondente a solid-runtime.js documento.

in molti entry Nello scenario, solo per ciascunoentry Tutto impostato allo stesso modoruntime valore, il codice runtime di Webpack verrà unito e scritto nello stesso blocco runtime, ottenendo infine l'ottimizzazione delle prestazioni del prodotto. Ad esempio, per la seguente configurazione:

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

Entrata indexhome condividere lo stessoruntime valore, e infine generare tre Chunk, rispettivamente:
Aggiungi la descrizione dell'immagine

Ingresso in questo momento chunk[index]chunk[home] con tempo di esecuzionechunk[solid-runtime] Si formerà anche una relazione di dipendenza genitore-figlio.

Problemi con le norme sul subappalto

Il problema più grande con le regole di sub-packaging predefinite è che non possono risolvere la duplicazione del modulo. Se più blocchi contengono lo stesso modulo contemporaneamente, questo modulo verrà ripetutamente impacchettato in questi blocchi senza restrizioni. Ad esempio, supponiamo di avere due voci main/index che dipendono entrambe dallo stesso modulo:
Inserisci qui la descrizione dell'immagine

Per impostazione predefinita, Webpack non eseguirà ulteriori elaborazioni su questo, ma semplicemente impacchetterà il modulo c nei blocchi principale/indice allo stesso tempo, formando infine:

Inserisci qui la descrizione dell'immagine

si può vedere chunk sono isolati l'uno dall'altro, il modulo c viene confezionato ripetutamente, il che potrebbe causare un'inutile perdita di prestazioni del prodotto finale!

Per risolvere questo problema, è stato introdotto Webpack 3 CommonChunkPlugin Il plugin tenta di estrarre le dipendenze comuni tra le voci in file separatichunk,Ma CommonChunkPlugin È essenzialmente implementato sulla base della semplice catena di relazioni genitore-figlio tra Chunks. È difficile dedurre che il terzo pacchetto estratto debba essere utilizzato comeentry padrechunk Ancora un ragazzinochunkCommonChunkPlugin elaborazione unificata come genitorechunk, in alcuni casi ha un notevole impatto negativo sulle prestazioni.

Per questo motivo, dopo il Webpack 4 sono state introdotte strutture dati più complesse—— ChunkGroup Specializzato nella realizzazione di gestione e cooperazione della catena di relazioniSplitChunksPlugin possono essere implementati in modo più efficiente e intelligenteSubappalto euristico.

Riassumere

In sintesi, la fase "Build" è responsabile della costruzione del ModuleGraph in base alla relazione di riferimento del modulo, la fase "Encapsulation" è responsabile della costruzione di una serie di oggetti Chunk basati sul ModuleGraph e dell'organizzazione delle dipendenze tra i Chunk (riferimenti asincroni, runtime); ) in ChunkGraph - Oggetto grafico Chunk Dependency. Similmente a ModuleGraph, l'introduzione della struttura ChunkGraph può anche disaccoppiare la logica di gestione delle dipendenze tra Chunk, rendendo la logica dell'architettura complessiva più ragionevole e più facile da espandere.

Tuttavia, anche se sembra molto complicato, l'obiettivo più importante della fase di "confezionamento" è comunque determinare quanti Chunk ci sono e quali Moduli sono inclusi in ciascun Chunk: questi sono i fattori chiave che influenzano realmente il risultato finale del packaging.

A questo punto, dobbiamo comprendere le tre regole di sotto-packaging integrate di Webpack5: Entry Chunk, Async Chunk e Runtime Chunk. Queste sono la logica del sotto-packaging più originale. Altri plug-in (come Plugin di suddivisione dei pezzi) sono tutti basati su questo, con l'aiuto di buildChunkGraph Vari hook attivati ​​successivamente dividono, uniscono e ottimizzano ulteriormente la struttura Chunk per ottenere effetti di subappalto ampliati.

pensare Chunk Verrà prodotto un solo file di prodotto? Perché?mini-css-extract-pluginfile-loader Come viene implementato in basso questo tipo di componente che può scrivere file aggiuntivi?