Merge pull request #7889 from webpack/refactor/chunk-graph

add ChunkGraph
This commit is contained in:
Tobias Koppers 2018-08-14 16:24:32 +02:00 committed by GitHub
commit 655d57f2d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 1256 additions and 929 deletions

View File

@ -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

View File

@ -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", {

View File

@ -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<T>} set the sortable set to convert to array
* @returns {Array<T>} the array returned from Array.from(set)
*/
const getArray = set => Array.from(set);
/**
* @param {SortableSet<Module>} 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<Module>} */
this._modules = new SortableSet(undefined, sortByIdentifier);
/** @type {string?} */
this.filenameTemplate = undefined;
/** @private @type {SortableSet<ChunkGroup>} */
@ -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<Module>} 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<Chunk>} a set of all the async chunks
*/
@ -570,9 +316,10 @@ class Chunk {
}
/**
* @param {ChunkGraph} chunkGraph the chunk graph
* @returns {Record<string, Set<TODO>[]>} 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<string|number, (string|number)[]>} id
* @property {Record<string|number, string>} hash
*/
/**
* @param {ModuleFilterPredicate} filterFn function used to filter modules
* @returns {ChunkModuleMaps} module map information
*/
getChunkModuleMaps(filterFn) {
/** @type {Record<string|number, (string|number)[]>} */
const chunkModuleIdMap = Object.create(null);
/** @type {Record<string|number, string>} */
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;

537
lib/ChunkGraph.js Normal file
View File

@ -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>) => T[]} SetToArrayFunction<T> */
/**
* @template T
* @param {SortableSet<T>} set the set
* @returns {T[]} set as array
*/
const getArray = set => {
return Array.from(set);
};
/** @type {WeakMap<Function, any>} */
const createOrderedArrayFunctionMap = new WeakMap();
/**
* @template T
* @param {function(T, T): -1|0|1} comparator comparator function
* @returns {SetToArrayFunction<T>} set as ordered array
*/
const createOrderedArrayFunction = comparator => {
/** @type {SetToArrayFunction<T>} */
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<Module>} 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<Chunk>} */
this.chunks = new SortableSet();
}
}
class ChunkGraphChunk {
constructor() {
/** @type {SortableSet<Module>} */
this.modules = new SortableSet();
}
}
class ChunkGraph {
constructor() {
/** @private @type {WeakMap<Module, ChunkGraphModule>} */
this._modules = new WeakMap();
/** @private @type {WeakMap<Chunk, ChunkGraphChunk>} */
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<Module>} 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<Chunk>} 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<Chunk>} 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<Module>} 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<Module>} 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<string|number, (string|number)[]>} id
* @property {Record<string|number, string>} hash
*/
/**
* @param {Chunk} chunk the chunk
* @param {ModuleFilterPredicate} filterFn function used to filter modules
* @returns {ChunkModuleMaps} module map information
*/
getChunkModuleMaps(chunk, filterFn) {
/** @type {Record<string|number, (string|number)[]>} */
const chunkModuleIdMap = Object.create(null);
/** @type {Record<string|number, string>} */
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;

View File

@ -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);
}

View File

@ -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<string, Entrypoint>} */
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<ChunkGroup, {block: AsyncDependenciesBlock, chunkGroup: ChunkGroup}[]>} */
const chunkDependencies = new Map();
/** @type {Set<ChunkGroup>} */
const allCreatedChunkGroups = new Set();
// PREPARE
@ -1525,6 +1531,8 @@ class Compilation {
}
}
const chunkGraph = this.chunkGraph;
// PART ONE
/** @type {Map<ChunkGroup, { index: number, index2: number }>} */
@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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<number|string>} */
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

View File

@ -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);
}

View File

@ -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;

View File

@ -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")<T>} SortableSet<T> */
/** @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<Chunk>} */
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<Chunk>} 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<Chunk>} 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}
*/

View File

@ -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(

View File

@ -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
});
}
}
});

View File

@ -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<string, ChunkGroup>} 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) {

View File

@ -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;

View File

@ -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) {

View File

@ -45,7 +45,7 @@ class ReadFileCompileWasmTemplatePlugin {
]);
const plugin = new WasmMainTemplatePlugin(
compilation.moduleGraph,
compilation,
Object.assign(
{
generateLoadBinaryCode,

View File

@ -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;
}
);
}

View File

@ -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 = [];

View File

@ -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);
});
}

View File

@ -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;

View File

@ -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();

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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++);

View File

@ -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);
}
}

View File

@ -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);
}
}
};

View File

@ -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
) {

View File

@ -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<Module>} DeterministicGroupingOptionsForModule */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../util/deterministicGrouping").GroupedItems<Module>} DeterministicGroupingGroupedItemsForModule */
/** @typedef {import("../util/deterministicGrouping").Options<Module>} 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<string, Set<Chunk>>} */
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

View File

@ -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);
}
}

75
lib/util/comparators.js Normal file
View File

@ -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;

View File

@ -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);
}

View File

@ -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.

View File

@ -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
)
);

View File

@ -21,7 +21,7 @@ class FetchCompileWasmTemplatePlugin {
`fetch(${mainTemplate.requireFn}.p + ${path})`;
const plugin = new WasmMainTemplatePlugin(
compilation.moduleGraph,
compilation,
Object.assign(
{
generateLoadBinaryCode,

View File

@ -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) || ""
);
}
);
}

View File

@ -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 &&

View File

@ -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
);

View File

@ -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")
});

View File

@ -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);
});
});
});
});

View File

@ -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

View File

@ -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));
}

View File

@ -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)}`;
}

View File

@ -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";