Автор: Сяо Лэй
Гитхаб:CommanderXL
В этой статье в основном исследуется, как веб-пакет генерирует соответствующий граф фрагментов с помощью графа модуля, созданного в процессе компиляции с помощью исходного кода.
Давайте сначала разберемся с некоторыми понятиями и их связью друг с другом:
- chunkGroup, состоящий из чанков, chunkGroup может содержать несколько чанков, которые будут использоваться при генерации/оптимизации графа чанков;
- Чанк, состоящий из модулей, чанк может содержать несколько модулей, что является окончательным выходным файлом после компиляции и упаковки веб-пакета;
- Модули — это разные файлы ресурсов, в том числе такие файлы, как js/css/images, предоставленные в вашем коде.В процессе компиляции webpack будет объединять и генерировать фрагменты в соответствии с зависимостями между различными модулями.
Мы все знаем, что когда веб-пакет упакован и собран, он определяет окончательный выходной файл на основе вашего конкретного бизнес-кода и конфигурации, связанной с веб-пакетом.Конкретное имя файла и количество файлов также связаны с этим. И эти файлы называются чанками. Например, если вы используете асинхронный API субподряда в своем бизнесе:
import('./foo.js').then(bar => bar())
В конечном выходном файлеfoo.js
Файл фрагмента будет выводиться отдельно.
Или в конфигурации вашего веб-пакета настройте оптимизацию для оптимизации генерации чанков:
module.exports = {
optimization: {
runtimeChunk: {
name: 'runtime-chunk'
}
}
}
В конце концов, веб-пакет разделит фрагмент времени выполнения веб-пакета на фрагмент, а затем выведет его в файл с именемruntime-chunk.js
документ.
Эти сгенерированные файлы фрагментов состоят из связанных модулей модулей.
Далее давайте посмотрим, как webpack генерирует чанки в рабочем процессе.Во-первых, давайте посмотрим на пример:
// a.js (webpack config 入口文件)
import add from './b.js'
add(1, 2)
import('./c').then(del => del(1, 2))
-----
// b.js
import mod from './d.js'
export default function add(n1, n2) {
return n1 + n2
}
mod(100, 11)
-----
// c.js
import mod from './d.js'
mod(100, 11)
import('./b.js').then(add => add(1, 2))
export default function del(n1, n2) {
return n1 - n2
}
-----
// d.js
export default function mod(n1, n2) {
return n1 % n2
}
конфигурация, связанная с веб-пакетом:
// webpack.config.js
module.exports = {
entry: {
app: 'a.js'
},
output: {
filename: '[name].[chunkhash].js',
chunkFilename: '[name].bundle.[chunkhash:8].js',
publicPath: '/'
},
optimization: {
runtimeChunk: {
name: 'bundle'
}
},
}
Где a.js — это файл записи, настроенный в конфигурации веб-пакета, a.js зависит от b.js/c.js, тогда как b.js зависит от d.js, а c.js зависит от d.js/b.js. . После окончательной компиляции с помощью веб-пакета будут сгенерированы 3 файла фрагментов, в том числе:
- bundle.js — содержит код модуля времени выполнения веб-пакета.
- app.bundle.js — содержит код для a.js/b.js/d.js
- 2.bundle.js — содержит код для c.js
Далее давайте посмотрим на исходный код, чтобы увидеть, какая стратегия используется внутри веб-пакета для завершения генерации фрагментов.
Веб-пакет рабочего процесса, из которого, когда все модули будут скомпилированы в работу, связанную с этапом уплотнения, начнет генерировать фрагмент:
// compilation.js
class Compilation {
...
seal () {
...
this.hooks.beforeChunks.call();
// 根据 addEntry 方法中收集到入口文件组成的 _preparedEntrypoints 数组
for (const preparedEntrypoint of this._preparedEntrypoints) {
const module = preparedEntrypoint.module;
const name = preparedEntrypoint.name;
const chunk = this.addChunk(name); // 入口 chunk 且为 runtimeChunk
const entrypoint = new Entrypoint(name); // 每一个 entryPoint 就是一个 chunkGroup
entrypoint.setRuntimeChunk(chunk); // 设置 runtime chunk
entrypoint.addOrigin(null, name, preparedEntrypoint.request);
this.namedChunkGroups.set(name, entrypoint); // 设置 chunkGroups 的内容
this.entrypoints.set(name, entrypoint);
this.chunkGroups.push(entrypoint);
// 建立起 chunkGroup 和 chunk 之间的关系
GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
// 建立起 chunk 和 module 之间的关系
GraphHelpers.connectChunkAndModule(chunk, module);
chunk.entryModule = module;
chunk.name = name;
this.assignDepth(module);
}
this.processDependenciesBlocksForChunkGroups(this.chunkGroups.slice());
// 对 module 进行排序
this.sortModules(this.modules);
// 创建完 chunk 之后的 hook
this.hooks.afterChunks.call(this.chunks);
//
this.hooks.optimize.call();
while (
this.hooks.optimizeModulesBasic.call(this.modules) ||
this.hooks.optimizeModules.call(this.modules) ||
this.hooks.optimizeModulesAdvanced.call(this.modules)
) {
/* empty */
}
// 优化 module 之后的 hook
this.hooks.afterOptimizeModules.call(this.modules);
while (
this.hooks.optimizeChunksBasic.call(this.chunks, this.chunkGroups) ||
this.hooks.optimizeChunks.call(this.chunks, this.chunkGroups) ||
// 主要涉及到 webpack config 当中的有关 optimization 配置的相关内容
this.hooks.optimizeChunksAdvanced.call(this.chunks, this.chunkGroups)
) {
/* empty */
}
// 优化 chunk 之后的 hook
this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups);
...
}
...
}
В этом процессе модули входа, настроенные в конфигурации веб-пакета, сначала проходятся, и каждый модуль входа проходит черезaddChunk
метод для создания чанка, и этот новый чанк является пустым чанком, то есть он не содержит никаких связанных с ним модулей. Затем создайте экземпляр entryPoint, и этот entryPoint является chunkGroup, каждая chunkGroup может содержать несколько фрагментов, и внутри будет специальный runtimeChunk (код времени выполнения веб-пакета, включенный при окончательной компиляции веб-пакета, в конечном итоге будет внедрен в runtimeChunk). В этот момент создаются только chunk и chunkGroup соответственно, а затем выполняется вызовGraphHelpers
предоставляется модулемconnectChunkGroupAndChunk
а такжеconnectChunkAndModule
метод для установления соединения между chunkGroup и чанком, а также чанком ивходной модульСвязь между (модуль зависимостей здесь не задействован):
// GraphHelpers.js
/**
* @param {ChunkGroup} chunkGroup the ChunkGroup to connect
* @param {Chunk} chunk chunk to tie to ChunkGroup
* @returns {void}
*/
GraphHelpers.connectChunkGroupAndChunk = (chunkGroup, chunk) => {
if (chunkGroup.pushChunk(chunk)) {
chunk.addGroup(chunkGroup);
}
};
/**
* @param {Chunk} chunk Chunk to connect to Module
* @param {Module} module Module to connect to Chunk
* @returns {void}
*/
GraphHelpers.connectChunkAndModule = (chunk, module) => {
if (module.addChunk(chunk)) {
chunk.addModule(module);
}
};
Например, в примере настроен только один входной модуль, тогда при обработке этапа entryPoints будет сгенерирована группа chunkGroup и чанк, который в данный момент содержит только входной модуль. Мы все знаем, что чанки, выдаваемые webpack, будут содержать связанные модули.На приведенном выше этапе процесса компиляции устанавливается только соединение между чанком и входным модулем, так как же чанк также устанавливает соединение с другими модулями? Далее давайте посмотрим, как webpack связывает зависимые модули в процессе создания чанков.
С этим связано то, что предоставляет экземпляр компиляцииprocessDependenciesBlocksForChunkGroups
метод. Внутренние детали этого метода более сложны, и он содержит два основных потока обработки:
- Пройдите по графу модуля графа зависимостей модуля, чтобы установить базовый граф зависимостей графа фрагментов;
- Пройдите граф зависимостей графа фрагментов, созданного на первом шаге, и оптимизируйте граф фрагментов в соответствии с предыдущим графом модуля (поскольку граф фрагментов является основой для окончательного вывода фрагмента с помощью веб-пакета, некоторые фрагменты, которые повторно создаются с помощью граф фрагментов будет исключен из потока обработки этого шага.)
Давайте сначала разберемся с внутренним процессом обработки этого метода с помощью общей блок-схемы:
Построить граф фрагментов на основе графа модуля
На первом шаге сначала проходите модули, собранные этой компиляцией, в процессе обхода модуля будут обрабатываться зависимости модуля, будут получены зависимые модули модуля, а также будет обработан сам модуль. блоки (то есть модули, загруженные вашим кодом через асинхронный API), каждый асинхронный блок будет добавлен в процесс обхода и обработан как модуль. Следовательно, после этого процесса обхода будет создан базовый граф модулей, включающий обычные модули и асинхронные модули (блоки), и, наконец, сохраненный в таблице сопоставления (blockInfoMap):
const iteratorBlockPrepare = b => {
blockInfoBlocks.push(b);
// 将 block 加入到 blockQueue 当中,从而进入到下一次的遍历过程当中
blockQueue.push(b);
};
// 这次 compilation 包含的所有的 module
for (const modules of this.modules) {
blockQueue = [module];
currentModule = module;
while (blockQueue.length > 0) {
block = blockQueue.pop(); // 当前正在被遍历的 module
blockInfoModules = new Set(); // module 依赖的同步的 module
blockInfoBlocks = []; // module 依赖的异步 module(block)
if (block.variables) {
iterationBlockVariable(block.variables, iteratorDependency);
}
// 在 blockInfoModules 数据集(set)当中添加 dependencies 中的普通 module
if (block.dependencies) {
iterationOfArrayCallback(block.dependencies, iteratorDependency);
}
// 在 blockInfoBlocks 和 blockQueue 数组当中添加异步 module(block),这样这些被加入到 blockQueue当中的
// module 也会进入到遍历的环节,去获取异步 module(block)的依赖
if (block.blocks) {
iterationOfArrayCallback(block.blocks, iteratorBlockPrepare);
}
const blockInfo = {
modules: Array.from(blockInfoModules),
blocks: blockInfoBlocks
};
// blockInfoMap 上保存了每个 module 依赖的同步 module 及 异步 blocks
blockInfoMap.set(block, blockInfo);
}
}
График модуля, сгенерированный в нашем примере:
Когда базовый граф модуля (т.е.blockInfoMap
), а затем начните генерировать базовый граф фрагментов в соответствии с графом модуля. Вначале это по-прежнему обработка данных, и входящая точка входа(chunkGroup) преобразуется в новую очередь, каждый элемент массива очереди содержит:
- действие (тип обрабатываемого модуля, модули с разными типами обработки будут обрабатываться в разных процессах, начальное значение ENTER_MODULE(1))
- блок (входной модуль)
- модуль (входной модуль)
- чанк (пустой чанк, созданный в начале фазы уплотнения для каждого входного модуля)
- chunkGroup (entryPoint — это тип chunkGroup)
В приведенном нами примере из-за того, что это одна запись, после инициализации очереди остается только один элемент.
{
action: ENTER_MODULE,
block: a.js,
module: a.js,
chunk,
chunkGroup: entryPoint
}
Далее вводим ссылку обхода очереди
// 创建异步的 block
// For each async Block in graph
/**
* @param {AsyncDependenciesBlock} b iterating over each Async DepBlock
* @returns {void}
*/
const iteratorBlock = b => {
// 1. We create a chunk for this Block
// but only once (blockChunkGroups map)
let c = blockChunkGroups.get(b);
if (c === undefined) {
c = this.namedChunkGroups.get(b.chunkName);
if (c && c.isInitial()) {
this.errors.push(
new AsyncDependencyToInitialChunkError(b.chunkName, module, b.loc)
);
c = chunkGroup;
} else {
// 通过 addChunkInGroup 方法创建新的 chunkGroup 及 chunk,并返回这个 chunkGroup
c = this.addChunkInGroup(
b.groupOptions || b.chunkName,
module, // 这个 block 所属的 module
b.loc,
b.request
);
chunkGroupCounters.set(c, { index: 0, index2: 0 });
blockChunkGroups.set(b, c);
allCreatedChunkGroups.add(c);
}
} else {
// TODO webpack 5 remove addOptions check
if (c.addOptions) c.addOptions(b.groupOptions);
c.addOrigin(module, b.loc, b.request);
}
// 2. We store the Block+Chunk mapping as dependency for the chunk
let deps = chunkDependencies.get(chunkGroup);
if (!deps) chunkDependencies.set(chunkGroup, (deps = []));
// 当前 chunkGroup 所依赖的 block 及 chunkGroup
deps.push({
block: b,
chunkGroup: c,
couldBeFiltered: true
});
// 异步的 block 使用创建的新的 chunkGroup
// 3. We enqueue the DependenciesBlock for traversal
queueDelayed.push({
action: PROCESS_BLOCK,
block: b,
module: module,
chunk: c.chunks[0], // 获取新创建的 chunkGroup 当中的第一个 chunk,即 block 需要被加入的 chunk
chunkGroup: c // 异步 block 使用新创建的 chunkGroup
});
};
...
const ADD_AND_ENTER_MODULE = 0;
const ENTER_MODULE = 1;
const PROCESS_BLOCK = 2;
const LEAVE_MODULE = 3;
...
const chunkGroupToQueueItem = chunkGroup => ({
action: ENTER_MODULE,
block: chunkGroup.chunks[0].entryModule,
module: chunkGroup.chunks[0].entryModule,
chunk: chunkGroup.chunks[0],
chunkGroup
});
let queue = inputChunkGroups.map(chunkGroupToQueueItem).reverse()
while (queue.length) { // 外层 queue 遍历
while (queue.length) { // 内层 queue 遍历
const queueItem = queue.pop();
module = queueItem.module;
block = queueItem.block;
chunk = queueItem.chunk;
chunkGroup = queueItem.chunkGroup;
switch (queueItem.action) {
case ADD_AND_ENTER_MODULE: {
// 添加 module 至 chunk 当中
// We connect Module and Chunk when not already done
if (chunk.addModule(module)) {
module.addChunk(chunk);
} else {
// already connected, skip it
break;
}
}
// fallthrough
case ENTER_MODULE: {
...
queue.push({
action: LEAVE_MODULE,
block,
module,
chunk,
chunkGroup
});
}
// fallthrough
case PROCESS_BLOCK: {
// get prepared block info
const blockInfo = blockInfoMap.get(block);
// Traverse all referenced modules
for (let i = blockInfo.modules.length - 1; i >= 0; i--) {
const refModule = blockInfo.modules[i];
if (chunk.containsModule(refModule)) {
// skip early if already connected
continue;
}
// enqueue the add and enter to enter in the correct order
// this is relevant with circular dependencies
queue.push({
action: ADD_AND_ENTER_MODULE,
block: refModule, // 依赖 module
module: refModule, // 依赖 module
chunk, // module 所属的 chunk
chunkGroup // module 所属的 chunkGroup
});
}
// 开始创建异步的 chunk
// Traverse all Blocks
iterationOfArrayCallback(blockInfo.blocks, iteratorBlock);
if (blockInfo.blocks.length > 0 && module !== block) {
blocksWithNestedBlocks.add(block);
}
break;
}
case LEAVE_MODULE: {
...
break;
}
}
}
const tempQueue = queue;
queue = queueDelayed.reverse();
queueDelayed = tempQueue;
}
Через исходный код мы обнаружили, что обработка очереди была проведена.2 операции обхода (внутренняя и внешняя)После того, как конкретные потребности Почему будет работать дважды, текст объяснит. Сначала смотрим обход внутреннего слоя, первый поток процесса переходит к соответствующему действию по типу которого:
Сначала войдите в стадию ENTRY_MODULE, элемент с действием LEAVE_MODULE будет добавлен в очередь и будет использоваться в последующем процессе обхода.Когда стадия ENTRY_MODULE будет завершена, она сразу войдет в стадию PROCESS_BLOCK:
На этом этапе синхронные зависимые модули и асинхронные зависимые блоки этого модуля (называемого A) получаются в соответствии с картой модуля blockInfoMap, сохраненной в графе зависимостей графа модулей.
Затем пройдитесь по включенным модулям (называемым B) в модулях, чтобы определить, содержит ли чанк, к которому принадлежит текущий модуль (A), модуль (B) в своих зависимых модулях.Если нет, он будет добавлен в очередь. элемент, действие вновь добавленного элемента — ADD_AND_ENTER_MODULE, то есть вновь добавленный элемент сначала войдет в стадию ADD_AND_ENTER_MODULE при следующем обходе.
Когда новый элемент помещается в очередь, то есть после добавления в очередь необработанного модуля (A), от которого зависит этот модуль, вызов начинается следующим.iteratorBlock
метод для обработки всех асинхронных блоков, от которых зависит этот модуль (A).Основная работа, выполняемая внутри этого метода:
-
передача
addChunkInGroup
Создайте новый блок и группу блоков для этого асинхронного блока и вызовите функцию connectChunkGroupAndChunk, предоставленную модулем GraphHelpers, чтобы установить соединение между новым блоком и группой блоков. Новый фрагмент здесь заключается в том, что когда вы используете асинхронный API для загрузки модуля в свой код, webpack в конечном итоге выводит фрагмент только для этого модуля, но этот фрагмент в настоящее время является пустым фрагментом, без добавления каких-либо зависимых модулей; -
Установите зависимость между chunkGroup и блоком, к которому принадлежит текущий модуль, и chunkGroup, к которому принадлежит этот блок, и сохраните ее в таблице chunkDependencies Map, которая в основном используется для последующей оптимизации графа чанков;
-
Добавьте тип действия PROCESS_BLOCK в queueDelayed, модуль — текущий модуль, блок — асинхронный модуль, от которого зависит текущий модуль, чанк (первый чанк в chunkGroup) и chunkGroup — новые элементы, сгенерированные обработкой асинхронных модулей, и здесь Новые элементы добавленные в набор данных queueDelayed, в первую очередь предназначены для внешнего обхода очереди.
На этапе ENTRY_MODULE в очередь добавляются зависимые модули модуля входа, после этого этапа начинается второй раунд обхода внутреннего слоя очереди:
В процессе обхода внутреннего слоя очереди мы в основном обращаем внимание на элементы, тип действия которых в очереди ADD_AND_ENTER_MODULE.Во время фактической обработки мы входим в стадию ADD_AND_ENTER_MODULE.Основная работа, выполняемая на этом этапе, состоит в том, чтобы судить, модуль, от которого зависит чанк, был добавлен внутрь чанка (chunk.addModule
метод), если нет, то модуль будет добавлен в чанк, и он попадет в стадию ENTRY_MODULE, и войдет в последующий процесс (см. выше), если он был добавлен, то этот обход будет пропущен.
Когда обход внутреннего слоя этого раунда очереди завершится (каждый раунд обхода внутреннего слоя соответствует одной и той же группе chunkGroup, то есть каждый раунд обхода внутреннего слоя должен обрабатывать все модули, содержащиеся в этой группе chunkGroup. ), и начнем для входа в обход внешнего слоя, то есть для обработки набора данных queueDelayed.
Вышеупомянутое находится вprocessDependenciesBlocksForChunkGroups
Первоначальная обработка графа модуля и графа фрагментов внутри метода, конечным результатом является построение графа фрагментов в соответствии с графом модулей и добавление соответствующих зависимостей модулей к исходному пустому фрагменту.
entryPoint содержит три модуля a, b, d, асинхронный модуль зависимостей c модуля a и синхронный модуль зависимостей d модуля c принадлежат только что созданной группе chunkGroup2, в группе chunkGroup2 есть только один фрагмент, а асинхронный модуль b модуля c принадлежит во вновь созданный chunkGroup3 .
Оптимизировать граф фрагментов
Затем перейдите ко второму шагу, просмотрите граф фрагментов, установите отношения родитель-потомок между различными группами фрагментов через отношения использования с зависимыми модулями и удалите некоторые фрагменты, которые не были связаны.
/**
* Helper function to check if all modules of a chunk are available
*
* @param {ChunkGroup} chunkGroup the chunkGroup to scan
* @param {Set<Module>} availableModules the comparitor set
* @returns {boolean} return true if all modules of a chunk are available
*/
// 判断chunkGroup当中是否已经包含了所有的 availableModules
const areModulesAvailable = (chunkGroup, availableModules) => {
for (const chunk of chunkGroup.chunks) {
for (const module of chunk.modulesIterable) {
// 如果在 availableModules 存在没有的 module,那么返回 false
if (!availableModules.has(module)) return false;
}
}
return true;
};
// For each edge in the basic chunk graph
/**
* @param {TODO} dep the dependency used for filtering
* @returns {boolean} used to filter "edges" (aka Dependencies) that were pointing
* to modules that are already available. Also filters circular dependencies in the chunks graph
*/
const filterFn = dep => {
const depChunkGroup = dep.chunkGroup;
if (!dep.couldBeFiltered) return true;
if (blocksWithNestedBlocks.has(dep.block)) return true;
if (areModulesAvailable(depChunkGroup, newAvailableModules)) {
return false; // break, all modules are already available
}
dep.couldBeFiltered = false;
return true;
};
/** @type {Map<ChunkGroup, ChunkGroupInfo>} */
const chunkGroupInfoMap = new Map();
/** @type {Queue<ChunkGroup>} */
const queue2 = new Queue(inputChunkGroups);
for (const chunkGroup of inputChunkGroups) {
chunkGroupInfoMap.set(chunkGroup, {
minAvailableModules: undefined,
availableModulesToBeMerged: [new Set()]
});
}
...
while (queue2.length) {
chunkGroup = queue2.dequeue();
const info = chunkGroupInfoMap.get(chunkGroup);
const availableModulesToBeMerged = info.availableModulesToBeMerged;
let minAvailableModules = info.minAvailableModules;
...
}
...
Во-первых, выполните некоторую работу по инициализации данных, chunkGroupInfoMap хранит информацию о разных группах chunkGroups:
- minAvailableModules (минимальный набор данных модуля, который может отслеживать chunkGroup)
- availableModulesToBeMerged (набор модулей, используемых обходной ссылкой)
/** @type {Map<ChunkGroup, ChunkGroupInfo>} */
const chunkGroupInfoMap = new Map();
/** @type {Queue<ChunkGroup>} */
const queue2 = new Queue(inputChunkGroups);
for (const chunkGroup of inputChunkGroups) {
chunkGroupInfoMap.set(chunkGroup, {
minAvailableModules: undefined,
availableModulesToBeMerged: [new Set()]
});
}
После завершения пройдите через очередь2, каждая из которых представляет собой группу фрагментов, которая изначально является группой фрагментов, соответствующей записи.В нашем примере, поскольку имеется динамически загружаемый модуль с, он также будет добавлен в очередь очереди2 как "независимый" Обрабатывается, но существует связь родитель-потомок, которая опирается на группу фрагментов, соответствующую записи записи.
while (queue2.length) {
chunkGroup = queue2.dequeue();
const info = chunkGroupInfoMap.get(chunkGroup);
const availableModulesToBeMerged = info.availableModulesToBeMerged;
let minAvailableModules = info.minAvailableModules;
// 1. Get minimal available modules
// It doesn't make sense to traverse a chunk again with more available modules.
// This step calculates the minimal available modules and skips traversal when
// the list didn't shrink.
availableModulesToBeMerged.sort(bySetSize);
let changed = false;
for (const availableModules of availableModulesToBeMerged) {
if (minAvailableModules === undefined) {
minAvailableModules = new Set(availableModules);
info.minAvailableModules = minAvailableModules;
changed = true;
} else {
for (const m of minAvailableModules) {
if (!availableModules.has(m)) {
minAvailableModules.delete(m);
changed = true;
}
}
}
}
availableModulesToBeMerged.length = 0;
if (!changed) continue;
// 获取这个 chunkGroup 的 deps 数组,包含异步的 block 及 对应的 chunkGroup
// 2. Get the edges at this point of the graph
const deps = chunkDependencies.get(chunkGroup);
if (!deps) continue;
if (deps.length === 0) continue;
// 根据之前的 minAvailableModules 创建一个新的 newAvailableModules 数据集
// 即之前所有遍历过的 chunk 当中的 module 都会保存到这个数据集当中,不停的累加
// 3. Create a new Set of available modules at this points
newAvailableModules = new Set(minAvailableModules);
for (const chunk of chunkGroup.chunks) {
for (const m of chunk.modulesIterable) { // 这个 chunk 当中所包含的 module
newAvailableModules.add(m);
}
}
// 边界条件,及异步的 block 所在的 chunkGroup
// 4. Foreach remaining edge
const nextChunkGroups = new Set();
// 异步 block 依赖
for (let i = 0; i < deps.length; i++) {
const dep = deps[i];
// Filter inline, rather than creating a new array from `.filter()`
if (!filterFn(dep)) {
continue;
}
// 这个 block 所属的 chunkGroup,在 iteratorBlock 方法内部创建的
const depChunkGroup = dep.chunkGroup;
const depBlock = dep.block;
// 开始建立 block 和 chunkGroup 之间的关系
// 在为 block 创建新的 chunk 时,仅仅建立起了 chunkGroup 和 chunk 之间的关系,
// 5. Connect block with chunk
GraphHelpers.connectDependenciesBlockAndChunkGroup(
depBlock,
depChunkGroup
);
// 建立起新创建的 chunkGroup 和此前的 chunkGroup 之间的相互联系
// 6. Connect chunk with parent
GraphHelpers.connectChunkGroupParentAndChild(chunkGroup, depChunkGroup);
nextChunkGroups.add(depChunkGroup);
}
// 7. Enqueue further traversal
for (const nextChunkGroup of nextChunkGroups) {
...
// As queue deduplicates enqueued items this makes sure that a ChunkGroup
// is not enqueued twice
queue2.enqueue(nextChunkGroup);
}
}
Получите зависимости массива deps для chunkGroup, кэшированные в chunkDependencies на первом этапе.В chunkDependencies хранятся асинхронные блоки, от которых зависят разные группы chunkGroups, и группа chunkGroup, созданная с помощью этого блока (в настоящее время они хранятся только в структуре карты, а также Зависимость между chunkGroup и блоком не установлена).
Если данные deps не существуют или длина равна 0, процесс обхода chunkGroup в deps будет пропущен, в противном случае для chunkGroup будет создан новый доступный модульный набор данных newAvailableModules, а модули, содержащиеся во всех чанках в chunkGroup будет пройден и добавлен в набор данных newAvailableModules. И начните обход зависимостей массива deps этой chunkGroup.Основная работа, проделанная на этом этапе:
- Судя по newAvailableModules, предоставленным chunkGroup (вы можете понимать newAvailableModules как setA всех модулей в этом chunkGroup) и chunkGroup в зависимости от deps (chunkGroup, созданный асинхронным блоком), содержит все наборы модулей (setB) в включенном фрагменте:
- Если есть модули (обычно асинхронные блоки), которых setA не имеет в setB, и они используются в качестве граничных условий в графе чанков, это означает, что setA, составленный из модулей в пройденных до сих пор чанках, не содержит всех используемый модуль, а эти не содержащиеся модули существуют в chunkGroup в зависимости от deps, поэтому необходимо продолжить обход chunkGroup в зависимости от deps
- Если все модули в setB уже существуют в setA, это означает, что все модули, используемые в зависимой группе chunkGroup, были включены в пройденные до сих пор куски, тогда нет необходимости выполнять следующий процесс, просто пропустите его напрямую, выполните следующий обход зависимости;
- Вспомогательные функции, предоставляемые модулем GraphHelpers
connectDependenciesBlockAndChunkGroup
Установить зависимости асинхронных блоков и chunkGroups в зависимостях deps; - Вспомогательные функции, предоставляемые модулем GraphHelpers
connectChunkGroupParentAndChild
Установите зависимость между chunkGroup и chunkGroup в зависимостях deps.(Эта зависимость также определяет, есть ли фрагменты, содержащиеся в chunkGroup в зависимости deps в выходном файле после компиляции веб-пакета); - Добавьте группу chunkGroup в зависимость deps к набору данных nextChunkGroups, а затем введите обход только что добавленной группы chunkGroup.
- Когда все вышеперечисленные процессы обхода завершены, начните обход набора данных (allCreatedChunkGroups), состоящего из chunkGroups, созданных путем обработки асинхронных блоков, и начните обработку chunkGroups без зависимостей (зависимости между chunkGroups устанавливаются на шаге 3 👆 в процессе), если chunkGroup без каких-либо зависимостей все фрагменты, содержащиеся в этих группах фрагментов, будут удалены из графа зависимостей графа фрагментов. В конце концов, эти фрагменты не будут созданы, когда процесс компиляции веб-пакета завершит вывод файла.
Тогда в приведенном нами примере, после указанных выше шагов, на первом этапе обрабатывается entryPoint (chunkGroup) и все содержащиеся в нем модули, в ходе обработки выясняется, что этот entryPoint зависит от асинхронного блока c, который включен в blocksWithNestedBlocks, согласно соответствующим правилам фильтрации, необходимо продолжить обход chunkGroup2, где находится асинхронный блок c. Далее, в процессе обработки chunkGroup2, она зависит от chunkGroup3, и эта chunkGroup3 содержит асинхронный блок d, так как в процессе обработки entryPoint на первом этапе завершается раунд сбора набора модулей, в который входит синхронный модуль d. Можно представить, что в итоге возможен только один выход синхронного модуля d и асинхронного блока d, причем синхронный модуль d имеет более высокий приоритет, чем асинхронный блок d. Поэтому код финального модуля d будет выведен в чанк, содержащийся в entryPoint, в виде синхронного модуля d, так что chunkGroup3, содержащая асинхронный блок d, больше не выводится, то есть будет удален из графа чанков. .
Окончательный сгенерированный граф зависимостей чанка:
Вышеизложенное предназначено для анализа того, как веб-пакет строит график модуля с помощью исходного кода и как генерировать график фрагментов с помощью графика модуля.После прочтения этой статьи у вас должно быть общее представление о приложении вашего проекта после завершения каждой сборки. Какие процессы создают различные файлы фрагментов, которые появляются в целевой выходной папке.