webpack
Version:
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
1,583 lines • 51.7 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const memoize = require("../util/memoize");
const { getEntryRuntime, mergeRuntime } = require("../util/runtime");
const ModuleGraphConnection = require("./ModuleGraphConnection");
const getAsyncDependencyToInitialChunkError = memoize(() =>
require("../errors/AsyncDependencyToInitialChunkError")
);
/** @import AsyncDependenciesBlock from "./AsyncDependenciesBlock" */
/** @import Chunk from "./Chunk" */
/** @import ChunkGroup from "./ChunkGroup" */
/** @import Compilation from "../Compilation" */
/** @import DependenciesBlock from "./DependenciesBlock" */
/** @import { DependencyLocation } from "./Dependency" */
/** @import Entrypoint, { EntryOptions } from "./Entrypoint" */
/** @import Module from "../module/Module" */
/** @import ModuleGraph from "./ModuleGraph" */
/** @import { ConnectionState } from "./ModuleGraphConnection" */
/** @import { Logger } from "../logging/Logger" */
/** @import { RuntimeSpec } from "../util/runtime" */
/**
* Defines the queue item type used by this module.
* @typedef {object} QueueItem
* @property {number} action
* @property {DependenciesBlock} block
* @property {Module} module
* @property {Chunk} chunk
* @property {ChunkGroup} chunkGroup
* @property {ChunkGroupInfo} chunkGroupInfo
*/
/**
* Defines the chunk group info type used by this module.
* @typedef {object} ChunkGroupInfo
* @property {ChunkGroup} chunkGroup the chunk group
* @property {RuntimeSpec} runtime the runtimes
* @property {bigint | undefined} minAvailableModules current minimal set of modules available at this point
* @property {bigint[]} availableModulesToBeMerged enqueued updates to the minimal set of available modules
* @property {Set<Module>=} skippedItems modules that were skipped because module is already available in parent chunks (need to reconsider when minAvailableModules changes)
* @property {Set<[Module, ModuleGraphConnection[]]>=} skippedModuleConnections referenced modules that where skipped because they were not active in this runtime
* @property {bigint | undefined} resultingAvailableModules set of modules available including modules from this chunk group
* @property {bigint | undefined} connectedAvailableModules resultingAvailableModules last passed to children, to detect when it grows
* @property {Set<ChunkGroupInfo> | undefined} children set of children chunk groups, that will be revisited when resultingAvailableModules changes
* @property {Set<ChunkGroupInfo> | undefined} parents set of parent chunk groups, whose resultingAvailableModules intersect to minAvailableModules
* @property {Set<ChunkGroupInfo> | undefined} availableSources set of chunk groups that are the source for minAvailableModules
* @property {Set<ChunkGroupInfo> | undefined} availableChildren set of chunk groups which depend on the this chunk group as availableSource
* @property {number} preOrderIndex next pre order index
* @property {number} postOrderIndex next post order index
* @property {boolean} chunkLoading has a chunk loading mechanism
* @property {boolean} asyncChunks create async chunks
* @property {Module | null} depModule the module that is the dependency of the block
* @property {boolean} circular Whether to deduplicate to avoid circular references
*/
/**
* Defines the block chunk group connection type used by this module.
* @typedef {object} BlockChunkGroupConnection
* @property {ChunkGroupInfo} originChunkGroupInfo origin chunk group
* @property {ChunkGroup} chunkGroup referenced chunk group
*/
/** @typedef {(Module | ConnectionState | ModuleGraphConnection)[]} BlockModulesInTuples */
/** @typedef {(Module | ConnectionState | ModuleGraphConnection[])[]} BlockModulesInFlattenTuples */
/** @typedef {Map<DependenciesBlock, BlockModulesInFlattenTuples>} BlockModulesMap */
/** @typedef {Map<Chunk, bigint>} MaskByChunk */
/** @typedef {Set<DependenciesBlock>} BlocksWithNestedBlocks */
/** @typedef {Map<AsyncDependenciesBlock, BlockChunkGroupConnection[]>} BlockConnections */
/** @typedef {Map<ChunkGroup, ChunkGroupInfo>} ChunkGroupInfoMap */
/** @typedef {Set<ChunkGroup>} AllCreatedChunkGroups */
/** @typedef {Map<Entrypoint, Module[]>} InputEntrypointsAndModules */
const ZERO_BIGINT = BigInt(0);
const ONE_BIGINT = BigInt(1);
/**
* Checks whether this object is ordinal set in mask.
* @param {bigint} mask The mask to test
* @param {number} ordinal The ordinal of the bit to test
* @returns {boolean} If the ordinal-th bit is set in the mask
*/
const isOrdinalSetInMask = (mask, ordinal) =>
BigInt.asUintN(1, mask >> BigInt(ordinal)) !== ZERO_BIGINT;
/**
* Gets active state of connections.
* @param {ModuleGraphConnection[]} connections list of connections
* @param {RuntimeSpec} runtime for which runtime
* @returns {ConnectionState} connection state
*/
const getActiveStateOfConnections = (connections, runtime) => {
let merged = connections[0].getActiveState(runtime);
if (merged === true) return true;
for (let i = 1; i < connections.length; i++) {
const c = connections[i];
merged = ModuleGraphConnection.addConnectionStates(
merged,
c.getActiveState(runtime)
);
if (merged === true) return true;
}
return merged;
};
/**
* Extract block modules.
* @param {Module} module module
* @param {ModuleGraph} moduleGraph module graph
* @param {RuntimeSpec} runtime runtime
* @param {BlockModulesMap} blockModulesMap block modules map
*/
const extractBlockModules = (module, moduleGraph, runtime, blockModulesMap) => {
/** @type {DependenciesBlock | undefined} */
let blockCache;
/** @type {BlockModulesInTuples | undefined} */
let modules;
/** @type {BlockModulesInTuples[]} */
const arrays = [];
/** @type {DependenciesBlock[]} */
const queue = [module];
while (queue.length > 0) {
const block = /** @type {DependenciesBlock} */ (queue.pop());
/** @type {Module[]} */
const arr = [];
arrays.push(arr);
blockModulesMap.set(block, arr);
for (const b of block.blocks) {
queue.push(b);
}
}
for (const connection of moduleGraph.getOutgoingConnections(module)) {
const d = connection.dependency;
// We skip connections without dependency
if (!d) continue;
const m = connection.module;
// We skip connections without Module pointer
if (!m) continue;
// We skip weak connections
if (connection.weak) continue;
const block = moduleGraph.getParentBlock(d);
let index = moduleGraph.getParentBlockIndex(d);
// deprecated fallback
if (index < 0) {
index = /** @type {DependenciesBlock} */ (block).dependencies.indexOf(d);
}
if (blockCache !== block) {
modules =
/** @type {BlockModulesInTuples} */
(
blockModulesMap.get(
(blockCache = /** @type {DependenciesBlock} */ (block))
)
);
}
const i = index * 3;
/** @type {BlockModulesInTuples} */
(modules)[i] = m;
/** @type {BlockModulesInTuples} */
(modules)[i + 1] = connection.getActiveState(runtime);
/** @type {BlockModulesInTuples} */
(modules)[i + 2] = connection;
}
for (const modules of arrays) {
if (modules.length === 0) continue;
/** @type {undefined | Map<Module | ModuleGraphConnection | ConnectionState, number>} */
let indexMap;
let length = 0;
outer: for (let j = 0; j < modules.length; j += 3) {
const m = modules[j];
if (m === undefined) continue;
const state = /** @type {ConnectionState} */ (modules[j + 1]);
const connection = /** @type {ModuleGraphConnection} */ (modules[j + 2]);
if (indexMap === undefined) {
let i = 0;
for (; i < length; i += 3) {
if (modules[i] === m) {
const merged = /** @type {ConnectionState} */ (modules[i + 1]);
/** @type {ModuleGraphConnection[]} */
(
/** @type {BlockModulesInFlattenTuples[number]} */ (
modules[i + 2]
)
).push(connection);
if (merged === true) continue outer;
modules[i + 1] = ModuleGraphConnection.addConnectionStates(
merged,
state
);
continue outer;
}
}
modules[length] = m;
length++;
modules[length] = state;
length++;
/** @type {ModuleGraphConnection[]} */
(/** @type {BlockModulesInFlattenTuples[number]} */ (modules[length])) =
[connection];
length++;
if (length > 30) {
// To avoid worse case performance, we will use an index map for
// linear cost access, which allows to maintain O(n) complexity
// while keeping allocations down to a minimum
indexMap = new Map();
for (let i = 0; i < length; i += 3) {
indexMap.set(modules[i], i + 1);
}
}
} else {
const idx = indexMap.get(m);
if (idx !== undefined) {
const merged = /** @type {ConnectionState} */ (modules[idx]);
/** @type {ModuleGraphConnection[]} */
(
/** @type {BlockModulesInFlattenTuples[number]} */ (
modules[idx + 1]
)
).push(connection);
if (merged === true) continue;
modules[idx] = ModuleGraphConnection.addConnectionStates(
merged,
state
);
} else {
modules[length] = m;
length++;
modules[length] = state;
indexMap.set(m, length);
length++;
/** @type {ModuleGraphConnection[]} */
(
/** @type {BlockModulesInFlattenTuples[number]} */
(modules[length])
) = [connection];
length++;
}
}
}
modules.length = length;
}
};
/**
* Derives block modules for another runtime from an already extracted result.
* Only the merged active state of each connection group can differ per
* runtime; modules and connection groups are reused. When all states turn
* out equal, the tuple array is shared instead of copied.
* @param {Module} module the root module
* @param {BlockModulesMap} source map containing the extracted result for all blocks of the module
* @param {RuntimeSpec} runtime runtime to derive for
* @param {BlockModulesMap} blockModulesMap block modules map to fill
*/
const deriveBlockModules = (module, source, runtime, blockModulesMap) => {
/** @type {DependenciesBlock[]} */
const queue = [module];
while (queue.length > 0) {
const block = /** @type {DependenciesBlock} */ (queue.pop());
const sourceModules =
/** @type {BlockModulesInFlattenTuples} */
(source.get(block));
let target = sourceModules;
for (let i = 0, len = sourceModules.length; i < len; i += 3) {
const state = getActiveStateOfConnections(
/** @type {ModuleGraphConnection[]} */ (sourceModules[i + 2]),
runtime
);
if (target === sourceModules) {
if (state === sourceModules[i + 1]) continue;
// states diverged, copy (states before i are identical)
target = [...sourceModules];
}
target[i + 1] = state;
}
blockModulesMap.set(block, target);
for (const b of block.blocks) {
queue.push(b);
}
}
};
/**
* Processes the provided logger.
* @param {Logger} logger a logger
* @param {Compilation} compilation the compilation
* @param {InputEntrypointsAndModules} inputEntrypointsAndModules chunk groups which are processed with the modules
* @param {ChunkGroupInfoMap} chunkGroupInfoMap mapping from chunk group to available modules
* @param {BlockConnections} blockConnections connection for blocks
* @param {BlocksWithNestedBlocks} blocksWithNestedBlocks flag for blocks that have nested blocks
* @param {AllCreatedChunkGroups} allCreatedChunkGroups filled with all chunk groups that are created here
* @param {MaskByChunk} maskByChunk module content mask by chunk
*/
const visitModules = (
logger,
compilation,
inputEntrypointsAndModules,
chunkGroupInfoMap,
blockConnections,
blocksWithNestedBlocks,
allCreatedChunkGroups,
maskByChunk
) => {
const { moduleGraph, chunkGraph, moduleMemCaches } = compilation;
/** @type {Map<RuntimeSpec, BlockModulesMap>} */
const blockModulesRuntimeMap = new Map();
// map containing the first extraction per root module,
// other runtimes derive their result from it
/** @type {Map<Module, BlockModulesMap>} */
const firstBlockModulesMapByModule = new Map();
/** @type {Map<Module, number>} */
const ordinalByModule = new Map();
/**
* Gets module ordinal.
* @param {Module} module The module to look up
* @returns {number} The ordinal of the module in masks
*/
const getModuleOrdinal = (module) => {
let ordinal = ordinalByModule.get(module);
if (ordinal === undefined) {
ordinal = ordinalByModule.size;
ordinalByModule.set(module, ordinal);
}
return ordinal;
};
for (const chunk of compilation.chunks) {
let mask = ZERO_BIGINT;
for (const m of chunkGraph.getChunkModulesIterable(chunk)) {
mask |= ONE_BIGINT << BigInt(getModuleOrdinal(m));
}
maskByChunk.set(chunk, mask);
}
/**
* Gets block modules.
* @param {DependenciesBlock} block block
* @param {RuntimeSpec} runtime runtime
* @returns {BlockModulesInFlattenTuples | undefined} block modules in flatten tuples
*/
const getBlockModules = (block, runtime) => {
let blockModulesMap = blockModulesRuntimeMap.get(runtime);
if (blockModulesMap === undefined) {
/** @type {BlockModulesMap} */
blockModulesMap = new Map();
blockModulesRuntimeMap.set(runtime, blockModulesMap);
}
let blockModules = blockModulesMap.get(block);
if (blockModules !== undefined) return blockModules;
const module = /** @type {Module} */ (block.getRootBlock());
const memCache = moduleMemCaches && moduleMemCaches.get(module);
if (memCache !== undefined) {
// an optimization (e.g. side effects) can flip a connection's active
// state without the module changing — key the cache by the states too
let stateEtag = "";
for (const connection of moduleGraph.getOutgoingConnections(module)) {
if (!connection.dependency || !connection.module || connection.weak) {
continue;
}
const state = connection.getActiveState(runtime);
stateEtag += state === true ? "T" : state === false ? "F" : "O";
}
/** @type {BlockModulesMap} */
const map = memCache.provide(
"bundleChunkGraph.blockModules",
runtime,
stateEtag,
() => {
logger.time("visitModules: prepare");
const map = new Map();
const source = firstBlockModulesMapByModule.get(module);
if (source !== undefined) {
deriveBlockModules(module, source, runtime, map);
} else {
extractBlockModules(module, moduleGraph, runtime, map);
}
logger.timeAggregate("visitModules: prepare");
return map;
}
);
// allow other runtimes to derive from a memCache hit too
if (!firstBlockModulesMapByModule.has(module)) {
firstBlockModulesMapByModule.set(module, map);
}
for (const [block, blockModules] of map) {
blockModulesMap.set(block, blockModules);
}
return map.get(block);
}
logger.time("visitModules: prepare");
const source = firstBlockModulesMapByModule.get(module);
if (source !== undefined) {
deriveBlockModules(module, source, runtime, blockModulesMap);
} else {
extractBlockModules(module, moduleGraph, runtime, blockModulesMap);
firstBlockModulesMapByModule.set(module, blockModulesMap);
}
blockModules =
/** @type {BlockModulesInFlattenTuples} */
(blockModulesMap.get(block));
logger.timeAggregate("visitModules: prepare");
return blockModules;
};
let statProcessedQueueItems = 0;
let statProcessedBlocks = 0;
let statConnectedChunkGroups = 0;
let statProcessedChunkGroupsForMerging = 0;
let statMergedAvailableModuleSets = 0;
const statForkedAvailableModules = 0;
const statForkedAvailableModulesCount = 0;
const statForkedAvailableModulesCountPlus = 0;
const statForkedMergedModulesCount = 0;
const statForkedMergedModulesCountPlus = 0;
const statForkedResultModulesCount = 0;
let statChunkGroupInfoUpdated = 0;
let statChildChunkGroupsReconnected = 0;
let nextChunkGroupIndex = 0;
let nextFreeModulePreOrderIndex = 0;
let nextFreeModulePostOrderIndex = 0;
/** @type {Map<DependenciesBlock, ChunkGroupInfo>} */
const blockChunkGroups = new Map();
/** @type {Map<ChunkGroupInfo, Set<DependenciesBlock>>} */
const blocksByChunkGroupInfo = new Map();
/** @typedef {Map<string, ChunkGroupInfo>} NamedChunkGroup */
/** @type {NamedChunkGroup} */
const namedChunkGroups = new Map();
/** @type {NamedChunkGroup} */
const namedAsyncEntrypoints = new Map();
/** @type {Map<Module, ChunkGroupInfo>} */
const depModuleAsyncEntrypoints = new Map();
/** @type {Set<ChunkGroupInfo>} */
const outdatedOrderIndexChunkGroups = new Set();
const ADD_AND_ENTER_ENTRY_MODULE = 0;
const ADD_AND_ENTER_MODULE = 1;
const ENTER_MODULE = 2;
const PROCESS_BLOCK = 3;
const PROCESS_ENTRY_BLOCK = 4;
const LEAVE_MODULE = 5;
/** @type {QueueItem[]} */
let queue = [];
/** @typedef {Set<[ChunkGroupInfo, QueueItem | null]>} ConnectList */
/** @type {Map<ChunkGroupInfo, ConnectList>} */
const queueConnect = new Map();
/** @type {Set<ChunkGroupInfo>} */
const chunkGroupsForCombining = new Set();
/** @type {Set<ChunkGroupInfo>} */
const chunkGroupsForGrowing = new Set();
// Fill queue with entrypoint modules
// Create ChunkGroupInfo for entrypoints
for (const [chunkGroup, modules] of inputEntrypointsAndModules) {
const runtime = getEntryRuntime(
compilation,
/** @type {string} */ (chunkGroup.name),
chunkGroup.options
);
/** @type {ChunkGroupInfo} */
const chunkGroupInfo = {
depModule: null,
circular: false,
chunkGroup,
runtime,
minAvailableModules: undefined,
availableModulesToBeMerged: [],
skippedItems: undefined,
resultingAvailableModules: undefined,
connectedAvailableModules: undefined,
children: undefined,
parents: undefined,
availableSources: undefined,
availableChildren: undefined,
preOrderIndex: 0,
postOrderIndex: 0,
chunkLoading:
chunkGroup.options.chunkLoading !== undefined
? chunkGroup.options.chunkLoading !== false
: compilation.outputOptions.chunkLoading !== false,
asyncChunks:
chunkGroup.options.asyncChunks !== undefined
? chunkGroup.options.asyncChunks
: compilation.outputOptions.asyncChunks !== false
};
chunkGroup.index = nextChunkGroupIndex++;
if (chunkGroup.getNumberOfParents() > 0) {
// minAvailableModules for child entrypoints are unknown yet, set to undefined.
// This means no module is added until other sets are merged into
// this minAvailableModules (by the parent entrypoints)
const skippedItems = new Set(modules);
chunkGroupInfo.skippedItems = skippedItems;
chunkGroupsForCombining.add(chunkGroupInfo);
} else {
// The application may start here: We start with an empty list of available modules
chunkGroupInfo.minAvailableModules = ZERO_BIGINT;
const chunk = chunkGroup.getEntrypointChunk();
for (const module of modules) {
queue.push({
action: ADD_AND_ENTER_MODULE,
block: module,
module,
chunk,
chunkGroup,
chunkGroupInfo
});
}
}
chunkGroupInfoMap.set(chunkGroup, chunkGroupInfo);
if (chunkGroup.name) {
namedChunkGroups.set(chunkGroup.name, chunkGroupInfo);
}
}
// Fill availableSources with parent-child dependencies between entrypoints
for (const chunkGroupInfo of chunkGroupsForCombining) {
const { chunkGroup } = chunkGroupInfo;
chunkGroupInfo.availableSources = new Set();
for (const parent of chunkGroup.parentsIterable) {
const parentChunkGroupInfo =
/** @type {ChunkGroupInfo} */
(chunkGroupInfoMap.get(parent));
chunkGroupInfo.availableSources.add(parentChunkGroupInfo);
if (parentChunkGroupInfo.availableChildren === undefined) {
parentChunkGroupInfo.availableChildren = new Set();
}
parentChunkGroupInfo.availableChildren.add(chunkGroupInfo);
}
}
// pop() is used to read from the queue
// so it need to be reversed to be iterated in
// correct order
queue.reverse();
/** @type {Set<ChunkGroupInfo>} */
const outdatedChunkGroupInfo = new Set();
/** @type {Set<[ChunkGroupInfo, QueueItem | null]>} */
const chunkGroupsForMerging = new Set();
/** @type {QueueItem[]} */
let queueDelayed = [];
/** @type {[Module, ModuleGraphConnection[]][]} */
const skipConnectionBuffer = [];
/** @type {Module[]} */
const skipBuffer = [];
/** @type {QueueItem[]} */
const queueBuffer = [];
/** @type {Module} */
let module;
/** @type {Chunk} */
let chunk;
/** @type {ChunkGroup} */
let chunkGroup;
/** @type {DependenciesBlock} */
let block;
/** @type {ChunkGroupInfo} */
let chunkGroupInfo;
// For each async Block in graph
/**
* Processes the provided b.
* @param {AsyncDependenciesBlock} b iterating over each Async DepBlock
* @returns {void}
*/
const iteratorBlock = (b) => {
// 1. We create a chunk group with single chunk in it for this Block
// but only once (blockChunkGroups map)
/** @type {ChunkGroupInfo | undefined} */
let cgi = blockChunkGroups.get(b);
/** @type {ChunkGroup | undefined} */
let c;
/** @type {Entrypoint | undefined} */
let entrypoint;
/** @type {Module | null} */
const depModule = moduleGraph.getModule(b.dependencies[0]);
const entryOptions = b.groupOptions && b.groupOptions.entryOptions;
if (cgi === undefined) {
const chunkName = (b.groupOptions && b.groupOptions.name) || b.chunkName;
if (entryOptions) {
cgi = namedAsyncEntrypoints.get(/** @type {string} */ (chunkName));
if (!cgi && !b.circular && depModule) {
cgi = depModuleAsyncEntrypoints.get(depModule);
}
if (!cgi) {
entrypoint = compilation.addAsyncEntrypoint(
entryOptions,
module,
/** @type {DependencyLocation} */ (b.loc),
/** @type {string} */ (b.request)
);
maskByChunk.set(entrypoint.chunks[0], ZERO_BIGINT);
entrypoint.index = nextChunkGroupIndex++;
cgi = {
depModule,
circular: b.circular,
chunkGroup: entrypoint,
runtime:
entrypoint.options.runtime ||
/** @type {string | undefined} */ (entrypoint.name),
minAvailableModules: ZERO_BIGINT,
availableModulesToBeMerged: [],
skippedItems: undefined,
resultingAvailableModules: undefined,
connectedAvailableModules: undefined,
children: undefined,
parents: undefined,
availableSources: undefined,
availableChildren: undefined,
preOrderIndex: 0,
postOrderIndex: 0,
chunkLoading:
entryOptions.chunkLoading !== undefined
? entryOptions.chunkLoading !== false
: chunkGroupInfo.chunkLoading,
asyncChunks:
entryOptions.asyncChunks !== undefined
? entryOptions.asyncChunks
: chunkGroupInfo.asyncChunks
};
chunkGroupInfoMap.set(entrypoint, cgi);
chunkGraph.connectBlockAndChunkGroup(b, entrypoint);
if (chunkName) {
namedAsyncEntrypoints.set(chunkName, cgi);
}
if (!b.circular && depModule) {
depModuleAsyncEntrypoints.set(depModule, cgi);
}
} else {
entrypoint = /** @type {Entrypoint} */ (cgi.chunkGroup);
// Fill in only the options still unset: blocks deduping to one
// entrypoint may carry different values, so the first to create it
// wins.
// `name` is excluded, being the entrypoint's identity and fixed at
// creation. Back-filling it would make codegen order-dependent, which
// breaks persistent caching.
const existingOptions = entrypoint.options;
for (const key_ of Object.keys(entryOptions)) {
const key =
/** @type {keyof EntryOptions} */
(key_);
if (key === "name") continue;
if (entryOptions[key] === undefined) continue;
if (existingOptions[key] !== undefined) continue;
/** @type {EntryOptions[keyof EntryOptions]} */
(existingOptions[key]) = entryOptions[key];
}
entrypoint.addOrigin(
module,
/** @type {DependencyLocation} */ (b.loc),
/** @type {string} */ (b.request)
);
chunkGraph.connectBlockAndChunkGroup(b, entrypoint);
}
// 2. We enqueue the DependenciesBlock for traversal
queueDelayed.push({
action: PROCESS_ENTRY_BLOCK,
block: b,
module,
chunk: entrypoint.chunks[0],
chunkGroup: entrypoint,
chunkGroupInfo: cgi
});
} else if (!chunkGroupInfo.asyncChunks || !chunkGroupInfo.chunkLoading) {
// Just queue the block into the current chunk group
queue.push({
action: PROCESS_BLOCK,
block: b,
module,
chunk,
chunkGroup,
chunkGroupInfo
});
} else {
cgi = chunkName ? namedChunkGroups.get(chunkName) : undefined;
if (!cgi) {
c = compilation.addChunkInGroup(
b.groupOptions || b.chunkName,
module,
/** @type {DependencyLocation} */ (b.loc),
/** @type {string} */ (b.request)
);
maskByChunk.set(c.chunks[0], ZERO_BIGINT);
c.index = nextChunkGroupIndex++;
cgi = {
depModule,
circular: b.circular,
chunkGroup: c,
runtime: chunkGroupInfo.runtime,
minAvailableModules: undefined,
availableModulesToBeMerged: [],
skippedItems: undefined,
resultingAvailableModules: undefined,
connectedAvailableModules: undefined,
children: undefined,
parents: undefined,
availableSources: undefined,
availableChildren: undefined,
preOrderIndex: 0,
postOrderIndex: 0,
chunkLoading: chunkGroupInfo.chunkLoading,
asyncChunks: chunkGroupInfo.asyncChunks
};
allCreatedChunkGroups.add(c);
chunkGroupInfoMap.set(c, cgi);
if (chunkName) {
namedChunkGroups.set(chunkName, cgi);
}
} else {
c = cgi.chunkGroup;
if (c.isInitial()) {
compilation.errors.push(
new (getAsyncDependencyToInitialChunkError())(
/** @type {string} */ (chunkName),
module,
/** @type {DependencyLocation} */ (b.loc)
)
);
c = chunkGroup;
} else {
c.addOptions(b.groupOptions);
}
c.addOrigin(
module,
/** @type {DependencyLocation} */ (b.loc),
/** @type {string} */ (b.request)
);
}
blockConnections.set(b, []);
}
blockChunkGroups.set(b, /** @type {ChunkGroupInfo} */ (cgi));
} else if (entryOptions) {
entrypoint = /** @type {Entrypoint} */ (cgi.chunkGroup);
} else {
c = cgi.chunkGroup;
}
if (c !== undefined) {
// 2. We store the connection for the block
// to connect it later if needed
/** @type {BlockChunkGroupConnection[]} */
(blockConnections.get(b)).push({
originChunkGroupInfo: chunkGroupInfo,
chunkGroup: c
});
// 3. We enqueue the chunk group info creation/updating
let connectList = queueConnect.get(chunkGroupInfo);
if (connectList === undefined) {
/** @type {ConnectList} */
connectList = new Set();
queueConnect.set(chunkGroupInfo, connectList);
}
connectList.add([
/** @type {ChunkGroupInfo} */ (cgi),
{
action: PROCESS_BLOCK,
block: b,
module,
chunk: c.chunks[0],
chunkGroup: c,
chunkGroupInfo: /** @type {ChunkGroupInfo} */ (cgi)
}
]);
} else if (
entrypoint !== undefined &&
(chunkGroupInfo.circular || chunkGroupInfo.depModule !== depModule)
) {
chunkGroupInfo.chunkGroup.addAsyncEntrypoint(entrypoint);
}
};
/**
* Processes the provided block.
* @param {DependenciesBlock} block the block
* @returns {void}
*/
const processBlock = (block) => {
statProcessedBlocks++;
// get prepared block info
const blockModules = getBlockModules(block, chunkGroupInfo.runtime);
if (blockModules !== undefined) {
const minAvailableModules =
/** @type {bigint} */
(chunkGroupInfo.minAvailableModules);
// Buffer items because order need to be reversed to get indices correct
// Traverse all referenced modules
for (let i = 0, len = blockModules.length; i < len; i += 3) {
const refModule = /** @type {Module} */ (blockModules[i]);
// For single comparisons this might be cheaper
const isModuleInChunk = chunkGraph.isModuleInChunk(refModule, chunk);
if (isModuleInChunk) {
// skip early if already connected
continue;
}
const activeState = /** @type {ConnectionState} */ (
blockModules[i + 1]
);
if (activeState !== true) {
const connections = /** @type {ModuleGraphConnection[]} */ (
blockModules[i + 2]
);
skipConnectionBuffer.push([refModule, connections]);
// We skip inactive connections
if (activeState === false) continue;
} else if (
// assigning ordinals lazily here keeps masks small
isOrdinalSetInMask(minAvailableModules, getModuleOrdinal(refModule))
) {
// already in parent chunks, skip it for now
skipBuffer.push(refModule);
continue;
}
// enqueue, then add and enter to be in the correct order
// this is relevant with circular dependencies
queueBuffer.push({
action: activeState === true ? ADD_AND_ENTER_MODULE : PROCESS_BLOCK,
block: refModule,
module: refModule,
chunk,
chunkGroup,
chunkGroupInfo
});
}
// Add buffered items in reverse order
if (skipConnectionBuffer.length > 0) {
let { skippedModuleConnections } = chunkGroupInfo;
if (skippedModuleConnections === undefined) {
chunkGroupInfo.skippedModuleConnections = skippedModuleConnections =
new Set();
}
// Drained by popping (still reverse order): `length = 0` would drop
// the buffer's backing store, so the next push reallocates one.
while (skipConnectionBuffer.length > 0) {
skippedModuleConnections.add(
/** @type {[Module, ModuleGraphConnection[]]} */
(skipConnectionBuffer.pop())
);
}
}
if (skipBuffer.length > 0) {
let { skippedItems } = chunkGroupInfo;
if (skippedItems === undefined) {
chunkGroupInfo.skippedItems = skippedItems = new Set();
}
while (skipBuffer.length > 0) {
skippedItems.add(/** @type {Module} */ (skipBuffer.pop()));
}
}
if (queueBuffer.length > 0) {
while (queueBuffer.length > 0) {
queue.push(/** @type {QueueItem} */ (queueBuffer.pop()));
}
}
}
// Traverse all Blocks
for (const b of block.blocks) {
iteratorBlock(b);
}
if (block.blocks.length > 0 && module !== block) {
blocksWithNestedBlocks.add(block);
}
};
/**
* Process entry block.
* @param {DependenciesBlock} block the block
* @returns {void}
*/
const processEntryBlock = (block) => {
statProcessedBlocks++;
// get prepared block info
const blockModules = getBlockModules(block, chunkGroupInfo.runtime);
if (blockModules !== undefined) {
// Traverse all referenced modules in reverse order
for (let i = blockModules.length - 3; i >= 0; i -= 3) {
const refModule = /** @type {Module} */ (blockModules[i]);
const activeState = /** @type {ConnectionState} */ (
blockModules[i + 1]
);
// enqueue, then add and enter to be in the correct order
// this is relevant with circular dependencies
queue.push({
action:
activeState === true ? ADD_AND_ENTER_ENTRY_MODULE : PROCESS_BLOCK,
block: refModule,
module: refModule,
chunk,
chunkGroup,
chunkGroupInfo
});
}
}
// Traverse all Blocks
for (const b of block.blocks) {
iteratorBlock(b);
}
if (block.blocks.length > 0 && module !== block) {
blocksWithNestedBlocks.add(block);
}
};
const processQueue = () => {
while (queue.length) {
statProcessedQueueItems++;
const queueItem = /** @type {QueueItem} */ (queue.pop());
module = queueItem.module;
block = queueItem.block;
chunk = queueItem.chunk;
chunkGroup = queueItem.chunkGroup;
chunkGroupInfo = queueItem.chunkGroupInfo;
switch (queueItem.action) {
case ADD_AND_ENTER_ENTRY_MODULE:
chunkGraph.connectChunkAndEntryModule(
chunk,
module,
/** @type {Entrypoint} */ (chunkGroup)
);
// fallthrough
case ADD_AND_ENTER_MODULE: {
const isModuleInChunk = chunkGraph.isModuleInChunk(module, chunk);
if (isModuleInChunk) {
// already connected, skip it
break;
}
// We connect Module and Chunk
chunkGraph.connectChunkAndModule(chunk, module);
const moduleOrdinal = getModuleOrdinal(module);
let chunkMask = /** @type {bigint} */ (maskByChunk.get(chunk));
chunkMask |= ONE_BIGINT << BigInt(moduleOrdinal);
maskByChunk.set(chunk, chunkMask);
if (chunkGroupInfo.resultingAvailableModules !== undefined) {
// A later block reached this chunk group after its children read this value,
// we clear it and mark outdated so the children reconnect and drop it
chunkGroupInfo.resultingAvailableModules = undefined;
outdatedChunkGroupInfo.add(chunkGroupInfo);
}
}
// fallthrough
case ENTER_MODULE: {
const index = chunkGroup.getModulePreOrderIndex(module);
if (index === undefined) {
chunkGroup.setModulePreOrderIndex(
module,
chunkGroupInfo.preOrderIndex++
);
}
if (
moduleGraph.setPreOrderIndexIfUnset(
module,
nextFreeModulePreOrderIndex
)
) {
nextFreeModulePreOrderIndex++;
}
// reuse queueItem
queueItem.action = LEAVE_MODULE;
queue.push(queueItem);
}
// fallthrough
case PROCESS_BLOCK: {
processBlock(block);
break;
}
case PROCESS_ENTRY_BLOCK: {
processEntryBlock(block);
break;
}
case LEAVE_MODULE: {
const index = chunkGroup.getModulePostOrderIndex(module);
if (index === undefined) {
chunkGroup.setModulePostOrderIndex(
module,
chunkGroupInfo.postOrderIndex++
);
}
if (
moduleGraph.setPostOrderIndexIfUnset(
module,
nextFreeModulePostOrderIndex
)
) {
nextFreeModulePostOrderIndex++;
}
break;
}
}
}
};
/**
* Get the modules in the chunks of a chunk group.
* @param {ChunkGroupInfo} chunkGroupInfo The info object for the chunk group
* @returns {bigint} The mask of modules in the chunks of the chunk group
*/
const getChunkGroupMask = (chunkGroupInfo) => {
let mask = ZERO_BIGINT;
for (const chunk of chunkGroupInfo.chunkGroup.chunks) {
mask |= /** @type {bigint} */ (maskByChunk.get(chunk));
}
return mask;
};
/**
* Calculate resulting available modules.
* @param {ChunkGroupInfo} chunkGroupInfo The info object for the chunk group
* @returns {bigint} The mask of available modules after the chunk group
*/
const calculateResultingAvailableModules = (chunkGroupInfo) => {
if (chunkGroupInfo.resultingAvailableModules !== undefined) {
return chunkGroupInfo.resultingAvailableModules;
}
return (chunkGroupInfo.resultingAvailableModules =
/** @type {bigint} */ (chunkGroupInfo.minAvailableModules) |
getChunkGroupMask(chunkGroupInfo));
};
const processConnectQueue = () => {
// Figure out new parents for chunk groups
// to get new available modules for these children
for (const [chunkGroupInfo, targets] of queueConnect) {
// 1. Add new targets to the list of children
if (chunkGroupInfo.children === undefined) {
chunkGroupInfo.children = new Set();
}
for (const [target] of targets) {
chunkGroupInfo.children.add(target);
}
// 2. Calculate resulting available modules
const resultingAvailableModules =
calculateResultingAvailableModules(chunkGroupInfo);
const connectedAvailableModules =
chunkGroupInfo.connectedAvailableModules;
// Merging only intersects, so when this grew all children need the growing phase
if (
connectedAvailableModules !== undefined &&
resultingAvailableModules !== connectedAvailableModules &&
(resultingAvailableModules & ~connectedAvailableModules) !== ZERO_BIGINT
) {
for (const child of chunkGroupInfo.children) {
if (!child.chunkGroup.isInitial()) chunkGroupsForGrowing.add(child);
}
}
chunkGroupInfo.connectedAvailableModules = resultingAvailableModules;
const runtime = chunkGroupInfo.runtime;
// 3. Update chunk group info
for (const [target, queueItem] of targets) {
if (target.parents === undefined) {
target.parents = new Set();
}
target.parents.add(chunkGroupInfo);
target.availableModulesToBeMerged.push(resultingAvailableModules);
chunkGroupsForMerging.add([target, queueItem]);
const oldRuntime = target.runtime;
const newRuntime = mergeRuntime(oldRuntime, runtime);
if (oldRuntime !== newRuntime) {
target.runtime = newRuntime;
outdatedChunkGroupInfo.add(target);
}
}
statConnectedChunkGroups += targets.size;
}
queueConnect.clear();
};
const processChunkGroupsForMerging = () => {
statProcessedChunkGroupsForMerging += chunkGroupsForMerging.size;
// Execute the merge
for (const [info, queueItem] of chunkGroupsForMerging) {
const availableModulesToBeMerged = info.availableModulesToBeMerged;
const cachedMinAvailableModules = info.minAvailableModules;
let minAvailableModules = cachedMinAvailableModules;
statMergedAvailableModuleSets += availableModulesToBeMerged.length;
// Drained by popping: the fold is commutative, and `length = 0` would
// drop the backing store that the next enqueue then reallocates.
while (availableModulesToBeMerged.length > 0) {
const availableModules =
/** @type {bigint} */
(availableModulesToBeMerged.pop());
if (minAvailableModules === undefined) {
minAvailableModules = availableModules;
} else {
minAvailableModules &= availableModules;
}
}
const changed = minAvailableModules !== cachedMinAvailableModules;
if (changed) {
info.minAvailableModules = minAvailableModules;
info.resultingAvailableModules = undefined;
outdatedChunkGroupInfo.add(info);
}
if (queueItem !== null) {
let blocks = blocksByChunkGroupInfo.get(info);
if (blocks === undefined) {
blocks = new Set();
blocksByChunkGroupInfo.set(info, blocks);
}
// A walk depends only on the block and minAvailableModules: skip it when both are seen
const newBlock = !blocks.has(queueItem.block);
if (newBlock) blocks.add(queueItem.block);
if (newBlock || changed) queueDelayed.push(queueItem);
}
}
chunkGroupsForMerging.clear();
};
const processChunkGroupsForCombining = () => {
for (const info of chunkGroupsForCombining) {
for (const source of /** @type {Set<ChunkGroupInfo>} */ (
info.availableSources
)) {
if (source.minAvailableModules === undefined) {
chunkGroupsForCombining.delete(info);
break;
}
}
}
for (const info of chunkGroupsForCombining) {
let availableModules = ZERO_BIGINT;
// combine minAvailableModules from all resultingAvailableModules
for (const source of /** @type {Set<ChunkGroupInfo>} */ (
info.availableSources
)) {
const resultingAvailableModules =
calculateResultingAvailableModules(source);
availableModules |= resultingAvailableModules;
}
info.minAvailableModules = availableModules;
info.resultingAvailableModules = undefined;
outdatedChunkGroupInfo.add(info);
}
chunkGroupsForCombining.clear();
};
const processChunkGroupsForGrowing = () => {
for (const info of chunkGroupsForGrowing) {
/** @type {bigint | undefined} */
let availableModules;
for (const parent of /** @type {Set<ChunkGroupInfo>} */ (info.parents)) {
const resultingAvailableModules =
calculateResultingAvailableModules(parent);
availableModules =
availableModules === undefined
? resultingAvailableModules
: availableModules & resultingAvailableModules;
// Each parent covers minAvailableModules, so the intersection cannot drop below it
if (availableModules === info.minAvailableModules) break;
}
if (availableModules === info.minAvailableModules) continue;
info.minAvailableModules = availableModules;
info.resultingAvailableModules = undefined;
outdatedChunkGroupInfo.add(info);
// Reverse of revisiting skipped items: move the own modules now available
// to skippedItems, where they are added back if it shrinks again
for (const chunk of info.chunkGroup.chunks) {
let chunkMask = /** @type {bigint} */ (maskByChunk.get(chunk));
const coveredModules =
chunkMask & /** @type {bigint} */ (availableModules);
if (coveredModules === ZERO_BIGINT) continue;
if (info.skippedItems === undefined) {
info.skippedItems = new Set();
}
for (const module of chunkGraph.getChunkModules(chunk)) {
const ordinal = getModuleOrdinal(module);
if (!isOrdinalSetInMask(coveredModules, ordinal)) continue;
chunkGraph.disconnectChunkAndModule(chunk, module);
chunkMask &= ~(ONE_BIGINT << BigInt(ordinal));
info.skippedItems.add(module);
}
maskByChunk.set(chunk, chunkMask);
}
}
chunkGroupsForGrowing.clear();
};
const processOutdatedChunkGroupInfo = () => {
statChunkGroupInfoUpdated += outdatedChunkGroupInfo.size;
// Revisit skipped elements
for (const info of outdatedChunkGroupInfo) {
// 1. Reconsider skipped items
if (info.skippedItems !== undefined) {
const minAvailableModules =
/** @type {bigint} */
(info.minAvailableModules);
for (const module of info.skippedItems) {
const ordinal = getModuleOrdinal(module);
if (!isOrdinalSetInMask(minAvailableModules, ordinal)) {
queue.push({
action: ADD_AND_ENTER_MODULE,
block: module,
module,
chunk: info.chunkGroup.chunks[0],
chunkGroup: info.chunkGroup,
chunkGroupInfo: info
});
info.skippedItems.delete(module);
}
}
}
// 2. Reconsider skipped connections
if (info.skippedModuleConnections !== undefined) {
const minAvailableModules =
/** @type {bigint} */
(info.minAvailableModules);
for (const entry of info.skippedModuleConnections) {
const [module, connections] = entry;
const activeState = getActiveStateOfConnections(
connections,
info.runtime
);
if (activeState === false) continue;
if (activeState === true) {
const ordinal = getModuleOrdinal(module);
info.skippedModuleConnections.delete(entry);
if (isOrdinalSetInMask(minAvailableModules, ordinal)) {
/** @type {NonNullable<ChunkGroupInfo["skippedItems"]>} */
(info.skippedItems).add(module);
continue;
}
}
queue.push({
action: activeState === true ? ADD_AND_ENTER_MODULE : PROCESS_BLOCK,
block: module,
module,
chunk: info.chunkGroup.chunks[0],
chunkGroup: info.chunkGroup,
chunkGroupInfo: info
});
}
}
// 3. Reconsider children chunk groups
if (info.children !== undefined && info.children.size > 0) {
statChildChunkGroupsReconnected += info.children.size;
let connectList = queueConnect.get(info);
if (connectList === undefined) {
/** @type {ConnectList} */
connectList = new Set();
queueConnect.set(info, connectList);
}
for (const cgi of info.children) {
connectList.add([cgi, null]);
}
}
// 4. Reconsider chunk groups for combining
if (info.availableChildren !== undefined) {
for (const cgi of info.availableChildren) {
chunkGroupsForCombining.add(cgi);
}
}
outdatedOrderIndexChunkGroups.add(info);
}
outdatedChunkGroupInfo.clear();
};
// Iterative traversal of the Module graph
// Recursive would be simpler to write but could result in Stack Overflows
while (
queue.length ||
queueConnect.size ||
outdatedChunkGroupInfo.size ||
chunkGroupsForCombining.size
) {
logger.time("visitModules: visiting");
processQueue();
logger.timeAggregateEnd("visitModules: prepare");
logger.timeEnd("visitModules: visiting");
if (chunkGroupsForCombining.size > 0) {
logger.time("visitModules: combine available modules");
processChunkGroupsForCombining();
logger.timeEnd("visitModules: combine available modules");
}
if (queueConnect.size > 0) {
logger.time("visitModules: calculating available modules");
processConnectQueue();
logger.timeEnd("visitModules: calculating available modules");
if (chunkGroupsForMerging.size > 0) {
logger.time("visitModules: merging available modules");
processChunkGroupsForMerging();
logger.timeEnd("visitModules: merging available modules");
}
}
if (outdatedChunkGroupInfo.size > 0) {
logger.time("visitModules: check modules for revisit");
processOutdatedChunkGroupInfo();
logger.timeEnd("visitModules: check modules for revisit");
}
// Run queueDelayed when all items of the queue are processed
// This is important to get the global indexing correct
// Async blocks should be processed after all sync blocks are processed
if (queue.length === 0) {
const tempQueue = queue;
queue = queueDelayed.reverse();
queueDelayed = tempQueue;
}
// Grow only after the shrinking phase finishes, so growth
// reads each parent's final `resultingAvailableModules`
if (
queue.length === 0 &&
queueConnect.size === 0 &&
chunkGroupsForCombining.size === 0 &&
chunkGroupsForGrowing.size > 0
) {
logger.time("visitModules: growing available modules");
processChunkGroupsForGrowing();
logger.timeEnd("visitModules: growing available modules");
}
}
for (const info of outdatedOrderIndexChunkGroups) {
const { chunkGroup, runtime } = info;
const blocks = blocksByChunkGroupInfo.get(info);
if (!blocks) {
continue;
}
for (const block of blocks) {
let preOrderIndex = 0;
let postOrderIndex = 0;
/**
* Processes the provided current.
* @param {DependenciesBlock} current current
* @param {BlocksWithNestedBlocks} visited visited dependencies blocks
*/
const process = (current, visited) => {
const blockModules =
/** @type {BlockModulesInFlattenTuples} */
(getBlockModules(current, runtime));
for (let i = 0, len = blockModules.length; i < len; i += 3) {
const activeState = /** @type {ConnectionState} */ (
blockModules[i + 1]
);
if (activeState === false) {
continue;
}
const refModule = /** @type {Module} */ (blockModules[i]);
if (visited.has(refModule)) {
continue;
}
visited.add(refModule);
if (refModule) {
chunkGroup.setModulePreOrderIndex(refModule, preOrderIndex++);
process(refModule, visited);
chunkGroup.setModulePostOrderIndex(refModule, postOrderIndex++);
}
}
};
process(block, new Set());
}
}
outdatedOrderIndexChunkGroups.clear();
ordinalByModule.clear();
logger.log(
`${statProcessedQueueItems} queue items processed (${statProcessedBlocks} blocks)`
);
logger.log(`${statConnectedChunkGroups} chunk groups connected`);
logger.log(
`${statProcessedChunkGroupsForMerging} chunk groups processed for merging (${statMergedAvailableModuleSets} module sets, ${statForkedAvailableModules} forked, ${statForkedAvailableModulesCount} + ${statForkedAvailableModulesCountPlus} modules forked, ${statForkedMergedModulesCount} + ${statForkedMergedModulesCountPlus} modules merged into fork, ${statForkedResultModulesCount} resulting modules)`
);
logger.log(
`${statChunkGroupInfoUpdated} chunk group info updated (${statChildChunkGroupsReconnected} already connected chunk groups reconnected)`
);
};
/**
* Connects chunk groups.
* @param {Compilation} compilation the compilation
* @param {BlocksWithNestedBlocks} blocksWithNestedBlocks flag for blocks that have nested blocks
* @param {BlockConnections} blockConnections connection for blocks
* @param {MaskByChunk} maskByChunk mapping from chunk to module mask
*/
const connectChunkGroups = (
compilation,
blocksWithNestedBlocks,
blockConnections,
maskByChunk
) => {
const { chunkGraph } = compilation;
/**
* Helper function to check if all modules of a chunk are available
* @param {ChunkGroup} chunkGroup the chunkGroup to scan
* @param {bigint} availableModules the comparator set
* @returns {boolean} return true if all modules of a chunk are available
*/
const areModulesAvailable = (chunkGroup, availableModules) => {
for (const chunk of chunkGroup.chunks) {
const chunkMask = /** @type {bigint} */ (maskByChunk.get(chunk));
if ((chunkMask & availableModules) !== chunkMask) return false;
}
return true;
};
// For each edge in the basic chunk graph
for (const [block, connections] of blockConnections) {
// Skip the whole set when no dependency needs connecting. They cannot be
// filtered one by one without inconsistent connections, since a module can
// only carry one version.
// TODO maybe decide this per runtime
if (
// Blocks with nested blocks must stay connected — skipping orphans the
// nested block's chunk group from this block's chunk group parent.
!blocksWithNestedBlocks.has(block) &&
connections.every(({ chunkGroup, originChunkGroupInfo }) =>
areModulesAvailable(
chunkGroup,
/** @type {bigint} */ (originChunkGroupInfo.resultingAvailableModules)
)
)
) {
continue;
}
// 2. Foreach edge
for (let i = 0; i < connections.length; i++) {
const { chunkGroup, originChunkGroupInfo } = connections[i];
// 3. Connect block with chunk
chunkGraph.connectBlockAndChunkGroup(block, chunkGroup);
// 4. Connect chunk with parent
if (originChunkGroupInfo.chunkGroup.addChild(chunkGroup)) {
chunkGroup.addParent(originChunkGroupInfo.chunkGroup);
}
}
}
};
/**
* Remove all unconnected chunk groups
* @param {Compilation} compilation the compilation
* @param {Iterable<ChunkGroup>} allCreatedChunkGroups all chunk groups that where created before
*/
const cleanupUnconnectedGroups = (compilation, allCreatedChunkGroups) => {
const { chunkGraph } = compilation;
for (const chunkGroup of allCreatedChunkGroups) {
if (chunkGroup.getNumberOfParents() === 0) {
for (const chunk of chunkGroup.chunks) {
compilation.chunks.delete(chunk);
chunkGraph.disconnectChunk(chunk);
}
chunkGraph.disconnectChunkGroup(chunkGroup);
chunkGroup.remove();
}
}
};
/**
* This method creates the Chunk graph from the Module graph
* @param {Compilation} compilation the compilation
* @param {InputEntrypointsAndModules} inputEntrypointsAndModules chunk groups which are processed with the modules
* @returns {void}
*/
const buildChunkGraph = (compilation, inputEntrypointsAndModules) => {
const logger = compilation.getLogger("webpack.buildChunkGraph");
// SHARED STATE
/** @type {BlockConnections} */
const blockConnections = new Map();
/** @type {AllCreatedChunkGroups} */
const allCreatedChunkGroups = new Set();
/** @type {ChunkGroupInfoMap} */
const chunkGroupInfoMap = new Map();
/** @type {BlocksWithNestedBlocks} */
const blocksWithNestedBlocks = new Set();
/** @type {MaskByChunk} */
const maskByChunk = new Map();
// PART ONE
logger.time("visitModules");
visitModules(
logger,
compilation,
inputEntrypointsAndModules,
chunkGroupInfoMap,
blockConnections,
blocksWithNestedBlocks,
allCreatedChunkGroups,
maskByChunk
);
logger.timeEnd("visitModules");
// PART TWO
logger.time("connectChunkGroups");
connectChunkGroups(
compilation,
blocksWithNestedBlocks,
blockConnections,
maskByChunk
);
logger.timeEnd("connectChunkGroups");
for (const [chunkGroup, chunkGroupInfo] of chunkGroupInfoMap) {
for (const chunk of chunkGroup.chunks) {
chunk.runtime = mergeRuntime(chunk.runtime, chunkGroupInfo.runtime);
}
}
// Cleanup work
logger.time("cleanup");
cleanupUnconnectedGroups(compilation, allCreatedChunkGroups);
logger.timeEnd("cleanup");
};
module.exports = buildChunkGraph;