技術共有

Webpack: 3 つの Chunk 製品のパッケージ化ロジック

2024-07-08

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

概要

  • 前の記事で Webpack: 依存関係グラフはモジュール間の依存関係を管理します では、「ビルド」フェーズがどのようにEntryから始まり、モジュールのコンテンツを徐々に再帰的に読み取って解析し、最後にモジュールの依存関係グラフであるModuleGraphオブジェクトを構築するかを詳しく説明しました。この記事では、次の「カプセル化」段階で ModuleGraph コンテンツに基づいてチャンクを編成する方法を引き続き説明し、さらに ChunkGroup および ChunkGraph 依存関係オブジェクトのメイン プロセスを構築します。

主なプロセスに加えて、いくつかのあいまいな概念についても詳しく説明します。

  • Chunk、ChunkGroup、および ChunGraph オブジェクトとは何ですか?彼らの間にはどのような相互作用があるのでしょうか?
  • Webpack のデフォルトのサブパッケージング ルールとルールの問題。

ChunkGraph の構築プロセス

前に 初期化、作成、封印「」では、Webpack の基礎となる構築ロジックが次のように大別できることを紹介しました。初期化、構築、パッケージ化「3つの段階:

ここに画像の説明を挿入します

で、"構築する「このフェーズは、モジュール間の依存関係を分析し、 依存関係グラフ(モジュールグラフ); 次に、「カプセル化」の段階では、依存関係グラフに基づいてモジュールが複数の Chunk オブジェクトに個別にカプセル化され、Chunk 間の親子依存関係が ChunkGraph と複数の ChunkGroup オブジェクトに分類されます。

「カプセル化」フェーズの最も重要な目標は、「構築」フェーズで収集された ModuleGraph 関係グラフに基づいて ChunkGraph 関係グラフを構築することです。このプロセスのロジックは比較的複雑です。

画像の説明を追加してください

ここで、いくつかの重要なステップの実装ロジックを簡単に分析してみましょう。

最初のステップは非常に重要です。 移行seal() 関数の後、トラバースしますentry 設定、エントリごとに空の設定を作成しますChunk そしてエントリーポイント オブジェクト (特別なChunkGroup)、最初に基本的な設定を行います。 ChunkGraph 構造的な関係、次のステップの準備、キーコード:

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

実行が完了すると、次のデータ構造が形成されます。
ここに画像の説明を挿入します

次に、この時点で構成されている場合、 entry.runtime, Webpack はこの段階でランタイム コードも提供します。 作成する 対応するチャンクと直接配布する 与えるentry 対応するChunkGroup物体。すべての準備ができたときに呼び出されます チャンクグラフの構築 機能がある場合は、次のステップに進みます。

ステップ2: 存在するbuildChunkGraph 関数内移行 visitModules 関数では、ModuleGraph を走査し、依存関係に従ってすべてのモジュールを異なるモジュールに割り当てます。Chunk このプロセス中にオブジェクトが見つかった場合非同期モジュール、次にモジュール 作成する新しい ChunkGroup そしてChunk オブジェクトであり、最終的には次のデータ構造を形成します。
画像の説明を追加してください

3番目のステップ: 存在するbuildChunkGraph 機能中移行 connectChunkGroups 方法、確立するChunkGroup 間、Chunk 完全な相互依存関係を生成するChunkGraph オブジェクトであり、最終的には次のデータ構造を形成します。
画像の説明を追加してください

4番目のステップ: 存在するbuildChunkGraph 機能中移行 cleanupUnconnectedGroups メソッド、クリーンアップが無効ですChunkGroup、主にパフォーマンスの最適化に役割を果たします。

この4つのステップを上から下まで経て、ModuleGraph に格納されたモジュールは、モジュール自体の性質に応じて Entry、Async、Runtime の 3 つの異なる Chunk オブジェクトに割り当てられ、チャンク間の依存関係は ChunkGraph コレクションと ChunkGroup コレクションに格納されます。これらのオブジェクトに基づいて続行できます。後で下請けポリシーを変更します (例:SplitChunksPlugin)、Module オブジェクトと Chunk オブジェクトの所有権を再編成して割り当てることで、下請けの最適化を実現します。

チャンク vs チャンクグループ vs チャンクグラフ

上記の構築プロセスには、Chunk、ChunkGroup、および ChunkGraph という 3 つの主要なオブジェクトが含まれます。まず、理解を深めるために、それらの概念と機能を要約します。

  • Chunk: モジュールは、モジュールのコンテンツの読み取り、モジュール間の依存関係の記録などに使用されます。一方、チャンクは、モジュールの依存関係に基づいて複数のモジュールをマージし、それらをアセット ファイルに出力します (プロダクトのマージと出力のロジックについては、次の章で説明します)。

画像の説明を追加してください

  • ChunkGroup:1つ ChunkGroup 1 つ以上が含まれていますChunk 物体;ChunkGroup そしてChunkGroup 親子依存関係は、以下の間で形成されます。

画像の説明を追加してください

  • ChunkGraph: 最後に、Webpack はチャンクとチャンクグループの間の依存関係を次の場所に保存します。 compilation.chunkGraph オブジェクト内では、次の型関係が形成されます。
    画像の説明を追加してください

デフォルトの下請けルール

上記 ChunkGraph ビルド プロセスでは、最終的にモジュールが 3 つの異なるタイプのチャンクに編成されます。

  • エントリチャンク: 同じ entry 下のタッチで到達したモジュールはチャンクに編成されます。
  • 非同期チャンク: 非同期モジュールはチャンクに個別に編成されます。
  • ランタイムチャンク:entry.runtime 空でない場合、ランタイム モジュールはチャンクに編成されます。

使用しない場合、これは Webpack に組み込まれます splitChunks 他のプラグインの場合、モジュールの入力を出力にマッピングするためのデフォルトのルールは、Webpack の基礎となる重要な原則の 1 つであるため、各チャンクの特定のルールを導入する必要があります。

エントリ チャンク:

エントリ チャンクから始めて、Webpack はまず次のことを行います。 entry 作成するChunk オブジェクト、たとえば次の構成:

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

トラバース entry オブジェクトのプロパティと作成chunk[main]chunk[home] 2 つのオブジェクト。この時点では、2 つのチャンクにはそれぞれ次のものが含まれています。mainhome モジュール:
ここに画像の説明を挿入します

初期化が完了すると、Webpack は ModuleGraph 依存関係データ、entry 以下によってタッチされるすべてのモジュールはチャンクに詰め込まれます (訪問モジュール メソッド)、たとえば、次のファイル依存関係の場合:
ここに画像の説明を挿入します

main.js 4 つのファイル a/b/c/d が直接的または間接的に同期的に参照される場合、Webpack は最初にmain.js このモジュールは Chunk オブジェクトと EntryPoint オブジェクトを作成し、a/b/c/d モジュールを段階的に追加します。chunk[main] 、最終的に形成されるもの:
ここに画像の説明を挿入します

非同期チャンク:

次に、Webpack は各非同期インポート ステートメントをコンパイルします (import(xxx) そしてrequire.ensure ) は別個のチャンク オブジェクトとして処理され、そのすべてのサブモジュールがこのチャンクに追加されます。これを非同期チャンクと呼びます。たとえば、次の例の場合:

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

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

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

エントリーモジュール内 index.js sync-a と sync-b は同期的に導入され、async-a では同時に async-a モジュールが同期的に導入されます。sync-c モジュールを作成し、次のモジュール依存関係図を形成します。
画像の説明を追加してください

この時点では、Webpack がエントリ ポイントになります。 index.js、非同期モジュール async-a.js サブパッケージを個別に作成して、次のチャンク構造を形成します。
ここに画像の説明を挿入します

そして chunk[index] そしてchunk[async-a] それらの間には一方向の依存関係が形成され、Webpack はこの依存関係を次の場所に保存します。ChunkGroup._parentsChunkGroup._children プロパティで。

ランタイム チャンク:

最後に、例外として、 entry , Webpack5 では、非同期モジュールに加えて、ランタイム コードをチャンクに分けて抽出することもサポートしています。ここで説明するランタイム コードは、パッケージ化された製品が正常に実行できるようにするために Webpack によって挿入される一連の基本的なフレームワーク コードを指します。たとえば、一般的な Webpack のパッケージ化された製品の構造は次のとおりです。
画像の説明を追加してください
上の図の赤いボックスで囲まれたコードの大部分は、Webpack によって動的に生成されたランタイム コードです。コンパイル時に、Webpack はビジネス コードに基づいてどのランタイム コードを出力するかを決定します。Dependency サブクラス)、例:

  • 必要 __webpack_require__.f__webpack_require__.r および最小限のモジュールサポートを実現するその他の機能。
  • 動的読み込み機能を使用する場合は、次のように記述する必要があります。 __webpack_require__.e 関数;
  • モジュールフェデレーション機能を使用する場合は、次のように記述する必要があります。 __webpack_require__.o 関数;

それぞれのランタイム コードは小さいかもしれませんが、機能が増加するにつれて、最終的な結果はますます大きくなり、特に複数のエントリを持つアプリケーションの場合、この Webpack5 が提供する同じようなランタイムを繰り返しパッケージ化するのは少し無駄です。 entry.runtime 構成項目は、ランタイム コードがどのようにパッケージ化されるかを宣言するために使用されます。使い方としては、そのまま使用してくださいentry 項目に文字列フォームを追加するruntime 値、たとえば:

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

存在する compilation.seal この関数では、Webpack の最初は次のとおりです。entry 作成するEntryPoint、その後判断します entry 構成には以下が含まれますか?runtime プロパティがある場合は、次のように作成します。runtime 値はチャンクの名前です。したがって、上記の設定例では 2 つのチャンクが生成されます。chunk[index.js]chunk[solid-runtime]これに基づいて、最終的に 2 つのファイルが出力されます。

  • 入口インデックスに対応 index.js 書類;
  • に対応するランタイム構成 solid-runtime.js 書類。

多くの中 entry シナリオの中で、それぞれにentry 全て同じ設定にruntime この値を設定すると、Webpack ランタイム コードがマージされて同じランタイム チャンクに書き込まれ、最終的に製品パフォーマンスの最適化が達成されます。たとえば、次の構成の場合:

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

入り口 indexhome 同じものを共有するruntime 値を指定し、最後に 3 つのチャンクをそれぞれ生成します。
画像の説明を追加してください

この時の入場 chunk[index]chunk[home] ランタイムありchunk[solid-runtime] 親子の依存関係も形成されます。

下請けルールの問題

デフォルトのサブパッケージ化ルールの最大の問題は、複数のチャンクに同じモジュールが同時に含まれている場合、このモジュールが制限なくこれらのチャンクに繰り返しパッケージ化されることです。たとえば、両方とも同じモジュールに依存する 2 つのエントリ main/index があるとします。
ここに画像の説明を挿入します

デフォルトでは、Webpack はこれに対して追加の処理を行わず、単純に c モジュールを main/index 2 つのチャンクに同時にパッケージ化し、最終的に次のように形成します。

ここに画像の説明を挿入します

見られます chunk モジュール c が互いに分離されていると、モジュール c が繰り返しパッケージ化され、最終製品に不必要なパフォーマンスの損失が発生する可能性があります。

この問題を解決するために、Webpack 3 が導入されました。 CommonChunkPlugin プラグインは、エントリ間の共通の依存関係を別のファイルに抽出しようとします。chunk、しかし CommonChunkPlugin これは基本的に、チャンク間の単純な親子関係チェーンに基づいて実装されます。抽出された 3 番目のパッケージを次のように使用する必要があると推測するのは困難です。entry 父親chunk まだ子供だよchunkCommonChunkPlugin 親としての統合処理chunk、場合によっては、パフォーマンスに重大な悪影響を及ぼします。

このため、Webpack 4 以降では、より複雑なデータ構造が特別に導入されました— ChunkGroup リレーションシップチェーンの管理と連携の実現に特化SplitChunksPlugin より効率的かつインテリジェントに実装できるヒューリスティックな下請け。

要約する

要約すると、「ビルド」フェーズはモジュールの参照関係に基づいて ModuleGraph を構築する役割を果たし、「カプセル化」フェーズは ModuleGraph に基づいて一連のチャンク オブジェクトを構築し、チャンク間の依存関係 (非同期参照、ランタイム) を整理する役割を担います。 ) ChunkGraph - チャンク依存関係グラフ オブジェクトに変換します。 ModuleGraph と同様に、ChunkGraph 構造の導入により、チャンク間の依存関係の管理ロジックを分離することもでき、全体的なアーキテクチャ ロジックがより合理的で拡張しやすくなります。

ただし、非常に複雑に見えますが、「パッケージ化」段階の最も重要な目標は、チャンクの数と各チャンクにどのモジュールが含まれるかを決定することです。これらは、最終的なパッケージ化の結果に実際に影響を与える重要な要素です。

この点については、Webpack5 に組み込まれている 3 つのサブコントラクト ルール、Entry Chunk、Async Chunk、Runtime Chunk を理解する必要があります。これらは、他のプラグイン (たとえば、 分割チャンクプラグイン) はすべてこれに基づいており、次の助けを借りています。 buildChunkGraph 後でトリガーされるさまざまなフックにより、チャンク構造がさらに分割、マージ、最適化され、拡張された下請け効果が実現されます。

考える Chunk 製品ファイルは 1 つだけ作成されますか?なぜ?mini-css-extract-pluginfile-loader 追加ファイルを書き込むことができるこの種のコンポーネントは、下部にどのように実装されているのでしょうか?