diff --git a/lib/AmdMainTemplatePlugin.js b/lib/AmdMainTemplatePlugin.js index 3405af552..add65becf 100644 --- a/lib/AmdMainTemplatePlugin.js +++ b/lib/AmdMainTemplatePlugin.js @@ -6,6 +6,7 @@ "use strict"; const { ConcatSource } = require("webpack-sources"); +const ExternalModule = require("./ExternalModule"); const Template = require("./Template"); /** @typedef {import("./Compilation")} Compilation */ @@ -27,10 +28,17 @@ class AmdMainTemplatePlugin { const { mainTemplate, chunkTemplate } = compilation; const onRenderWithEntry = (source, chunk, hash) => { - const externals = chunk.getModules().filter(m => m.external); + const chunkGraph = compilation.chunkGraph; + const modules = chunkGraph + .getChunkModules(chunk) + .filter(m => m instanceof ExternalModule); + const externals = /** @type {ExternalModule[]} */ (modules); const externalsDepsArray = JSON.stringify( externals.map( - m => (typeof m.request === "object" ? m.request.amd : m.request) + m => + typeof m.request === "object" && !Array.isArray(m.request) + ? m.request.amd + : m.request ) ); const externalsArguments = externals diff --git a/lib/AsyncDependenciesBlock.js b/lib/AsyncDependenciesBlock.js index 8dc266e41..956f96a30 100644 --- a/lib/AsyncDependenciesBlock.js +++ b/lib/AsyncDependenciesBlock.js @@ -88,15 +88,6 @@ class AsyncDependenciesBlock extends DependenciesBlock { this.chunkGroup = undefined; super.unseal(); } - - /** - * Sorts items in this module - * @param {boolean=} sortChunks sort the chunks too - * @returns {void} - */ - sortItems(sortChunks) { - super.sortItems(); - } } Object.defineProperty(AsyncDependenciesBlock.prototype, "module", { diff --git a/lib/Chunk.js b/lib/Chunk.js index f55c6f8d7..cb90d8daf 100644 --- a/lib/Chunk.js +++ b/lib/Chunk.js @@ -6,14 +6,12 @@ "use strict"; const Entrypoint = require("./Entrypoint"); -const { - connectChunkAndModule, - disconnectChunkAndModule -} = require("./GraphHelpers"); const { intersect } = require("./util/SetHelpers"); const SortableSet = require("./util/SortableSet"); +const { compareModulesById } = require("./util/comparators"); /** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ /** @typedef {import("./ChunkGroup")} ChunkGroup */ /** @typedef {import("./Module")} Module */ /** @typedef {import("./ModuleReason")} ModuleReason */ @@ -33,17 +31,10 @@ const SortableSet = require("./util/SortableSet"); // TODO use @callback /** @typedef {(a: Module, b: Module) => -1|0|1} ModuleSortPredicate */ -/** @typedef {(m: Module) => boolean} ModuleFilterPredicate */ /** @typedef {(c: Chunk) => boolean} ChunkFilterPredicate */ let debugId = 1000; -const sortModuleById = (a, b) => { - if (a.id < b.id) return -1; - if (b.id < a.id) return 1; - return 0; -}; - /** * Compare two ChunkGroups based on their ids for sorting * @param {ChunkGroup} a chunk group @@ -56,50 +47,6 @@ const sortChunkGroupById = (a, b) => { return 0; }; -/** - * Compare two Identifiables , based on their ids for sorting - * @param {Module} a first object with ident fn - * @param {Module} b second object with ident fn - * @returns {-1|0|1} The order number of the sort - */ -const sortByIdentifier = (a, b) => { - if (a.identifier() > b.identifier()) return 1; - if (a.identifier() < b.identifier()) return -1; - return 0; -}; - -/** - * @returns {string} a concatenation of module identifiers sorted - * @param {SortableSet} set to pull module identifiers from - */ -const getModulesIdent = set => { - set.sort(); - let str = ""; - for (const m of set) { - str += m.identifier() + "#"; - } - return str; -}; - -/** - * @template T - * @param {SortableSet} set the sortable set to convert to array - * @returns {Array} the array returned from Array.from(set) - */ -const getArray = set => Array.from(set); - -/** - * @param {SortableSet} set the sortable Set to get the count/size of - * @returns {number} the size of the modules - */ -const getModulesSize = set => { - let size = 0; - for (const module of set) { - size += module.size(); - } - return size; -}; - /** * A Chunk is a unit of encapsulation for Modules. * Chunks are "rendered" into bundles that get emitted when the build completes. @@ -121,8 +68,6 @@ class Chunk { this.preventIntegration = false; /** @type {Module=} */ this.entryModule = undefined; - /** @private @type {SortableSet} */ - this._modules = new SortableSet(undefined, sortByIdentifier); /** @type {string?} */ this.filenameTemplate = undefined; /** @private @type {SortableSet} */ @@ -187,52 +132,6 @@ class Chunk { return !!this.entryModule; } - /** - * @param {Module} module the module that will be added to this chunk. - * @returns {boolean} returns true if the chunk doesn't have the module and it was added - */ - addModule(module) { - if (!this._modules.has(module)) { - this._modules.add(module); - return true; - } - return false; - } - - /** - * @param {Module} module the module that will be removed from this chunk - * @returns {boolean} returns true if chunk exists and is successfully deleted - */ - removeModule(module) { - if (this._modules.delete(module)) { - module.removeChunk(this); - return true; - } - return false; - } - - /** - * @param {Module[]} modules the new modules to be set - * @returns {void} set new modules to this chunk and return nothing - */ - setModules(modules) { - this._modules = new SortableSet(modules, sortByIdentifier); - } - - /** - * @returns {number} the amount of modules in chunk - */ - getNumberOfModules() { - return this._modules.size; - } - - /** - * @returns {SortableSet} return the modules SortableSet for this chunk - */ - get modulesIterable() { - return this._modules; - } - /** * @param {ChunkGroup} chunkGroup the chunkGroup the chunk is being added * @returns {boolean} returns true if chunk is not apart of chunkGroup and is added successfully @@ -276,106 +175,14 @@ class Chunk { } /** - * @param {Chunk} otherChunk the chunk to compare itself with - * @returns {-1|0|1} this is a comparitor function like sort and returns -1, 0, or 1 based on sort order + * @returns {void} */ - compareTo(otherChunk) { - this._modules.sort(); - otherChunk._modules.sort(); - if (this._modules.size > otherChunk._modules.size) return -1; - if (this._modules.size < otherChunk._modules.size) return 1; - const a = this._modules[Symbol.iterator](); - const b = otherChunk._modules[Symbol.iterator](); - // eslint-disable-next-line no-constant-condition - while (true) { - const aItem = a.next(); - const bItem = b.next(); - if (aItem.done) return 0; - const aModuleIdentifier = aItem.value.identifier(); - const bModuleIdentifier = bItem.value.identifier(); - if (aModuleIdentifier < bModuleIdentifier) return -1; - if (aModuleIdentifier > bModuleIdentifier) return 1; - } - } - - /** - * @param {Module} module Module to check - * @returns {boolean} returns true if module does exist in this chunk - */ - containsModule(module) { - return this._modules.has(module); - } - - /** - * @returns {Module[]} an array of all modules in this chunk - */ - getModules() { - return this._modules.getFromCache(getArray); - } - - getModulesIdent() { - return this._modules.getFromUnorderedCache(getModulesIdent); - } - - remove() { - // cleanup modules - // Array.from is used here to create a clone, because removeChunk modifies this._modules - for (const module of Array.from(this._modules)) { - module.removeChunk(this); - } + disconnectFromGroups() { for (const chunkGroup of this._groups) { chunkGroup.removeChunk(this); } } - /** - * - * @param {Module} module module to move - * @param {Chunk} otherChunk other chunk to move it to - * @returns {void} - */ - moveModule(module, otherChunk) { - disconnectChunkAndModule(this, module); - connectChunkAndModule(otherChunk, module); - } - - /** - * - * @param {Chunk} otherChunk the chunk to integrate with - * @param {ModuleReason} reason reason why the module is being integrated - * @returns {boolean} returns true or false if integration succeeds or fails - */ - integrate(otherChunk, reason) { - if (!this.canBeIntegrated(otherChunk)) { - return false; - } - - // Array.from is used here to create a clone, because moveModule modifies otherChunk._modules - for (const module of Array.from(otherChunk._modules)) { - otherChunk.moveModule(module, this); - } - otherChunk._modules.clear(); - - for (const chunkGroup of otherChunk._groups) { - chunkGroup.replaceChunk(otherChunk, this); - this.addGroup(chunkGroup); - } - otherChunk._groups.clear(); - - if (this.name && otherChunk.name) { - if (this.name.length !== otherChunk.name.length) { - this.name = - this.name.length < otherChunk.name.length - ? this.name - : otherChunk.name; - } else { - this.name = this.name < otherChunk.name ? this.name : otherChunk.name; - } - } - - return true; - } - /** * @param {Chunk} newChunk the new chunk that will be split out of, and then chunk raphi twil= * @returns {void} @@ -387,15 +194,19 @@ class Chunk { } } - isEmpty() { - return this._modules.size === 0; - } - - updateHash(hash) { + /** + * @param {Hash} hash hash (will be modified) + * @param {ChunkGraph} chunkGraph the chunk graph + * @returns {void} + */ + updateHash(hash, chunkGraph) { hash.update(`${this.id} `); hash.update(this.ids ? this.ids.join(",") : ""); hash.update(`${this.name || ""} `); - for (const m of this._modules) { + for (const m of chunkGraph.getOrderedChunkModulesIterable( + this, + compareModulesById + )) { hash.update(m.hash); } } @@ -434,71 +245,6 @@ class Chunk { return true; } - /** - * - * @param {number} size the size - * @param {Object} options the options passed in - * @returns {number} the multiplier returned - */ - addMultiplierAndOverhead(size, options) { - const overhead = - typeof options.chunkOverhead === "number" ? options.chunkOverhead : 10000; - const multiplicator = this.canBeInitial() - ? options.entryChunkMultiplicator || 10 - : 1; - - return size * multiplicator + overhead; - } - - /** - * @returns {number} the size of all modules - */ - modulesSize() { - return this._modules.getFromUnorderedCache(getModulesSize); - } - - /** - * @param {Object} options the size display options - * @returns {number} the chunk size - */ - size(options) { - return this.addMultiplierAndOverhead(this.modulesSize(), options); - } - - /** - * @param {Chunk} otherChunk the other chunk - * @param {TODO} options the options for this function - * @returns {number | false} the size, or false if it can't be integrated - */ - integratedSize(otherChunk, options) { - // Chunk if it's possible to integrate this chunk - if (!this.canBeIntegrated(otherChunk)) { - return false; - } - - let integratedModulesSize = this.modulesSize(); - // only count modules that do not exist in this chunk! - for (const otherModule of otherChunk._modules) { - if (!this._modules.has(otherModule)) { - integratedModulesSize += otherModule.size(); - } - } - - return this.addMultiplierAndOverhead(integratedModulesSize, options); - } - - /** - * @param {function(Module, Module): -1|0|1=} sortByFn a predicate function used to sort modules - * @returns {void} - */ - sortModules(sortByFn) { - this._modules.sortWith(sortByFn || sortModuleById); - } - - sortItems() { - this.sortModules(); - } - /** * @returns {Set} a set of all the async chunks */ @@ -570,9 +316,10 @@ class Chunk { } /** + * @param {ChunkGraph} chunkGraph the chunk graph * @returns {Record[]>} a record object of names to lists of child ids(?) */ - getChildIdsByOrders() { + getChildIdsByOrders(chunkGraph) { const lists = new Map(); for (const group of this.groupsIterable) { if (group.chunks[group.chunks.length - 1] === this) { @@ -596,7 +343,7 @@ class Chunk { list.sort((a, b) => { const cmp = b.order - a.order; if (cmp !== 0) return cmp; - return a.group.compareTo(b.group); + return a.group.compareTo(chunkGraph, b.group); }); result[name] = Array.from( list.reduce((set, item) => { @@ -610,11 +357,11 @@ class Chunk { return result; } - getChildIdsByOrdersMap(includeDirectChildren) { + getChildIdsByOrdersMap(chunkGraph, includeDirectChildren) { const chunkMaps = Object.create(null); const addChildIdsByOrdersToMap = chunk => { - const data = chunk.getChildIdsByOrders(); + const data = chunk.getChildIdsByOrders(chunkGraph); for (const key of Object.keys(data)) { let chunkMap = chunkMaps[key]; if (chunkMap === undefined) { @@ -634,80 +381,6 @@ class Chunk { return chunkMaps; } - - /** - * @typedef {Object} ChunkModuleMaps - * @property {Record} id - * @property {Record} hash - */ - - /** - * @param {ModuleFilterPredicate} filterFn function used to filter modules - * @returns {ChunkModuleMaps} module map information - */ - getChunkModuleMaps(filterFn) { - /** @type {Record} */ - const chunkModuleIdMap = Object.create(null); - /** @type {Record} */ - const chunkModuleHashMap = Object.create(null); - - for (const chunk of this.getAllAsyncChunks()) { - /** @type {(string|number)[]} */ - let array; - for (const module of chunk.modulesIterable) { - if (filterFn(module)) { - if (array === undefined) { - array = []; - chunkModuleIdMap[chunk.id] = array; - } - array.push(module.id); - chunkModuleHashMap[module.id] = module.renderedHash; - } - } - if (array !== undefined) { - array.sort(); - } - } - - return { - id: chunkModuleIdMap, - hash: chunkModuleHashMap - }; - } - - /** - * - * @param {function(Module): boolean} filterFn predicate function used to filter modules - * @param {function(Chunk): boolean} filterChunkFn predicate function used to filter chunks - * @returns {boolean} return true if module exists in graph - */ - hasModuleInGraph(filterFn, filterChunkFn) { - const queue = new Set(this.groupsIterable); - const chunksProcessed = new Set(); - - for (const chunkGroup of queue) { - for (const chunk of chunkGroup.chunks) { - if (!chunksProcessed.has(chunk)) { - chunksProcessed.add(chunk); - if (!filterChunkFn || filterChunkFn(chunk)) { - for (const module of chunk.modulesIterable) { - if (filterFn(module)) { - return true; - } - } - } - } - } - for (const child of chunkGroup.childrenIterable) { - queue.add(child); - } - } - return false; - } - - toString() { - return `Chunk[${Array.from(this._modules).join()}]`; - } } module.exports = Chunk; diff --git a/lib/ChunkGraph.js b/lib/ChunkGraph.js new file mode 100644 index 000000000..c3dadc3a0 --- /dev/null +++ b/lib/ChunkGraph.js @@ -0,0 +1,537 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const SortableSet = require("./util/SortableSet"); +const { compareModulesById } = require("./util/comparators"); + +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./ChunkGroup")} ChunkGroup */ +/** @typedef {import("./Module")} Module */ + +/** @typedef {(m: Module) => boolean} ModuleFilterPredicate */ + +/** + * @param {Chunk} a chunk + * @param {Chunk} b chunk + * @returns {number} compare result + */ +const sortChunksByDebugId = (a, b) => { + return a.debugId - b.debugId; +}; + +/** @template T @typedef {(set: SortableSet) => T[]} SetToArrayFunction */ + +/** + * @template T + * @param {SortableSet} set the set + * @returns {T[]} set as array + */ +const getArray = set => { + return Array.from(set); +}; + +/** @type {WeakMap} */ +const createOrderedArrayFunctionMap = new WeakMap(); + +/** + * @template T + * @param {function(T, T): -1|0|1} comparator comparator function + * @returns {SetToArrayFunction} set as ordered array + */ +const createOrderedArrayFunction = comparator => { + /** @type {SetToArrayFunction} */ + let fn = createOrderedArrayFunctionMap.get(comparator); + if (fn !== undefined) return fn; + fn = set => { + set.sortWith(comparator); + return Array.from(set); + }; + createOrderedArrayFunctionMap.set(comparator, fn); + return fn; +}; + +/** + * @param {SortableSet} set the sortable Set to get the count/size of + * @returns {number} the size of the modules + */ +const getModulesSize = set => { + let size = 0; + for (const module of set) { + size += module.size(); + } + return size; +}; + +class ChunkGraphModule { + constructor() { + /** @type {SortableSet} */ + this.chunks = new SortableSet(); + } +} + +class ChunkGraphChunk { + constructor() { + /** @type {SortableSet} */ + this.modules = new SortableSet(); + } +} + +class ChunkGraph { + constructor() { + /** @private @type {WeakMap} */ + this._modules = new WeakMap(); + /** @private @type {WeakMap} */ + this._chunks = new WeakMap(); + } + + /** + * @private + * @param {Module} module the module + * @returns {ChunkGraphModule} internal module + */ + _getChunkGraphModule(module) { + let m = this._modules.get(module); + if (m === undefined) { + m = new ChunkGraphModule(); + this._modules.set(module, m); + } + return m; + } + + /** + * @private + * @param {Chunk} chunk the chunk + * @returns {ChunkGraphChunk} internal chunk + */ + _getChunkGraphChunk(chunk) { + let c = this._chunks.get(chunk); + if (c === undefined) { + c = new ChunkGraphChunk(); + this._chunks.set(chunk, c); + } + return c; + } + + /** + * @param {Chunk} chunk the new chunk + * @param {Module} module the module + * @returns {boolean} true, if the chunk could be added. false if it was already added + */ + connectChunkAndModule(chunk, module) { + const cgm = this._getChunkGraphModule(module); + const cgc = this._getChunkGraphChunk(chunk); + // TODO refactor to remove return value + if (cgm.chunks.has(chunk) && cgc.modules.has(module)) return false; + cgm.chunks.add(chunk); + cgc.modules.add(module); + return true; + } + + /** + * @param {Chunk} chunk the chunk + * @param {Module} module the module + * @returns {void} + */ + disconnectChunkAndModule(chunk, module) { + const cgm = this._getChunkGraphModule(module); + const cgc = this._getChunkGraphChunk(chunk); + cgc.modules.delete(module); + cgm.chunks.delete(chunk); + } + + /** + * @param {Chunk} chunk the chunk which will be disconnected + * @returns {void} + */ + disconnectChunk(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + for (const module of cgc.modules) { + const cgm = this._getChunkGraphModule(module); + cgm.chunks.delete(chunk); + } + cgc.modules.clear(); + chunk.disconnectFromGroups(); + } + + /** + * @param {Chunk} chunk the chunk + * @param {Iterable} modules the modules + * @returns {void} + */ + attachModules(chunk, modules) { + const cgc = this._getChunkGraphChunk(chunk); + for (const module of modules) { + cgc.modules.add(module); + } + } + + /** + * @param {Module} oldModule the replaced module + * @param {Module} newModule the replacing module + * @returns {void} + */ + replaceModule(oldModule, newModule) { + const oldCgm = this._getChunkGraphModule(oldModule); + const newCgm = this._getChunkGraphModule(newModule); + const chunks = this.getModuleChunks(oldModule); + for (const chunk of chunks) { + const cgc = this._getChunkGraphChunk(chunk); + cgc.modules.delete(oldModule); + cgc.modules.add(newModule); + oldCgm.chunks.delete(chunk); + newCgm.chunks.add(chunk); + } + } + + /** + * @param {Module} module the checked module + * @param {Chunk} chunk the checked chunk + * @returns {boolean} true, if the chunk contains the module + */ + isModuleInChunk(module, chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.modules.has(module); + } + + /** + * @param {Module} module the checked module + * @param {ChunkGroup} chunkGroup the checked chunk group + * @returns {boolean} true, if the chunk contains the module + */ + isModuleInChunkGroup(module, chunkGroup) { + for (const chunk of chunkGroup.chunks) { + if (this.isModuleInChunk(module, chunk)) return true; + } + return false; + } + + /** + * @param {Module} module the checked module + * @returns {boolean} true, if the module is entry of any chunk + */ + isEntryModule(module) { + const cgm = this._getChunkGraphModule(module); + for (const chunk of cgm.chunks) { + if (chunk.entryModule === module) return true; + } + return false; + } + + /** + * @param {Module} module the module + * @returns {Iterable} iterable of chunks (do not modify) + */ + getModuleChunksIterable(module) { + const cgm = this._getChunkGraphModule(module); + return cgm.chunks; + } + + /** + * @param {Module} module the module + * @param {function(Chunk, Chunk): -1|0|1} sortFn sort function + * @returns {Iterable} iterable of chunks (do not modify) + */ + getOrderedModuleChunksIterable(module, sortFn) { + const cgm = this._getChunkGraphModule(module); + cgm.chunks.sortWith(sortFn); + return cgm.chunks; + } + + /** + * @param {Module} module the module + * @returns {Chunk[]} array of chunks (cached, do not modify) + */ + getModuleChunks(module) { + const cgm = this._getChunkGraphModule(module); + return cgm.chunks.getFromCache(getArray); + } + + /** + * @param {Module} module the module + * @returns {number} the number of chunk which contain the module + */ + getNumberOfModuleChunks(module) { + const cgm = this._getChunkGraphModule(module); + return cgm.chunks.size; + } + + /** + * @param {Module} moduleA some module + * @param {Module} moduleB some module + * @returns {boolean} true, if modules are in the same chunks + */ + haveModulesEqualChunks(moduleA, moduleB) { + const cgmA = this._getChunkGraphModule(moduleA); + const cgmB = this._getChunkGraphModule(moduleB); + if (cgmA.chunks.size !== cgmB.chunks.size) return false; + cgmA.chunks.sortWith(sortChunksByDebugId); + cgmB.chunks.sortWith(sortChunksByDebugId); + const a = cgmA.chunks[Symbol.iterator](); + const b = cgmB.chunks[Symbol.iterator](); + // eslint-disable-next-line no-constant-condition + while (true) { + const aItem = a.next(); + if (aItem.done) return true; + const bItem = b.next(); + if (aItem.value !== bItem.value) return false; + } + } + + /** + * @param {Chunk} chunk the chunk + * @returns {number} the number of module which are contained in this chunk + */ + getNumberOfChunkModules(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.modules.size; + } + + /** + * @param {Chunk} chunk the chunk + * @returns {Iterable} return the modules for this chunk + */ + getChunkModulesIterable(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.modules; + } + + /** + * @param {Chunk} chunk the chunk + * @param {function(Module, Module): -1|0|1} comparator comparator function + * @returns {Iterable} return the modules for this chunk + */ + getOrderedChunkModulesIterable(chunk, comparator) { + const cgc = this._getChunkGraphChunk(chunk); + cgc.modules.sortWith(comparator); + return cgc.modules; + } + + /** + * @param {Chunk} chunk the chunk + * @returns {Module[]} return the modules for this chunk (cached, do not modify) + */ + getChunkModules(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.modules.getFromUnorderedCache(getArray); + } + + /** + * @param {Chunk} chunk the chunk + * @param {function(Module, Module): -1|0|1} comparator comparator function + * @returns {Module[]} return the modules for this chunk (cached, do not modify) + */ + getOrderedChunkModules(chunk, comparator) { + const cgc = this._getChunkGraphChunk(chunk); + const arrayFunction = createOrderedArrayFunction(comparator); + return cgc.modules.getFromUnorderedCache(arrayFunction); + } + + /** + * @typedef {Object} ChunkModuleMaps + * @property {Record} id + * @property {Record} hash + */ + + /** + * @param {Chunk} chunk the chunk + * @param {ModuleFilterPredicate} filterFn function used to filter modules + * @returns {ChunkModuleMaps} module map information + */ + getChunkModuleMaps(chunk, filterFn) { + /** @type {Record} */ + const chunkModuleIdMap = Object.create(null); + /** @type {Record} */ + const chunkModuleHashMap = Object.create(null); + + for (const asyncChunk of chunk.getAllAsyncChunks()) { + /** @type {(string|number)[]} */ + let array; + for (const module of this.getOrderedChunkModulesIterable( + asyncChunk, + compareModulesById + )) { + if (filterFn(module)) { + if (array === undefined) { + array = []; + chunkModuleIdMap[asyncChunk.id] = array; + } + array.push(module.id); + chunkModuleHashMap[module.id] = module.renderedHash; + } + } + if (array !== undefined) { + array.sort(); + } + } + + return { + id: chunkModuleIdMap, + hash: chunkModuleHashMap + }; + } + + /** + * @param {Chunk} chunk the chunk + * @param {function(Module): boolean} filterFn predicate function used to filter modules + * @param {(function(Chunk): boolean)=} filterChunkFn predicate function used to filter chunks + * @returns {boolean} return true if module exists in graph + */ + hasModuleInGraph(chunk, filterFn, filterChunkFn) { + const queue = new Set(chunk.groupsIterable); + const chunksProcessed = new Set(); + + for (const chunkGroup of queue) { + for (const innerChunk of chunkGroup.chunks) { + if (!chunksProcessed.has(innerChunk)) { + chunksProcessed.add(innerChunk); + if (!filterChunkFn || filterChunkFn(innerChunk)) { + for (const module of this.getChunkModulesIterable(innerChunk)) { + if (filterFn(module)) { + return true; + } + } + } + } + } + for (const child of chunkGroup.childrenIterable) { + queue.add(child); + } + } + return false; + } + + /** + * @param {Chunk} chunkA first chunk + * @param {Chunk} chunkB second chunk + * @returns {-1|0|1} this is a comparitor function like sort and returns -1, 0, or 1 based on sort order + */ + compareChunks(chunkA, chunkB) { + const cgcA = this._getChunkGraphChunk(chunkA); + const cgcB = this._getChunkGraphChunk(chunkB); + if (cgcA.modules.size > cgcB.modules.size) return -1; + if (cgcA.modules.size < cgcB.modules.size) return 1; + cgcA.modules.sortWith(compareModulesById); + cgcB.modules.sortWith(compareModulesById); + const a = cgcA.modules[Symbol.iterator](); + const b = cgcB.modules[Symbol.iterator](); + // eslint-disable-next-line no-constant-condition + while (true) { + const aItem = a.next(); + if (aItem.done) return 0; + const bItem = b.next(); + const aModuleIdentifier = aItem.value.identifier(); + const bModuleIdentifier = bItem.value.identifier(); + if (aModuleIdentifier < bModuleIdentifier) return -1; + if (aModuleIdentifier > bModuleIdentifier) return 1; + } + } + + /** + * @param {Chunk} chunk the chunk + * @returns {number} total size of all modules in the chunk + */ + getChunkModulesSize(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.modules.getFromUnorderedCache(getModulesSize); + } + + /** + * @typedef {Object} ChunkSizeOptions + * @property {number=} chunkOverhead constant overhead for a chunk + * @property {number=} entryChunkMultiplicator multiplicator for initial chunks + */ + + /** + * @param {Chunk} chunk the chunk + * @param {ChunkSizeOptions} options options object + * @returns {number} total size of the chunk + */ + getChunkSize(chunk, options) { + const cgc = this._getChunkGraphChunk(chunk); + const modulesSize = cgc.modules.getFromUnorderedCache(getModulesSize); + const chunkOverhead = + typeof options.chunkOverhead === "number" ? options.chunkOverhead : 10000; + const entryChunkMultiplicator = + typeof options.entryChunkMultiplicator === "number" + ? options.entryChunkMultiplicator + : 10; + return ( + chunkOverhead + + modulesSize * (chunk.canBeInitial() ? entryChunkMultiplicator : 1) + ); + } + + /** + * @param {Chunk} chunkA chunk + * @param {Chunk} chunkB chunk + * @param {ChunkSizeOptions} options options object + * @returns {number} total size of the chunk or false if chunks can't be integrated + */ + getIntegratedChunksSize(chunkA, chunkB, options) { + const cgcA = this._getChunkGraphChunk(chunkA); + const cgcB = this._getChunkGraphChunk(chunkB); + const allModules = new Set(cgcA.modules); + for (const m of cgcB.modules) allModules.add(m); + let modulesSize = 0; + for (const module of allModules) { + modulesSize += module.size(); + } + const chunkOverhead = + typeof options.chunkOverhead === "number" ? options.chunkOverhead : 10000; + const entryChunkMultiplicator = + typeof options.entryChunkMultiplicator === "number" + ? options.entryChunkMultiplicator + : 10; + return ( + chunkOverhead + + modulesSize * + (chunkA.canBeInitial() || chunkB.canBeInitial() + ? entryChunkMultiplicator + : 1) + ); + } + + /** + * @param {Chunk} chunkA chunk + * @param {Chunk} chunkB chunk + * @returns {boolean} true, if chunks could be integrated + */ + canChunksBeIntegrated(chunkA, chunkB) { + return chunkA.canBeIntegrated(chunkB); + } + + /** + * @param {Chunk} chunkA the target chunk + * @param {Chunk} chunkB the chunk to integrate + * @returns {void} + */ + integrateChunks(chunkA, chunkB) { + // getChunkModules is used here to create a clone, because disconnectChunkAndModule modifies + for (const module of this.getChunkModules(chunkB)) { + this.disconnectChunkAndModule(chunkB, module); + this.connectChunkAndModule(chunkA, module); + } + + for (const chunkGroup of chunkB.groupsIterable) { + chunkGroup.replaceChunk(chunkB, chunkA); + chunkA.addGroup(chunkGroup); + chunkB.removeGroup(chunkGroup); + } + + // Decide for one name (deterministic) + if (chunkA.name && chunkB.name) { + if (chunkA.name.length !== chunkB.name.length) { + chunkA.name = + chunkA.name.length < chunkB.name.length ? chunkA.name : chunkB.name; + } else { + chunkA.name = chunkA.name < chunkB.name ? chunkA.name : chunkB.name; + } + } + } +} + +module.exports = ChunkGraph; diff --git a/lib/ChunkGroup.js b/lib/ChunkGroup.js index 9491cc76d..c9af6317f 100644 --- a/lib/ChunkGroup.js +++ b/lib/ChunkGroup.js @@ -9,6 +9,7 @@ const compareLocations = require("./compareLocations"); const SortableSet = require("./util/SortableSet"); /** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ /** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ /** @typedef {import("./Module")} Module */ /** @typedef {import("./ModuleReason")} ModuleReason */ @@ -285,9 +286,9 @@ class ChunkGroup { return this._parents; } - removeParent(chunk) { - if (this._parents.delete(chunk)) { - chunk.removeChunk(this); + removeParent(chunkGroup) { + if (this._parents.delete(chunkGroup)) { + chunkGroup.removeChunk(this); return true; } return false; @@ -334,13 +335,6 @@ class ChunkGroup { }); } - containsModule(module) { - for (const chunk of this.chunks) { - if (chunk.containsModule(module)) return true; - } - return false; - } - getFiles() { const files = new Set(); @@ -354,7 +348,7 @@ class ChunkGroup { } /** - * @param {ModuleReason} reason reason for removing ChunkGroup + * @param {string=} reason reason for removing ChunkGroup * @returns {void} */ remove(reason) { @@ -408,10 +402,11 @@ class ChunkGroup { * Sorting predicate which allows current ChunkGroup to be compared against another. * Sorting values are based off of number of chunks in ChunkGroup. * + * @param {ChunkGraph} chunkGraph the chunk graph * @param {ChunkGroup} otherGroup the chunkGroup to compare this against * @returns {-1|0|1} sort position for comparison */ - compareTo(otherGroup) { + compareTo(chunkGraph, otherGroup) { if (this.chunks.length > otherGroup.chunks.length) return -1; if (this.chunks.length < otherGroup.chunks.length) return 1; const a = this.chunks[Symbol.iterator](); @@ -421,12 +416,12 @@ class ChunkGroup { const aItem = a.next(); const bItem = b.next(); if (aItem.done) return 0; - const cmp = aItem.value.compareTo(bItem.value); + const cmp = chunkGraph.compareChunks(aItem.value, bItem.value); if (cmp !== 0) return cmp; } } - getChildrenByOrders() { + getChildrenByOrders(chunkGraph) { const lists = new Map(); for (const childGroup of this._children) { for (const key of Object.keys(childGroup.options)) { @@ -448,7 +443,7 @@ class ChunkGroup { list.sort((a, b) => { const cmp = b.order - a.order; if (cmp !== 0) return cmp; - return a.group.compareTo(b.group); + return a.group.compareTo(chunkGraph, b.group); }); result[name] = list.map(i => i.group); } diff --git a/lib/Compilation.js b/lib/Compilation.js index 02afdac30..34a84fc4a 100644 --- a/lib/Compilation.js +++ b/lib/Compilation.js @@ -15,6 +15,7 @@ const { const { CachedSource } = require("webpack-sources"); const AsyncDependencyToInitialChunkError = require("./AsyncDependencyToInitialChunkError"); const Chunk = require("./Chunk"); +const ChunkGraph = require("./ChunkGraph"); const ChunkGroup = require("./ChunkGroup"); const ChunkRenderError = require("./ChunkRenderError"); const ChunkTemplate = require("./ChunkTemplate"); @@ -22,7 +23,6 @@ const DependencyTemplates = require("./DependencyTemplates"); const EntryModuleNotFoundError = require("./EntryModuleNotFoundError"); const Entrypoint = require("./Entrypoint"); const { - connectChunkAndModule, connectChunkGroupAndChunk, connectChunkGroupParentAndChild, connectDependenciesBlockAndChunkGroup @@ -389,12 +389,14 @@ class Compilation { }; this.moduleGraph = new ModuleGraph(); + this.chunkGraph = undefined; this.semaphore = new Semaphore(options.parallelism || 100); this.entries = []; /** @private @type {{name: string, request: string, module: Module}[]} */ this._preparedEntrypoints = []; + /** @private @type {Map} */ this.entrypoints = new Map(); /** @type {Chunk[]} */ this.chunks = []; @@ -1121,6 +1123,9 @@ class Compilation { * @returns {void} */ seal(callback) { + const chunkGraph = new ChunkGraph(); + this.chunkGraph = chunkGraph; + this.hooks.seal.call(); while ( @@ -1145,7 +1150,7 @@ class Compilation { this.chunkGroups.push(entrypoint); connectChunkGroupAndChunk(entrypoint, chunk); - connectChunkAndModule(chunk, module); + chunkGraph.connectChunkAndModule(chunk, module); chunk.entryModule = module; chunk.name = name; @@ -1452,6 +1457,7 @@ class Compilation { /** @type {Map} */ const chunkDependencies = new Map(); + /** @type {Set} */ const allCreatedChunkGroups = new Set(); // PREPARE @@ -1525,6 +1531,8 @@ class Compilation { } } + const chunkGraph = this.chunkGraph; + // PART ONE /** @type {Map} */ @@ -1644,9 +1652,7 @@ class Compilation { switch (queueItem.action) { case ADD_AND_ENTER_MODULE: { // We connect Module and Chunk when not already done - if (chunk.addModule(module)) { - module.addChunk(chunk); - } else { + if (!chunkGraph.connectChunkAndModule(chunk, module)) { // already connected, skip it break; } @@ -1683,7 +1689,7 @@ class Compilation { // Traverse all referenced modules for (let i = blockInfo.modules.length - 1; i >= 0; i--) { const refModule = blockInfo.modules[i]; - if (chunk.containsModule(refModule)) { + if (chunkGraph.isModuleInChunk(refModule, chunk)) { // skip early if already connected continue; } @@ -1750,7 +1756,7 @@ class Compilation { */ const areModulesAvailable = (chunkGroup, availableModules) => { for (const chunk of chunkGroup.chunks) { - for (const module of chunk.modulesIterable) { + for (const module of chunkGraph.getChunkModulesIterable(chunk)) { if (!availableModules.has(module)) return false; } } @@ -1806,7 +1812,7 @@ class Compilation { // 3. Create a new Set of available modules at this points newAvailableModules = new Set(availableModules); for (const chunk of chunkGroup.chunks) { - for (const m of chunk.modulesIterable) { + for (const m of chunkGraph.getChunkModulesIterable(chunk)) { newAvailableModules.add(m); } } @@ -1845,7 +1851,7 @@ class Compilation { for (const chunk of chunkGroup.chunks) { const idx = this.chunks.indexOf(chunk); if (idx >= 0) this.chunks.splice(idx, 1); - chunk.remove("unconnected"); + chunkGraph.disconnectChunk(chunk); } chunkGroup.remove("unconnected"); } @@ -1859,12 +1865,13 @@ class Compilation { * @returns {void} */ removeReasonsOfDependencyBlock(module, block) { + const chunkGraph = this.chunkGraph; const iteratorDependency = d => { if (!d.module) { return; } if (d.module.removeReason(module, d)) { - for (const chunk of d.module.chunksIterable) { + for (const chunk of chunkGraph.getModuleChunksIterable(d.module)) { this.patchChunksAfterReasonRemoval(d.module, chunk); } } @@ -1890,8 +1897,9 @@ class Compilation { if (!module.hasReasons(this.moduleGraph)) { this.removeReasonsOfDependencyBlock(module, module); } - if (!module.hasReasonForChunk(chunk, this.moduleGraph)) { - if (module.removeChunk(chunk)) { + if (!module.hasReasonForChunk(chunk, this.moduleGraph, this.chunkGraph)) { + if (this.chunkGraph.isModuleInChunk(module, chunk)) { + this.chunkGraph.disconnectChunkAndModule(chunk, module); this.removeChunkFromDependencies(module, chunk); } } @@ -1932,6 +1940,8 @@ class Compilation { } applyModuleIds() { + const chunkGraph = this.chunkGraph; + const unusedIds = []; let nextFreeModuleId = 0; const usedIds = new Set(); @@ -1972,7 +1982,7 @@ class Compilation { for (let indexModule2 = 0; indexModule2 < modules2.length; indexModule2++) { const module2 = modules2[indexModule2]; // Module that are not in any chunk don't need ids - if (module2.getNumberOfChunks() === 0) continue; + if (chunkGraph.getNumberOfModuleChunks(module2) === 0) continue; if (module2.id === null) { if (unusedIds.length > 0) { module2.id = unusedIds.pop(); @@ -2048,16 +2058,6 @@ class Compilation { sortItemsWithModuleIds() { this.modules.sort(byIdOrIdentifier); - - const modules = this.modules; - for (let indexModule = 0; indexModule < modules.length; indexModule++) { - modules[indexModule].sortItems(false); - } - - const chunks = this.chunks; - for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { - chunks[indexChunk].sortItems(); - } } sortItemsWithChunkIds() { @@ -2067,19 +2067,6 @@ class Compilation { this.chunks.sort(byId); - for ( - let indexModule = 0; - indexModule < this.modules.length; - indexModule++ - ) { - this.modules[indexModule].sortItems(true); - } - - const chunks = this.chunks; - for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { - chunks[indexChunk].sortItems(); - } - /** * Used to sort errors and warnings in compilation. this.warnings, and * this.errors contribute to the compilation hash and therefore should be @@ -2202,7 +2189,7 @@ class Compilation { if (outputOptions.hashSalt) { chunkHash.update(outputOptions.hashSalt); } - chunk.updateHash(chunkHash); + chunk.updateHash(chunkHash, this.chunkGraph); const template = chunk.hasRuntime() ? this.mainTemplate : this.chunkTemplate; diff --git a/lib/DependenciesBlock.js b/lib/DependenciesBlock.js index 4b9b9a2b6..1d87ae25b 100644 --- a/lib/DependenciesBlock.js +++ b/lib/DependenciesBlock.js @@ -93,17 +93,6 @@ class DependenciesBlock { return false; } - - /** - * Sorts items in this module - * @param {boolean=} sortChunks sort the chunks too - * @returns {void} - */ - sortItems(sortChunks) { - for (const block of this.blocks) { - block.sortItems(); - } - } } module.exports = DependenciesBlock; diff --git a/lib/FlagInitialModulesAsUsedPlugin.js b/lib/FlagInitialModulesAsUsedPlugin.js index 86828f079..dca0f105d 100644 --- a/lib/FlagInitialModulesAsUsedPlugin.js +++ b/lib/FlagInitialModulesAsUsedPlugin.js @@ -24,11 +24,12 @@ class FlagInitialModulesAsUsedPlugin { compilation.hooks.afterOptimizeChunks.tap( "FlagInitialModulesAsUsedPlugin", chunks => { + const chunkGraph = compilation.chunkGraph; for (const chunk of chunks) { if (!chunk.isOnlyInitial()) { return; } - for (const module of chunk.modulesIterable) { + for (const module of chunkGraph.getChunkModulesIterable(chunk)) { module.setUsedExports(moduleGraph, true); moduleGraph.addExtraReason(module, this.explanation); } diff --git a/lib/GraphHelpers.js b/lib/GraphHelpers.js index 258121075..0e67d127d 100644 --- a/lib/GraphHelpers.js +++ b/lib/GraphHelpers.js @@ -33,27 +33,6 @@ const connectChunkGroupParentAndChild = (parent, child) => { } }; -/** - * @param {Chunk} chunk Chunk to connect to Module - * @param {Module} module Module to connect to Chunk - * @returns {void} - */ -const connectChunkAndModule = (chunk, module) => { - if (module.addChunk(chunk)) { - chunk.addModule(module); - } -}; - -/** - * @param {Chunk} chunk Chunk being disconnected - * @param {Module} module Module being disconnected - * @returns {void} - */ -const disconnectChunkAndModule = (chunk, module) => { - chunk.removeModule(module); - module.removeChunk(chunk); -}; - /** * @param {AsyncDependenciesBlock} depBlock DepBlock being tied to ChunkGroup * @param {ChunkGroup} chunkGroup ChunkGroup being tied to DepBlock @@ -67,6 +46,4 @@ const connectDependenciesBlockAndChunkGroup = (depBlock, chunkGroup) => { exports.connectChunkGroupAndChunk = connectChunkGroupAndChunk; exports.connectChunkGroupParentAndChild = connectChunkGroupParentAndChild; -exports.connectChunkAndModule = connectChunkAndModule; -exports.disconnectChunkAndModule = disconnectChunkAndModule; exports.connectDependenciesBlockAndChunkGroup = connectDependenciesBlockAndChunkGroup; diff --git a/lib/HotModuleReplacementPlugin.js b/lib/HotModuleReplacementPlugin.js index 9c05ef617..314b5e051 100644 --- a/lib/HotModuleReplacementPlugin.js +++ b/lib/HotModuleReplacementPlugin.js @@ -20,6 +20,7 @@ const Template = require("./Template"); const ConstDependency = require("./dependencies/ConstDependency"); const ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency"); const ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency"); +const { compareModulesById } = require("./util/comparators"); /** @typedef {import("./Compiler")} Compiler */ /** @typedef {import("./Module")} Module */ @@ -205,6 +206,7 @@ module.exports = class HotModuleReplacementPlugin { "HotModuleReplacementPlugin", (compilation, records) => { if (records.hash === compilation.hash) return; + const chunkGraph = compilation.chunkGraph; records.hash = compilation.hash; records.moduleHashs = {}; for (const module of compilation.modules) { @@ -218,7 +220,10 @@ module.exports = class HotModuleReplacementPlugin { records.chunkModuleIds = {}; for (const chunk of compilation.chunks) { records.chunkModuleIds[chunk.id] = Array.from( - chunk.modulesIterable, + chunkGraph.getOrderedChunkModulesIterable( + chunk, + compareModulesById + ), m => m.id ); } @@ -259,6 +264,7 @@ module.exports = class HotModuleReplacementPlugin { compilation.hooks.additionalChunkAssets.tap( "HotModuleReplacementPlugin", () => { + const chunkGraph = compilation.chunkGraph; const records = compilation.records; if (records.hash === compilation.hash) return; if ( @@ -286,12 +292,14 @@ module.exports = class HotModuleReplacementPlugin { chunk => chunk.id === chunkId ); if (currentChunk) { - const newModules = currentChunk - .getModules() + const newModules = chunkGraph + .getChunkModules(currentChunk) .filter(module => updatedModules.has(module)); /** @type {Set} */ const allModules = new Set(); - for (const module of currentChunk.modulesIterable) { + for (const module of chunkGraph.getChunkModulesIterable( + currentChunk + )) { allModules.add(module.id); } const removedModules = records.chunkModuleIds[chunkId].filter( @@ -300,14 +308,15 @@ module.exports = class HotModuleReplacementPlugin { if (newModules.length > 0 || removedModules.length > 0) { const hotUpdateChunk = new HotUpdateChunk(); hotUpdateChunk.id = chunkId; - hotUpdateChunk.setModules(newModules); + chunkGraph.attachModules(hotUpdateChunk, newModules); hotUpdateChunk.removedModules = removedModules; const source = hotUpdateChunkTemplate.render( { chunk: hotUpdateChunk, dependencyTemplates: compilation.dependencyTemplates, runtimeTemplate: compilation.runtimeTemplate, - moduleGraph: compilation.moduleGraph + moduleGraph: compilation.moduleGraph, + chunkGraph: compilation.chunkGraph }, compilation.moduleTemplates.javascript, compilation.hash diff --git a/lib/JavascriptModulesPlugin.js b/lib/JavascriptModulesPlugin.js index 110a185f8..fcdf136de 100644 --- a/lib/JavascriptModulesPlugin.js +++ b/lib/JavascriptModulesPlugin.js @@ -11,6 +11,7 @@ const Compilation = require("./Compilation"); const JavascriptGenerator = require("./JavascriptGenerator"); const JavascriptParser = require("./JavascriptParser"); const Template = require("./Template"); +const { compareModulesById } = require("./util/comparators"); const createHash = require("./util/createHash"); /** @typedef {import("webpack-sources").Source} Source */ @@ -126,7 +127,8 @@ class JavascriptModulesPlugin { chunk, dependencyTemplates, runtimeTemplate: compilation.runtimeTemplate, - moduleGraph: compilation.moduleGraph + moduleGraph: compilation.moduleGraph, + chunkGraph: compilation.chunkGraph }, m => typeof m.source === "function", moduleTemplate, @@ -160,7 +162,8 @@ class JavascriptModulesPlugin { chunk, dependencyTemplates, runtimeTemplate: compilation.runtimeTemplate, - moduleGraph: compilation.moduleGraph + moduleGraph: compilation.moduleGraph, + chunkGraph: compilation.chunkGraph } ), filenameTemplate, @@ -176,20 +179,25 @@ class JavascriptModulesPlugin { } ); compilation.hooks.contentHash.tap("JavascriptModulesPlugin", chunk => { - const outputOptions = compilation.outputOptions; const { - hashSalt, - hashDigest, - hashDigestLength, - hashFunction - } = outputOptions; + chunkGraph, + outputOptions: { + hashSalt, + hashDigest, + hashDigestLength, + hashFunction + } + } = compilation; const hash = createHash(hashFunction); if (hashSalt) hash.update(hashSalt); const template = chunk.hasRuntime() ? compilation.mainTemplate : compilation.chunkTemplate; template.updateHashForChunk(hash, chunk); - for (const m of chunk.modulesIterable) { + for (const m of chunkGraph.getOrderedChunkModulesIterable( + chunk, + compareModulesById + )) { if (typeof m.source === "function") { hash.update(m.hash); } diff --git a/lib/LibManifestPlugin.js b/lib/LibManifestPlugin.js index ed3aae58a..5fc3cca5f 100644 --- a/lib/LibManifestPlugin.js +++ b/lib/LibManifestPlugin.js @@ -8,6 +8,7 @@ const asyncLib = require("neo-async"); const path = require("path"); const SingleEntryDependency = require("./dependencies/SingleEntryDependency"); +const { compareModulesById } = require("./util/comparators"); class LibManifestPlugin { constructor(options) { @@ -25,6 +26,7 @@ class LibManifestPlugin { callback(); return; } + const chunkGraph = compilation.chunkGraph; const targetPath = compilation.getPath(this.options.path, { hash: compilation.hash, chunk @@ -38,28 +40,34 @@ class LibManifestPlugin { const manifest = { name, type: this.options.type, - content: Array.from(chunk.modulesIterable, module => { - if ( - this.options.entryOnly && - !compilation.moduleGraph - .getIncomingConnections(module) - .some(c => c.dependency instanceof SingleEntryDependency) - ) { - return; + content: Array.from( + chunkGraph.getOrderedChunkModulesIterable( + chunk, + compareModulesById + ), + module => { + if ( + this.options.entryOnly && + !compilation.moduleGraph + .getIncomingConnections(module) + .some(c => c.dependency instanceof SingleEntryDependency) + ) { + return; + } + const ident = module.libIdent({ + context: this.options.context || compiler.options.context + }); + if (ident) { + return { + ident, + data: { + id: module.id, + buildMeta: module.buildMeta + } + }; + } } - const ident = module.libIdent({ - context: this.options.context || compiler.options.context - }); - if (ident) { - return { - ident, - data: { - id: module.id, - buildMeta: module.buildMeta - } - }; - } - }) + ) .filter(Boolean) .reduce((obj, item) => { obj[item.ident] = item.data; diff --git a/lib/Module.js b/lib/Module.js index e63db4db7..2d3df8b2b 100644 --- a/lib/Module.js +++ b/lib/Module.js @@ -7,10 +7,10 @@ const DependenciesBlock = require("./DependenciesBlock"); const Template = require("./Template"); -const SortableSet = require("./util/SortableSet"); /** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ /** @typedef {import("./ChunkGroup")} ChunkGroup */ /** @typedef {import("./Compilation")} Compilation */ /** @typedef {import("./Dependency")} Dependency */ @@ -19,6 +19,7 @@ const SortableSet = require("./util/SortableSet"); /** @typedef {import("./RequestShortener")} RequestShortener */ /** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ /** @typedef {import("./WebpackError")} WebpackError */ +/** @template T @typedef {import("./util/SortableSet")} SortableSet */ /** @typedef {import("./util/createHash").Hash} Hash */ /** @@ -26,6 +27,7 @@ const SortableSet = require("./util/SortableSet"); * @property {DependencyTemplates} dependencyTemplates the dependency templates * @property {RuntimeTemplate} runtimeTemplate the runtime template * @property {ModuleGraph} moduleGraph the module graph + * @property {ChunkGraph} chunkGraph the chunk graph * @property {string=} type the type of source that should be generated */ @@ -49,14 +51,6 @@ const issuerSymbol = Symbol("issuer"); let debugId = 1000; -const sortById = (a, b) => { - return a.id - b.id; -}; - -const sortByDebugId = (a, b) => { - return a.debugId - b.debugId; -}; - const getIndexMap = set => { set.sort(); const map = new Map(); @@ -108,10 +102,6 @@ class Module extends DependenciesBlock { /** @type {object} */ this.buildInfo = undefined; - // Graph (per Compilation) - /** @type {SortableSet} */ - this._chunks = new SortableSet(undefined, sortById); - // Info from Compilation (per Compilation) /** @type {number|string} */ this.id = null; @@ -211,8 +201,6 @@ class Module extends DependenciesBlock { this.hash = undefined; this.renderedHash = undefined; - this._chunks.clear(); - this.id = null; this.index = null; this.index2 = null; @@ -232,60 +220,9 @@ class Module extends DependenciesBlock { this.index = null; this.index2 = null; this.depth = null; - this._chunks.clear(); super.unseal(); } - /** - * Sets the chunks to a new value - * @protected - * @param {Iterable} chunks the new chunks - * @returns {void} - */ - setChunks(chunks) { - this._chunks = new SortableSet(chunks, sortById); - } - - /** - * @param {Chunk} chunk added chunk - * @returns {boolean} true, if the chunk could be added - */ - addChunk(chunk) { - if (this._chunks.has(chunk)) return false; - this._chunks.add(chunk); - return true; - } - - /** - * @param {Chunk} chunk removed chunk - * @returns {boolean} true, if the chunk could be removed - */ - removeChunk(chunk) { - if (this._chunks.delete(chunk)) { - chunk.removeModule(this); - return true; - } - return false; - } - - /** - * @param {Chunk} chunk chunk to be tested - * @returns {boolean} true, if the module is in a chunk - */ - isInChunk(chunk) { - return this._chunks.has(chunk); - } - - /** - * @returns {boolean} true, if the module is entry of any chunk - */ - isEntryModule() { - for (const chunk of this._chunks) { - if (chunk.entryModule === this) return true; - } - return false; - } - /** * @param {ModuleGraph} moduleGraph the module graph * @returns {boolean} true, if the module is optional @@ -299,64 +236,26 @@ class Module extends DependenciesBlock { } /** - * @returns {Chunk[]} all chunks which contain the module - */ - getChunks() { - return Array.from(this._chunks); - } - - /** - * @returns {number} the number of chunk which contain the module - */ - getNumberOfChunks() { - return this._chunks.size; - } - - /** - * @returns {Iterable} chunks that contain the module - */ - get chunksIterable() { - return this._chunks; - } - - /** - * @param {Module} otherModule some other module - * @returns {boolean} true, if modules are in the same chunks - */ - hasEqualChunks(otherModule) { - if (this._chunks.size !== otherModule._chunks.size) return false; - this._chunks.sortWith(sortByDebugId); - otherModule._chunks.sortWith(sortByDebugId); - const a = this._chunks[Symbol.iterator](); - const b = otherModule._chunks[Symbol.iterator](); - // eslint-disable-next-line no-constant-condition - while (true) { - const aItem = a.next(); - const bItem = b.next(); - if (aItem.done) return true; - if (aItem.value !== bItem.value) return false; - } - } - - /** + * @param {ChunkGraph} chunkGraph the chunk graph * @param {Chunk} chunk a chunk * @param {Chunk=} ignoreChunk chunk to be ignored * @returns {boolean} true, if the module is accessible from "chunk" when ignoring "ignoreChunk" */ - isAccessibleInChunk(chunk, ignoreChunk) { + isAccessibleInChunk(chunkGraph, chunk, ignoreChunk) { // Check if module is accessible in ALL chunk groups for (const chunkGroup of chunk.groupsIterable) { - if (!this.isAccessibleInChunkGroup(chunkGroup)) return false; + if (!this.isAccessibleInChunkGroup(chunkGraph, chunkGroup)) return false; } return true; } /** + * @param {ChunkGraph} chunkGraph the chunk graph * @param {ChunkGroup} chunkGroup a chunk group * @param {Chunk=} ignoreChunk chunk to be ignored * @returns {boolean} true, if the module is accessible from "chunkGroup" when ignoring "ignoreChunk" */ - isAccessibleInChunkGroup(chunkGroup, ignoreChunk) { + isAccessibleInChunkGroup(chunkGraph, chunkGroup, ignoreChunk) { const queue = new Set([chunkGroup]); // Check if module is accessible from all items of the queue @@ -364,7 +263,7 @@ class Module extends DependenciesBlock { // 1. If module is in one of the chunks of the group we can continue checking the next items // because it's accessible. for (const chunk of cg.chunks) { - if (chunk !== ignoreChunk && chunk.containsModule(this)) + if (chunk !== ignoreChunk && chunkGraph.isModuleInChunk(this, chunk)) continue queueFor; } // 2. If the chunk group is initial, we can break here because it's not accessible. @@ -379,15 +278,19 @@ class Module extends DependenciesBlock { /** * @param {Chunk} chunk a chunk * @param {ModuleGraph} moduleGraph the module graph + * @param {ChunkGraph} chunkGraph the chunk graph * @returns {boolean} true, if the module has any reason why "chunk" should be included */ - hasReasonForChunk(chunk, moduleGraph) { + hasReasonForChunk(chunk, moduleGraph, chunkGraph) { // check for each reason if we need the chunk for (const connection of moduleGraph.getIncomingConnections(this)) { const fromModule = connection.originModule; - for (const originChunk of fromModule.chunksIterable) { + for (const originChunk of chunkGraph.getModuleChunksIterable( + fromModule + )) { // return true if module this is not reachable from originChunk when ignoring cunk - if (!this.isAccessibleInChunk(originChunk, chunk)) return true; + if (!this.isAccessibleInChunk(chunkGraph, originChunk, chunk)) + return true; } } return false; @@ -499,16 +402,6 @@ class Module extends DependenciesBlock { super.updateHash(hash, compilation); } - /** - * Sorts items in this module - * @param {boolean=} sortChunks sort the chunks too - * @returns {void} - */ - sortItems(sortChunks) { - super.sortItems(); - if (sortChunks) this._chunks.sort(); - } - /** * @returns {void} */ diff --git a/lib/ModuleTemplate.js b/lib/ModuleTemplate.js index 3c9d1e68e..3b7ae93ed 100644 --- a/lib/ModuleTemplate.js +++ b/lib/ModuleTemplate.js @@ -9,6 +9,7 @@ const { SyncWaterfallHook, SyncHook } = require("tapable"); /** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ /** @typedef {import("./DependencyTemplates")} DependencyTemplates */ /** @typedef {import("./Module")} Module */ /** @typedef {import("./ModuleGraph")} ModuleGraph */ @@ -20,6 +21,7 @@ const { SyncWaterfallHook, SyncHook } = require("tapable"); * @property {DependencyTemplates} dependencyTemplates the dependency templates * @property {RuntimeTemplate} runtimeTemplate the runtime template * @property {ModuleGraph} moduleGraph the module graph + * @property {ChunkGraph} chunkGraph the chunk graph */ module.exports = class ModuleTemplate { @@ -46,11 +48,17 @@ module.exports = class ModuleTemplate { */ render(module, ctx) { try { - const { runtimeTemplate, dependencyTemplates, moduleGraph } = ctx; + const { + runtimeTemplate, + dependencyTemplates, + moduleGraph, + chunkGraph + } = ctx; const moduleSource = module.source({ dependencyTemplates, runtimeTemplate, moduleGraph, + chunkGraph, type: this.type }); const moduleSourcePostContent = this.hooks.content.call( diff --git a/lib/NamedChunksPlugin.js b/lib/NamedChunksPlugin.js index 113ce2502..dda8aaa7d 100644 --- a/lib/NamedChunksPlugin.js +++ b/lib/NamedChunksPlugin.js @@ -19,7 +19,10 @@ class NamedChunksPlugin { compilation.hooks.beforeChunkIds.tap("NamedChunksPlugin", chunks => { for (const chunk of chunks) { if (chunk.id === null) { - chunk.id = this.nameResolver(chunk); + chunk.id = this.nameResolver(chunk, { + moduleGraph: compilation.moduleGraph, + chunkGraph: compilation.chunkGraph + }); } } }); diff --git a/lib/Stats.js b/lib/Stats.js index b9f6140ef..27e7510b2 100644 --- a/lib/Stats.js +++ b/lib/Stats.js @@ -10,8 +10,12 @@ const { formatSize } = require("./SizeFormatHelpers"); const compareLocations = require("./compareLocations"); const formatLocation = require("./formatLocation"); const AggressiveSplittingPlugin = require("./optimize/AggressiveSplittingPlugin"); +const SizeLimitsPlugin = require("./performance/SizeLimitsPlugin"); +const { compareChunksById, compareIds } = require("./util/comparators"); const identifierUtils = require("./util/identifier"); +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./ChunkGroup")} ChunkGroup */ /** @typedef {import("./Compilation")} Compilation */ const optionsOrFallback = (...args) => { @@ -20,12 +24,6 @@ const optionsOrFallback = (...args) => { return optionValues.find(optionValue => typeof optionValue !== "undefined"); }; -const compareId = (a, b) => { - if (a < b) return -1; - if (a > b) return 1; - return 0; -}; - class Stats { /** * @param {Compilation} compilation webpack compilation @@ -142,6 +140,7 @@ class Stats { const compilation = this.compilation; const moduleGraph = compilation.moduleGraph; + const chunkGraph = compilation.chunkGraph; const context = optionsOrFallback( options.context, compilation.compiler.context @@ -231,7 +230,9 @@ class Stats { if (!showOrphanModules) { excludeModules.push((ident, module, type) => { - return module.getNumberOfChunks() === 0 && type !== "nested"; + return ( + chunkGraph.getNumberOfModuleChunks(module) === 0 && type !== "nested" + ); }); } @@ -416,7 +417,9 @@ class Stats { }; if (showPerformance) { - obj.isOverSizeLimit = compilation.assets[asset].isOverSizeLimit; + obj.isOverSizeLimit = SizeLimitsPlugin.isOverSizeLimit( + compilation.assets[asset] + ); } assetsByFile[asset] = obj; @@ -447,12 +450,16 @@ class Stats { obj.assets.sort(sortByField(sortAssets)); } + /** + * @param {Map} groupMap map from name to chunk group + * @returns {Object} chunk group stats object + */ const fnChunkGroup = groupMap => { const obj = {}; for (const keyValuePair of groupMap) { const name = keyValuePair[0]; const cg = keyValuePair[1]; - const children = cg.getChildrenByOrders(); + const children = cg.getChildrenByOrders(chunkGraph); obj[name] = { chunks: cg.chunks.map(c => c.id), assets: cg.chunks.reduce( @@ -487,7 +494,7 @@ class Stats { }, Object.create(null)) }; if (showPerformance) { - obj[name].isOverSizeLimit = cg.isOverSizeLimit; + obj[name].isOverSizeLimit = SizeLimitsPlugin.isOverSizeLimit(cg); } } @@ -522,7 +529,10 @@ class Stats { built: !!module.built, optional: module.isOptional(moduleGraph), prefetched: module.prefetched, - chunks: Array.from(module.chunksIterable, chunk => chunk.id), + chunks: Array.from( + chunkGraph.getOrderedModuleChunksIterable(module, compareChunksById), + chunk => chunk.id + ), issuer: issuer && issuer.identifier(), issuerId: issuer && issuer.id, issuerName: issuer && issuer.readableIdentifier(requestShortener), @@ -540,7 +550,7 @@ class Stats { warnings: module.warnings ? module.warnings.length : 0 }; if (showOrphanModules && !nested) { - obj.orphan = module.getNumberOfChunks() === 0; + obj.orphan = chunkGraph.getNumberOfModuleChunks(module) === 0; } if (showModuleAssets) { obj.assets = Object.keys(module.buildInfo.assets || {}); @@ -552,7 +562,7 @@ class Stats { if (a.originModule && !b.originModule) return -1; if (!a.originModule && b.originModule) return 1; if (a.originModule && b.originModule) { - const cmp = compareId(a.originModule.id, b.originModule.id); + const cmp = compareIds(a.originModule.id, b.originModule.id); if (cmp) return cmp; } if (a.dependency && !b.dependency) return -1; @@ -641,7 +651,7 @@ class Stats { const parents = new Set(); const children = new Set(); const siblings = new Set(); - const childIdByOrder = chunk.getChildIdsByOrders(); + const childIdByOrder = chunk.getChildIdsByOrders(chunkGraph); for (const chunkGroup of chunk.groupsIterable) { for (const parentGroup of chunkGroup.parentsIterable) { for (const chunk of parentGroup.chunks) { @@ -664,22 +674,24 @@ class Stats { entry: chunk.hasRuntime(), recorded: AggressiveSplittingPlugin.wasChunkRecorded(chunk), reason: chunk.chunkReason, - size: chunk.modulesSize(), + size: chunkGraph.getChunkModulesSize(chunk), names: chunk.name ? [chunk.name] : [], files: chunk.files.slice(), hash: chunk.renderedHash, - siblings: Array.from(siblings).sort(compareId), - parents: Array.from(parents).sort(compareId), - children: Array.from(children).sort(compareId), + siblings: Array.from(siblings).sort(compareIds), + parents: Array.from(parents).sort(compareIds), + children: Array.from(children).sort(compareIds), childrenByOrder: childIdByOrder }; if (showChunkModules) { - obj.modules = chunk - .getModules() + obj.modules = chunkGraph + .getChunkModules(chunk) + .slice() .sort(sortByField("depth")) .filter(createModuleFilter("chunk")) .map(m => fnModule(m)); - obj.filteredModules = chunk.getNumberOfModules() - obj.modules.length; + obj.filteredModules = + chunkGraph.getNumberOfChunkModules(chunk) - obj.modules.length; obj.modules.sort(sortByField(sortModules)); } if (showChunkOrigins) { diff --git a/lib/Template.js b/lib/Template.js index e36ecaad9..93300129d 100644 --- a/lib/Template.js +++ b/lib/Template.js @@ -217,9 +217,9 @@ class Template { moduleTemplate, prefix = "" ) { - const chunk = renderContext.chunk; + const { chunk, chunkGraph } = renderContext; var source = new ConcatSource(); - const modules = chunk.getModules().filter(filterFn); + const modules = chunkGraph.getChunkModules(chunk).filter(filterFn); let removedModules; if (chunk instanceof HotUpdateChunk) { removedModules = chunk.removedModules; diff --git a/lib/UmdMainTemplatePlugin.js b/lib/UmdMainTemplatePlugin.js index 2322536a8..657e8ed33 100644 --- a/lib/UmdMainTemplatePlugin.js +++ b/lib/UmdMainTemplatePlugin.js @@ -95,15 +95,18 @@ class UmdMainTemplatePlugin { * @returns {Source} new source */ const onRenderWithEntry = (source, chunk, hash) => { - /** @type {ExternalModule[]} */ - let externals = /** @type {ExternalModule[]} */ (chunk - .getModules() + const chunkGraph = compilation.chunkGraph; + const modules = chunkGraph + .getChunkModules(chunk) .filter( m => m instanceof ExternalModule && (m.externalType === "umd" || m.externalType === "umd2") - )); + ); + let externals = /** @type {ExternalModule[]} */ (modules); + /** @type {ExternalModule[]} */ const optionalExternals = []; + /** @type {ExternalModule[]} */ let requiredExternals = []; if (this.optionalAmdExternalAsGlobal) { for (const m of externals) { diff --git a/lib/node/ReadFileCompileWasmTemplatePlugin.js b/lib/node/ReadFileCompileWasmTemplatePlugin.js index a9cc29270..554c7d788 100644 --- a/lib/node/ReadFileCompileWasmTemplatePlugin.js +++ b/lib/node/ReadFileCompileWasmTemplatePlugin.js @@ -45,7 +45,7 @@ class ReadFileCompileWasmTemplatePlugin { ]); const plugin = new WasmMainTemplatePlugin( - compilation.moduleGraph, + compilation, Object.assign( { generateLoadBinaryCode, diff --git a/lib/optimize/AggressiveMergingPlugin.js b/lib/optimize/AggressiveMergingPlugin.js index 9c18b1a0a..5ce063701 100644 --- a/lib/optimize/AggressiveMergingPlugin.js +++ b/lib/optimize/AggressiveMergingPlugin.js @@ -5,6 +5,9 @@ "use strict"; +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + class AggressiveMergingPlugin { constructor(options) { if ( @@ -18,6 +21,10 @@ class AggressiveMergingPlugin { this.options = options || {}; } + /** + * @param {Compiler} compiler webpack compiler + * @returns {void} + */ apply(compiler) { const options = this.options; const minSizeReduce = options.minSizeReduce || 1.5; @@ -28,43 +35,35 @@ class AggressiveMergingPlugin { compilation.hooks.optimizeChunksAdvanced.tap( "AggressiveMergingPlugin", chunks => { + const chunkGraph = compilation.chunkGraph; + /** @type {{a: Chunk, b: Chunk, improvement: number}[]} */ let combinations = []; chunks.forEach((a, idx) => { if (a.canBeInitial()) return; for (let i = 0; i < idx; i++) { const b = chunks[i]; if (b.canBeInitial()) continue; + if (!chunkGraph.canChunksBeIntegrated(a, b)) { + continue; + } + const aSize = chunkGraph.getChunkSize(b, { + chunkOverhead: 0 + }); + const bSize = chunkGraph.getChunkSize(a, { + chunkOverhead: 0 + }); + const abSize = chunkGraph.getIntegratedChunksSize(b, a, { + chunkOverhead: 0 + }); + const improvement = (aSize + bSize) / abSize; combinations.push({ a, b, - improvement: undefined + improvement }); } }); - for (const pair of combinations) { - const a = pair.b.size({ - chunkOverhead: 0 - }); - const b = pair.a.size({ - chunkOverhead: 0 - }); - const ab = pair.b.integratedSize(pair.a, { - chunkOverhead: 0 - }); - let newSize; - if (ab === false) { - pair.improvement = false; - return; - } else { - newSize = ab; - } - - pair.improvement = (a + b) / newSize; - } - combinations = combinations.filter(pair => { - return pair.improvement !== false; - }); combinations.sort((a, b) => { return b.improvement - a.improvement; }); @@ -74,10 +73,9 @@ class AggressiveMergingPlugin { if (!pair) return; if (pair.improvement < minSizeReduce) return; - if (pair.b.integrate(pair.a, "aggressive-merge")) { - chunks.splice(chunks.indexOf(pair.a), 1); - return true; - } + chunkGraph.integrateChunks(pair.b, pair.a); + chunks.splice(chunks.indexOf(pair.a), 1); + return true; } ); } diff --git a/lib/optimize/AggressiveSplittingPlugin.js b/lib/optimize/AggressiveSplittingPlugin.js index 8526a0b99..28161a601 100644 --- a/lib/optimize/AggressiveSplittingPlugin.js +++ b/lib/optimize/AggressiveSplittingPlugin.js @@ -8,13 +8,16 @@ const validateOptions = require("schema-utils"); const schema = require("../../schemas/plugins/optimize/AggressiveSplittingPlugin.json"); const { intersect } = require("../util/SetHelpers"); +const { compareModulesById } = require("../util/comparators"); const identifierUtils = require("../util/identifier"); /** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ -const moveModuleBetween = (oldChunk, newChunk) => { +const moveModuleBetween = (chunkGraph, oldChunk, newChunk) => { return module => { - oldChunk.moveModule(module, newChunk); + chunkGraph.disconnectChunkAndModule(oldChunk, module); + chunkGraph.connectChunkAndModule(newChunk, module); }; }; @@ -54,6 +57,10 @@ class AggressiveSplittingPlugin { return recordedChunks.has(chunk); } + /** + * @param {Compiler} compiler webpack compiler + * @returns {void} + */ apply(compiler) { compiler.hooks.thisCompilation.tap( "AggressiveSplittingPlugin", @@ -70,6 +77,7 @@ class AggressiveSplittingPlugin { compilation.hooks.optimizeChunksAdvanced.tap( "AggressiveSplittingPlugin", chunks => { + const chunkGraph = compilation.chunkGraph; // Precompute stuff const nameToModuleMap = new Map(); const moduleToNameMap = new Map(); @@ -122,7 +130,9 @@ class AggressiveSplittingPlugin { // get chunks with all modules const selectedChunks = intersect( - selectedModules.map(m => new Set(m.chunksIterable)) + selectedModules.map( + m => new Set(chunkGraph.getModuleChunksIterable(m)) + ) ); // No relevant chunks found @@ -131,8 +141,9 @@ class AggressiveSplittingPlugin { // The found chunk is already the split or similar if ( selectedChunks.size === 1 && - Array.from(selectedChunks)[0].getNumberOfModules() === - selectedModules.length + chunkGraph.getNumberOfChunkModules( + Array.from(selectedChunks)[0] + ) === selectedModules.length ) { const chunk = Array.from(selectedChunks)[0]; if (fromAggressiveSplittingSet.has(chunk)) return false; @@ -145,7 +156,9 @@ class AggressiveSplittingPlugin { const newChunk = compilation.addChunk(); newChunk.chunkReason = "aggressive splitted"; for (const chunk of selectedChunks) { - selectedModules.forEach(moveModuleBetween(chunk, newChunk)); + selectedModules.forEach( + moveModuleBetween(chunkGraph, chunk, newChunk) + ); chunk.split(newChunk); chunk.name = null; } @@ -168,14 +181,20 @@ class AggressiveSplittingPlugin { // for any chunk which isn't splitted yet, split it and create a new entry // start with the biggest chunk const sortedChunks = chunks.slice().sort((a, b) => { - const diff1 = b.modulesSize() - a.modulesSize(); + const diff1 = + chunkGraph.getChunkModulesSize(b) - + chunkGraph.getChunkModulesSize(a); if (diff1) return diff1; - const diff2 = a.getNumberOfModules() - b.getNumberOfModules(); + const diff2 = + chunkGraph.getNumberOfChunkModules(a) - + chunkGraph.getNumberOfChunkModules(b); if (diff2) return diff2; - const modulesA = Array.from(a.modulesIterable); - const modulesB = Array.from(b.modulesIterable); - modulesA.sort(); - modulesB.sort(); + const modulesA = Array.from( + chunkGraph.getOrderedChunkModulesIterable(a, compareModulesById) + ); + const modulesB = Array.from( + chunkGraph.getOrderedChunkModulesIterable(b, compareModulesById) + ); const aI = modulesA[Symbol.iterator](); const bI = modulesB[Symbol.iterator](); // eslint-disable-next-line no-constant-condition @@ -191,16 +210,19 @@ class AggressiveSplittingPlugin { }); for (const chunk of sortedChunks) { if (fromAggressiveSplittingSet.has(chunk)) continue; - const size = chunk.modulesSize(); - if (size > maxSize && chunk.getNumberOfModules() > 1) { - const modules = chunk - .getModules() + const size = chunkGraph.getChunkModulesSize(chunk); + if ( + size > maxSize && + chunkGraph.getNumberOfChunkModules(chunk) > 1 + ) { + const modules = chunkGraph + .getChunkModules(chunk) .filter(isNotAEntryModule(chunk.entryModule)) .sort((a, b) => { - a = a.identifier(); - b = b.identifier(); - if (a > b) return 1; - if (a < b) return -1; + const aIdentifer = a.identifier(); + const bIdentifer = b.identifier(); + if (aIdentifer > bIdentifer) return 1; + if (aIdentifer < bIdentifer) return -1; return 0; }); const selectedModules = []; diff --git a/lib/optimize/ChunkModuleIdRangePlugin.js b/lib/optimize/ChunkModuleIdRangePlugin.js index aacfa4029..fd3c956b3 100644 --- a/lib/optimize/ChunkModuleIdRangePlugin.js +++ b/lib/optimize/ChunkModuleIdRangePlugin.js @@ -5,23 +5,27 @@ "use strict"; -const sortByIndex = (a, b) => { - return a.index - b.index; -}; +const { + compareModulesByIndex, + compareModulesByIndex2 +} = require("../util/comparators"); -const sortByIndex2 = (a, b) => { - return a.index2 - b.index2; -}; +/** @typedef {import("../Compiler")} Compiler */ class ChunkModuleIdRangePlugin { constructor(options) { this.options = options; } + /** + * @param {Compiler} compiler webpack compiler + * @returns {void} + */ apply(compiler) { const options = this.options; compiler.hooks.compilation.tap("ChunkModuleIdRangePlugin", compilation => { compilation.hooks.moduleIds.tap("ChunkModuleIdRangePlugin", modules => { + const chunkGraph = compilation.chunkGraph; const chunk = compilation.chunks.find( chunk => chunk.name === options.name ); @@ -35,22 +39,23 @@ class ChunkModuleIdRangePlugin { let chunkModules; if (options.order) { - chunkModules = Array.from(chunk.modulesIterable); + let cmpFn; switch (options.order) { case "index": - chunkModules.sort(sortByIndex); + cmpFn = compareModulesByIndex; break; case "index2": - chunkModules.sort(sortByIndex2); + cmpFn = compareModulesByIndex2; break; default: throw new Error( "ChunkModuleIdRangePlugin: unexpected value of order" ); } + chunkModules = chunkGraph.getOrderedChunkModules(chunk, cmpFn); } else { chunkModules = modules.filter(m => { - return m.chunksIterable.has(chunk); + return chunkGraph.isModuleInChunk(m, chunk); }); } diff --git a/lib/optimize/ConcatenatedModule.js b/lib/optimize/ConcatenatedModule.js index f041b6708..abaed74b2 100644 --- a/lib/optimize/ConcatenatedModule.js +++ b/lib/optimize/ConcatenatedModule.js @@ -310,7 +310,6 @@ class ConcatenatedModule extends Module { */ constructor(rootModule, modules, compilation) { super("javascript/esm", null); - super.setChunks(rootModule._chunks); const moduleGraph = compilation.moduleGraph; @@ -542,7 +541,7 @@ class ConcatenatedModule extends Module { * @param {SourceContext} sourceContext source context * @returns {Source} generated source */ - source({ dependencyTemplates, runtimeTemplate, moduleGraph }) { + source({ dependencyTemplates, runtimeTemplate, moduleGraph, chunkGraph }) { const requestShortener = runtimeTemplate.requestShortener; // Metainfo for each module const modulesWithInfo = this._orderedConcatenationList.map((info, idx) => { @@ -699,7 +698,8 @@ class ConcatenatedModule extends Module { const source = m.source({ dependencyTemplates: innerDependencyTemplates, runtimeTemplate, - moduleGraph + moduleGraph, + chunkGraph }); const code = source.source(); let ast; diff --git a/lib/optimize/EnsureChunkConditionsPlugin.js b/lib/optimize/EnsureChunkConditionsPlugin.js index e17274381..67d44715e 100644 --- a/lib/optimize/EnsureChunkConditionsPlugin.js +++ b/lib/optimize/EnsureChunkConditionsPlugin.js @@ -5,24 +5,20 @@ "use strict"; -const { - connectChunkAndModule, - disconnectChunkAndModule -} = require("../GraphHelpers"); - class EnsureChunkConditionsPlugin { apply(compiler) { compiler.hooks.compilation.tap( "EnsureChunkConditionsPlugin", compilation => { const handler = chunks => { + const chunkGraph = compilation.chunkGraph; let changed = false; // These sets are hoisted here to save memory // They are cleared at the end of every loop const sourceChunks = new Set(); const chunkGroups = new Set(); for (const module of compilation.modules) { - for (const chunk of module.chunksIterable) { + for (const chunk of chunkGraph.getModuleChunksIterable(module)) { if (!module.chunkCondition(chunk)) { sourceChunks.add(chunk); for (const group of chunk.groupsIterable) { @@ -52,10 +48,10 @@ class EnsureChunkConditionsPlugin { } } for (const sourceChunk of sourceChunks) { - disconnectChunkAndModule(sourceChunk, module); + chunkGraph.disconnectChunkAndModule(sourceChunk, module); } for (const targetChunk of targetChunks) { - connectChunkAndModule(targetChunk, module); + chunkGraph.connectChunkAndModule(targetChunk, module); } sourceChunks.clear(); chunkGroups.clear(); diff --git a/lib/optimize/FlagIncludedChunksPlugin.js b/lib/optimize/FlagIncludedChunksPlugin.js index 860b02f72..66ca29e1e 100644 --- a/lib/optimize/FlagIncludedChunksPlugin.js +++ b/lib/optimize/FlagIncludedChunksPlugin.js @@ -11,6 +11,8 @@ class FlagIncludedChunksPlugin { compilation.hooks.optimizeChunkIds.tap( "FlagIncludedChunksPlugin", chunks => { + const chunkGraph = compilation.chunkGraph; + // prepare two bit integers for each module // 2^31 is the max number represented as SMI in v8 // we want the bits distributed this way: @@ -47,7 +49,7 @@ class FlagIncludedChunksPlugin { const chunkModulesHash = new WeakMap(); for (const chunk of chunks) { let hash = 0; - for (const module of chunk.modulesIterable) { + for (const module of chunkGraph.getChunkModulesIterable(chunk)) { hash |= moduleBits.get(module); } chunkModulesHash.set(chunk, hash); @@ -55,22 +57,29 @@ class FlagIncludedChunksPlugin { for (const chunkA of chunks) { const chunkAHash = chunkModulesHash.get(chunkA); - const chunkAModulesCount = chunkA.getNumberOfModules(); + const chunkAModulesCount = chunkGraph.getNumberOfChunkModules( + chunkA + ); if (chunkAModulesCount === 0) continue; let bestModule = undefined; - for (const module of chunkA.modulesIterable) { + for (const module of chunkGraph.getChunkModulesIterable(chunkA)) { if ( bestModule === undefined || - bestModule.getNumberOfChunks() > module.getNumberOfChunks() + chunkGraph.getNumberOfModuleChunks(bestModule) > + chunkGraph.getNumberOfModuleChunks(module) ) bestModule = module; } - loopB: for (const chunkB of bestModule.chunksIterable) { + loopB: for (const chunkB of chunkGraph.getModuleChunksIterable( + bestModule + )) { // as we iterate the same iterables twice // skip if we find ourselves if (chunkA === chunkB) continue; - const chunkBModulesCount = chunkB.getNumberOfModules(); + const chunkBModulesCount = chunkGraph.getNumberOfChunkModules( + chunkB + ); // ids for empty chunks are not included if (chunkBModulesCount === 0) continue; @@ -86,8 +95,8 @@ class FlagIncludedChunksPlugin { if ((chunkBHash & chunkAHash) !== chunkAHash) continue; // compare all modules - for (const m of chunkA.modulesIterable) { - if (!chunkB.containsModule(m)) continue loopB; + for (const m of chunkGraph.getChunkModulesIterable(chunkA)) { + if (!chunkGraph.isModuleInChunk(m, chunkB)) continue loopB; } chunkB.ids.push(chunkA.id); } diff --git a/lib/optimize/LimitChunkCountPlugin.js b/lib/optimize/LimitChunkCountPlugin.js index 50a3db801..b52611143 100644 --- a/lib/optimize/LimitChunkCountPlugin.js +++ b/lib/optimize/LimitChunkCountPlugin.js @@ -8,42 +8,55 @@ const validateOptions = require("schema-utils"); const schema = require("../../schemas/plugins/optimize/LimitChunkCountPlugin.json"); +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + class LimitChunkCountPlugin { constructor(options) { validateOptions(schema, options || {}, "Limit Chunk Count Plugin"); this.options = options || {}; } + + /** + * @param {Compiler} compiler webpack compiler + * @returns {void} + */ apply(compiler) { const options = this.options; compiler.hooks.compilation.tap("LimitChunkCountPlugin", compilation => { compilation.hooks.optimizeChunksAdvanced.tap( "LimitChunkCountPlugin", chunks => { + const chunkGraph = compilation.chunkGraph; const maxChunks = options.maxChunks; if (!maxChunks) return; if (maxChunks < 1) return; if (chunks.length <= maxChunks) return; const sortedExtendedPairCombinations = chunks - .reduce((combinations, a, idx) => { + .reduce((/** @type {[Chunk, Chunk][]} */ combinations, a, idx) => { // create combination pairs for (let i = 0; i < idx; i++) { const b = chunks[i]; - combinations.push([b, a]); + // filter pairs that can NOT be integrated! + if (chunkGraph.canChunksBeIntegrated(b, a)) { + combinations.push([b, a]); + } } return combinations; }, []) .map(pair => { // extend combination pairs with size and integrated size - const a = pair[0].size(options); - const b = pair[1].size(options); - const ab = pair[0].integratedSize(pair[1], options); - return [a + b - ab, ab, pair[0], pair[1], a, b]; - }) - .filter(extendedPair => { - // filter pairs that do not have an integratedSize - // meaning they can NOT be integrated! - return extendedPair[1] !== false; + const a = chunkGraph.getChunkSize(pair[0], options); + const b = chunkGraph.getChunkSize(pair[1], options); + const ab = chunkGraph.getIntegratedChunksSize( + pair[0], + pair[1], + options + ); + /** @type {[number, number, Chunk, Chunk, number, number]} */ + const extendedPair = [a + b - ab, ab, pair[0], pair[1], a, b]; + return extendedPair; }) .sort((a, b) => { // sadly javascript does an inplace sort here @@ -55,7 +68,8 @@ class LimitChunkCountPlugin { const pair = sortedExtendedPairCombinations[0]; - if (pair && pair[2].integrate(pair[3], "limit")) { + if (pair) { + chunkGraph.integrateChunks(pair[2], pair[3]); chunks.splice(chunks.indexOf(pair[3]), 1); return true; } diff --git a/lib/optimize/MergeDuplicateChunksPlugin.js b/lib/optimize/MergeDuplicateChunksPlugin.js index 299399eef..50e1e59ba 100644 --- a/lib/optimize/MergeDuplicateChunksPlugin.js +++ b/lib/optimize/MergeDuplicateChunksPlugin.js @@ -5,7 +5,13 @@ "use strict"; +/** @typedef {import("../Compiler")} Compiler */ + class MergeDuplicateChunksPlugin { + /** + * @param {Compiler} compiler the compiler + * @returns {void} + */ apply(compiler) { compiler.hooks.compilation.tap( "MergeDuplicateChunksPlugin", @@ -13,6 +19,8 @@ class MergeDuplicateChunksPlugin { compilation.hooks.optimizeChunksBasic.tap( "MergeDuplicateChunksPlugin", chunks => { + const chunkGraph = compilation.chunkGraph; + // remember already tested chunks for performance const notDuplicates = new Set(); @@ -20,15 +28,18 @@ class MergeDuplicateChunksPlugin { for (const chunk of chunks) { // track a Set of all chunk that could be duplicates let possibleDuplicates; - for (const module of chunk.modulesIterable) { + for (const module of chunkGraph.getChunkModulesIterable(chunk)) { if (possibleDuplicates === undefined) { // when possibleDuplicates is not yet set, // create a new Set from chunks of the current module // including only chunks with the same number of modules - for (const dup of module.chunksIterable) { + for (const dup of chunkGraph.getModuleChunksIterable( + module + )) { if ( dup !== chunk && - chunk.getNumberOfModules() === dup.getNumberOfModules() && + chunkGraph.getNumberOfChunkModules(chunk) === + chunkGraph.getNumberOfChunkModules(dup) && !notDuplicates.has(dup) ) { // delay allocating the new Set until here, reduce memory pressure @@ -44,7 +55,7 @@ class MergeDuplicateChunksPlugin { // validate existing possible duplicates for (const dup of possibleDuplicates) { // remove possible duplicate when module is not contained - if (!dup.containsModule(module)) { + if (!chunkGraph.isModuleInChunk(module, dup)) { possibleDuplicates.delete(dup); } } @@ -61,7 +72,8 @@ class MergeDuplicateChunksPlugin { for (const otherChunk of possibleDuplicates) { if (otherChunk.hasRuntime() !== chunk.hasRuntime()) continue; // merge them - if (chunk.integrate(otherChunk, "duplicate")) { + if (chunkGraph.canChunksBeIntegrated(chunk, otherChunk)) { + chunkGraph.integrateChunks(chunk, otherChunk); chunks.splice(chunks.indexOf(otherChunk), 1); } } diff --git a/lib/optimize/MinChunkSizePlugin.js b/lib/optimize/MinChunkSizePlugin.js index 188d12ce9..5edd41178 100644 --- a/lib/optimize/MinChunkSizePlugin.js +++ b/lib/optimize/MinChunkSizePlugin.js @@ -8,12 +8,19 @@ const validateOptions = require("schema-utils"); const schema = require("../../schemas/plugins/optimize/MinChunkSizePlugin.json"); +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + class MinChunkSizePlugin { constructor(options) { validateOptions(schema, options, "Min Chunk Size Plugin"); this.options = options; } + /** + * @param {Compiler} compiler webpack compiler + * @returns {void} + */ apply(compiler) { const options = this.options; const minChunkSize = options.minChunkSize; @@ -21,13 +28,14 @@ class MinChunkSizePlugin { compilation.hooks.optimizeChunksAdvanced.tap( "MinChunkSizePlugin", chunks => { + const chunkGraph = compilation.chunkGraph; const equalOptions = { chunkOverhead: 1, entryChunkMultiplicator: 1 }; const sortedSizeFilteredExtendedPairCombinations = chunks - .reduce((combinations, a, idx) => { + .reduce((/** @type {[Chunk, Chunk][]} */ combinations, a, idx) => { // create combination pairs for (let i = 0; i < idx; i++) { const b = chunks[i]; @@ -38,22 +46,26 @@ class MinChunkSizePlugin { .filter(pair => { // check if one of the chunks sizes is smaller than the minChunkSize const p0SmallerThanMinChunkSize = - pair[0].size(equalOptions) < minChunkSize; + chunkGraph.getChunkSize(pair[0], equalOptions) < minChunkSize; const p1SmallerThanMinChunkSize = - pair[1].size(equalOptions) < minChunkSize; - return p0SmallerThanMinChunkSize || p1SmallerThanMinChunkSize; + chunkGraph.getChunkSize(pair[1], equalOptions) < minChunkSize; + if (!p0SmallerThanMinChunkSize && !p1SmallerThanMinChunkSize) + return false; + // filter pairs that can NOT be integrated! + return chunkGraph.canChunksBeIntegrated(pair[0], pair[1]); }) .map(pair => { // extend combination pairs with size and integrated size - const a = pair[0].size(options); - const b = pair[1].size(options); - const ab = pair[0].integratedSize(pair[1], options); - return [a + b - ab, ab, pair[0], pair[1]]; - }) - .filter(pair => { - // filter pairs that do not have an integratedSize - // meaning they can NOT be integrated! - return pair[1] !== false; + const a = chunkGraph.getChunkSize(pair[0], options); + const b = chunkGraph.getChunkSize(pair[1], options); + const ab = chunkGraph.getIntegratedChunksSize( + pair[0], + pair[1], + options + ); + /** @type {[number, number, Chunk, Chunk]} */ + const extendedPair = [a + b - ab, ab, pair[0], pair[1]]; + return extendedPair; }) .sort((a, b) => { // sadly javascript does an inplace sort here @@ -67,7 +79,7 @@ class MinChunkSizePlugin { const pair = sortedSizeFilteredExtendedPairCombinations[0]; - pair[2].integrate(pair[3], "min-size"); + chunkGraph.integrateChunks(pair[2], pair[3]); chunks.splice(chunks.indexOf(pair[3]), 1); return true; } diff --git a/lib/optimize/ModuleConcatenationPlugin.js b/lib/optimize/ModuleConcatenationPlugin.js index dddc222bf..c3ba0932f 100644 --- a/lib/optimize/ModuleConcatenationPlugin.js +++ b/lib/optimize/ModuleConcatenationPlugin.js @@ -99,6 +99,7 @@ class ModuleConcatenationPlugin { compilation.hooks.optimizeChunkModules.tap( "ModuleConcatenationPlugin", (chunks, modules) => { + const chunkGraph = compilation.chunkGraph; const relevantModules = []; const possibleInners = new Set(); for (const module of modules) { @@ -147,7 +148,7 @@ class ModuleConcatenationPlugin { } // Module must be in any chunk (we don't want to do useless work) - if (module.getNumberOfChunks() === 0) { + if (chunkGraph.getNumberOfModuleChunks(module) === 0) { setBailoutReason(module, "Module is not in any chunk"); continue; } @@ -155,7 +156,7 @@ class ModuleConcatenationPlugin { relevantModules.push(module); // Module must not be the entry points - if (module.isEntryModule()) { + if (chunkGraph.isEntryModule(module)) { setInnerBailoutReason(module, "Module is an entry point"); continue; } @@ -235,7 +236,10 @@ class ModuleConcatenationPlugin { connection => { return ( connection.originModule && - !connection.originModule.hasEqualChunks(module) + !chunkGraph.haveModulesEqualChunks( + connection.originModule, + module + ) ); } ); @@ -328,13 +332,13 @@ class ModuleConcatenationPlugin { .getOptimizationBailout(moduleGraph) .push(formatBailoutWarning(warning[0], warning[1])); } - const chunks = concatConfiguration.rootModule.getChunks(); + const chunks = chunkGraph.getModuleChunks( + concatConfiguration.rootModule + ); for (const m of modules) { usedModules.add(m); // remove module from chunk - for (const chunk of chunks) { - chunk.removeModule(m); - } + chunkGraph.replaceModule(m, newModule); // replace module references with the concatenated module moduleGraph.replaceModule(m, newModule, c => { return !( @@ -346,8 +350,6 @@ class ModuleConcatenationPlugin { } // add concatenated module to the chunks for (const chunk of chunks) { - chunk.addModule(newModule); - newModule.addChunk(chunk); if (chunk.entryModule === concatConfiguration.rootModule) { chunk.entryModule = newModule; } diff --git a/lib/optimize/NaturalChunkOrderPlugin.js b/lib/optimize/NaturalChunkOrderPlugin.js index 4acd34ef2..34fbf8d5d 100644 --- a/lib/optimize/NaturalChunkOrderPlugin.js +++ b/lib/optimize/NaturalChunkOrderPlugin.js @@ -5,6 +5,8 @@ "use strict"; +const { compareModulesById } = require("../util/comparators"); + /** @typedef {import("../Compiler")} Compiler */ class NaturalChunkOrderPlugin { @@ -17,9 +19,14 @@ class NaturalChunkOrderPlugin { compilation.hooks.optimizeChunkOrder.tap( "NaturalChunkOrderPlugin", chunks => { + const chunkGraph = compilation.chunkGraph; chunks.sort((chunkA, chunkB) => { - const a = chunkA.modulesIterable[Symbol.iterator](); - const b = chunkB.modulesIterable[Symbol.iterator](); + const a = chunkGraph + .getOrderedChunkModulesIterable(chunkA, compareModulesById) + [Symbol.iterator](); + const b = chunkGraph + .getOrderedChunkModulesIterable(chunkB, compareModulesById) + [Symbol.iterator](); // eslint-disable-next-line no-constant-condition while (true) { const aItem = a.next(); diff --git a/lib/optimize/OccurrenceModuleOrderPlugin.js b/lib/optimize/OccurrenceModuleOrderPlugin.js index 905afdd03..897a4115c 100644 --- a/lib/optimize/OccurrenceModuleOrderPlugin.js +++ b/lib/optimize/OccurrenceModuleOrderPlugin.js @@ -30,6 +30,8 @@ class OccurrenceOrderModuleIdsPlugin { compilation.hooks.optimizeModuleOrder.tap( "OccurrenceOrderModuleIdsPlugin", modules => { + const chunkGraph = compilation.chunkGraph; + const occursInInitialChunksMap = new Map(); const occursInAllChunksMap = new Map(); @@ -38,7 +40,7 @@ class OccurrenceOrderModuleIdsPlugin { for (const m of modules) { let initial = 0; let entry = 0; - for (const c of m.chunksIterable) { + for (const c of chunkGraph.getModuleChunksIterable(m)) { if (c.canBeInitial()) initial++; if (c.entryModule === m) entry++; } @@ -71,7 +73,10 @@ class OccurrenceOrderModuleIdsPlugin { if (factor === 0) { return sum; } - return sum + factor * c.originModule.getNumberOfChunks(); + return ( + sum + + factor * chunkGraph.getNumberOfModuleChunks(c.originModule) + ); }; if (prioritiseInitial) { @@ -91,7 +96,7 @@ class OccurrenceOrderModuleIdsPlugin { for (const m of modules) { const result = moduleGraph.getIncomingConnections(m).reduce(countOccurs, 0) + - m.getNumberOfChunks() + + chunkGraph.getNumberOfModuleChunks(m) + entryCountMap.get(m); occursInAllChunksMap.set(m, result); originalOrder.set(m, i++); diff --git a/lib/optimize/RemoveEmptyChunksPlugin.js b/lib/optimize/RemoveEmptyChunksPlugin.js index 44fb80b23..ef3985ca0 100644 --- a/lib/optimize/RemoveEmptyChunksPlugin.js +++ b/lib/optimize/RemoveEmptyChunksPlugin.js @@ -5,18 +5,30 @@ "use strict"; +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + class RemoveEmptyChunksPlugin { + /** + * @param {Compiler} compiler webpack compiler + * @returns {void} + */ apply(compiler) { compiler.hooks.compilation.tap("RemoveEmptyChunksPlugin", compilation => { + /** + * @param {Chunk[]} chunks the chunks array + * @returns {void} + */ const handler = chunks => { + const chunkGraph = compilation.chunkGraph; for (let i = chunks.length - 1; i >= 0; i--) { const chunk = chunks[i]; if ( - chunk.isEmpty() && + chunkGraph.getNumberOfChunkModules(chunk) === 0 && !chunk.hasRuntime() && !chunk.hasEntryModule() ) { - chunk.remove("empty"); + compilation.chunkGraph.disconnectChunk(chunk); chunks.splice(i, 1); } } diff --git a/lib/optimize/RemoveParentModulesPlugin.js b/lib/optimize/RemoveParentModulesPlugin.js index 588a6ebff..64a06289b 100644 --- a/lib/optimize/RemoveParentModulesPlugin.js +++ b/lib/optimize/RemoveParentModulesPlugin.js @@ -8,10 +8,17 @@ const Queue = require("../util/Queue"); const { intersect } = require("../util/SetHelpers"); +/** @typedef {import("../Compiler")} Compiler */ + class RemoveParentModulesPlugin { + /** + * @param {Compiler} compiler the compiler + * @returns {void} + */ apply(compiler) { compiler.hooks.compilation.tap("RemoveParentModulesPlugin", compilation => { const handler = (chunks, chunkGroups) => { + const chunkGraph = compilation.chunkGraph; const queue = new Queue(); const availableModulesMap = new WeakMap(); @@ -35,7 +42,7 @@ class RemoveParentModulesPlugin { // if we have not own info yet: create new entry availableModules = new Set(availableModulesInParent); for (const chunk of parent.chunks) { - for (const m of chunk.modulesIterable) { + for (const m of chunkGraph.getChunkModulesIterable(chunk)) { availableModules.add(m); } } @@ -44,7 +51,7 @@ class RemoveParentModulesPlugin { } else { for (const m of availableModules) { if ( - !parent.containsModule(m) && + !chunkGraph.isModuleInChunkGroup(m, parent) && !availableModulesInParent.has(m) ) { availableModules.delete(m); @@ -73,23 +80,23 @@ class RemoveParentModulesPlugin { availableModulesSets.length === 1 ? availableModulesSets[0] : intersect(availableModulesSets); - const numberOfModules = chunk.getNumberOfModules(); + const numberOfModules = chunkGraph.getNumberOfChunkModules(chunk); const toRemove = new Set(); if (numberOfModules < availableModules.size) { - for (const m of chunk.modulesIterable) { + for (const m of chunkGraph.getChunkModulesIterable(chunk)) { if (availableModules.has(m)) { toRemove.add(m); } } } else { for (const m of availableModules) { - if (chunk.containsModule(m)) { + if (chunkGraph.isModuleInChunk(m, chunk)) { toRemove.add(m); } } } for (const module of toRemove) { - chunk.removeModule(module); + chunkGraph.disconnectChunkAndModule(chunk, module); } } }; diff --git a/lib/optimize/RuntimeChunkPlugin.js b/lib/optimize/RuntimeChunkPlugin.js index ab592b0d2..0b0d9da9d 100644 --- a/lib/optimize/RuntimeChunkPlugin.js +++ b/lib/optimize/RuntimeChunkPlugin.js @@ -18,6 +18,7 @@ module.exports = class RuntimeChunkPlugin { apply(compiler) { compiler.hooks.thisCompilation.tap("RuntimeChunkPlugin", compilation => { compilation.hooks.optimizeChunksAdvanced.tap("RuntimeChunkPlugin", () => { + const chunkGraph = compilation.chunkGraph; for (const entrypoint of compilation.entrypoints.values()) { const chunk = entrypoint.getRuntimeChunk(); let name = this.options.name; @@ -25,7 +26,7 @@ module.exports = class RuntimeChunkPlugin { name = name(entrypoint); } if ( - chunk.getNumberOfModules() > 0 || + chunkGraph.getNumberOfChunkModules(chunk) > 0 || !chunk.preventIntegration || chunk.name !== name ) { diff --git a/lib/optimize/SplitChunksPlugin.js b/lib/optimize/SplitChunksPlugin.js index 9dc660d18..f225f8afa 100644 --- a/lib/optimize/SplitChunksPlugin.js +++ b/lib/optimize/SplitChunksPlugin.js @@ -6,17 +6,18 @@ "use strict"; const crypto = require("crypto"); -const { connectChunkAndModule } = require("../GraphHelpers"); const { isSubset } = require("../util/SetHelpers"); const SortableSet = require("../util/SortableSet"); const deterministicGrouping = require("../util/deterministicGrouping"); const contextify = require("../util/identifier").contextify; -/** @typedef {import("../Compiler")} Compiler */ /** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compiler")} Compiler */ /** @typedef {import("../Module")} Module */ -/** @typedef {import("../util/deterministicGrouping").Options} DeterministicGroupingOptionsForModule */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../util/deterministicGrouping").GroupedItems} DeterministicGroupingGroupedItemsForModule */ +/** @typedef {import("../util/deterministicGrouping").Options} DeterministicGroupingOptionsForModule */ const deterministicGroupingForModules = /** @type {function(DeterministicGroupingOptionsForModule): DeterministicGroupingGroupedItemsForModule[]} */ (deterministicGrouping); @@ -216,7 +217,7 @@ module.exports = class SplitChunksPlugin { return cacheGroups; } if (cacheGroups && typeof cacheGroups === "object") { - const fn = module => { + const fn = (module, context) => { let results; for (const key of Object.keys(cacheGroups)) { let option = cacheGroups[key]; @@ -241,7 +242,9 @@ module.exports = class SplitChunksPlugin { results.push(result); } } - } else if (SplitChunksPlugin.checkTest(option.test, module)) { + } else if ( + SplitChunksPlugin.checkTest(option.test, module, context) + ) { if (results === undefined) results = []; results.push({ key: key, @@ -277,13 +280,22 @@ module.exports = class SplitChunksPlugin { return fn; } - static checkTest(test, module) { + /** + * @typedef {Object} TestContext + * @property {ModuleGraph} moduleGraph the module graph + * @property {ChunkGraph} chunkGraph the chunk graph + */ + + /** + * @param {undefined|boolean|string|RegExp|function(Module, TestContext): boolean} test test option + * @param {Module} module the module + * @param {TestContext} context context object + * @returns {boolean} true, if the module should be selected + */ + static checkTest(test, module, context) { if (test === undefined) return true; if (typeof test === "function") { - if (test.length !== 1) { - return test(module, module.getChunks()); - } - return test(module); + return test(module, context); } if (typeof test === "boolean") return test; if (typeof test === "string") { @@ -291,7 +303,7 @@ module.exports = class SplitChunksPlugin { if (name && name.startsWith(test)) { return true; } - for (const chunk of module.chunksIterable) { + for (const chunk of context.chunkGraph.getModuleChunksIterable(module)) { if (chunk.name && chunk.name.startsWith(test)) { return true; } @@ -303,7 +315,7 @@ module.exports = class SplitChunksPlugin { if (name && test.test(name)) { return true; } - for (const chunk of module.chunksIterable) { + for (const chunk of context.chunkGraph.getModuleChunksIterable(module)) { if (chunk.name && test.test(chunk.name)) { return true; } @@ -328,6 +340,8 @@ module.exports = class SplitChunksPlugin { chunks => { if (alreadyOptimized) return; alreadyOptimized = true; + const chunkGraph = compilation.chunkGraph; + const moduleGraph = compilation.moduleGraph; // Give each selected chunk an index (to create strings from chunks) const indexMap = new Map(); let index = 1; @@ -342,9 +356,14 @@ module.exports = class SplitChunksPlugin { /** @type {Map>} */ const chunkSetsInGraph = new Map(); for (const module of compilation.modules) { - const chunksKey = getKey(module.chunksIterable); + const chunksKey = getKey( + chunkGraph.getModuleChunksIterable(module) + ); if (!chunkSetsInGraph.has(chunksKey)) { - chunkSetsInGraph.set(chunksKey, new Set(module.chunksIterable)); + chunkSetsInGraph.set( + chunksKey, + new Set(chunkGraph.getModuleChunksIterable(module)) + ); } } @@ -503,16 +522,23 @@ module.exports = class SplitChunksPlugin { } }; + const context = { + moduleGraph, + chunkGraph + }; + // Walk through all modules for (const module of compilation.modules) { // Get cache group - let cacheGroups = this.options.getCacheGroups(module); + let cacheGroups = this.options.getCacheGroups(module, context); if (!Array.isArray(cacheGroups) || cacheGroups.length === 0) { continue; } // Prepare some values - const chunksKey = getKey(module.chunksIterable); + const chunksKey = getKey( + chunkGraph.getModuleChunksIterable(module) + ); let combs = combinationsCache.get(chunksKey); if (combs === undefined) { combs = getCombinations(chunksKey); @@ -627,10 +653,15 @@ module.exports = class SplitChunksPlugin { let isReused = false; if (item.cacheGroup.reuseExistingChunk) { outer: for (const chunk of item.chunks) { - if (chunk.getNumberOfModules() !== item.modules.size) continue; + if ( + chunkGraph.getNumberOfChunkModules(chunk) !== + item.modules.size + ) + continue; if (chunk.hasEntryModule()) continue; for (const module of item.modules) { - if (!chunk.containsModule(module)) continue outer; + if (!chunkGraph.isModuleInChunk(module, chunk)) + continue outer; } if (!newChunk || !newChunk.name) { newChunk = chunk; @@ -730,17 +761,17 @@ module.exports = class SplitChunksPlugin { for (const module of item.modules) { if (!module.chunkCondition(newChunk)) continue; // Add module to new chunk - connectChunkAndModule(newChunk, module); + chunkGraph.connectChunkAndModule(newChunk, module); // Remove module from used chunks for (const chunk of usedChunks) { - chunk.removeModule(module); + chunkGraph.disconnectChunkAndModule(chunk, module); } } } else { // Remove all modules from used chunks for (const module of item.modules) { for (const chunk of usedChunks) { - chunk.removeModule(module); + chunkGraph.disconnectChunkAndModule(chunk, module); } } } @@ -789,7 +820,7 @@ module.exports = class SplitChunksPlugin { const results = deterministicGroupingForModules({ maxSize, minSize, - items: chunk.modulesIterable, + items: chunkGraph.getChunkModulesIterable(chunk), getKey(module) { const ident = contextify( compilation.options.context, @@ -837,9 +868,9 @@ module.exports = class SplitChunksPlugin { for (const module of group.items) { if (!module.chunkCondition(newPart)) continue; // Add module to new chunk - connectChunkAndModule(newPart, module); + chunkGraph.connectChunkAndModule(newPart, module); // Remove module from used chunks - chunk.removeModule(module); + chunkGraph.disconnectChunkAndModule(chunk, module); } } else { // change the chunk to be a part diff --git a/lib/performance/SizeLimitsPlugin.js b/lib/performance/SizeLimitsPlugin.js index 0ac247fde..ffa91191a 100644 --- a/lib/performance/SizeLimitsPlugin.js +++ b/lib/performance/SizeLimitsPlugin.js @@ -9,6 +9,8 @@ const AssetsOverSizeLimitWarning = require("./AssetsOverSizeLimitWarning"); const EntrypointsOverSizeLimitWarning = require("./EntrypointsOverSizeLimitWarning"); const NoAsyncChunksWarning = require("./NoAsyncChunksWarning"); +const isOverSizeLimitSet = new WeakSet(); + module.exports = class SizeLimitsPlugin { constructor(options) { this.hints = options.hints; @@ -16,6 +18,11 @@ module.exports = class SizeLimitsPlugin { this.maxEntrypointSize = options.maxEntrypointSize; this.assetFilter = options.assetFilter; } + + static isOverSizeLimit(thing) { + return isOverSizeLimitSet.has(thing); + } + apply(compiler) { const entrypointSizeLimit = this.maxEntrypointSize; const assetSizeLimit = this.maxAssetSize; @@ -47,7 +54,7 @@ module.exports = class SizeLimitsPlugin { name: assetName, size: size }); - asset.isOverSizeLimit = true; + isOverSizeLimitSet.add(asset); } } @@ -63,7 +70,7 @@ module.exports = class SizeLimitsPlugin { size: size, files: entry.getFiles().filter(assetFilter) }); - entry.isOverSizeLimit = true; + isOverSizeLimitSet.add(entry); } } diff --git a/lib/util/comparators.js b/lib/util/comparators.js new file mode 100644 index 000000000..b0e10098b --- /dev/null +++ b/lib/util/comparators.js @@ -0,0 +1,75 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Module")} Module */ + +/** + * @param {Chunk} a chunk + * @param {Chunk} b chunk + * @returns {-1|0|1} compare result + */ +exports.compareChunksById = (a, b) => { + return compareIds(a.id, b.id); +}; + +/** + * @param {Module} a module + * @param {Module} b module + * @returns {-1|0|1} compare result + */ +exports.compareModulesById = (a, b) => { + return compareIds(a.id, b.id); +}; + +/** + * @param {number} a number + * @param {number} b number + * @returns {-1|0|1} compare result + */ +const compareNumbers = (a, b) => { + if (typeof a !== typeof b) { + return typeof a < typeof b ? -1 : 1; + } + if (a < b) return -1; + if (a > b) return 1; + return 0; +}; + +/** + * @param {Module} a module + * @param {Module} b module + * @returns {-1|0|1} compare result + */ +exports.compareModulesByIndex = (a, b) => { + return compareNumbers(a.index, b.index); +}; + +/** + * @param {Module} a module + * @param {Module} b module + * @returns {-1|0|1} compare result + */ +exports.compareModulesByIndex2 = (a, b) => { + return compareNumbers(a.index2, b.index2); +}; + +/** + * @param {string|number} a first id + * @param {string|number} b second id + * @returns {-1|0|1} compare result + */ +const compareIds = (a, b) => { + if (typeof a !== typeof b) { + return typeof a < typeof b ? -1 : 1; + } + if (a < b) return -1; + if (a > b) return 1; + return 0; +}; + +exports.compareIds = compareIds; diff --git a/lib/wasm/WasmMainTemplatePlugin.js b/lib/wasm/WasmMainTemplatePlugin.js index 7950727ea..ce85776b7 100644 --- a/lib/wasm/WasmMainTemplatePlugin.js +++ b/lib/wasm/WasmMainTemplatePlugin.js @@ -6,18 +6,24 @@ "use strict"; const Template = require("../Template"); +const { compareModulesById } = require("../util/comparators"); const WebAssemblyUtils = require("./WebAssemblyUtils"); +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation")} Compilation */ /** @typedef {import("../MainTemplate")} MainTemplate */ /** @typedef {import("../Module")} Module */ /** @typedef {import("../ModuleGraph")} ModuleGraph */ // Get all wasm modules -const getAllWasmModules = chunk => { +const getAllWasmModules = (chunkGraph, chunk) => { const wasmModules = chunk.getAllAsyncChunks(); const array = []; for (const chunk of wasmModules) { - for (const m of chunk.modulesIterable) { + for (const m of chunkGraph.getOrderedChunkModulesIterable( + chunk, + compareModulesById + )) { if (m.type.startsWith("webassembly")) { array.push(m); } @@ -161,17 +167,17 @@ const generateImportObject = (moduleGraph, module, mangle) => { class WasmMainTemplatePlugin { /** - * @param {ModuleGraph} moduleGraph the module graph + * @param {Compilation} compilation the compilation * @param {Object} options options * @param {function(string): string} options.generateLoadBinaryCode function to generate the load code * @param {boolean=} options.supportsStreaming whether the generateLoadBinaryCode function supports streaming compilation * @param {boolean=} options.mangleImports whether imports should be mangled */ constructor( - moduleGraph, + compilation, { generateLoadBinaryCode, supportsStreaming, mangleImports } ) { - this.moduleGraph = moduleGraph; + this.compilation = compilation; this.generateLoadBinaryCode = generateLoadBinaryCode; this.supportsStreaming = supportsStreaming; this.mangleImports = mangleImports; @@ -185,11 +191,14 @@ class WasmMainTemplatePlugin { mainTemplate.hooks.localVars.tap( "WasmMainTemplatePlugin", (source, chunk) => { - const wasmModules = getAllWasmModules(chunk); + const wasmModules = getAllWasmModules( + this.compilation.chunkGraph, + chunk + ); if (wasmModules.length === 0) return source; const importObjects = wasmModules.map(module => { return generateImportObject( - this.moduleGraph, + this.compilation.moduleGraph, module, this.mangleImports ); @@ -218,8 +227,9 @@ class WasmMainTemplatePlugin { const webassemblyModuleFilename = mainTemplate.outputOptions.webassemblyModuleFilename; - const chunkModuleMaps = chunk.getChunkModuleMaps(m => - m.type.startsWith("webassembly") + const chunkModuleMaps = this.compilation.chunkGraph.getChunkModuleMaps( + chunk, + m => m.type.startsWith("webassembly") ); if (Object.keys(chunkModuleMaps.id).length === 0) return source; const wasmModuleSrcPath = mainTemplate.getAssetPath( @@ -342,7 +352,12 @@ class WasmMainTemplatePlugin { mainTemplate.hooks.requireExtensions.tap( "WasmMainTemplatePlugin", (source, chunk) => { - if (!chunk.hasModuleInGraph(m => m.type.startsWith("webassembly"))) { + const chunkGraph = this.compilation.chunkGraph; + if ( + !chunkGraph.hasModuleInGraph(chunk, m => + m.type.startsWith("webassembly") + ) + ) { return source; } return Template.asString([ @@ -362,11 +377,15 @@ class WasmMainTemplatePlugin { mainTemplate.hooks.hashForChunk.tap( "WasmMainTemplatePlugin", (hash, chunk) => { - const chunkModuleMaps = chunk.getChunkModuleMaps(m => - m.type.startsWith("webassembly") + const chunkModuleMaps = this.compilation.chunkGraph.getChunkModuleMaps( + chunk, + m => m.type.startsWith("webassembly") ); hash.update(JSON.stringify(chunkModuleMaps.id)); - const wasmModules = getAllWasmModules(chunk); + const wasmModules = getAllWasmModules( + this.compilation.chunkGraph, + chunk + ); for (const module of wasmModules) { hash.update(module.hash); } diff --git a/lib/wasm/WebAssemblyInInitialChunkError.js b/lib/wasm/WebAssemblyInInitialChunkError.js index 20a3fbb28..98d6f3eb8 100644 --- a/lib/wasm/WebAssemblyInInitialChunkError.js +++ b/lib/wasm/WebAssemblyInInitialChunkError.js @@ -6,6 +6,7 @@ const WebpackError = require("../WebpackError"); +/** @typedef {import("../ChunkGraph")} ChunkGraph */ /** @typedef {import("../Module")} Module */ /** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../RequestShortener")} RequestShortener */ @@ -13,10 +14,16 @@ const WebpackError = require("../WebpackError"); /** * @param {Module} module module to get chains from * @param {ModuleGraph} moduleGraph the module graph + * @param {ChunkGraph} chunkGraph the chunk graph * @param {RequestShortener} requestShortener to make readable identifiers * @returns {string[]} all chains to the module */ -const getInitialModuleChains = (module, moduleGraph, requestShortener) => { +const getInitialModuleChains = ( + module, + moduleGraph, + chunkGraph, + requestShortener +) => { const queue = [ { head: module, message: module.readableIdentifier(requestShortener) } ]; @@ -35,7 +42,8 @@ const getInitialModuleChains = (module, moduleGraph, requestShortener) => { for (const connection of moduleGraph.getIncomingConnections(head)) { const newHead = connection.originModule; if (newHead) { - if (!newHead.getChunks().some(c => c.canBeInitial())) continue; + if (!chunkGraph.getModuleChunks(newHead).some(c => c.canBeInitial())) + continue; final = false; if (alreadyReferencedModules.has(newHead)) continue; alreadyReferencedModules.add(newHead); @@ -75,12 +83,14 @@ module.exports = class WebAssemblyInInitialChunkError extends WebpackError { /** * @param {Module} module WASM module * @param {ModuleGraph} moduleGraph the module graph + * @param {ChunkGraph} chunkGraph the chunk graph * @param {RequestShortener} requestShortener request shortener */ - constructor(module, moduleGraph, requestShortener) { + constructor(module, moduleGraph, chunkGraph, requestShortener) { const moduleChains = getInitialModuleChains( module, moduleGraph, + chunkGraph, requestShortener ); const message = `WebAssembly module is included in initial chunk. diff --git a/lib/wasm/WebAssemblyModulesPlugin.js b/lib/wasm/WebAssemblyModulesPlugin.js index cb79ad362..841c639b4 100644 --- a/lib/wasm/WebAssemblyModulesPlugin.js +++ b/lib/wasm/WebAssemblyModulesPlugin.js @@ -9,6 +9,7 @@ const Generator = require("../Generator"); const JavascriptModulesPlugin = require("../JavascriptModulesPlugin"); const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); +const { compareModulesById } = require("../util/comparators"); const WebAssemblyGenerator = require("./WebAssemblyGenerator"); const WebAssemblyInInitialChunkError = require("./WebAssemblyInInitialChunkError"); const WebAssemblyJavascriptGenerator = require("./WebAssemblyJavascriptGenerator"); @@ -66,12 +67,16 @@ class WebAssemblyModulesPlugin { compilation.chunkTemplate.hooks.renderManifest.tap( "WebAssemblyModulesPlugin", (result, options) => { + const chunkGraph = compilation.chunkGraph; const chunk = options.chunk; const outputOptions = options.outputOptions; const moduleTemplates = options.moduleTemplates; const dependencyTemplates = options.dependencyTemplates; - for (const module of chunk.modulesIterable) { + for (const module of chunkGraph.getOrderedChunkModulesIterable( + chunk, + compareModulesById + )) { if (module.type && module.type.startsWith("webassembly")) { const filenameTemplate = outputOptions.webassemblyModuleFilename; @@ -85,7 +90,8 @@ class WebAssemblyModulesPlugin { chunk, dependencyTemplates, runtimeTemplate: compilation.runtimeTemplate, - moduleGraph: compilation.moduleGraph + moduleGraph: compilation.moduleGraph, + chunkGraph: compilation.chunkGraph } ), filenameTemplate, @@ -103,10 +109,11 @@ class WebAssemblyModulesPlugin { ); compilation.hooks.afterChunks.tap("WebAssemblyModulesPlugin", () => { + const chunkGraph = compilation.chunkGraph; const initialWasmModules = new Set(); for (const chunk of compilation.chunks) { if (chunk.canBeInitial()) { - for (const module of chunk.modulesIterable) { + for (const module of chunkGraph.getChunkModulesIterable(chunk)) { if (module.type.startsWith("webassembly")) { initialWasmModules.add(module); } @@ -118,6 +125,7 @@ class WebAssemblyModulesPlugin { new WebAssemblyInInitialChunkError( module, compilation.moduleGraph, + compilation.chunkGraph, compilation.requestShortener ) ); diff --git a/lib/web/FetchCompileWasmTemplatePlugin.js b/lib/web/FetchCompileWasmTemplatePlugin.js index 2d00be4de..f1addd5e2 100644 --- a/lib/web/FetchCompileWasmTemplatePlugin.js +++ b/lib/web/FetchCompileWasmTemplatePlugin.js @@ -21,7 +21,7 @@ class FetchCompileWasmTemplatePlugin { `fetch(${mainTemplate.requireFn}.p + ${path})`; const plugin = new WasmMainTemplatePlugin( - compilation.moduleGraph, + compilation, Object.assign( { generateLoadBinaryCode, diff --git a/lib/web/JsonpChunkTemplatePlugin.js b/lib/web/JsonpChunkTemplatePlugin.js index 76493d259..00de4308a 100644 --- a/lib/web/JsonpChunkTemplatePlugin.js +++ b/lib/web/JsonpChunkTemplatePlugin.js @@ -8,6 +8,7 @@ const { ConcatSource } = require("webpack-sources"); /** @typedef {import("../ChunkTemplate")} ChunkTemplate */ +/** @typedef {import("../Compilation")} Compilation */ const getEntryInfo = chunk => { return [chunk.entryModule].filter(Boolean).map(m => @@ -20,6 +21,13 @@ const getEntryInfo = chunk => { }; class JsonpChunkTemplatePlugin { + /** + * @param {Compilation} compilation the compilation + */ + constructor(compilation) { + this.compilation = compilation; + } + /** * @param {ChunkTemplate} chunkTemplate the chunk template * @returns {void} @@ -27,11 +35,11 @@ class JsonpChunkTemplatePlugin { apply(chunkTemplate) { chunkTemplate.hooks.render.tap( "JsonpChunkTemplatePlugin", - (modules, moduleTemplate, { chunk }) => { + (modules, moduleTemplate, { chunk, chunkGraph }) => { const jsonpFunction = chunkTemplate.outputOptions.jsonpFunction; const globalObject = chunkTemplate.outputOptions.globalObject; const source = new ConcatSource(); - const prefetchChunks = chunk.getChildIdsByOrders().prefetch; + const prefetchChunks = chunk.getChildIdsByOrders(chunkGraph).prefetch; source.add( `(${globalObject}[${JSON.stringify( jsonpFunction @@ -63,8 +71,11 @@ class JsonpChunkTemplatePlugin { chunkTemplate.hooks.hashForChunk.tap( "JsonpChunkTemplatePlugin", (hash, chunk) => { + const chunkGraph = this.compilation.chunkGraph; hash.update(JSON.stringify(getEntryInfo(chunk))); - hash.update(JSON.stringify(chunk.getChildIdsByOrders().prefetch) || ""); + hash.update( + JSON.stringify(chunk.getChildIdsByOrders(chunkGraph).prefetch) || "" + ); } ); } diff --git a/lib/web/JsonpMainTemplatePlugin.js b/lib/web/JsonpMainTemplatePlugin.js index acd0e328c..91c76750e 100644 --- a/lib/web/JsonpMainTemplatePlugin.js +++ b/lib/web/JsonpMainTemplatePlugin.js @@ -9,9 +9,18 @@ const { SyncWaterfallHook } = require("tapable"); const MainTemplate = require("../MainTemplate"); const Template = require("../Template"); +/** @typedef {import("../Compilation")} Compilation */ + const mainTemplateHooksMap = new WeakMap(); class JsonpMainTemplatePlugin { + /** + * @param {Compilation} compilation the compilation + */ + constructor(compilation) { + this.compilation = compilation; + } + static getHooks(mainTemplate) { if (!(mainTemplate instanceof MainTemplate)) { throw new TypeError( @@ -51,7 +60,10 @@ class JsonpMainTemplatePlugin { return false; }; const needPrefetchingCode = chunk => { - const allPrefetchChunks = chunk.getChildIdsByOrdersMap(true).prefetch; + const allPrefetchChunks = chunk.getChildIdsByOrdersMap( + this.compilation.chunkGraph, + true + ).prefetch; return allPrefetchChunks && Object.keys(allPrefetchChunks).length; }; @@ -311,7 +323,9 @@ class JsonpMainTemplatePlugin { stage: 10 }, (source, chunk, hash) => { - const chunkMap = chunk.getChildIdsByOrdersMap().preload; + const chunkMap = chunk.getChildIdsByOrdersMap( + this.compilation.chunkGraph + ).preload; if (!chunkMap || Object.keys(chunkMap).length === 0) return source; return Template.asString([ source, @@ -492,7 +506,8 @@ class JsonpMainTemplatePlugin { mainTemplate.hooks.beforeStartup.tap( "JsonpMainTemplatePlugin", (source, chunk, hash) => { - const prefetchChunks = chunk.getChildIdsByOrders().prefetch; + const chunkGraph = this.compilation.chunkGraph; + const prefetchChunks = chunk.getChildIdsByOrders(chunkGraph).prefetch; if ( needChunkLoadingCode(chunk) && prefetchChunks && diff --git a/lib/web/JsonpTemplatePlugin.js b/lib/web/JsonpTemplatePlugin.js index d0da932c3..aff55bcea 100644 --- a/lib/web/JsonpTemplatePlugin.js +++ b/lib/web/JsonpTemplatePlugin.js @@ -12,8 +12,10 @@ const JsonpMainTemplatePlugin = require("./JsonpMainTemplatePlugin"); class JsonpTemplatePlugin { apply(compiler) { compiler.hooks.thisCompilation.tap("JsonpTemplatePlugin", compilation => { - new JsonpMainTemplatePlugin().apply(compilation.mainTemplate); - new JsonpChunkTemplatePlugin().apply(compilation.chunkTemplate); + new JsonpMainTemplatePlugin(compilation).apply(compilation.mainTemplate); + new JsonpChunkTemplatePlugin(compilation).apply( + compilation.chunkTemplate + ); new JsonpHotUpdateChunkTemplatePlugin().apply( compilation.hotUpdateChunkTemplate ); diff --git a/lib/webpack.js b/lib/webpack.js index f0b49276f..4781b784f 100644 --- a/lib/webpack.js +++ b/lib/webpack.js @@ -162,5 +162,6 @@ exportPlugins((exports.debug = {}), { ProfilingPlugin: () => require("./debug/ProfilingPlugin") }); exportPlugins((exports.util = {}), { - createHash: () => require("./util/createHash") + createHash: () => require("./util/createHash"), + comparators: () => require("./util/comparators") }); diff --git a/test/Chunk.unittest.js b/test/Chunk.unittest.js index af34250c6..3f47c8e52 100644 --- a/test/Chunk.unittest.js +++ b/test/Chunk.unittest.js @@ -14,10 +14,6 @@ describe("Chunk", () => { expect(ChunkInstance.debugId).toBeGreaterThan(999); }); - it("returns a string with modules information", () => { - expect(ChunkInstance.toString()).toBe("Chunk[]"); - }); - it("should not be the initial instance", () => { expect(ChunkInstance.canBeInitial()).toBe(false); }); @@ -27,62 +23,4 @@ describe("Chunk", () => { expect(ChunkInstance.hasRuntime()).toBe(false); }); }); - - describe("isEmpty", () => { - it("should NOT have any module by default", () => { - expect(ChunkInstance.isEmpty()).toBe(true); - }); - }); - - describe("size", () => { - it("should NOT have any module by default", () => { - expect( - ChunkInstance.size({ - chunkOverhead: 10, - entryChunkMultiplicator: 2 - }) - ).toBe(10); - }); - }); - - describe("removeModule", () => { - let module; - let removeChunkSpy; - - beforeEach(() => { - removeChunkSpy = jest.fn(); - module = { - removeChunk: removeChunkSpy - }; - }); - - describe("and the chunk does not contain this module", () => { - it("returns false", () => { - expect(ChunkInstance.removeModule(module)).toBe(false); - }); - }); - - describe("and the chunk does contain this module", () => { - beforeEach(() => { - ChunkInstance._modules = new Set([module]); - }); - - it("calls module.removeChunk with itself and returns true", () => { - expect(ChunkInstance.removeModule(module)).toBe(true); - - expect(removeChunkSpy.mock.calls.length).toBe(1); - expect(removeChunkSpy.mock.calls[0][0]).toBe(ChunkInstance); - }); - }); - - describe("getNumberOfGroups", () => { - beforeEach(() => { - ChunkInstance._groups = new Set(); - }); - - it("should return the number of chunk groups contained by the chunk", () => { - expect(ChunkInstance.getNumberOfGroups()).toBe(0); - }); - }); - }); }); diff --git a/test/__snapshots__/StatsTestCases.test.js.snap b/test/__snapshots__/StatsTestCases.test.js.snap index b70fa1e55..c4b4cbb48 100644 --- a/test/__snapshots__/StatsTestCases.test.js.snap +++ b/test/__snapshots__/StatsTestCases.test.js.snap @@ -1,63 +1,63 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`StatsTestCases should print correct stats for aggressive-splitting-entry 1`] = ` -"Hash: 13158616d9265e218cf913158616d9265e218cf9 +"Hash: 44a278e3b0741b9b5da644a278e3b0741b9b5da6 Child fitting: - Hash: 13158616d9265e218cf9 + Hash: 44a278e3b0741b9b5da6 Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names d4b551c6319035df2898.js 1.05 KiB 0 [emitted] - 138d0972019f89a65bcf.js 1.94 KiB 1 [emitted] - 0f93dbc57653279c9b6c.js 1.94 KiB 2 [emitted] - 5c03d474bf8ab9d8029d.js 11.1 KiB 3 [emitted] - Entrypoint main = 138d0972019f89a65bcf.js 0f93dbc57653279c9b6c.js 5c03d474bf8ab9d8029d.js + 7029d2a129d88406bdea.js 1.94 KiB 1 [emitted] + 9d3d08d313ade4d2c47a.js 11.1 KiB 2 [emitted] + 5d5aae6ca1af5d6e6ea4.js 1.94 KiB 3 [emitted] + Entrypoint main = 5d5aae6ca1af5d6e6ea4.js 7029d2a129d88406bdea.js 9d3d08d313ade4d2c47a.js chunk {0} d4b551c6319035df2898.js 916 bytes <{1}> <{2}> <{3}> > ./g [4] ./index.js 7:0-13 [7] ./g.js 916 bytes {0} [built] - chunk {1} 138d0972019f89a65bcf.js 1.76 KiB ={2}= ={3}= >{0}< [initial] [rendered] [recorded] aggressive splitted + chunk {1} 7029d2a129d88406bdea.js 1.76 KiB ={2}= ={3}= >{0}< [initial] [rendered] [recorded] aggressive splitted > ./index main - [0] ./b.js 899 bytes {1} [built] - [5] ./a.js 899 bytes {1} [built] - chunk {2} 0f93dbc57653279c9b6c.js 1.76 KiB ={1}= ={3}= >{0}< [initial] [rendered] [recorded] aggressive splitted + [1] ./c.js 899 bytes {1} [built] + [2] ./d.js 899 bytes {1} [built] + chunk {2} 9d3d08d313ade4d2c47a.js 1.87 KiB ={1}= ={3}= >{0}< [entry] [rendered] > ./index main - [1] ./c.js 899 bytes {2} [built] - [2] ./d.js 899 bytes {2} [built] - chunk {3} 5c03d474bf8ab9d8029d.js 1.87 KiB ={1}= ={2}= >{0}< [entry] [rendered] + [3] ./e.js 899 bytes {2} [built] + [4] ./index.js 111 bytes {2} [built] + [6] ./f.js 900 bytes {2} [built] + chunk {3} 5d5aae6ca1af5d6e6ea4.js 1.76 KiB ={1}= ={2}= >{0}< [initial] [rendered] [recorded] aggressive splitted > ./index main - [3] ./e.js 899 bytes {3} [built] - [4] ./index.js 111 bytes {3} [built] - [6] ./f.js 900 bytes {3} [built] + [0] ./b.js 899 bytes {3} [built] + [5] ./a.js 899 bytes {3} [built] Child content-change: - Hash: 13158616d9265e218cf9 + Hash: 44a278e3b0741b9b5da6 Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names d4b551c6319035df2898.js 1.05 KiB 0 [emitted] - 138d0972019f89a65bcf.js 1.94 KiB 1 [emitted] - 0f93dbc57653279c9b6c.js 1.94 KiB 2 [emitted] - 5c03d474bf8ab9d8029d.js 11.1 KiB 3 [emitted] - Entrypoint main = 138d0972019f89a65bcf.js 0f93dbc57653279c9b6c.js 5c03d474bf8ab9d8029d.js + 7029d2a129d88406bdea.js 1.94 KiB 1 [emitted] + 9d3d08d313ade4d2c47a.js 11.1 KiB 2 [emitted] + 5d5aae6ca1af5d6e6ea4.js 1.94 KiB 3 [emitted] + Entrypoint main = 5d5aae6ca1af5d6e6ea4.js 7029d2a129d88406bdea.js 9d3d08d313ade4d2c47a.js chunk {0} d4b551c6319035df2898.js 916 bytes <{1}> <{2}> <{3}> > ./g [4] ./index.js 7:0-13 [7] ./g.js 916 bytes {0} [built] - chunk {1} 138d0972019f89a65bcf.js 1.76 KiB ={2}= ={3}= >{0}< [initial] [rendered] [recorded] aggressive splitted + chunk {1} 7029d2a129d88406bdea.js 1.76 KiB ={2}= ={3}= >{0}< [initial] [rendered] [recorded] aggressive splitted > ./index main - [0] ./b.js 899 bytes {1} [built] - [5] ./a.js 899 bytes {1} [built] - chunk {2} 0f93dbc57653279c9b6c.js 1.76 KiB ={1}= ={3}= >{0}< [initial] [rendered] [recorded] aggressive splitted + [1] ./c.js 899 bytes {1} [built] + [2] ./d.js 899 bytes {1} [built] + chunk {2} 9d3d08d313ade4d2c47a.js 1.87 KiB ={1}= ={3}= >{0}< [entry] [rendered] > ./index main - [1] ./c.js 899 bytes {2} [built] - [2] ./d.js 899 bytes {2} [built] - chunk {3} 5c03d474bf8ab9d8029d.js 1.87 KiB ={1}= ={2}= >{0}< [entry] [rendered] + [3] ./e.js 899 bytes {2} [built] + [4] ./index.js 111 bytes {2} [built] + [6] ./f.js 900 bytes {2} [built] + chunk {3} 5d5aae6ca1af5d6e6ea4.js 1.76 KiB ={1}= ={2}= >{0}< [initial] [rendered] [recorded] aggressive splitted > ./index main - [3] ./e.js 899 bytes {3} [built] - [4] ./index.js 111 bytes {3} [built] - [6] ./f.js 900 bytes {3} [built]" + [0] ./b.js 899 bytes {3} [built] + [5] ./a.js 899 bytes {3} [built]" `; exports[`StatsTestCases should print correct stats for aggressive-splitting-on-demand 1`] = ` -"Hash: 974518ddadca5f73bda5 +"Hash: 02a058acce1bfbcd9896 Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names @@ -69,11 +69,11 @@ f0ef1f91cb22147f3f2c.js 1 KiB 4 [emitted] c99c160aba2d9a94e5d1.js 1.94 KiB 5 [emitted] 138d0972019f89a65bcf.js 1.94 KiB 1 [emitted] 6a8e74d82c35e3f013d2.js 1 KiB 7 [emitted] -5bc7f208cd99a83b4e33.js 1.94 KiB 8 [emitted] +ee043b525cd899e33ec0.js 1.94 KiB 8 [emitted] 01a8254701931adbf278.js 1.01 KiB 9 [emitted] -c083e218f1f3ce7f1e48.js 9.7 KiB 10 [emitted] main +57253719544987787b40.js 9.7 KiB 10 [emitted] main ba9fedb7aa0c69201639.js 1.94 KiB 11 [emitted] -Entrypoint main = c083e218f1f3ce7f1e48.js +Entrypoint main = 57253719544987787b40.js chunk {0} 2736cf9d79233cd0a9b6.js 1.76 KiB <{10}> ={2}= ={3}= ={4}= ={5}= ={7}= [recorded] aggressive splitted > ./b ./d ./e ./f ./g [11] ./index.js 5:0-44 > ./b ./d ./e ./f ./g ./h ./i ./j ./k [11] ./index.js 6:0-72 @@ -108,14 +108,14 @@ chunk {6} 58f368c01f66002b0eb3.js 1.76 KiB <{10}> ={2}= ={11}= chunk {7} 6a8e74d82c35e3f013d2.js 899 bytes <{10}> ={0}= ={2}= ={3}= ={5}= > ./b ./d ./e ./f ./g ./h ./i ./j ./k [11] ./index.js 6:0-72 [9] ./k.js 899 bytes {6} {7} [built] -chunk {8} 5bc7f208cd99a83b4e33.js 1.76 KiB <{10}> ={4}= [recorded] aggressive splitted +chunk {8} ee043b525cd899e33ec0.js 1.76 KiB <{10}> ={4}= [recorded] aggressive splitted > ./c ./d ./e [11] ./index.js 3:0-30 [1] ./d.js 899 bytes {0} {8} [built] [5] ./c.js 899 bytes {1} {8} [built] chunk {9} 01a8254701931adbf278.js 899 bytes <{10}> > ./a [11] ./index.js 1:0-16 [10] ./a.js 899 bytes {9} [built] -chunk {10} c083e218f1f3ce7f1e48.js (main) 248 bytes >{0}< >{1}< >{2}< >{3}< >{4}< >{5}< >{6}< >{7}< >{8}< >{9}< >{11}< [entry] [rendered] +chunk {10} 57253719544987787b40.js (main) 248 bytes >{0}< >{1}< >{2}< >{3}< >{4}< >{5}< >{6}< >{7}< >{8}< >{9}< >{11}< [entry] [rendered] > ./index main [11] ./index.js 248 bytes {10} [built] chunk {11} ba9fedb7aa0c69201639.js 1.76 KiB <{10}> ={2}= ={6}= [rendered] [recorded] aggressive splitted @@ -2228,10 +2228,10 @@ main.js 9.32 KiB 0 [emitted] main Entrypoint main = main.js [0] ./components/src/CompAB/CompA.js 89 bytes {0} [built] [only some exports used: default] - harmony side effect evaluation ./CompA ./components/src/CompAB/index.js 1:0-43 - harmony export imported specifier ./CompA ./components/src/CompAB/index.js 1:0-43 harmony import specifier ./components [2] ./foo.js 3:20-25 (skipped side-effect-free modules) harmony import specifier ./components [3] ./main.js + 1 modules 3:15-20 (skipped side-effect-free modules) + harmony side effect evaluation ./CompA ./components/src/CompAB/index.js 1:0-43 + harmony export imported specifier ./CompA ./components/src/CompAB/index.js 1:0-43 [1] ./components/src/CompAB/utils.js 97 bytes {0} [built] harmony side effect evaluation ./utils [0] ./components/src/CompAB/CompA.js 1:0-35 harmony import specifier ./utils [0] ./components/src/CompAB/CompA.js 5:5-12 diff --git a/test/configCases/chunk-index/order-multiple-entries/webpack.config.js b/test/configCases/chunk-index/order-multiple-entries/webpack.config.js index 29d229781..fbac66a06 100644 --- a/test/configCases/chunk-index/order-multiple-entries/webpack.config.js +++ b/test/configCases/chunk-index/order-multiple-entries/webpack.config.js @@ -23,7 +23,9 @@ module.exports = { const modules = new Map(); const modules2 = new Map(); for (const chunk of group.chunks) { - for (const module of chunk.modulesIterable) { + for (const module of compilation.chunkGraph.getChunkModulesIterable( + chunk + )) { modules.set(module, group.getModuleIndex(module)); modules2.set(module, group.getModuleIndex2(module)); } diff --git a/test/statsCases/named-chunks-plugin-async/webpack.config.js b/test/statsCases/named-chunks-plugin-async/webpack.config.js index bce34aa5c..b3d2dc442 100644 --- a/test/statsCases/named-chunks-plugin-async/webpack.config.js +++ b/test/statsCases/named-chunks-plugin-async/webpack.config.js @@ -2,6 +2,7 @@ const NamedChunksPlugin = require("../../../lib/NamedChunksPlugin"); const RequestShortener = require("../../../lib/RequestShortener"); +const { compareModulesById } = require("../../../lib/util/comparators"); module.exports = { mode: "production", @@ -10,17 +11,20 @@ module.exports = { entry: "./entry" }, plugins: [ - new NamedChunksPlugin(function(chunk) { + new NamedChunksPlugin(function(chunk, { chunkGraph }) { if (chunk.name) { return chunk.name; } const chunkModulesToName = chunk => - Array.from(chunk.modulesIterable, mod => { - const rs = new RequestShortener(mod.context); - return rs.shorten(mod.request).replace(/[./\\]/g, "_"); - }).join("-"); + Array.from( + chunkGraph.getOrderedChunkModulesIterable(chunk, compareModulesById), + mod => { + const rs = new RequestShortener(mod.context); + return rs.shorten(mod.request).replace(/[./\\]/g, "_"); + } + ).join("-"); - if (chunk.getNumberOfModules() > 0) { + if (chunkGraph.getNumberOfChunkModules(chunk) > 0) { return `chunk-containing-${chunkModulesToName(chunk)}`; } diff --git a/tooling/format-file-header.js b/tooling/format-file-header.js index 9809f5174..7495d0f30 100644 --- a/tooling/format-file-header.js +++ b/tooling/format-file-header.js @@ -97,12 +97,12 @@ const schema = [ }, { title: "type imports", - regexp: /(\/\*\* @typedef \{import\("[^"]+"\)(\.\w+)*\} \w+ \*\/\n)+\n/g, + regexp: /(\/\*\* (?:@template \w+ )*@typedef \{import\("[^"]+"\)(\.\w+)*(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)?\} \w+(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)? \*\/\n)+\n/g, updateMessage: "sort type imports alphabetically", update(content) { const items = execToArray( content, - /\/\*\* @typedef \{import\("([^"]+)"\)((?:\.\w+)*)\} \w+ \*\/\n/g + /\/\*\* (?:@template \w+ )*@typedef \{import\("([^"]+)"\)((?:\.\w+)*(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)?)\} \w+(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)? \*\/\n/g ); items.sort(sortImport); return items.map(item => item.content).join("") + "\n";