Обмен технологиями

Webpack: логика упаковки трех продуктов Chunk.

2024-07-08

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

Обзор

  • в предыдущей статье Webpack: Dependency Graph управляет зависимостями между модулями. мы подробно объяснили, как этап «сборки» начинается с Entry и постепенно рекурсивно считывает и анализирует содержимое модуля и, наконец, строит граф зависимостей модуля — объект ModuleGraph. В этой статье мы продолжаем объяснять, как организовать чанки на основе содержимого ModuleGraph на следующем этапе «инкапсуляции», а также строить основной процесс объектов зависимостей ChunkGroup и ChunkGraph.

Помимо основного процесса, мы также подробно объясним несколько расплывчатых понятий:

  • Что такое объекты Chunk, ChunkGroup и ChunGraph? Какое взаимодействие между ними?
  • Правила субупаковки Webpack по умолчанию и проблемы в правилах.

Процесс построения ChunkGraph

спереди Инициализация, Создание, Печать"В " мы представили, что базовую логику построения Webpack можно грубо разделить на: "Инициализация, сборка, упаковка«три фазы:

Вставьте сюда описание изображения

в,"Построить«Этап отвечает за анализ зависимостей между модулями и установление Граф зависимостей(ModuleGraph); затем в "ИнкапсуляцияНа этапе графа зависимостей модули отдельно инкапсулируются в несколько объектов Chunk, а отношения родительско-дочерних зависимостей между Chunks сортируются в ChunkGraph и несколько объектов ChunkGroup.

Наиболее важной целью этапа «инкапсуляции» является построение графа отношений ChunkGraph на основе графа отношений ModuleGraph, собранного на этапе «сборки». Логика этого процесса относительно сложна:

Пожалуйста, добавьте описание изображения

Давайте кратко проанализируем логику реализации нескольких важных шагов.

Первый шаг очень важен: передача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 объект.Звоню, когда все будет готовоbuildChunkGraph функцию, перейдите к следующему шагу.

Шаг второй: существоватьbuildChunkGraph внутри функциипередача visitModules Функция, просмотрите ModuleGraph и назначьте все модули различным модулям в соответствии с их зависимостями.Chunk Объект, если он обнаружен во время этого процесса;асинхронный модуль, то модуль создаватьновый ChunkGroup иChunk Object, в конечном итоге образуя следующую структуру данных:
Пожалуйста, добавьте описание изображения

третий шаг: существоватьbuildChunkGraph в функциипередача connectChunkGroups метод, построитьChunkGroup между,Chunk зависимости между собой для создания полногоChunkGraph Object, в конечном итоге образуя следующую структуру данных:
Пожалуйста, добавьте описание изображения

четвертый шаг: существоватьbuildChunkGraph в функциипередача cleanupUnconnectedGroups Метод, очистка недействительнаChunkGroup, в основном играет роль в оптимизации производительности.

Пройдя эти четыре шага сверху вниз,ModuleGraph Модули, хранящиеся в, будут назначены трем различным объектам Chunk: Entry, Async и Runtime в соответствии с характером самого модуля, а зависимости между чанками будут храниться в коллекциях ChunkGraph и ChunkGroup. Вы можете продолжить работу на основе этих объектов. в будущем изменить политику субподряда (например.SplitChunksPlugin), реализовать оптимизацию субподряда путем реорганизации и распределения прав собственности на объекты Module и Chunk.

Chunk против ChunkGroup против ChunkGraph

Описанный выше процесс построения включает в себя три ключевых объекта: Chunk, ChunkGroup и ChunkGraph. Давайте сначала суммируем их концепции и функции, чтобы углубить наше понимание:

  • Chunk: Модуль используется для чтения содержимого модуля, записи межмодульных зависимостей и т. д., в то время как Chunk объединяет несколько Модулей на основе зависимостей модулей и выводит их в файлы ресурсов (логика объединения и вывода продуктов будет объяснена в следующей главе):

Пожалуйста, добавьте описание изображения

  • ChunkGroup:один ChunkGroup содержит один или несколькоChunk объект;ChunkGroup иChunkGroup Отношения зависимости между родителями и детьми формируются между:

Пожалуйста, добавьте описание изображения

  • ChunkGraph: Наконец, Webpack сохранит зависимости между чанками и группами чанк в compilation.chunkGraph В объекте формируются следующие отношения типов:
    Пожалуйста, добавьте описание изображения

Правила субподряда по умолчанию

Выше ChunkGraph В конечном итоге процесс сборки разделит модуль на три разных типа блоков:

  • Entry Chunk: то же самое entry Модули, доступные при нижнем касании, организованы в блок;
  • Асинхронный блок: асинхронный модуль организован отдельно в чанк;
  • Выполняемый фрагмент:entry.runtime Если он не пуст, модуль времени выполнения будет организован в чанк.

Это встроено в Webpack, если не используется. splitChunks В случае других плагинов правила по умолчанию для сопоставления ввода и вывода модуля являются одним из ключевых основополагающих принципов Webpack, поэтому необходимо ввести конкретные правила для каждого чанка.

Входной фрагмент:

Начиная с Entry Chunk, Webpack сначала entry создаватьChunk Объект, например, следующей конфигурации:

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

Траверс entry свойства объекта и создатьchunk[main]chunk[home] Два объекта, в настоящее время два куска соответственно содержатmainhome Модуль:
Вставьте сюда описание изображения

После завершения инициализации Webpack будет ModuleGraph данные зависимости, будутentry Все модули, затрагиваемые следующим, помещаются в чанк (происходит впосетитеМодули метод), например, для следующих зависимостей файлов:
Вставьте сюда описание изображения

main.js Если четыре файла a/b/c/d прямо или косвенно ссылаются синхронно, Webpack сначалаmain.js Модуль создает объекты Chunk и EntryPoint, а затем постепенно добавляет модули a/b/c/d вchunk[main] , окончательно образуя:
Вставьте сюда описание изображения

Асинхронный фрагмент:

Во-вторых, Webpack скомпилирует каждый оператор асинхронного импорта (import(xxx) иrequire.ensure ) обрабатывается как отдельный объект Chunk, и все его подмодули добавляются в этот Chunk — мы называем его Async Chunk. Например, для следующего примера:

// 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 функция;
  • и т. д.

Хотя каждый фрагмент кода времени выполнения может быть небольшим, по мере увеличения возможностей конечный результат будет становиться все больше и больше. Особенно для приложений с несколькими входами, несколько раз упаковывать одинаковую среду выполнения для каждой записи — немного расточительно. entry.runtime Элементы конфигурации используются для объявления того, как упаковывается код среды выполнения.Для использования просто используйтеentry Добавить строковую форму к элементуruntime значение, например:

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

существовать compilation.seal В функции сначала Webpackentry создаватьEntryPoint, тогда суди entry Содержит ли конфигурацияruntime свойства, если они есть, создайте их с помощьюruntime Значением является имя чанка. Следовательно, приведенный выше пример конфигурации будет генерировать два чанка:chunk[index.js]chunk[solid-runtime], и на основании этого окончательно выводятся два файла:

  • Соответствует входному индексу 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 value и, наконец, сгенерируйте три чанка соответственно:
Пожалуйста, добавьте описание изображения

Вход в это время chunk[index]chunk[home] со временем выполненияchunk[solid-runtime] Также будут сформированы отношения зависимости между родителями и детьми.

Проблемы с правилами субподряда

Самая большая проблема с правилами субупаковки по умолчанию заключается в том, что они не могут решить проблему дублирования модулей. Если несколько чанк одновременно содержат один и тот же модуль, то этот модуль будет повторно упаковываться в эти чанки без ограничений. Например, предположим, что у нас есть две записи main/index, которые зависят от одного и того же модуля:
Вставьте сюда описание изображения

По умолчанию Webpack не будет выполнять дополнительную обработку, а просто одновременно упаковывает модуль c в фрагменты main/index, в конечном итоге формируя:

Вставьте сюда описание изображения

можно увидеть chunk изолированы друг от друга, модуль c упаковывается повторно, что может привести к ненужной потере производительности конечного продукта!

Чтобы решить эту проблему, в Webpack 3 появился CommonChunkPlugin Плагин пытается извлечь общие зависимости между записями в отдельныеchunk,но CommonChunkPlugin По сути, он реализован на основе простой цепочки отношений родитель-потомок между чанками. Трудно сделать вывод, что извлеченный третий пакет следует использовать в качестве.entry отецchunk Еще ребенокchunkCommonChunkPlugin унифицированная обработка в качестве родительскогоchunk, в некоторых случаях это оказывает существенное негативное влияние на производительность.

По этой причине после Webpack 4 были специально введены более сложные структуры данных. ChunkGroup Специализируется на реализации управления цепочкой взаимоотношений и сотрудничества.SplitChunksPlugin может быть реализовано более эффективно и разумноЭвристический субподряд.

Подведем итог

Таким образом, этап «Сборка» отвечает за построение ModuleGraph на основе ссылочных отношений модуля; этап «Инкапсуляция» отвечает за построение серии объектов Chunk на основе ModuleGraph и организацию зависимостей между чанками (асинхронные ссылки, среда выполнения). ) в ChunkGraph — объект графа зависимостей фрагментов. Подобно ModuleGraph, введение структуры ChunkGraph также может отделить логику управления зависимостями между чанками, что делает общую логику архитектуры более разумной и легкой для расширения.

Однако, хотя это и выглядит очень сложно, самой важной целью этапа «упаковки» по-прежнему является определение того, сколько имеется чанков и какие модули включены в каждый чанк — это ключевые факторы, которые действительно влияют на конечный результат упаковки.

Для этого нам нужно понять три встроенных правила суб-упаковки Webpack5: Entry Chunk, Async Chunk и Runtime Chunk. Это самая оригинальная логика суб-упаковки. Другие плагины. splitChunksPlugin) все основано на этом, с помощью buildChunkGraph Различные хуки, срабатывающие позже, дополнительно разделяли, объединяли и оптимизировали структуру чанка для достижения расширенного эффекта субподряда.

думать Chunk Будет ли создан только один файл продукта? Почему?mini-css-extract-pluginfile-loader Как реализован этот тип компонента, который может записывать дополнительные файлы?