From bbff7403fbf46f9ad92240ac213df8d30ef31b64 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 20 Sep 2018 02:56:13 +0200 Subject: update packages --- node_modules/webpack/lib/Compilation.js | 3968 +++++++++++++++++++------------ 1 file changed, 2513 insertions(+), 1455 deletions(-) (limited to 'node_modules/webpack/lib/Compilation.js') diff --git a/node_modules/webpack/lib/Compilation.js b/node_modules/webpack/lib/Compilation.js index 05f5be60d..a420da696 100644 --- a/node_modules/webpack/lib/Compilation.js +++ b/node_modules/webpack/lib/Compilation.js @@ -1,1455 +1,2513 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra - */ -"use strict"; - -const asyncLib = require("async"); -const crypto = require("crypto"); -const Tapable = require("tapable"); -const EntryModuleNotFoundError = require("./EntryModuleNotFoundError"); -const ModuleNotFoundError = require("./ModuleNotFoundError"); -const ModuleDependencyWarning = require("./ModuleDependencyWarning"); -const ModuleDependencyError = require("./ModuleDependencyError"); -const Module = require("./Module"); -const Chunk = require("./Chunk"); -const Entrypoint = require("./Entrypoint"); -const MainTemplate = require("./MainTemplate"); -const ChunkTemplate = require("./ChunkTemplate"); -const HotUpdateChunkTemplate = require("./HotUpdateChunkTemplate"); -const ModuleTemplate = require("./ModuleTemplate"); -const Dependency = require("./Dependency"); -const ChunkRenderError = require("./ChunkRenderError"); -const AsyncDependencyToInitialChunkWarning = require("./AsyncDependencyToInitialChunkWarning"); -const CachedSource = require("webpack-sources").CachedSource; -const Stats = require("./Stats"); -const Semaphore = require("./util/Semaphore"); -const Queue = require("./util/Queue"); - -function byId(a, b) { - if(a.id < b.id) return -1; - if(a.id > b.id) return 1; - return 0; -} - -function iterationBlockVariable(variables, fn) { - for(let indexVariable = 0; indexVariable < variables.length; indexVariable++) { - let varDep = variables[indexVariable].dependencies; - for(let indexVDep = 0; indexVDep < varDep.length; indexVDep++) { - fn(varDep[indexVDep]); - } - } -} - -function iterationOfArrayCallback(arr, fn) { - for(let index = 0; index < arr.length; index++) { - fn(arr[index]); - } -} - -class Compilation extends Tapable { - constructor(compiler) { - super(); - this.compiler = compiler; - this.resolvers = compiler.resolvers; - this.inputFileSystem = compiler.inputFileSystem; - - const options = this.options = compiler.options; - this.outputOptions = options && options.output; - this.bail = options && options.bail; - this.profile = options && options.profile; - this.performance = options && options.performance; - - this.mainTemplate = new MainTemplate(this.outputOptions); - this.chunkTemplate = new ChunkTemplate(this.outputOptions); - this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(this.outputOptions); - this.moduleTemplate = new ModuleTemplate(this.outputOptions); - - this.semaphore = new Semaphore(options.parallelism || 100); - - this.entries = []; - this.preparedChunks = []; - this.entrypoints = {}; - this.chunks = []; - this.namedChunks = {}; - this.modules = []; - this._modules = {}; - this.cache = null; - this.records = null; - this.nextFreeModuleIndex = undefined; - this.nextFreeModuleIndex2 = undefined; - this.additionalChunkAssets = []; - this.assets = {}; - this.errors = []; - this.warnings = []; - this.children = []; - this.dependencyFactories = new Map(); - this.dependencyTemplates = new Map(); - this.dependencyTemplates.set("hash", ""); - this.childrenCounters = {}; - } - - getStats() { - return new Stats(this); - } - - templatesPlugin(name, fn) { - this.mainTemplate.plugin(name, fn); - this.chunkTemplate.plugin(name, fn); - } - - addModule(module, cacheGroup) { - const identifier = module.identifier(); - if(this._modules[identifier]) { - return false; - } - const cacheName = (cacheGroup || "m") + identifier; - if(this.cache && this.cache[cacheName]) { - const cacheModule = this.cache[cacheName]; - - let rebuild = true; - if(!cacheModule.error && cacheModule.cacheable && this.fileTimestamps && this.contextTimestamps) { - rebuild = cacheModule.needRebuild(this.fileTimestamps, this.contextTimestamps); - } - - if(!rebuild) { - cacheModule.disconnect(); - this._modules[identifier] = cacheModule; - this.modules.push(cacheModule); - cacheModule.errors.forEach(err => this.errors.push(err), this); - cacheModule.warnings.forEach(err => this.warnings.push(err), this); - return cacheModule; - } - } - module.unbuild(); - this._modules[identifier] = module; - if(this.cache) { - this.cache[cacheName] = module; - } - this.modules.push(module); - return true; - } - - getModule(module) { - const identifier = module.identifier(); - return this._modules[identifier]; - } - - findModule(identifier) { - return this._modules[identifier]; - } - - buildModule(module, optional, origin, dependencies, thisCallback) { - this.applyPlugins1("build-module", module); - if(module.building) return module.building.push(thisCallback); - const building = module.building = [thisCallback]; - - function callback(err) { - module.building = undefined; - building.forEach(cb => cb(err)); - } - module.build(this.options, this, this.resolvers.normal, this.inputFileSystem, (error) => { - const errors = module.errors; - for(let indexError = 0; indexError < errors.length; indexError++) { - const err = errors[indexError]; - err.origin = origin; - err.dependencies = dependencies; - if(optional) - this.warnings.push(err); - else - this.errors.push(err); - } - - const warnings = module.warnings; - for(let indexWarning = 0; indexWarning < warnings.length; indexWarning++) { - const war = warnings[indexWarning]; - war.origin = origin; - war.dependencies = dependencies; - this.warnings.push(war); - } - module.dependencies.sort(Dependency.compare); - if(error) { - this.applyPlugins2("failed-module", module, error); - return callback(error); - } - this.applyPlugins1("succeed-module", module); - return callback(); - }); - } - - processModuleDependencies(module, callback) { - const dependencies = []; - - function addDependency(dep) { - for(let i = 0; i < dependencies.length; i++) { - if(dep.isEqualResource(dependencies[i][0])) { - return dependencies[i].push(dep); - } - } - dependencies.push([dep]); - } - - function addDependenciesBlock(block) { - if(block.dependencies) { - iterationOfArrayCallback(block.dependencies, addDependency); - } - if(block.blocks) { - iterationOfArrayCallback(block.blocks, addDependenciesBlock); - } - if(block.variables) { - iterationBlockVariable(block.variables, addDependency); - } - } - addDependenciesBlock(module); - this.addModuleDependencies(module, dependencies, this.bail, null, true, callback); - } - - addModuleDependencies(module, dependencies, bail, cacheGroup, recursive, callback) { - let _this = this; - const start = _this.profile && Date.now(); - - const factories = []; - for(let i = 0; i < dependencies.length; i++) { - const factory = _this.dependencyFactories.get(dependencies[i][0].constructor); - if(!factory) { - return callback(new Error(`No module factory available for dependency type: ${dependencies[i][0].constructor.name}`)); - } - factories[i] = [factory, dependencies[i]]; - } - asyncLib.forEach(factories, function iteratorFactory(item, callback) { - const dependencies = item[1]; - - const errorAndCallback = function errorAndCallback(err) { - err.origin = module; - _this.errors.push(err); - if(bail) { - callback(err); - } else { - callback(); - } - }; - const warningAndCallback = function warningAndCallback(err) { - err.origin = module; - _this.warnings.push(err); - callback(); - }; - - const semaphore = _this.semaphore; - semaphore.acquire(() => { - if(_this === null) return semaphore.release(); - - const factory = item[0]; - factory.create({ - contextInfo: { - issuer: module.nameForCondition && module.nameForCondition(), - compiler: _this.compiler.name - }, - context: module.context, - dependencies: dependencies - }, function factoryCallback(err, dependentModule) { - if(_this === null) return semaphore.release(); - - let afterFactory; - - function isOptional() { - return dependencies.filter(d => !d.optional).length === 0; - } - - function errorOrWarningAndCallback(err) { - if(isOptional()) { - return warningAndCallback(err); - } else { - return errorAndCallback(err); - } - } - - function iterationDependencies(depend) { - for(let index = 0; index < depend.length; index++) { - const dep = depend[index]; - dep.module = dependentModule; - dependentModule.addReason(module, dep); - } - } - - if(err) { - semaphore.release(); - return errorOrWarningAndCallback(new ModuleNotFoundError(module, err, dependencies)); - } - if(!dependentModule) { - semaphore.release(); - return process.nextTick(callback); - } - if(_this.profile) { - if(!dependentModule.profile) { - dependentModule.profile = {}; - } - afterFactory = Date.now(); - dependentModule.profile.factory = afterFactory - start; - } - - dependentModule.issuer = module; - const newModule = _this.addModule(dependentModule, cacheGroup); - - if(!newModule) { // from cache - dependentModule = _this.getModule(dependentModule); - - if(dependentModule.optional) { - dependentModule.optional = isOptional(); - } - - iterationDependencies(dependencies); - - if(_this.profile) { - if(!module.profile) { - module.profile = {}; - } - const time = Date.now() - start; - if(!module.profile.dependencies || time > module.profile.dependencies) { - module.profile.dependencies = time; - } - } - - semaphore.release(); - return process.nextTick(callback); - } - - if(newModule instanceof Module) { - if(_this.profile) { - newModule.profile = dependentModule.profile; - } - - newModule.optional = isOptional(); - newModule.issuer = dependentModule.issuer; - dependentModule = newModule; - - iterationDependencies(dependencies); - - if(_this.profile) { - const afterBuilding = Date.now(); - module.profile.building = afterBuilding - afterFactory; - } - - semaphore.release(); - if(recursive) { - return process.nextTick(_this.processModuleDependencies.bind(_this, dependentModule, callback)); - } else { - return process.nextTick(callback); - } - } - - dependentModule.optional = isOptional(); - - iterationDependencies(dependencies); - - _this.buildModule(dependentModule, isOptional(), module, dependencies, err => { - if(_this === null) return semaphore.release(); - - if(err) { - semaphore.release(); - return errorOrWarningAndCallback(err); - } - - if(_this.profile) { - const afterBuilding = Date.now(); - dependentModule.profile.building = afterBuilding - afterFactory; - } - - semaphore.release(); - if(recursive) { - _this.processModuleDependencies(dependentModule, callback); - } else { - return callback(); - } - }); - - }); - }); - }, function finalCallbackAddModuleDependencies(err) { - // In V8, the Error objects keep a reference to the functions on the stack. These warnings & - // errors are created inside closures that keep a reference to the Compilation, so errors are - // leaking the Compilation object. Setting _this to null workarounds the following issue in V8. - // https://bugs.chromium.org/p/chromium/issues/detail?id=612191 - _this = null; - - if(err) { - return callback(err); - } - - return process.nextTick(callback); - }); - } - - _addModuleChain(context, dependency, onModule, callback) { - const start = this.profile && Date.now(); - - const errorAndCallback = this.bail ? (err) => { - callback(err); - } : (err) => { - err.dependencies = [dependency]; - this.errors.push(err); - callback(); - }; - - if(typeof dependency !== "object" || dependency === null || !dependency.constructor) { - throw new Error("Parameter 'dependency' must be a Dependency"); - } - - const moduleFactory = this.dependencyFactories.get(dependency.constructor); - if(!moduleFactory) { - throw new Error(`No dependency factory available for this dependency type: ${dependency.constructor.name}`); - } - - this.semaphore.acquire(() => { - moduleFactory.create({ - contextInfo: { - issuer: "", - compiler: this.compiler.name - }, - context: context, - dependencies: [dependency] - }, (err, module) => { - if(err) { - this.semaphore.release(); - return errorAndCallback(new EntryModuleNotFoundError(err)); - } - - let afterFactory; - - if(this.profile) { - if(!module.profile) { - module.profile = {}; - } - afterFactory = Date.now(); - module.profile.factory = afterFactory - start; - } - - const result = this.addModule(module); - if(!result) { - module = this.getModule(module); - - onModule(module); - - if(this.profile) { - const afterBuilding = Date.now(); - module.profile.building = afterBuilding - afterFactory; - } - - this.semaphore.release(); - return callback(null, module); - } - - if(result instanceof Module) { - if(this.profile) { - result.profile = module.profile; - } - - module = result; - - onModule(module); - - moduleReady.call(this); - return; - } - - onModule(module); - - this.buildModule(module, false, null, null, (err) => { - if(err) { - this.semaphore.release(); - return errorAndCallback(err); - } - - if(this.profile) { - const afterBuilding = Date.now(); - module.profile.building = afterBuilding - afterFactory; - } - - moduleReady.call(this); - }); - - function moduleReady() { - this.semaphore.release(); - this.processModuleDependencies(module, err => { - if(err) { - return callback(err); - } - - return callback(null, module); - }); - } - }); - }); - } - - addEntry(context, entry, name, callback) { - const slot = { - name: name, - module: null - }; - this.preparedChunks.push(slot); - this._addModuleChain(context, entry, (module) => { - - entry.module = module; - this.entries.push(module); - module.issuer = null; - - }, (err, module) => { - if(err) { - return callback(err); - } - - if(module) { - slot.module = module; - } else { - const idx = this.preparedChunks.indexOf(slot); - this.preparedChunks.splice(idx, 1); - } - return callback(null, module); - }); - } - - prefetch(context, dependency, callback) { - this._addModuleChain(context, dependency, module => { - - module.prefetched = true; - module.issuer = null; - - }, callback); - } - - rebuildModule(module, thisCallback) { - if(module.variables.length || module.blocks.length) - throw new Error("Cannot rebuild a complex module with variables or blocks"); - if(module.rebuilding) { - return module.rebuilding.push(thisCallback); - } - const rebuilding = module.rebuilding = [thisCallback]; - - function callback(err) { - module.rebuilding = undefined; - rebuilding.forEach(cb => cb(err)); - } - const deps = module.dependencies.slice(); - this.buildModule(module, false, module, null, (err) => { - if(err) return callback(err); - - this.processModuleDependencies(module, (err) => { - if(err) return callback(err); - deps.forEach(d => { - if(d.module && d.module.removeReason(module, d)) { - module.forEachChunk(chunk => { - if(!d.module.hasReasonForChunk(chunk)) { - if(d.module.removeChunk(chunk)) { - this.removeChunkFromDependencies(d.module, chunk); - } - } - }); - } - }); - callback(); - }); - - }); - } - - finish() { - const modules = this.modules; - this.applyPlugins1("finish-modules", modules); - - for(let index = 0; index < modules.length; index++) { - const module = modules[index]; - this.reportDependencyErrorsAndWarnings(module, [module]); - } - } - - unseal() { - this.applyPlugins0("unseal"); - this.chunks.length = 0; - this.namedChunks = {}; - this.additionalChunkAssets.length = 0; - this.assets = {}; - this.modules.forEach(module => module.unseal()); - } - - seal(callback) { - const self = this; - self.applyPlugins0("seal"); - self.nextFreeModuleIndex = 0; - self.nextFreeModuleIndex2 = 0; - self.preparedChunks.forEach(preparedChunk => { - const module = preparedChunk.module; - const chunk = self.addChunk(preparedChunk.name, module); - const entrypoint = self.entrypoints[chunk.name] = new Entrypoint(chunk.name); - entrypoint.unshiftChunk(chunk); - - chunk.addModule(module); - module.addChunk(chunk); - chunk.entryModule = module; - self.assignIndex(module); - self.assignDepth(module); - }); - self.processDependenciesBlocksForChunks(self.chunks.slice()); - self.sortModules(self.modules); - self.applyPlugins0("optimize"); - - while(self.applyPluginsBailResult1("optimize-modules-basic", self.modules) || - self.applyPluginsBailResult1("optimize-modules", self.modules) || - self.applyPluginsBailResult1("optimize-modules-advanced", self.modules)) { /* empty */ } - self.applyPlugins1("after-optimize-modules", self.modules); - - while(self.applyPluginsBailResult1("optimize-chunks-basic", self.chunks) || - self.applyPluginsBailResult1("optimize-chunks", self.chunks) || - self.applyPluginsBailResult1("optimize-chunks-advanced", self.chunks)) { /* empty */ } - self.applyPlugins1("after-optimize-chunks", self.chunks); - - self.applyPluginsAsyncSeries("optimize-tree", self.chunks, self.modules, function sealPart2(err) { - if(err) { - return callback(err); - } - - self.applyPlugins2("after-optimize-tree", self.chunks, self.modules); - - while(self.applyPluginsBailResult("optimize-chunk-modules-basic", self.chunks, self.modules) || - self.applyPluginsBailResult("optimize-chunk-modules", self.chunks, self.modules) || - self.applyPluginsBailResult("optimize-chunk-modules-advanced", self.chunks, self.modules)) { /* empty */ } - self.applyPlugins2("after-optimize-chunk-modules", self.chunks, self.modules); - - const shouldRecord = self.applyPluginsBailResult("should-record") !== false; - - self.applyPlugins2("revive-modules", self.modules, self.records); - self.applyPlugins1("optimize-module-order", self.modules); - self.applyPlugins1("advanced-optimize-module-order", self.modules); - self.applyPlugins1("before-module-ids", self.modules); - self.applyPlugins1("module-ids", self.modules); - self.applyModuleIds(); - self.applyPlugins1("optimize-module-ids", self.modules); - self.applyPlugins1("after-optimize-module-ids", self.modules); - - self.sortItemsWithModuleIds(); - - self.applyPlugins2("revive-chunks", self.chunks, self.records); - self.applyPlugins1("optimize-chunk-order", self.chunks); - self.applyPlugins1("before-chunk-ids", self.chunks); - self.applyChunkIds(); - self.applyPlugins1("optimize-chunk-ids", self.chunks); - self.applyPlugins1("after-optimize-chunk-ids", self.chunks); - - self.sortItemsWithChunkIds(); - - if(shouldRecord) - self.applyPlugins2("record-modules", self.modules, self.records); - if(shouldRecord) - self.applyPlugins2("record-chunks", self.chunks, self.records); - - self.applyPlugins0("before-hash"); - self.createHash(); - self.applyPlugins0("after-hash"); - - if(shouldRecord) - self.applyPlugins1("record-hash", self.records); - - self.applyPlugins0("before-module-assets"); - self.createModuleAssets(); - if(self.applyPluginsBailResult("should-generate-chunk-assets") !== false) { - self.applyPlugins0("before-chunk-assets"); - self.createChunkAssets(); - } - self.applyPlugins1("additional-chunk-assets", self.chunks); - self.summarizeDependencies(); - if(shouldRecord) - self.applyPlugins2("record", self, self.records); - - self.applyPluginsAsync("additional-assets", err => { - if(err) { - return callback(err); - } - self.applyPluginsAsync("optimize-chunk-assets", self.chunks, err => { - if(err) { - return callback(err); - } - self.applyPlugins1("after-optimize-chunk-assets", self.chunks); - self.applyPluginsAsync("optimize-assets", self.assets, err => { - if(err) { - return callback(err); - } - self.applyPlugins1("after-optimize-assets", self.assets); - if(self.applyPluginsBailResult("need-additional-seal")) { - self.unseal(); - return self.seal(callback); - } - return self.applyPluginsAsync("after-seal", callback); - }); - }); - }); - }); - } - - sortModules(modules) { - modules.sort((a, b) => { - if(a.index < b.index) return -1; - if(a.index > b.index) return 1; - return 0; - }); - } - - reportDependencyErrorsAndWarnings(module, blocks) { - for(let indexBlock = 0; indexBlock < blocks.length; indexBlock++) { - const block = blocks[indexBlock]; - const dependencies = block.dependencies; - - for(let indexDep = 0; indexDep < dependencies.length; indexDep++) { - const d = dependencies[indexDep]; - - const warnings = d.getWarnings(); - if(warnings) { - for(let indexWar = 0; indexWar < warnings.length; indexWar++) { - const w = warnings[indexWar]; - - const warning = new ModuleDependencyWarning(module, w, d.loc); - this.warnings.push(warning); - } - } - const errors = d.getErrors(); - if(errors) { - for(let indexErr = 0; indexErr < errors.length; indexErr++) { - const e = errors[indexErr]; - - const error = new ModuleDependencyError(module, e, d.loc); - this.errors.push(error); - } - } - } - - this.reportDependencyErrorsAndWarnings(module, block.blocks); - } - } - - addChunk(name, module, loc) { - if(name) { - if(Object.prototype.hasOwnProperty.call(this.namedChunks, name)) { - const chunk = this.namedChunks[name]; - if(module) { - chunk.addOrigin(module, loc); - } - return chunk; - } - } - const chunk = new Chunk(name, module, loc); - this.chunks.push(chunk); - if(name) { - this.namedChunks[name] = chunk; - } - return chunk; - } - - assignIndex(module) { - const _this = this; - - const queue = [() => { - assignIndexToModule(module); - }]; - - const iteratorAllDependencies = d => { - queue.push(() => assignIndexToDependency(d)); - }; - - function assignIndexToModule(module) { - // enter module - if(typeof module.index !== "number") { - module.index = _this.nextFreeModuleIndex++; - - // leave module - queue.push(() => module.index2 = _this.nextFreeModuleIndex2++); - - // enter it as block - assignIndexToDependencyBlock(module); - } - } - - function assignIndexToDependency(dependency) { - if(dependency.module) { - queue.push(() => assignIndexToModule(dependency.module)); - } - } - - function assignIndexToDependencyBlock(block) { - let allDependencies = []; - - function iteratorDependency(d) { - allDependencies.push(d); - } - - function iteratorBlock(b) { - queue.push(() => assignIndexToDependencyBlock(b)); - } - - if(block.variables) { - iterationBlockVariable(block.variables, iteratorDependency); - } - - if(block.dependencies) { - iterationOfArrayCallback(block.dependencies, iteratorDependency); - } - if(block.blocks) { - const blocks = block.blocks; - let indexBlock = blocks.length; - while(indexBlock--) { - iteratorBlock(blocks[indexBlock]); - } - } - - let indexAll = allDependencies.length; - while(indexAll--) { - iteratorAllDependencies(allDependencies[indexAll]); - } - } - - while(queue.length) { - queue.pop()(); - } - } - - assignDepth(module) { - function assignDepthToModule(module, depth) { - // enter module - if(typeof module.depth === "number" && module.depth <= depth) return; - module.depth = depth; - - // enter it as block - assignDepthToDependencyBlock(module, depth + 1); - } - - function assignDepthToDependency(dependency, depth) { - if(dependency.module) { - queue.push(() => assignDepthToModule(dependency.module, depth)); - } - } - - function assignDepthToDependencyBlock(block, depth) { - function iteratorDependency(d) { - assignDepthToDependency(d, depth); - } - - function iteratorBlock(b) { - assignDepthToDependencyBlock(b, depth); - } - - if(block.variables) { - iterationBlockVariable(block.variables, iteratorDependency); - } - - if(block.dependencies) { - iterationOfArrayCallback(block.dependencies, iteratorDependency); - } - - if(block.blocks) { - iterationOfArrayCallback(block.blocks, iteratorBlock); - } - } - - const queue = [() => { - assignDepthToModule(module, 0); - }]; - while(queue.length) { - queue.pop()(); - } - } - - // This method creates the Chunk graph from the Module graph - processDependenciesBlocksForChunks(inputChunks) { - // Process is splitting into two parts: - // Part one traverse the module graph and builds a very basic chunks graph - // in chunkDependencies. - // Part two traverse every possible way through the basic chunk graph and - // tracks the available modules. While traversing it connects chunks with - // eachother and Blocks with Chunks. It stops traversing when all modules - // for a chunk are already available. So it doesn't connect unneeded chunks. - - const chunkDependencies = new Map(); // Map> - const allCreatedChunks = new Set(); - - // PART ONE - - const blockChunks = new Map(); - - // Start with the provided modules/chunks - const queue = inputChunks.map(chunk => ({ - block: chunk.entryModule, - chunk: chunk - })); - - let block, chunk; - - // For each async Block in graph - const iteratorBlock = b => { - // 1. We create a chunk for this Block - // but only once (blockChunks map) - let c = blockChunks.get(b); - if(c === undefined) { - c = this.namedChunks[b.chunkName]; - if(c && c.isInitial()) { - // TODO webpack 4: convert this to an error - this.warnings.push(new AsyncDependencyToInitialChunkWarning(b.chunkName, b.module, b.loc)); - c = chunk; - } else { - c = this.addChunk(b.chunkName, b.module, b.loc); - blockChunks.set(b, c); - allCreatedChunks.add(c); - // We initialize the chunks property - // this is later filled with the chunk when needed - b.chunks = []; - } - } - - // 2. We store the Block+Chunk mapping as dependency for the chunk - let deps = chunkDependencies.get(chunk); - if(!deps) chunkDependencies.set(chunk, deps = []); - deps.push({ - block: b, - chunk: c - }); - - // 3. We enqueue the DependenciesBlock for traversal - queue.push({ - block: b, - chunk: c - }); - }; - - // For each Dependency in the graph - const iteratorDependency = d => { - // We skip Dependencies without Module pointer - if(!d.module) { - return; - } - // We skip weak Dependencies - if(d.weak) { - return; - } - // We connect Module and Chunk when not already done - if(chunk.addModule(d.module)) { - d.module.addChunk(chunk); - - // And enqueue the Module for traversal - queue.push({ - block: d.module, - chunk - }); - } - }; - - // Iterative traversal of the Module graph - // Recursive would be simpler to write but could result in Stack Overflows - while(queue.length) { - const queueItem = queue.pop(); - block = queueItem.block; - chunk = queueItem.chunk; - - // Traverse all variables, Dependencies and Blocks - if(block.variables) { - iterationBlockVariable(block.variables, iteratorDependency); - } - - if(block.dependencies) { - iterationOfArrayCallback(block.dependencies, iteratorDependency); - } - - if(block.blocks) { - iterationOfArrayCallback(block.blocks, iteratorBlock); - } - } - - // PART TWO - - let availableModules; - let newAvailableModules; - const queue2 = new Queue(inputChunks.map(chunk => ({ - chunk, - availableModules: new Set() - }))); - - // Helper function to check if all modules of a chunk are available - const areModulesAvailable = (chunk, availableModules) => { - for(const module of chunk.modulesIterable) { - if(!availableModules.has(module)) - return false; - } - return true; - }; - - // For each edge in the basic chunk graph - const filterFn = dep => { - // Filter egdes that are not needed because all modules are already available - // This also filters circular dependencies in the chunks graph - const depChunk = dep.chunk; - if(areModulesAvailable(depChunk, newAvailableModules)) - return false; // break all modules are already available - return true; - }; - - const minAvailableModulesMap = new Map(); - - // Iterative traversing of the basic chunk graph - while(queue2.length) { - const queueItem = queue2.dequeue(); - chunk = queueItem.chunk; - availableModules = queueItem.availableModules; - - // 1. Get minimal available modules - // It doesn't make sense to traverse a chunk again with more available modules. - // This step calculates the minimal available modules and skips traversal when - // the list didn't shrink. - let minAvailableModules = minAvailableModulesMap.get(chunk); - if(minAvailableModules === undefined) { - minAvailableModulesMap.set(chunk, new Set(availableModules)); - } else { - let deletedModules = false; - for(const m of minAvailableModules) { - if(!availableModules.has(m)) { - minAvailableModules.delete(m); - deletedModules = true; - } - } - if(!deletedModules) - continue; - availableModules = minAvailableModules; - } - - // 2. Get the edges at this point of the graph - const deps = chunkDependencies.get(chunk); - if(!deps) continue; - if(deps.length === 0) continue; - - // 3. Create a new Set of available modules at this points - newAvailableModules = new Set(availableModules); - for(const m of chunk.modulesIterable) - newAvailableModules.add(m); - - // 4. Filter edges with available modules - const filteredDeps = deps.filter(filterFn); - - // 5. Foreach remaining edge - const nextChunks = new Set(); - for(let i = 0; i < filteredDeps.length; i++) { - const dep = filteredDeps[i]; - const depChunk = dep.chunk; - const depBlock = dep.block; - - // 6. Connnect block with chunk - if(depChunk.addBlock(depBlock)) { - depBlock.chunks.push(depChunk); - } - - // 7. Connect chunk with parent - if(chunk.addChunk(depChunk)) { - depChunk.addParent(chunk); - } - - nextChunks.add(depChunk); - } - - // 8. Enqueue further traversal - for(const nextChunk of nextChunks) { - queue2.enqueue({ - chunk: nextChunk, - availableModules: newAvailableModules - }); - } - } - - // Remove all unconnected chunks - for(const chunk of allCreatedChunks) { - if(chunk.parents.length === 0) - chunk.remove("unconnected"); - } - } - - removeChunkFromDependencies(block, chunk) { - const iteratorDependency = d => { - if(!d.module) { - return; - } - if(!d.module.hasReasonForChunk(chunk)) { - if(d.module.removeChunk(chunk)) { - this.removeChunkFromDependencies(d.module, chunk); - } - } - }; - - const blocks = block.blocks; - for(let indexBlock = 0; indexBlock < blocks.length; indexBlock++) { - const chunks = blocks[indexBlock].chunks; - for(let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { - const blockChunk = chunks[indexChunk]; - chunk.removeChunk(blockChunk); - blockChunk.removeParent(chunk); - this.removeChunkFromDependencies(chunks, blockChunk); - } - } - - if(block.dependencies) { - iterationOfArrayCallback(block.dependencies, iteratorDependency); - } - - if(block.variables) { - iterationBlockVariable(block.variables, iteratorDependency); - } - } - - applyModuleIds() { - let unusedIds = []; - let nextFreeModuleId = 0; - let usedIds = []; - // TODO consider Map when performance has improved https://gist.github.com/sokra/234c077e1299b7369461f1708519c392 - const usedIdMap = Object.create(null); - if(this.usedModuleIds) { - Object.keys(this.usedModuleIds).forEach(key => { - const id = this.usedModuleIds[key]; - if(!usedIdMap[id]) { - usedIds.push(id); - usedIdMap[id] = true; - } - }); - } - - const modules1 = this.modules; - for(let indexModule1 = 0; indexModule1 < modules1.length; indexModule1++) { - const module1 = modules1[indexModule1]; - if(module1.id && !usedIdMap[module1.id]) { - usedIds.push(module1.id); - usedIdMap[module1.id] = true; - } - } - - if(usedIds.length > 0) { - let usedIdMax = -1; - for(let index = 0; index < usedIds.length; index++) { - const usedIdKey = usedIds[index]; - - if(typeof usedIdKey !== "number") { - continue; - } - - usedIdMax = Math.max(usedIdMax, usedIdKey); - } - - let lengthFreeModules = nextFreeModuleId = usedIdMax + 1; - - while(lengthFreeModules--) { - if(!usedIdMap[lengthFreeModules]) { - unusedIds.push(lengthFreeModules); - } - } - } - - const modules2 = this.modules; - for(let indexModule2 = 0; indexModule2 < modules2.length; indexModule2++) { - const module2 = modules2[indexModule2]; - if(module2.id === null) { - if(unusedIds.length > 0) - module2.id = unusedIds.pop(); - else - module2.id = nextFreeModuleId++; - } - } - } - - applyChunkIds() { - const unusedIds = []; - let nextFreeChunkId = 0; - - function getNextFreeChunkId(usedChunkIds) { - const keyChunks = Object.keys(usedChunkIds); - let result = -1; - - for(let index = 0; index < keyChunks.length; index++) { - const usedIdKey = keyChunks[index]; - const usedIdValue = usedChunkIds[usedIdKey]; - - if(typeof usedIdValue !== "number") { - continue; - } - - result = Math.max(result, usedIdValue); - } - - return result; - } - - if(this.usedChunkIds) { - nextFreeChunkId = getNextFreeChunkId(this.usedChunkIds) + 1; - let index = nextFreeChunkId; - while(index--) { - if(this.usedChunkIds[index] !== index) { - unusedIds.push(index); - } - } - } - - const chunks = this.chunks; - for(let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { - const chunk = chunks[indexChunk]; - if(chunk.id === null) { - if(unusedIds.length > 0) - chunk.id = unusedIds.pop(); - else - chunk.id = nextFreeChunkId++; - } - if(!chunk.ids) { - chunk.ids = [chunk.id]; - } - } - } - - sortItemsWithModuleIds() { - this.modules.sort(byId); - - 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() { - this.chunks.sort(byId); - - const modules = this.modules; - for(let indexModule = 0; indexModule < modules.length; indexModule++) { - modules[indexModule].sortItems(true); - } - - const chunks = this.chunks; - for(let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { - chunks[indexChunk].sortItems(); - } - - const byMessage = (a, b) => { - const ma = `${a.message}`; - const mb = `${b.message}`; - if(ma < mb) return -1; - if(mb < ma) return 1; - return 0; - }; - - this.errors.sort(byMessage); - this.warnings.sort(byMessage); - } - - summarizeDependencies() { - function filterDups(array) { - const newArray = []; - for(let i = 0; i < array.length; i++) { - if(i === 0 || array[i - 1] !== array[i]) - newArray.push(array[i]); - } - return newArray; - } - this.fileDependencies = (this.compilationDependencies || []).slice(); - this.contextDependencies = []; - this.missingDependencies = []; - - const children = this.children; - for(let indexChildren = 0; indexChildren < children.length; indexChildren++) { - const child = children[indexChildren]; - - this.fileDependencies = this.fileDependencies.concat(child.fileDependencies); - this.contextDependencies = this.contextDependencies.concat(child.contextDependencies); - this.missingDependencies = this.missingDependencies.concat(child.missingDependencies); - } - - const modules = this.modules; - for(let indexModule = 0; indexModule < modules.length; indexModule++) { - const module = modules[indexModule]; - - if(module.fileDependencies) { - const fileDependencies = module.fileDependencies; - for(let indexFileDep = 0; indexFileDep < fileDependencies.length; indexFileDep++) { - this.fileDependencies.push(fileDependencies[indexFileDep]); - } - } - if(module.contextDependencies) { - const contextDependencies = module.contextDependencies; - for(let indexContextDep = 0; indexContextDep < contextDependencies.length; indexContextDep++) { - this.contextDependencies.push(contextDependencies[indexContextDep]); - } - } - } - this.errors.forEach(error => { - if(Array.isArray(error.missing)) { - error.missing.forEach(item => this.missingDependencies.push(item)); - } - }); - this.fileDependencies.sort(); - this.fileDependencies = filterDups(this.fileDependencies); - this.contextDependencies.sort(); - this.contextDependencies = filterDups(this.contextDependencies); - this.missingDependencies.sort(); - this.missingDependencies = filterDups(this.missingDependencies); - } - - createHash() { - const outputOptions = this.outputOptions; - const hashFunction = outputOptions.hashFunction; - const hashDigest = outputOptions.hashDigest; - const hashDigestLength = outputOptions.hashDigestLength; - const hash = crypto.createHash(hashFunction); - if(outputOptions.hashSalt) - hash.update(outputOptions.hashSalt); - this.mainTemplate.updateHash(hash); - this.chunkTemplate.updateHash(hash); - this.moduleTemplate.updateHash(hash); - this.children.forEach(function(child) { - hash.update(child.hash); - }); - this.warnings.forEach(function(warning) { - hash.update(`${warning.message}`); - }); - this.errors.forEach(function(error) { - hash.update(`${error.message}`); - }); - // clone needed as sort below is inplace mutation - const chunks = this.chunks.slice(); - /** - * sort here will bring all "falsy" values to the beginning - * this is needed as the "hasRuntime()" chunks are dependent on the - * hashes of the non-runtime chunks. - */ - chunks.sort((a, b) => { - const aEntry = a.hasRuntime(); - const bEntry = b.hasRuntime(); - if(aEntry && !bEntry) return 1; - if(!aEntry && bEntry) return -1; - return 0; - }); - for(let i = 0; i < chunks.length; i++) { - const chunk = chunks[i]; - const chunkHash = crypto.createHash(hashFunction); - if(outputOptions.hashSalt) - chunkHash.update(outputOptions.hashSalt); - chunk.updateHash(chunkHash); - if(chunk.hasRuntime()) { - this.mainTemplate.updateHashForChunk(chunkHash, chunk); - } else { - this.chunkTemplate.updateHashForChunk(chunkHash, chunk); - } - this.applyPlugins2("chunk-hash", chunk, chunkHash); - chunk.hash = chunkHash.digest(hashDigest); - hash.update(chunk.hash); - chunk.renderedHash = chunk.hash.substr(0, hashDigestLength); - } - this.fullHash = hash.digest(hashDigest); - this.hash = this.fullHash.substr(0, hashDigestLength); - } - - modifyHash(update) { - const outputOptions = this.outputOptions; - const hashFunction = outputOptions.hashFunction; - const hashDigest = outputOptions.hashDigest; - const hashDigestLength = outputOptions.hashDigestLength; - const hash = crypto.createHash(hashFunction); - hash.update(this.fullHash); - hash.update(update); - this.fullHash = hash.digest(hashDigest); - this.hash = this.fullHash.substr(0, hashDigestLength); - } - - createModuleAssets() { - for(let i = 0; i < this.modules.length; i++) { - const module = this.modules[i]; - if(module.assets) { - Object.keys(module.assets).forEach((assetName) => { - const fileName = this.getPath(assetName); - this.assets[fileName] = module.assets[assetName]; - this.applyPlugins2("module-asset", module, fileName); - }); - } - } - } - - createChunkAssets() { - const outputOptions = this.outputOptions; - const filename = outputOptions.filename; - const chunkFilename = outputOptions.chunkFilename; - for(let i = 0; i < this.chunks.length; i++) { - const chunk = this.chunks[i]; - chunk.files = []; - const chunkHash = chunk.hash; - let source; - let file; - const filenameTemplate = chunk.filenameTemplate ? chunk.filenameTemplate : - chunk.isInitial() ? filename : - chunkFilename; - try { - const useChunkHash = !chunk.hasRuntime() || (this.mainTemplate.useChunkHash && this.mainTemplate.useChunkHash(chunk)); - const usedHash = useChunkHash ? chunkHash : this.fullHash; - const cacheName = "c" + chunk.id; - if(this.cache && this.cache[cacheName] && this.cache[cacheName].hash === usedHash) { - source = this.cache[cacheName].source; - } else { - if(chunk.hasRuntime()) { - source = this.mainTemplate.render(this.hash, chunk, this.moduleTemplate, this.dependencyTemplates); - } else { - source = this.chunkTemplate.render(chunk, this.moduleTemplate, this.dependencyTemplates); - } - if(this.cache) { - this.cache[cacheName] = { - hash: usedHash, - source: source = (source instanceof CachedSource ? source : new CachedSource(source)) - }; - } - } - file = this.getPath(filenameTemplate, { - noChunkHash: !useChunkHash, - chunk - }); - if(this.assets[file]) - throw new Error(`Conflict: Multiple assets emit to the same filename ${file}`); - this.assets[file] = source; - chunk.files.push(file); - this.applyPlugins2("chunk-asset", chunk, file); - } catch(err) { - this.errors.push(new ChunkRenderError(chunk, file || filenameTemplate, err)); - } - } - } - - getPath(filename, data) { - data = data || {}; - data.hash = data.hash || this.hash; - return this.mainTemplate.applyPluginsWaterfall("asset-path", filename, data); - } - - createChildCompiler(name, outputOptions, plugins) { - const idx = (this.childrenCounters[name] || 0); - this.childrenCounters[name] = idx + 1; - return this.compiler.createChildCompiler(this, name, idx, outputOptions, plugins); - } - - checkConstraints() { - const usedIds = {}; - - const modules = this.modules; - for(let indexModule = 0; indexModule < modules.length; indexModule++) { - const moduleId = modules[indexModule].id; - - if(usedIds[moduleId]) - throw new Error(`checkConstraints: duplicate module id ${moduleId}`); - } - - const chunks = this.chunks; - for(let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { - const chunk = chunks[indexChunk]; - - if(chunks.indexOf(chunk) !== indexChunk) - throw new Error(`checkConstraints: duplicate chunk in compilation ${chunk.debugId}`); - chunk.checkConstraints(); - } - } -} - -module.exports = Compilation; +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra + */ +"use strict"; + +const asyncLib = require("neo-async"); +const util = require("util"); +const { CachedSource } = require("webpack-sources"); +const { + Tapable, + SyncHook, + SyncBailHook, + SyncWaterfallHook, + AsyncSeriesHook +} = require("tapable"); +const EntryModuleNotFoundError = require("./EntryModuleNotFoundError"); +const ModuleNotFoundError = require("./ModuleNotFoundError"); +const ModuleDependencyWarning = require("./ModuleDependencyWarning"); +const ModuleDependencyError = require("./ModuleDependencyError"); +const ChunkGroup = require("./ChunkGroup"); +const Chunk = require("./Chunk"); +const Entrypoint = require("./Entrypoint"); +const MainTemplate = require("./MainTemplate"); +const ChunkTemplate = require("./ChunkTemplate"); +const HotUpdateChunkTemplate = require("./HotUpdateChunkTemplate"); +const ModuleTemplate = require("./ModuleTemplate"); +const RuntimeTemplate = require("./RuntimeTemplate"); +const ChunkRenderError = require("./ChunkRenderError"); +const AsyncDependencyToInitialChunkError = require("./AsyncDependencyToInitialChunkError"); +const Stats = require("./Stats"); +const Semaphore = require("./util/Semaphore"); +const createHash = require("./util/createHash"); +const Queue = require("./util/Queue"); +const SortableSet = require("./util/SortableSet"); +const GraphHelpers = require("./GraphHelpers"); +const ModuleDependency = require("./dependencies/ModuleDependency"); +const compareLocations = require("./compareLocations"); + +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("./WebpackError")} WebpackError */ +/** @typedef {import("./DependenciesBlockVariable")} DependenciesBlockVariable */ +/** @typedef {import("./dependencies/SingleEntryDependency")} SingleEntryDependency */ +/** @typedef {import("./dependencies/MultiEntryDependency")} MultiEntryDependency */ +/** @typedef {import("./dependencies/DllEntryDependency")} DllEntryDependency */ +/** @typedef {import("./dependencies/DependencyReference")} DependencyReference */ +/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ +/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ +/** @typedef {import("./Dependency")} Dependency */ +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./Dependency").DependencyTemplate} DependencyTemplate */ +/** @typedef {import("./util/createHash").Hash} Hash */ + +// TODO use @callback +/** @typedef {{[assetName: string]: Source}} CompilationAssets */ +/** @typedef {(err: Error|null, result?: Module) => void } ModuleCallback */ +/** @typedef {(err?: Error|null, result?: Module) => void } ModuleChainCallback */ +/** @typedef {(module: Module) => void} OnModuleCallback */ +/** @typedef {(err?: Error|null) => void} Callback */ +/** @typedef {(d: Dependency) => any} DepBlockVarDependenciesCallback */ +/** @typedef {new (...args: any[]) => Dependency} DepConstructor */ +/** @typedef {{apply: () => void}} Plugin */ + +/** + * @typedef {Object} ModuleFactoryCreateDataContextInfo + * @property {string} issuer + * @property {string} compiler + */ + +/** + * @typedef {Object} ModuleFactoryCreateData + * @property {ModuleFactoryCreateDataContextInfo} contextInfo + * @property {any=} resolveOptions + * @property {string} context + * @property {Dependency[]} dependencies + */ + +/** + * @typedef {Object} ModuleFactory + * @property {(data: ModuleFactoryCreateData, callback: ModuleCallback) => any} create + */ + +/** + * @typedef {Object} SortedDependency + * @property {ModuleFactory} factory + * @property {Dependency[]} dependencies + */ + +/** + * @typedef {Object} AvailableModulesChunkGroupMapping + * @property {ChunkGroup} chunkGroup + * @property {Set} availableModules + */ + +/** + * @typedef {Object} DependenciesBlockLike + * @property {Dependency[]} dependencies + * @property {AsyncDependenciesBlock[]} blocks + * @property {DependenciesBlockVariable[]} variables + */ + +/** + * @param {Chunk} a first chunk to sort by id + * @param {Chunk} b second chunk to sort by id + * @returns {-1|0|1} sort value + */ +const byId = (a, b) => { + if (a.id !== null && b.id !== null) { + if (a.id < b.id) return -1; + if (a.id > b.id) return 1; + } + return 0; +}; + +/** + * @param {Module} a first module to sort by + * @param {Module} b second module to sort by + * @returns {-1|0|1} sort value + */ +const byIdOrIdentifier = (a, b) => { + if (a.id < b.id) return -1; + if (a.id > b.id) return 1; + const identA = a.identifier(); + const identB = b.identifier(); + if (identA < identB) return -1; + if (identA > identB) return 1; + return 0; +}; + +/** + * @param {Module} a first module to sort by + * @param {Module} b second module to sort by + * @returns {-1|0|1} sort value + */ +const byIndexOrIdentifier = (a, b) => { + if (a.index < b.index) return -1; + if (a.index > b.index) return 1; + const identA = a.identifier(); + const identB = b.identifier(); + if (identA < identB) return -1; + if (identA > identB) return 1; + return 0; +}; + +/** + * @param {Compilation} a first compilation to sort by + * @param {Compilation} b second compilation to sort by + * @returns {-1|0|1} sort value + */ +const byNameOrHash = (a, b) => { + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + if (a.fullHash < b.fullHash) return -1; + if (a.fullHash > b.fullHash) return 1; + return 0; +}; + +/** + * @param {DependenciesBlockVariable[]} variables DepBlock Variables to iterate over + * @param {DepBlockVarDependenciesCallback} fn callback to apply on iterated elements + * @returns {void} + */ +const iterationBlockVariable = (variables, fn) => { + for ( + let indexVariable = 0; + indexVariable < variables.length; + indexVariable++ + ) { + const varDep = variables[indexVariable].dependencies; + for (let indexVDep = 0; indexVDep < varDep.length; indexVDep++) { + fn(varDep[indexVDep]); + } + } +}; + +/** + * @template T + * @param {T[]} arr array of elements to iterate over + * @param {function(T): void} fn callback applied to each element + * @returns {void} + */ +const iterationOfArrayCallback = (arr, fn) => { + for (let index = 0; index < arr.length; index++) { + fn(arr[index]); + } +}; + +/** + * @template T + * @param {Set} set set to add items to + * @param {Set} otherSet set to add items from + * @returns {void} + */ +const addAllToSet = (set, otherSet) => { + for (const item of otherSet) { + set.add(item); + } +}; + +class Compilation extends Tapable { + /** + * Creates an instance of Compilation. + * @param {Compiler} compiler the compiler which created the compilation + */ + constructor(compiler) { + super(); + this.hooks = { + /** @type {SyncHook} */ + buildModule: new SyncHook(["module"]), + /** @type {SyncHook} */ + rebuildModule: new SyncHook(["module"]), + /** @type {SyncHook} */ + failedModule: new SyncHook(["module", "error"]), + /** @type {SyncHook} */ + succeedModule: new SyncHook(["module"]), + + /** @type {SyncWaterfallHook} */ + dependencyReference: new SyncWaterfallHook([ + "dependencyReference", + "dependency", + "module" + ]), + + /** @type {SyncHook} */ + finishModules: new SyncHook(["modules"]), + /** @type {SyncHook} */ + finishRebuildingModule: new SyncHook(["module"]), + /** @type {SyncHook} */ + unseal: new SyncHook([]), + /** @type {SyncHook} */ + seal: new SyncHook([]), + + /** @type {SyncHook} */ + beforeChunks: new SyncHook([]), + /** @type {SyncHook} */ + afterChunks: new SyncHook(["chunks"]), + + /** @type {SyncBailHook} */ + optimizeDependenciesBasic: new SyncBailHook(["modules"]), + /** @type {SyncBailHook} */ + optimizeDependencies: new SyncBailHook(["modules"]), + /** @type {SyncBailHook} */ + optimizeDependenciesAdvanced: new SyncBailHook(["modules"]), + /** @type {SyncBailHook} */ + afterOptimizeDependencies: new SyncHook(["modules"]), + + /** @type {SyncHook} */ + optimize: new SyncHook([]), + /** @type {SyncBailHook} */ + optimizeModulesBasic: new SyncBailHook(["modules"]), + /** @type {SyncBailHook} */ + optimizeModules: new SyncBailHook(["modules"]), + /** @type {SyncBailHook} */ + optimizeModulesAdvanced: new SyncBailHook(["modules"]), + /** @type {SyncHook} */ + afterOptimizeModules: new SyncHook(["modules"]), + + /** @type {SyncBailHook} */ + optimizeChunksBasic: new SyncBailHook(["chunks", "chunkGroups"]), + /** @type {SyncBailHook} */ + optimizeChunks: new SyncBailHook(["chunks", "chunkGroups"]), + /** @type {SyncBailHook} */ + optimizeChunksAdvanced: new SyncBailHook(["chunks", "chunkGroups"]), + /** @type {SyncHook} */ + afterOptimizeChunks: new SyncHook(["chunks", "chunkGroups"]), + + /** @type {AsyncSeriesHook} */ + optimizeTree: new AsyncSeriesHook(["chunks", "modules"]), + /** @type {SyncHook} */ + afterOptimizeTree: new SyncHook(["chunks", "modules"]), + + /** @type {SyncBailHook} */ + optimizeChunkModulesBasic: new SyncBailHook(["chunks", "modules"]), + /** @type {SyncBailHook} */ + optimizeChunkModules: new SyncBailHook(["chunks", "modules"]), + /** @type {SyncBailHook} */ + optimizeChunkModulesAdvanced: new SyncBailHook(["chunks", "modules"]), + /** @type {SyncHook} */ + afterOptimizeChunkModules: new SyncHook(["chunks", "modules"]), + /** @type {SyncBailHook} */ + shouldRecord: new SyncBailHook([]), + + /** @type {SyncHook} */ + reviveModules: new SyncHook(["modules", "records"]), + /** @type {SyncHook} */ + optimizeModuleOrder: new SyncHook(["modules"]), + /** @type {SyncHook} */ + advancedOptimizeModuleOrder: new SyncHook(["modules"]), + /** @type {SyncHook} */ + beforeModuleIds: new SyncHook(["modules"]), + /** @type {SyncHook} */ + moduleIds: new SyncHook(["modules"]), + /** @type {SyncHook} */ + optimizeModuleIds: new SyncHook(["modules"]), + /** @type {SyncHook} */ + afterOptimizeModuleIds: new SyncHook(["modules"]), + + /** @type {SyncHook} */ + reviveChunks: new SyncHook(["chunks", "records"]), + /** @type {SyncHook} */ + optimizeChunkOrder: new SyncHook(["chunks"]), + /** @type {SyncHook} */ + beforeChunkIds: new SyncHook(["chunks"]), + /** @type {SyncHook} */ + optimizeChunkIds: new SyncHook(["chunks"]), + /** @type {SyncHook} */ + afterOptimizeChunkIds: new SyncHook(["chunks"]), + + /** @type {SyncHook} */ + recordModules: new SyncHook(["modules", "records"]), + /** @type {SyncHook} */ + recordChunks: new SyncHook(["chunks", "records"]), + + /** @type {SyncHook} */ + beforeHash: new SyncHook([]), + /** @type {SyncHook} */ + contentHash: new SyncHook(["chunk"]), + /** @type {SyncHook} */ + afterHash: new SyncHook([]), + /** @type {SyncHook} */ + recordHash: new SyncHook(["records"]), + /** @type {SyncHook} */ + record: new SyncHook(["compilation", "records"]), + + /** @type {SyncHook} */ + beforeModuleAssets: new SyncHook([]), + /** @type {SyncBailHook} */ + shouldGenerateChunkAssets: new SyncBailHook([]), + /** @type {SyncHook} */ + beforeChunkAssets: new SyncHook([]), + /** @type {SyncHook} */ + additionalChunkAssets: new SyncHook(["chunks"]), + + /** @type {AsyncSeriesHook} */ + additionalAssets: new AsyncSeriesHook([]), + /** @type {AsyncSeriesHook} */ + optimizeChunkAssets: new AsyncSeriesHook(["chunks"]), + /** @type {SyncHook} */ + afterOptimizeChunkAssets: new SyncHook(["chunks"]), + /** @type {AsyncSeriesHook} */ + optimizeAssets: new AsyncSeriesHook(["assets"]), + /** @type {SyncHook} */ + afterOptimizeAssets: new SyncHook(["assets"]), + + /** @type {SyncBailHook} */ + needAdditionalSeal: new SyncBailHook([]), + /** @type {AsyncSeriesHook} */ + afterSeal: new AsyncSeriesHook([]), + + /** @type {SyncHook} */ + chunkHash: new SyncHook(["chunk", "chunkHash"]), + /** @type {SyncHook} */ + moduleAsset: new SyncHook(["module", "filename"]), + /** @type {SyncHook} */ + chunkAsset: new SyncHook(["chunk", "filename"]), + + /** @type {SyncWaterfallHook} */ + assetPath: new SyncWaterfallHook(["filename", "data"]), // TODO MainTemplate + + /** @type {SyncBailHook} */ + needAdditionalPass: new SyncBailHook([]), + + /** @type {SyncHook} */ + childCompiler: new SyncHook([ + "childCompiler", + "compilerName", + "compilerIndex" + ]), + + // TODO the following hooks are weirdly located here + // TODO move them for webpack 5 + /** @type {SyncHook} */ + normalModuleLoader: new SyncHook(["loaderContext", "module"]), + + /** @type {SyncBailHook} */ + optimizeExtractedChunksBasic: new SyncBailHook(["chunks"]), + /** @type {SyncBailHook} */ + optimizeExtractedChunks: new SyncBailHook(["chunks"]), + /** @type {SyncBailHook} */ + optimizeExtractedChunksAdvanced: new SyncBailHook(["chunks"]), + /** @type {SyncHook} */ + afterOptimizeExtractedChunks: new SyncHook(["chunks"]) + }; + this._pluginCompat.tap("Compilation", options => { + switch (options.name) { + case "optimize-tree": + case "additional-assets": + case "optimize-chunk-assets": + case "optimize-assets": + case "after-seal": + options.async = true; + break; + } + }); + /** @type {string=} */ + this.name = undefined; + /** @type {Compiler} */ + this.compiler = compiler; + this.resolverFactory = compiler.resolverFactory; + this.inputFileSystem = compiler.inputFileSystem; + this.requestShortener = compiler.requestShortener; + + const options = (this.options = compiler.options); + this.outputOptions = options && options.output; + /** @type {boolean=} */ + this.bail = options && options.bail; + this.profile = options && options.profile; + this.performance = options && options.performance; + + this.mainTemplate = new MainTemplate(this.outputOptions); + this.chunkTemplate = new ChunkTemplate(this.outputOptions); + this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate( + this.outputOptions + ); + this.runtimeTemplate = new RuntimeTemplate( + this.outputOptions, + this.requestShortener + ); + this.moduleTemplates = { + javascript: new ModuleTemplate(this.runtimeTemplate, "javascript"), + webassembly: new ModuleTemplate(this.runtimeTemplate, "webassembly") + }; + + this.semaphore = new Semaphore(options.parallelism || 100); + + this.entries = []; + /** @private @type {{name: string, request: string, module: Module}[]} */ + this._preparedEntrypoints = []; + this.entrypoints = new Map(); + /** @type {Chunk[]} */ + this.chunks = []; + /** @type {ChunkGroup[]} */ + this.chunkGroups = []; + /** @type {Map} */ + this.namedChunkGroups = new Map(); + /** @type {Map} */ + this.namedChunks = new Map(); + /** @type {Module[]} */ + this.modules = []; + /** @private @type {Map} */ + this._modules = new Map(); + this.cache = null; + this.records = null; + /** @type {string[]} */ + this.additionalChunkAssets = []; + /** @type {CompilationAssets} */ + this.assets = {}; + /** @type {WebpackError[]} */ + this.errors = []; + /** @type {WebpackError[]} */ + this.warnings = []; + /** @type {Compilation[]} */ + this.children = []; + /** @type {Map} */ + this.dependencyFactories = new Map(); + /** @type {Map} */ + this.dependencyTemplates = new Map(); + // TODO refactor this in webpack 5 to a custom DependencyTemplates class with a hash property + // @ts-ignore + this.dependencyTemplates.set("hash", ""); + this.childrenCounters = {}; + /** @type {Set} */ + this.usedChunkIds = null; + /** @type {Set} */ + this.usedModuleIds = null; + /** @type {Map=} */ + this.fileTimestamps = undefined; + /** @type {Map=} */ + this.contextTimestamps = undefined; + /** @type {Set=} */ + this.compilationDependencies = undefined; + /** @private @type {Map} */ + this._buildingModules = new Map(); + /** @private @type {Map} */ + this._rebuildingModules = new Map(); + } + + getStats() { + return new Stats(this); + } + + /** + * @typedef {Object} AddModuleResult + * @property {Module} module the added or existing module + * @property {boolean} issuer was this the first request for this module + * @property {boolean} build should the module be build + * @property {boolean} dependencies should dependencies be walked + */ + + /** + * @param {Module} module module to be added that was created + * @param {any=} cacheGroup cacheGroup it is apart of + * @returns {AddModuleResult} returns meta about whether or not the module had built + * had an issuer, or any dependnecies + */ + addModule(module, cacheGroup) { + const identifier = module.identifier(); + const alreadyAddedModule = this._modules.get(identifier); + if (alreadyAddedModule) { + return { + module: alreadyAddedModule, + issuer: false, + build: false, + dependencies: false + }; + } + const cacheName = (cacheGroup || "m") + identifier; + if (this.cache && this.cache[cacheName]) { + const cacheModule = this.cache[cacheName]; + + if (typeof cacheModule.updateCacheModule === "function") { + cacheModule.updateCacheModule(module); + } + + let rebuild = true; + if (this.fileTimestamps && this.contextTimestamps) { + rebuild = cacheModule.needRebuild( + this.fileTimestamps, + this.contextTimestamps + ); + } + + if (!rebuild) { + cacheModule.disconnect(); + this._modules.set(identifier, cacheModule); + this.modules.push(cacheModule); + for (const err of cacheModule.errors) { + this.errors.push(err); + } + for (const err of cacheModule.warnings) { + this.warnings.push(err); + } + return { + module: cacheModule, + issuer: true, + build: false, + dependencies: true + }; + } + cacheModule.unbuild(); + module = cacheModule; + } + this._modules.set(identifier, module); + if (this.cache) { + this.cache[cacheName] = module; + } + this.modules.push(module); + return { + module: module, + issuer: true, + build: true, + dependencies: true + }; + } + + /** + * Fetches a module from a compilation by its identifier + * @param {Module} module the module provided + * @returns {Module} the module requested + */ + getModule(module) { + const identifier = module.identifier(); + return this._modules.get(identifier); + } + + /** + * Attempts to search for a module by its identifier + * @param {string} identifier identifier (usually path) for module + * @returns {Module|undefined} attempt to search for module and return it, else undefined + */ + findModule(identifier) { + return this._modules.get(identifier); + } + + /** + * @param {Module} module module with its callback list + * @param {Callback} callback the callback function + * @returns {void} + */ + waitForBuildingFinished(module, callback) { + let callbackList = this._buildingModules.get(module); + if (callbackList) { + callbackList.push(() => callback()); + } else { + process.nextTick(callback); + } + } + + /** + * Builds the module object + * + * @param {Module} module module to be built + * @param {boolean} optional optional flag + * @param {Module=} origin origin module this module build was requested from + * @param {Dependency[]=} dependencies optional dependencies from the module to be built + * @param {TODO} thisCallback the callback + * @returns {TODO} returns the callback function with results + */ + buildModule(module, optional, origin, dependencies, thisCallback) { + let callbackList = this._buildingModules.get(module); + if (callbackList) { + callbackList.push(thisCallback); + return; + } + this._buildingModules.set(module, (callbackList = [thisCallback])); + + const callback = err => { + this._buildingModules.delete(module); + for (const cb of callbackList) { + cb(err); + } + }; + + this.hooks.buildModule.call(module); + module.build( + this.options, + this, + this.resolverFactory.get("normal", module.resolveOptions), + this.inputFileSystem, + error => { + const errors = module.errors; + for (let indexError = 0; indexError < errors.length; indexError++) { + const err = errors[indexError]; + err.origin = origin; + err.dependencies = dependencies; + if (optional) { + this.warnings.push(err); + } else { + this.errors.push(err); + } + } + + const warnings = module.warnings; + for ( + let indexWarning = 0; + indexWarning < warnings.length; + indexWarning++ + ) { + const war = warnings[indexWarning]; + war.origin = origin; + war.dependencies = dependencies; + this.warnings.push(war); + } + module.dependencies.sort((a, b) => compareLocations(a.loc, b.loc)); + if (error) { + this.hooks.failedModule.call(module, error); + return callback(error); + } + this.hooks.succeedModule.call(module); + return callback(); + } + ); + } + + /** + * @param {Module} module to be processed for deps + * @param {ModuleCallback} callback callback to be triggered + * @returns {void} + */ + processModuleDependencies(module, callback) { + const dependencies = new Map(); + + const addDependency = dep => { + const resourceIdent = dep.getResourceIdentifier(); + if (resourceIdent) { + const factory = this.dependencyFactories.get(dep.constructor); + if (factory === undefined) { + throw new Error( + `No module factory available for dependency type: ${ + dep.constructor.name + }` + ); + } + let innerMap = dependencies.get(factory); + if (innerMap === undefined) { + dependencies.set(factory, (innerMap = new Map())); + } + let list = innerMap.get(resourceIdent); + if (list === undefined) innerMap.set(resourceIdent, (list = [])); + list.push(dep); + } + }; + + const addDependenciesBlock = block => { + if (block.dependencies) { + iterationOfArrayCallback(block.dependencies, addDependency); + } + if (block.blocks) { + iterationOfArrayCallback(block.blocks, addDependenciesBlock); + } + if (block.variables) { + iterationBlockVariable(block.variables, addDependency); + } + }; + + try { + addDependenciesBlock(module); + } catch (e) { + callback(e); + } + + const sortedDependencies = []; + + for (const pair1 of dependencies) { + for (const pair2 of pair1[1]) { + sortedDependencies.push({ + factory: pair1[0], + dependencies: pair2[1] + }); + } + } + + this.addModuleDependencies( + module, + sortedDependencies, + this.bail, + null, + true, + callback + ); + } + + /** + * @param {Module} module module to add deps to + * @param {SortedDependency[]} dependencies set of sorted dependencies to iterate through + * @param {(boolean|null)=} bail whether to bail or not + * @param {TODO} cacheGroup optional cacheGroup + * @param {boolean} recursive whether it is recursive traversal + * @param {function} callback callback for when dependencies are finished being added + * @returns {void} + */ + addModuleDependencies( + module, + dependencies, + bail, + cacheGroup, + recursive, + callback + ) { + const start = this.profile && Date.now(); + const currentProfile = this.profile && {}; + + asyncLib.forEach( + dependencies, + (item, callback) => { + const dependencies = item.dependencies; + + const errorAndCallback = err => { + err.origin = module; + err.dependencies = dependencies; + this.errors.push(err); + if (bail) { + callback(err); + } else { + callback(); + } + }; + const warningAndCallback = err => { + err.origin = module; + this.warnings.push(err); + callback(); + }; + + const semaphore = this.semaphore; + semaphore.acquire(() => { + const factory = item.factory; + factory.create( + { + contextInfo: { + issuer: module.nameForCondition && module.nameForCondition(), + compiler: this.compiler.name + }, + resolveOptions: module.resolveOptions, + context: module.context, + dependencies: dependencies + }, + (err, dependentModule) => { + let afterFactory; + + const isOptional = () => { + return dependencies.every(d => d.optional); + }; + + const errorOrWarningAndCallback = err => { + if (isOptional()) { + return warningAndCallback(err); + } else { + return errorAndCallback(err); + } + }; + + if (err) { + semaphore.release(); + return errorOrWarningAndCallback( + new ModuleNotFoundError(module, err) + ); + } + if (!dependentModule) { + semaphore.release(); + return process.nextTick(callback); + } + if (currentProfile) { + afterFactory = Date.now(); + currentProfile.factory = afterFactory - start; + } + + const iterationDependencies = depend => { + for (let index = 0; index < depend.length; index++) { + const dep = depend[index]; + dep.module = dependentModule; + dependentModule.addReason(module, dep); + } + }; + + const addModuleResult = this.addModule( + dependentModule, + cacheGroup + ); + dependentModule = addModuleResult.module; + iterationDependencies(dependencies); + + const afterBuild = () => { + if (currentProfile) { + const afterBuilding = Date.now(); + currentProfile.building = afterBuilding - afterFactory; + } + + if (recursive && addModuleResult.dependencies) { + this.processModuleDependencies(dependentModule, callback); + } else { + return callback(); + } + }; + + if (addModuleResult.issuer) { + if (currentProfile) { + dependentModule.profile = currentProfile; + } + + dependentModule.issuer = module; + } else { + if (this.profile) { + if (module.profile) { + const time = Date.now() - start; + if ( + !module.profile.dependencies || + time > module.profile.dependencies + ) { + module.profile.dependencies = time; + } + } + } + } + + if (addModuleResult.build) { + this.buildModule( + dependentModule, + isOptional(), + module, + dependencies, + err => { + if (err) { + semaphore.release(); + return errorOrWarningAndCallback(err); + } + + if (currentProfile) { + const afterBuilding = Date.now(); + currentProfile.building = afterBuilding - afterFactory; + } + + semaphore.release(); + afterBuild(); + } + ); + } else { + semaphore.release(); + this.waitForBuildingFinished(dependentModule, afterBuild); + } + } + ); + }); + }, + err => { + // In V8, the Error objects keep a reference to the functions on the stack. These warnings & + // errors are created inside closures that keep a reference to the Compilation, so errors are + // leaking the Compilation object. + + if (err) { + // eslint-disable-next-line no-self-assign + err.stack = err.stack; + return callback(err); + } + + return process.nextTick(callback); + } + ); + } + + /** + * + * @param {string} context context string path + * @param {Dependency} dependency dependency used to create Module chain + * @param {OnModuleCallback} onModule function invoked on modules creation + * @param {ModuleChainCallback} callback callback for when module chain is complete + * @returns {void} will throw if dependency instance is not a valid Dependency + */ + _addModuleChain(context, dependency, onModule, callback) { + const start = this.profile && Date.now(); + const currentProfile = this.profile && {}; + + const errorAndCallback = this.bail + ? err => { + callback(err); + } + : err => { + err.dependencies = [dependency]; + this.errors.push(err); + callback(); + }; + + if ( + typeof dependency !== "object" || + dependency === null || + !dependency.constructor + ) { + throw new Error("Parameter 'dependency' must be a Dependency"); + } + const Dep = /** @type {DepConstructor} */ (dependency.constructor); + const moduleFactory = this.dependencyFactories.get(Dep); + if (!moduleFactory) { + throw new Error( + `No dependency factory available for this dependency type: ${ + dependency.constructor.name + }` + ); + } + + this.semaphore.acquire(() => { + moduleFactory.create( + { + contextInfo: { + issuer: "", + compiler: this.compiler.name + }, + context: context, + dependencies: [dependency] + }, + (err, module) => { + if (err) { + this.semaphore.release(); + return errorAndCallback(new EntryModuleNotFoundError(err)); + } + + let afterFactory; + + if (currentProfile) { + afterFactory = Date.now(); + currentProfile.factory = afterFactory - start; + } + + const addModuleResult = this.addModule(module); + module = addModuleResult.module; + + onModule(module); + + dependency.module = module; + module.addReason(null, dependency); + + const afterBuild = () => { + if (currentProfile) { + const afterBuilding = Date.now(); + currentProfile.building = afterBuilding - afterFactory; + } + + if (addModuleResult.dependencies) { + this.processModuleDependencies(module, err => { + if (err) return callback(err); + callback(null, module); + }); + } else { + return callback(null, module); + } + }; + + if (addModuleResult.issuer) { + if (currentProfile) { + module.profile = currentProfile; + } + } + + if (addModuleResult.build) { + this.buildModule(module, false, null, null, err => { + if (err) { + this.semaphore.release(); + return errorAndCallback(err); + } + + if (currentProfile) { + const afterBuilding = Date.now(); + currentProfile.building = afterBuilding - afterFactory; + } + + this.semaphore.release(); + afterBuild(); + }); + } else { + this.semaphore.release(); + this.waitForBuildingFinished(module, afterBuild); + } + } + ); + }); + } + + /** + * + * @param {string} context context path for entry + * @param {Dependency} entry entry dependency being created + * @param {string} name name of entry + * @param {ModuleCallback} callback callback function + * @returns {void} returns + */ + addEntry(context, entry, name, callback) { + const slot = { + name: name, + // TODO webpack 5 remove `request` + request: null, + module: null + }; + + if (entry instanceof ModuleDependency) { + slot.request = entry.request; + } + + // TODO webpack 5: merge modules instead when multiple entry modules are supported + const idx = this._preparedEntrypoints.findIndex(slot => slot.name === name); + if (idx >= 0) { + // Overwrite existing entrypoint + this._preparedEntrypoints[idx] = slot; + } else { + this._preparedEntrypoints.push(slot); + } + this._addModuleChain( + context, + entry, + module => { + this.entries.push(module); + }, + (err, module) => { + if (err) { + return callback(err); + } + + if (module) { + slot.module = module; + } else { + const idx = this._preparedEntrypoints.indexOf(slot); + if (idx >= 0) { + this._preparedEntrypoints.splice(idx, 1); + } + } + return callback(null, module); + } + ); + } + + /** + * @param {string} context context path string + * @param {Dependency} dependency dep used to create module + * @param {ModuleCallback} callback module callback sending module up a level + * @returns {void} + */ + prefetch(context, dependency, callback) { + this._addModuleChain( + context, + dependency, + module => { + module.prefetched = true; + }, + callback + ); + } + + /** + * @param {Module} module module to be rebuilt + * @param {Callback} thisCallback callback when module finishes rebuilding + * @returns {void} + */ + rebuildModule(module, thisCallback) { + let callbackList = this._rebuildingModules.get(module); + if (callbackList) { + callbackList.push(thisCallback); + return; + } + this._rebuildingModules.set(module, (callbackList = [thisCallback])); + + const callback = err => { + this._rebuildingModules.delete(module); + for (const cb of callbackList) { + cb(err); + } + }; + + this.hooks.rebuildModule.call(module); + const oldDependencies = module.dependencies.slice(); + const oldVariables = module.variables.slice(); + const oldBlocks = module.blocks.slice(); + module.unbuild(); + this.buildModule(module, false, module, null, err => { + if (err) { + this.hooks.finishRebuildingModule.call(module); + return callback(err); + } + + this.processModuleDependencies(module, err => { + if (err) return callback(err); + this.removeReasonsOfDependencyBlock(module, { + dependencies: oldDependencies, + variables: oldVariables, + blocks: oldBlocks + }); + this.hooks.finishRebuildingModule.call(module); + callback(); + }); + }); + } + + finish() { + const modules = this.modules; + this.hooks.finishModules.call(modules); + + for (let index = 0; index < modules.length; index++) { + const module = modules[index]; + this.reportDependencyErrorsAndWarnings(module, [module]); + } + } + + unseal() { + this.hooks.unseal.call(); + this.chunks.length = 0; + this.chunkGroups.length = 0; + this.namedChunks.clear(); + this.namedChunkGroups.clear(); + this.additionalChunkAssets.length = 0; + this.assets = {}; + for (const module of this.modules) { + module.unseal(); + } + } + + /** + * @param {Callback} callback signals when the seal method is finishes + * @returns {void} + */ + seal(callback) { + this.hooks.seal.call(); + + while ( + this.hooks.optimizeDependenciesBasic.call(this.modules) || + this.hooks.optimizeDependencies.call(this.modules) || + this.hooks.optimizeDependenciesAdvanced.call(this.modules) + ) { + /* empty */ + } + this.hooks.afterOptimizeDependencies.call(this.modules); + + this.hooks.beforeChunks.call(); + for (const preparedEntrypoint of this._preparedEntrypoints) { + const module = preparedEntrypoint.module; + const name = preparedEntrypoint.name; + const chunk = this.addChunk(name); + const entrypoint = new Entrypoint(name); + entrypoint.setRuntimeChunk(chunk); + entrypoint.addOrigin(null, name, preparedEntrypoint.request); + this.namedChunkGroups.set(name, entrypoint); + this.entrypoints.set(name, entrypoint); + this.chunkGroups.push(entrypoint); + + GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk); + GraphHelpers.connectChunkAndModule(chunk, module); + + chunk.entryModule = module; + chunk.name = name; + + this.assignDepth(module); + } + this.processDependenciesBlocksForChunkGroups(this.chunkGroups.slice()); + this.sortModules(this.modules); + this.hooks.afterChunks.call(this.chunks); + + this.hooks.optimize.call(); + + while ( + this.hooks.optimizeModulesBasic.call(this.modules) || + this.hooks.optimizeModules.call(this.modules) || + this.hooks.optimizeModulesAdvanced.call(this.modules) + ) { + /* empty */ + } + this.hooks.afterOptimizeModules.call(this.modules); + + while ( + this.hooks.optimizeChunksBasic.call(this.chunks, this.chunkGroups) || + this.hooks.optimizeChunks.call(this.chunks, this.chunkGroups) || + this.hooks.optimizeChunksAdvanced.call(this.chunks, this.chunkGroups) + ) { + /* empty */ + } + this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups); + + this.hooks.optimizeTree.callAsync(this.chunks, this.modules, err => { + if (err) { + return callback(err); + } + + this.hooks.afterOptimizeTree.call(this.chunks, this.modules); + + while ( + this.hooks.optimizeChunkModulesBasic.call(this.chunks, this.modules) || + this.hooks.optimizeChunkModules.call(this.chunks, this.modules) || + this.hooks.optimizeChunkModulesAdvanced.call(this.chunks, this.modules) + ) { + /* empty */ + } + this.hooks.afterOptimizeChunkModules.call(this.chunks, this.modules); + + const shouldRecord = this.hooks.shouldRecord.call() !== false; + + this.hooks.reviveModules.call(this.modules, this.records); + this.hooks.optimizeModuleOrder.call(this.modules); + this.hooks.advancedOptimizeModuleOrder.call(this.modules); + this.hooks.beforeModuleIds.call(this.modules); + this.hooks.moduleIds.call(this.modules); + this.applyModuleIds(); + this.hooks.optimizeModuleIds.call(this.modules); + this.hooks.afterOptimizeModuleIds.call(this.modules); + + this.sortItemsWithModuleIds(); + + this.hooks.reviveChunks.call(this.chunks, this.records); + this.hooks.optimizeChunkOrder.call(this.chunks); + this.hooks.beforeChunkIds.call(this.chunks); + this.applyChunkIds(); + this.hooks.optimizeChunkIds.call(this.chunks); + this.hooks.afterOptimizeChunkIds.call(this.chunks); + + this.sortItemsWithChunkIds(); + + if (shouldRecord) { + this.hooks.recordModules.call(this.modules, this.records); + this.hooks.recordChunks.call(this.chunks, this.records); + } + + this.hooks.beforeHash.call(); + this.createHash(); + this.hooks.afterHash.call(); + + if (shouldRecord) { + this.hooks.recordHash.call(this.records); + } + + this.hooks.beforeModuleAssets.call(); + this.createModuleAssets(); + if (this.hooks.shouldGenerateChunkAssets.call() !== false) { + this.hooks.beforeChunkAssets.call(); + this.createChunkAssets(); + } + this.hooks.additionalChunkAssets.call(this.chunks); + this.summarizeDependencies(); + if (shouldRecord) { + this.hooks.record.call(this, this.records); + } + + this.hooks.additionalAssets.callAsync(err => { + if (err) { + return callback(err); + } + this.hooks.optimizeChunkAssets.callAsync(this.chunks, err => { + if (err) { + return callback(err); + } + this.hooks.afterOptimizeChunkAssets.call(this.chunks); + this.hooks.optimizeAssets.callAsync(this.assets, err => { + if (err) { + return callback(err); + } + this.hooks.afterOptimizeAssets.call(this.assets); + if (this.hooks.needAdditionalSeal.call()) { + this.unseal(); + return this.seal(callback); + } + return this.hooks.afterSeal.callAsync(callback); + }); + }); + }); + }); + } + + /** + * @param {Module[]} modules the modules array on compilation to perform the sort for + * @returns {void} + */ + sortModules(modules) { + // TODO webpack 5: this should only be enabled when `moduleIds: "natural"` + // TODO move it into a plugin (NaturalModuleIdsPlugin) and use this in WebpackOptionsApply + // TODO remove this method + modules.sort(byIndexOrIdentifier); + } + + /** + * @param {Module} module moulde to report from + * @param {DependenciesBlock[]} blocks blocks to report from + * @returns {void} + */ + reportDependencyErrorsAndWarnings(module, blocks) { + for (let indexBlock = 0; indexBlock < blocks.length; indexBlock++) { + const block = blocks[indexBlock]; + const dependencies = block.dependencies; + + for (let indexDep = 0; indexDep < dependencies.length; indexDep++) { + const d = dependencies[indexDep]; + + const warnings = d.getWarnings(); + if (warnings) { + for (let indexWar = 0; indexWar < warnings.length; indexWar++) { + const w = warnings[indexWar]; + + const warning = new ModuleDependencyWarning(module, w, d.loc); + this.warnings.push(warning); + } + } + const errors = d.getErrors(); + if (errors) { + for (let indexErr = 0; indexErr < errors.length; indexErr++) { + const e = errors[indexErr]; + + const error = new ModuleDependencyError(module, e, d.loc); + this.errors.push(error); + } + } + } + + this.reportDependencyErrorsAndWarnings(module, block.blocks); + } + } + + /** + * @param {TODO} groupOptions options for the chunk group + * @param {Module} module the module the references the chunk group + * @param {DependencyLocation} loc the location from with the chunk group is referenced (inside of module) + * @param {string} request the request from which the the chunk group is referenced + * @returns {ChunkGroup} the new or existing chunk group + */ + addChunkInGroup(groupOptions, module, loc, request) { + if (typeof groupOptions === "string") { + groupOptions = { name: groupOptions }; + } + const name = groupOptions.name; + if (name) { + const chunkGroup = this.namedChunkGroups.get(name); + if (chunkGroup !== undefined) { + chunkGroup.addOptions(groupOptions); + if (module) { + chunkGroup.addOrigin(module, loc, request); + } + return chunkGroup; + } + } + const chunkGroup = new ChunkGroup(groupOptions); + if (module) chunkGroup.addOrigin(module, loc, request); + const chunk = this.addChunk(name); + + GraphHelpers.connectChunkGroupAndChunk(chunkGroup, chunk); + + this.chunkGroups.push(chunkGroup); + if (name) { + this.namedChunkGroups.set(name, chunkGroup); + } + return chunkGroup; + } + + /** + * This method first looks to see if a name is provided for a new chunk, + * and first looks to see if any named chunks already exist and reuse that chunk instead. + * + * @param {string=} name optional chunk name to be provided + * @returns {Chunk} create a chunk (invoked during seal event) + */ + addChunk(name) { + if (name) { + const chunk = this.namedChunks.get(name); + if (chunk !== undefined) { + return chunk; + } + } + const chunk = new Chunk(name); + this.chunks.push(chunk); + if (name) { + this.namedChunks.set(name, chunk); + } + return chunk; + } + + /** + * @param {Module} module module to assign depth + * @returns {void} + */ + assignDepth(module) { + const queue = new Set([module]); + let depth; + + module.depth = 0; + + /** + * @param {Module} module module for processeing + * @returns {void} + */ + const enqueueJob = module => { + const d = module.depth; + if (typeof d === "number" && d <= depth) return; + queue.add(module); + module.depth = depth; + }; + + /** + * @param {Dependency} dependency dependency to assign depth to + * @returns {void} + */ + const assignDepthToDependency = dependency => { + if (dependency.module) { + enqueueJob(dependency.module); + } + }; + + /** + * @param {DependenciesBlock} block block to assign depth to + * @returns {void} + */ + const assignDepthToDependencyBlock = block => { + if (block.variables) { + iterationBlockVariable(block.variables, assignDepthToDependency); + } + + if (block.dependencies) { + iterationOfArrayCallback(block.dependencies, assignDepthToDependency); + } + + if (block.blocks) { + iterationOfArrayCallback(block.blocks, assignDepthToDependencyBlock); + } + }; + + for (module of queue) { + queue.delete(module); + depth = module.depth; + + depth++; + assignDepthToDependencyBlock(module); + } + } + + /** + * @param {Module} module the module containing the dependency + * @param {Dependency} dependency the dependency + * @returns {DependencyReference} a reference for the dependency + */ + getDependencyReference(module, dependency) { + // TODO remove dep.getReference existence check in webpack 5 + if (typeof dependency.getReference !== "function") return null; + const ref = dependency.getReference(); + if (!ref) return null; + return this.hooks.dependencyReference.call(ref, dependency, module); + } + + /** + * This method creates the Chunk graph from the Module graph + * @private + * @param {TODO[]} inputChunkGroups chunk groups which are processed + * @returns {void} + */ + processDependenciesBlocksForChunkGroups(inputChunkGroups) { + // Process is splitting into two parts: + // Part one traverse the module graph and builds a very basic chunks graph + // in chunkDependencies. + // Part two traverse every possible way through the basic chunk graph and + // tracks the available modules. While traversing it connects chunks with + // eachother and Blocks with Chunks. It stops traversing when all modules + // for a chunk are already available. So it doesn't connect unneeded chunks. + + /** @type {Map} */ + const chunkDependencies = new Map(); + const allCreatedChunkGroups = new Set(); + + // PREPARE + /** @type {Map} */ + const blockInfoMap = new Map(); + + /** + * @param {Dependency} d dependency to iterate over + * @returns {void} + */ + const iteratorDependency = d => { + // We skip Dependencies without Reference + const ref = this.getDependencyReference(currentModule, d); + if (!ref) { + return; + } + // We skip Dependencies without Module pointer + const refModule = ref.module; + if (!refModule) { + return; + } + // We skip weak Dependencies + if (ref.weak) { + return; + } + + blockInfoModules.add(refModule); + }; + + /** + * @param {AsyncDependenciesBlock} b blocks to prepare + * @returns {void} + */ + const iteratorBlockPrepare = b => { + blockInfoBlocks.push(b); + blockQueue.push(b); + }; + + /** @type {Module} */ + let currentModule; + /** @type {DependenciesBlock} */ + let block; + /** @type {DependenciesBlock[]} */ + let blockQueue; + /** @type {Set} */ + let blockInfoModules; + /** @type {AsyncDependenciesBlock[]} */ + let blockInfoBlocks; + + for (const module of this.modules) { + blockQueue = [module]; + currentModule = module; + while (blockQueue.length > 0) { + block = blockQueue.pop(); + blockInfoModules = new Set(); + blockInfoBlocks = []; + + if (block.variables) { + iterationBlockVariable(block.variables, iteratorDependency); + } + + if (block.dependencies) { + iterationOfArrayCallback(block.dependencies, iteratorDependency); + } + + if (block.blocks) { + iterationOfArrayCallback(block.blocks, iteratorBlockPrepare); + } + + const blockInfo = { + modules: Array.from(blockInfoModules), + blocks: blockInfoBlocks + }; + blockInfoMap.set(block, blockInfo); + } + } + + // PART ONE + + /** @type {Map} */ + const chunkGroupCounters = new Map(); + for (const chunkGroup of inputChunkGroups) { + chunkGroupCounters.set(chunkGroup, { index: 0, index2: 0 }); + } + + let nextFreeModuleIndex = 0; + let nextFreeModuleIndex2 = 0; + + /** @type {Map} */ + const blockChunkGroups = new Map(); + + /** @type {Set} */ + const blocksWithNestedBlocks = new Set(); + + const ADD_AND_ENTER_MODULE = 0; + const ENTER_MODULE = 1; + const PROCESS_BLOCK = 2; + const LEAVE_MODULE = 3; + + /** + * @typedef {Object} QueueItem + * @property {number} action + * @property {DependenciesBlock} block + * @property {Module} module + * @property {Chunk} chunk + * @property {ChunkGroup} chunkGroup + */ + + /** + * @param {ChunkGroup} chunkGroup chunk group + * @returns {QueueItem} queue item + */ + const chunkGroupToQueueItem = chunkGroup => ({ + action: ENTER_MODULE, + block: chunkGroup.chunks[0].entryModule, + module: chunkGroup.chunks[0].entryModule, + chunk: chunkGroup.chunks[0], + chunkGroup + }); + + // Start with the provided modules/chunks + /** @type {QueueItem[]} */ + let queue = inputChunkGroups.map(chunkGroupToQueueItem).reverse(); + /** @type {QueueItem[]} */ + let queueDelayed = []; + + /** @type {Module} */ + let module; + /** @type {Chunk} */ + let chunk; + /** @type {ChunkGroup} */ + let chunkGroup; + + // For each async Block in graph + /** + * @param {AsyncDependenciesBlock} b iterating over each Async DepBlock + * @returns {void} + */ + const iteratorBlock = b => { + // 1. We create a chunk for this Block + // but only once (blockChunkGroups map) + let c = blockChunkGroups.get(b); + if (c === undefined) { + c = this.namedChunkGroups.get(b.chunkName); + if (c && c.isInitial()) { + this.errors.push( + new AsyncDependencyToInitialChunkError(b.chunkName, module, b.loc) + ); + c = chunkGroup; + } else { + c = this.addChunkInGroup( + b.groupOptions || b.chunkName, + module, + b.loc, + b.request + ); + chunkGroupCounters.set(c, { index: 0, index2: 0 }); + blockChunkGroups.set(b, c); + allCreatedChunkGroups.add(c); + } + } else { + // TODO webpack 5 remove addOptions check + if (c.addOptions) c.addOptions(b.groupOptions); + c.addOrigin(module, b.loc, b.request); + } + + // 2. We store the Block+Chunk mapping as dependency for the chunk + let deps = chunkDependencies.get(chunkGroup); + if (!deps) chunkDependencies.set(chunkGroup, (deps = [])); + deps.push({ + block: b, + chunkGroup: c + }); + + // 3. We enqueue the DependenciesBlock for traversal + queueDelayed.push({ + action: PROCESS_BLOCK, + block: b, + module: module, + chunk: c.chunks[0], + chunkGroup: c + }); + }; + + // Iterative traversal of the Module graph + // Recursive would be simpler to write but could result in Stack Overflows + while (queue.length) { + while (queue.length) { + const queueItem = queue.pop(); + module = queueItem.module; + block = queueItem.block; + chunk = queueItem.chunk; + chunkGroup = queueItem.chunkGroup; + + 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 { + // already connected, skip it + break; + } + } + // fallthrough + case ENTER_MODULE: { + if (chunkGroup !== undefined) { + const index = chunkGroup.getModuleIndex(module); + if (index === undefined) { + chunkGroup.setModuleIndex( + module, + chunkGroupCounters.get(chunkGroup).index++ + ); + } + } + + if (module.index === null) { + module.index = nextFreeModuleIndex++; + } + + queue.push({ + action: LEAVE_MODULE, + block, + module, + chunk, + chunkGroup + }); + } + // fallthrough + case PROCESS_BLOCK: { + // get prepared block info + const blockInfo = blockInfoMap.get(block); + + // Traverse all referenced modules + for (let i = blockInfo.modules.length - 1; i >= 0; i--) { + const refModule = blockInfo.modules[i]; + if (chunk.containsModule(refModule)) { + // skip early if already connected + continue; + } + // enqueue the add and enter to enter in the correct order + // this is relevant with circular dependencies + queue.push({ + action: ADD_AND_ENTER_MODULE, + block: refModule, + module: refModule, + chunk, + chunkGroup + }); + } + + // Traverse all Blocks + iterationOfArrayCallback(blockInfo.blocks, iteratorBlock); + + if (blockInfo.blocks.length > 0 && module !== block) { + blocksWithNestedBlocks.add(block); + } + break; + } + case LEAVE_MODULE: { + if (chunkGroup !== undefined) { + const index = chunkGroup.getModuleIndex2(module); + if (index === undefined) { + chunkGroup.setModuleIndex2( + module, + chunkGroupCounters.get(chunkGroup).index2++ + ); + } + } + + if (module.index2 === null) { + module.index2 = nextFreeModuleIndex2++; + } + break; + } + } + } + const tempQueue = queue; + queue = queueDelayed.reverse(); + queueDelayed = tempQueue; + } + + // PART TWO + /** @type {Set} */ + let availableModules; + let newAvailableModules; + /** @type {Queue} */ + const queue2 = new Queue( + inputChunkGroups.map(chunkGroup => ({ + chunkGroup, + availableModules: new Set() + })) + ); + + /** + * Helper function to check if all modules of a chunk are available + * + * @param {ChunkGroup} chunkGroup the chunkGroup to scan + * @param {Set} availableModules the comparitor set + * @returns {boolean} return true if all modules of a chunk are available + */ + const areModulesAvailable = (chunkGroup, availableModules) => { + for (const chunk of chunkGroup.chunks) { + for (const module of chunk.modulesIterable) { + if (!availableModules.has(module)) return false; + } + } + return true; + }; + + // For each edge in the basic chunk graph + /** + * @param {TODO} dep the dependency used for filtering + * @returns {boolean} used to filter "edges" (aka Dependencies) that were pointing + * to modules that are already available. Also filters circular dependencies in the chunks graph + */ + const filterFn = dep => { + const depChunkGroup = dep.chunkGroup; + if (blocksWithNestedBlocks.has(dep.block)) return true; + if (areModulesAvailable(depChunkGroup, newAvailableModules)) return false; // break all modules are already available + return true; + }; + + /** @type {Map>} */ + const minAvailableModulesMap = new Map(); + + // Iterative traversing of the basic chunk graph + while (queue2.length) { + const queueItem = queue2.dequeue(); + chunkGroup = queueItem.chunkGroup; + availableModules = queueItem.availableModules; + + // 1. Get minimal available modules + // It doesn't make sense to traverse a chunk again with more available modules. + // This step calculates the minimal available modules and skips traversal when + // the list didn't shrink. + let minAvailableModules = minAvailableModulesMap.get(chunkGroup); + if (minAvailableModules === undefined) { + minAvailableModulesMap.set(chunkGroup, new Set(availableModules)); + } else { + let deletedModules = false; + for (const m of minAvailableModules) { + if (!availableModules.has(m)) { + minAvailableModules.delete(m); + deletedModules = true; + } + } + if (!deletedModules) continue; + availableModules = minAvailableModules; + } + + // 2. Get the edges at this point of the graph + const deps = chunkDependencies.get(chunkGroup); + if (!deps) continue; + if (deps.length === 0) continue; + + // 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) { + newAvailableModules.add(m); + } + } + + // 4. Filter edges with available modules + const filteredDeps = deps.filter(filterFn); + + // 5. Foreach remaining edge + const nextChunkGroups = new Set(); + for (let i = 0; i < filteredDeps.length; i++) { + const dep = filteredDeps[i]; + const depChunkGroup = dep.chunkGroup; + const depBlock = dep.block; + + // 6. Connect block with chunk + GraphHelpers.connectDependenciesBlockAndChunkGroup( + depBlock, + depChunkGroup + ); + + // 7. Connect chunk with parent + GraphHelpers.connectChunkGroupParentAndChild(chunkGroup, depChunkGroup); + + nextChunkGroups.add(depChunkGroup); + } + + // 8. Enqueue further traversal + for (const nextChunkGroup of nextChunkGroups) { + queue2.enqueue({ + chunkGroup: nextChunkGroup, + availableModules: newAvailableModules + }); + } + } + + // Remove all unconnected chunk groups + for (const chunkGroup of allCreatedChunkGroups) { + if (chunkGroup.getNumberOfParents() === 0) { + for (const chunk of chunkGroup.chunks) { + const idx = this.chunks.indexOf(chunk); + if (idx >= 0) this.chunks.splice(idx, 1); + chunk.remove("unconnected"); + } + chunkGroup.remove("unconnected"); + } + } + } + + /** + * + * @param {Module} module module relationship for removal + * @param {DependenciesBlockLike} block //TODO: good description + * @returns {void} + */ + removeReasonsOfDependencyBlock(module, block) { + const iteratorDependency = d => { + if (!d.module) { + return; + } + if (d.module.removeReason(module, d)) { + for (const chunk of d.module.chunksIterable) { + this.patchChunksAfterReasonRemoval(d.module, chunk); + } + } + }; + + if (block.blocks) { + iterationOfArrayCallback(block.blocks, block => + this.removeReasonsOfDependencyBlock(module, block) + ); + } + + if (block.dependencies) { + iterationOfArrayCallback(block.dependencies, iteratorDependency); + } + + if (block.variables) { + iterationBlockVariable(block.variables, iteratorDependency); + } + } + + /** + * @param {Module} module module to patch tie + * @param {Chunk} chunk chunk to patch tie + * @returns {void} + */ + patchChunksAfterReasonRemoval(module, chunk) { + if (!module.hasReasons()) { + this.removeReasonsOfDependencyBlock(module, module); + } + if (!module.hasReasonForChunk(chunk)) { + if (module.removeChunk(chunk)) { + this.removeChunkFromDependencies(module, chunk); + } + } + } + + /** + * + * @param {DependenciesBlock} block block tie for Chunk + * @param {Chunk} chunk chunk to remove from dep + * @returns {void} + */ + removeChunkFromDependencies(block, chunk) { + const iteratorDependency = d => { + if (!d.module) { + return; + } + this.patchChunksAfterReasonRemoval(d.module, chunk); + }; + + const blocks = block.blocks; + for (let indexBlock = 0; indexBlock < blocks.length; indexBlock++) { + const asyncBlock = blocks[indexBlock]; + // Grab all chunks from the first Block's AsyncDepBlock + const chunks = asyncBlock.chunkGroup.chunks; + // For each chunk in chunkGroup + for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { + const iteratedChunk = chunks[indexChunk]; + asyncBlock.chunkGroup.removeChunk(iteratedChunk); + asyncBlock.chunkGroup.removeParent(iteratedChunk); + // Recurse + this.removeChunkFromDependencies(block, iteratedChunk); + } + } + + if (block.dependencies) { + iterationOfArrayCallback(block.dependencies, iteratorDependency); + } + + if (block.variables) { + iterationBlockVariable(block.variables, iteratorDependency); + } + } + + applyModuleIds() { + const unusedIds = []; + let nextFreeModuleId = 0; + const usedIds = new Set(); + if (this.usedModuleIds) { + for (const id of this.usedModuleIds) { + usedIds.add(id); + } + } + + const modules1 = this.modules; + for (let indexModule1 = 0; indexModule1 < modules1.length; indexModule1++) { + const module1 = modules1[indexModule1]; + if (module1.id !== null) { + usedIds.add(module1.id); + } + } + + if (usedIds.size > 0) { + let usedIdMax = -1; + for (const usedIdKey of usedIds) { + if (typeof usedIdKey !== "number") { + continue; + } + + usedIdMax = Math.max(usedIdMax, usedIdKey); + } + + let lengthFreeModules = (nextFreeModuleId = usedIdMax + 1); + + while (lengthFreeModules--) { + if (!usedIds.has(lengthFreeModules)) { + unusedIds.push(lengthFreeModules); + } + } + } + + const modules2 = this.modules; + for (let indexModule2 = 0; indexModule2 < modules2.length; indexModule2++) { + const module2 = modules2[indexModule2]; + if (module2.id === null) { + if (unusedIds.length > 0) { + module2.id = unusedIds.pop(); + } else { + module2.id = nextFreeModuleId++; + } + } + } + } + + applyChunkIds() { + /** @type {Set} */ + const usedIds = new Set(); + + // Get used ids from usedChunkIds property (i. e. from records) + if (this.usedChunkIds) { + for (const id of this.usedChunkIds) { + if (typeof id !== "number") { + continue; + } + + usedIds.add(id); + } + } + + // Get used ids from existing chunks + const chunks = this.chunks; + for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { + const chunk = chunks[indexChunk]; + const usedIdValue = chunk.id; + + if (typeof usedIdValue !== "number") { + continue; + } + + usedIds.add(usedIdValue); + } + + // Calculate maximum assigned chunk id + let nextFreeChunkId = -1; + for (const id of usedIds) { + nextFreeChunkId = Math.max(nextFreeChunkId, id); + } + nextFreeChunkId++; + + // Determine free chunk ids from 0 to maximum + /** @type {number[]} */ + const unusedIds = []; + if (nextFreeChunkId > 0) { + let index = nextFreeChunkId; + while (index--) { + if (!usedIds.has(index)) { + unusedIds.push(index); + } + } + } + + // Assign ids to chunk which has no id + for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { + const chunk = chunks[indexChunk]; + if (chunk.id === null) { + if (unusedIds.length > 0) { + chunk.id = unusedIds.pop(); + } else { + chunk.id = nextFreeChunkId++; + } + } + if (!chunk.ids) { + chunk.ids = [chunk.id]; + } + } + } + + 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() { + for (const chunkGroup of this.chunkGroups) { + chunkGroup.sortItems(); + } + + 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 + * updated whenever other references (having a chunk id) are sorted. This preserves the hash + * integrity + * + * @param {WebpackError} a first WebpackError instance (including subclasses) + * @param {WebpackError} b second WebpackError instance (including subclasses) + * @returns {-1|0|1} sort order index + */ + const byMessage = (a, b) => { + const ma = `${a.message}`; + const mb = `${b.message}`; + if (ma < mb) return -1; + if (mb < ma) return 1; + return 0; + }; + + this.errors.sort(byMessage); + this.warnings.sort(byMessage); + this.children.sort(byNameOrHash); + } + + summarizeDependencies() { + this.fileDependencies = new SortableSet(this.compilationDependencies); + this.contextDependencies = new SortableSet(); + this.missingDependencies = new SortableSet(); + + for ( + let indexChildren = 0; + indexChildren < this.children.length; + indexChildren++ + ) { + const child = this.children[indexChildren]; + + addAllToSet(this.fileDependencies, child.fileDependencies); + addAllToSet(this.contextDependencies, child.contextDependencies); + addAllToSet(this.missingDependencies, child.missingDependencies); + } + + for ( + let indexModule = 0; + indexModule < this.modules.length; + indexModule++ + ) { + const module = this.modules[indexModule]; + + if (module.buildInfo.fileDependencies) { + addAllToSet(this.fileDependencies, module.buildInfo.fileDependencies); + } + if (module.buildInfo.contextDependencies) { + addAllToSet( + this.contextDependencies, + module.buildInfo.contextDependencies + ); + } + } + for (const error of this.errors) { + if ( + typeof error.missing === "object" && + error.missing && + error.missing[Symbol.iterator] + ) { + addAllToSet(this.missingDependencies, error.missing); + } + } + this.fileDependencies.sort(); + this.contextDependencies.sort(); + this.missingDependencies.sort(); + } + + createHash() { + const outputOptions = this.outputOptions; + const hashFunction = outputOptions.hashFunction; + const hashDigest = outputOptions.hashDigest; + const hashDigestLength = outputOptions.hashDigestLength; + const hash = createHash(hashFunction); + if (outputOptions.hashSalt) { + hash.update(outputOptions.hashSalt); + } + this.mainTemplate.updateHash(hash); + this.chunkTemplate.updateHash(hash); + for (const key of Object.keys(this.moduleTemplates).sort()) { + this.moduleTemplates[key].updateHash(hash); + } + for (const child of this.children) { + hash.update(child.hash); + } + for (const warning of this.warnings) { + hash.update(`${warning.message}`); + } + for (const error of this.errors) { + hash.update(`${error.message}`); + } + const modules = this.modules; + for (let i = 0; i < modules.length; i++) { + const module = modules[i]; + const moduleHash = createHash(hashFunction); + module.updateHash(moduleHash); + module.hash = moduleHash.digest(hashDigest); + module.renderedHash = module.hash.substr(0, hashDigestLength); + } + // clone needed as sort below is inplace mutation + const chunks = this.chunks.slice(); + /** + * sort here will bring all "falsy" values to the beginning + * this is needed as the "hasRuntime()" chunks are dependent on the + * hashes of the non-runtime chunks. + */ + chunks.sort((a, b) => { + const aEntry = a.hasRuntime(); + const bEntry = b.hasRuntime(); + if (aEntry && !bEntry) return 1; + if (!aEntry && bEntry) return -1; + return byId(a, b); + }); + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + const chunkHash = createHash(hashFunction); + if (outputOptions.hashSalt) { + chunkHash.update(outputOptions.hashSalt); + } + chunk.updateHash(chunkHash); + const template = chunk.hasRuntime() + ? this.mainTemplate + : this.chunkTemplate; + template.updateHashForChunk( + chunkHash, + chunk, + this.moduleTemplates.javascript, + this.dependencyTemplates + ); + this.hooks.chunkHash.call(chunk, chunkHash); + chunk.hash = chunkHash.digest(hashDigest); + hash.update(chunk.hash); + chunk.renderedHash = chunk.hash.substr(0, hashDigestLength); + this.hooks.contentHash.call(chunk); + } + this.fullHash = hash.digest(hashDigest); + this.hash = this.fullHash.substr(0, hashDigestLength); + } + + /** + * @param {string} update extra information + * @returns {void} + */ + modifyHash(update) { + const outputOptions = this.outputOptions; + const hashFunction = outputOptions.hashFunction; + const hashDigest = outputOptions.hashDigest; + const hashDigestLength = outputOptions.hashDigestLength; + const hash = createHash(hashFunction); + hash.update(this.fullHash); + hash.update(update); + this.fullHash = hash.digest(hashDigest); + this.hash = this.fullHash.substr(0, hashDigestLength); + } + + createModuleAssets() { + for (let i = 0; i < this.modules.length; i++) { + const module = this.modules[i]; + if (module.buildInfo.assets) { + for (const assetName of Object.keys(module.buildInfo.assets)) { + const fileName = this.getPath(assetName); + this.assets[fileName] = module.buildInfo.assets[assetName]; + this.hooks.moduleAsset.call(module, fileName); + } + } + } + } + + createChunkAssets() { + const outputOptions = this.outputOptions; + const cachedSourceMap = new Map(); + /** @type {Map} */ + const alreadyWrittenFiles = new Map(); + for (let i = 0; i < this.chunks.length; i++) { + const chunk = this.chunks[i]; + chunk.files = []; + let source; + let file; + let filenameTemplate; + try { + const template = chunk.hasRuntime() + ? this.mainTemplate + : this.chunkTemplate; + const manifest = template.getRenderManifest({ + chunk, + hash: this.hash, + fullHash: this.fullHash, + outputOptions, + moduleTemplates: this.moduleTemplates, + dependencyTemplates: this.dependencyTemplates + }); // [{ render(), filenameTemplate, pathOptions, identifier, hash }] + for (const fileManifest of manifest) { + const cacheName = fileManifest.identifier; + const usedHash = fileManifest.hash; + filenameTemplate = fileManifest.filenameTemplate; + file = this.getPath(filenameTemplate, fileManifest.pathOptions); + + // check if the same filename was already written by another chunk + const alreadyWritten = alreadyWrittenFiles.get(file); + if (alreadyWritten !== undefined) { + if (alreadyWritten.hash === usedHash) { + if (this.cache) { + this.cache[cacheName] = { + hash: usedHash, + source: alreadyWritten.source + }; + } + chunk.files.push(file); + this.hooks.chunkAsset.call(chunk, file); + continue; + } else { + throw new Error( + `Conflict: Multiple chunks emit assets to the same filename ${file}` + + ` (chunks ${alreadyWritten.chunk.id} and ${chunk.id})` + ); + } + } + if ( + this.cache && + this.cache[cacheName] && + this.cache[cacheName].hash === usedHash + ) { + source = this.cache[cacheName].source; + } else { + source = fileManifest.render(); + // Ensure that source is a cached source to avoid additional cost because of repeated access + if (!(source instanceof CachedSource)) { + const cacheEntry = cachedSourceMap.get(source); + if (cacheEntry) { + source = cacheEntry; + } else { + const cachedSource = new CachedSource(source); + cachedSourceMap.set(source, cachedSource); + source = cachedSource; + } + } + if (this.cache) { + this.cache[cacheName] = { + hash: usedHash, + source + }; + } + } + if (this.assets[file] && this.assets[file] !== source) { + throw new Error( + `Conflict: Multiple assets emit to the same filename ${file}` + ); + } + this.assets[file] = source; + chunk.files.push(file); + this.hooks.chunkAsset.call(chunk, file); + alreadyWrittenFiles.set(file, { + hash: usedHash, + source, + chunk + }); + } + } catch (err) { + this.errors.push( + new ChunkRenderError(chunk, file || filenameTemplate, err) + ); + } + } + } + + /** + * @param {string} filename used to get asset path with hash + * @param {TODO=} data // TODO: figure out this param type + * @returns {string} interpolated path + */ + getPath(filename, data) { + data = data || {}; + data.hash = data.hash || this.hash; + return this.mainTemplate.getAssetPath(filename, data); + } + + /** + * This function allows you to run another instance of webpack inside of webpack however as + * a child with different settings and configurations (if desired) applied. It copies all hooks, plugins + * from parent (or top level compiler) and creates a child Compilation + * + * @param {string} name name of the child compiler + * @param {TODO} outputOptions // Need to convert config schema to types for this + * @param {Plugin[]} plugins webpack plugins that will be applied + * @returns {Compiler} creates a child Compiler instance + */ + createChildCompiler(name, outputOptions, plugins) { + const idx = this.childrenCounters[name] || 0; + this.childrenCounters[name] = idx + 1; + return this.compiler.createChildCompiler( + this, + name, + idx, + outputOptions, + plugins + ); + } + + checkConstraints() { + /** @type {Set} */ + const usedIds = new Set(); + + const modules = this.modules; + for (let indexModule = 0; indexModule < modules.length; indexModule++) { + const moduleId = modules[indexModule].id; + if (moduleId === null) continue; + if (usedIds.has(moduleId)) { + throw new Error(`checkConstraints: duplicate module id ${moduleId}`); + } + usedIds.add(moduleId); + } + + const chunks = this.chunks; + for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { + const chunk = chunks[indexChunk]; + if (chunks.indexOf(chunk) !== indexChunk) { + throw new Error( + `checkConstraints: duplicate chunk in compilation ${chunk.debugId}` + ); + } + } + + for (const chunkGroup of this.chunkGroups) { + chunkGroup.checkConstraints(); + } + } +} + +// TODO remove in webpack 5 +Compilation.prototype.applyPlugins = util.deprecate( + /** + * @deprecated + * @param {string} name Name + * @param {any[]} args Other arguments + * @returns {void} + * @this {Compilation} + */ + function(name, ...args) { + this.hooks[ + name.replace(/[- ]([a-z])/g, match => match[1].toUpperCase()) + ].call(...args); + }, + "Compilation.applyPlugins is deprecated. Use new API on `.hooks` instead" +); + +// TODO remove in webpack 5 +Object.defineProperty(Compilation.prototype, "moduleTemplate", { + configurable: false, + get: util.deprecate( + /** + * @deprecated + * @this {Compilation} + * @returns {TODO} module template + */ + function() { + return this.moduleTemplates.javascript; + }, + "Compilation.moduleTemplate: Use Compilation.moduleTemplates.javascript instead" + ), + set: util.deprecate( + /** + * @deprecated + * @param {ModuleTemplate} value Template value + * @this {Compilation} + * @returns {void} + */ + function(value) { + this.moduleTemplates.javascript = value; + }, + "Compilation.moduleTemplate: Use Compilation.moduleTemplates.javascript instead." + ) +}); + +module.exports = Compilation; -- cgit v1.2.3