/* 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 Stats = require("./Stats"); const Semaphore = require("./util/Semaphore"); const createHash = require("./util/createHash"); const SortableSet = require("./util/SortableSet"); const GraphHelpers = require("./GraphHelpers"); const ModuleDependency = require("./dependencies/ModuleDependency"); const compareLocations = require("./compareLocations"); const { Logger, LogType } = require("./logging/Logger"); const ErrorHelpers = require("./ErrorHelpers"); const buildChunkGraph = require("./buildChunkGraph"); const WebpackError = require("./WebpackError"); /** @typedef {import("./Module")} Module */ /** @typedef {import("./Compiler")} Compiler */ /** @typedef {import("webpack-sources").Source} Source */ /** @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} DependenciesBlockLike * @property {Dependency[]} dependencies * @property {AsyncDependenciesBlock[]} blocks * @property {DependenciesBlockVariable[]} variables */ /** * @typedef {Object} LogEntry * @property {string} type * @property {any[]} args * @property {number} time * @property {string[]=} trace */ /** * @typedef {Object} AssetInfo * @property {boolean=} immutable true, if the asset can be long term cached forever (contains a hash) * @property {number=} size size in bytes, only set after asset has been emitted * @property {boolean=} development true, when asset is only used for development and doesn't count towards user-facing assets * @property {boolean=} hotModuleReplacement true, when asset ships data for updating an existing application (HMR) */ /** * @typedef {Object} Asset * @property {string} name the filename of the asset * @property {Source} source source of the asset * @property {AssetInfo} info info about the asset */ /** * @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 (typeof a.id !== typeof b.id) { return typeof a.id < typeof b.id ? -1 : 1; } 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 (typeof a.id !== typeof b.id) { return typeof a.id < typeof b.id ? -1 : 1; } 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<T>} set set to add items to * @param {Set<T>} otherSet set to add items from * @returns {void} */ const addAllToSet = (set, otherSet) => { for (const item of otherSet) { set.add(item); } }; /** * @param {Source} a a source * @param {Source} b another source * @returns {boolean} true, when both sources are equal */ const isSourceEqual = (a, b) => { if (a === b) return true; // TODO webpack 5: check .buffer() instead, it's called anyway during emit /** @type {Buffer|string} */ let aSource = a.source(); /** @type {Buffer|string} */ let bSource = b.source(); if (aSource === bSource) return true; if (typeof aSource === "string" && typeof bSource === "string") return false; if (!Buffer.isBuffer(aSource)) aSource = Buffer.from(aSource, "utf-8"); if (!Buffer.isBuffer(bSource)) bSource = Buffer.from(bSource, "utf-8"); return aSource.equals(bSource); }; 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<Module>} */ buildModule: new SyncHook(["module"]), /** @type {SyncHook<Module>} */ rebuildModule: new SyncHook(["module"]), /** @type {SyncHook<Module, Error>} */ failedModule: new SyncHook(["module", "error"]), /** @type {SyncHook<Module>} */ succeedModule: new SyncHook(["module"]), /** @type {SyncHook<Dependency, string>} */ addEntry: new SyncHook(["entry", "name"]), /** @type {SyncHook<Dependency, string, Error>} */ failedEntry: new SyncHook(["entry", "name", "error"]), /** @type {SyncHook<Dependency, string, Module>} */ succeedEntry: new SyncHook(["entry", "name", "module"]), /** @type {SyncWaterfallHook<DependencyReference, Dependency, Module>} */ dependencyReference: new SyncWaterfallHook([ "dependencyReference", "dependency", "module" ]), /** @type {AsyncSeriesHook<Module[]>} */ finishModules: new AsyncSeriesHook(["modules"]), /** @type {SyncHook<Module>} */ finishRebuildingModule: new SyncHook(["module"]), /** @type {SyncHook} */ unseal: new SyncHook([]), /** @type {SyncHook} */ seal: new SyncHook([]), /** @type {SyncHook} */ beforeChunks: new SyncHook([]), /** @type {SyncHook<Chunk[]>} */ afterChunks: new SyncHook(["chunks"]), /** @type {SyncBailHook<Module[]>} */ optimizeDependenciesBasic: new SyncBailHook(["modules"]), /** @type {SyncBailHook<Module[]>} */ optimizeDependencies: new SyncBailHook(["modules"]), /** @type {SyncBailHook<Module[]>} */ optimizeDependenciesAdvanced: new SyncBailHook(["modules"]), /** @type {SyncBailHook<Module[]>} */ afterOptimizeDependencies: new SyncHook(["modules"]), /** @type {SyncHook} */ optimize: new SyncHook([]), /** @type {SyncBailHook<Module[]>} */ optimizeModulesBasic: new SyncBailHook(["modules"]), /** @type {SyncBailHook<Module[]>} */ optimizeModules: new SyncBailHook(["modules"]), /** @type {SyncBailHook<Module[]>} */ optimizeModulesAdvanced: new SyncBailHook(["modules"]), /** @type {SyncHook<Module[]>} */ afterOptimizeModules: new SyncHook(["modules"]), /** @type {SyncBailHook<Chunk[], ChunkGroup[]>} */ optimizeChunksBasic: new SyncBailHook(["chunks", "chunkGroups"]), /** @type {SyncBailHook<Chunk[], ChunkGroup[]>} */ optimizeChunks: new SyncBailHook(["chunks", "chunkGroups"]), /** @type {SyncBailHook<Chunk[], ChunkGroup[]>} */ optimizeChunksAdvanced: new SyncBailHook(["chunks", "chunkGroups"]), /** @type {SyncHook<Chunk[], ChunkGroup[]>} */ afterOptimizeChunks: new SyncHook(["chunks", "chunkGroups"]), /** @type {AsyncSeriesHook<Chunk[], Module[]>} */ optimizeTree: new AsyncSeriesHook(["chunks", "modules"]), /** @type {SyncHook<Chunk[], Module[]>} */ afterOptimizeTree: new SyncHook(["chunks", "modules"]), /** @type {SyncBailHook<Chunk[], Module[]>} */ optimizeChunkModulesBasic: new SyncBailHook(["chunks", "modules"]), /** @type {SyncBailHook<Chunk[], Module[]>} */ optimizeChunkModules: new SyncBailHook(["chunks", "modules"]), /** @type {SyncBailHook<Chunk[], Module[]>} */ optimizeChunkModulesAdvanced: new SyncBailHook(["chunks", "modules"]), /** @type {SyncHook<Chunk[], Module[]>} */ afterOptimizeChunkModules: new SyncHook(["chunks", "modules"]), /** @type {SyncBailHook} */ shouldRecord: new SyncBailHook([]), /** @type {SyncHook<Module[], any>} */ reviveModules: new SyncHook(["modules", "records"]), /** @type {SyncHook<Module[]>} */ optimizeModuleOrder: new SyncHook(["modules"]), /** @type {SyncHook<Module[]>} */ advancedOptimizeModuleOrder: new SyncHook(["modules"]), /** @type {SyncHook<Module[]>} */ beforeModuleIds: new SyncHook(["modules"]), /** @type {SyncHook<Module[]>} */ moduleIds: new SyncHook(["modules"]), /** @type {SyncHook<Module[]>} */ optimizeModuleIds: new SyncHook(["modules"]), /** @type {SyncHook<Module[]>} */ afterOptimizeModuleIds: new SyncHook(["modules"]), /** @type {SyncHook<Chunk[], any>} */ reviveChunks: new SyncHook(["chunks", "records"]), /** @type {SyncHook<Chunk[]>} */ optimizeChunkOrder: new SyncHook(["chunks"]), /** @type {SyncHook<Chunk[]>} */ beforeChunkIds: new SyncHook(["chunks"]), /** @type {SyncHook<Chunk[]>} */ optimizeChunkIds: new SyncHook(["chunks"]), /** @type {SyncHook<Chunk[]>} */ afterOptimizeChunkIds: new SyncHook(["chunks"]), /** @type {SyncHook<Module[], any>} */ recordModules: new SyncHook(["modules", "records"]), /** @type {SyncHook<Chunk[], any>} */ recordChunks: new SyncHook(["chunks", "records"]), /** @type {SyncHook} */ beforeHash: new SyncHook([]), /** @type {SyncHook<Chunk>} */ contentHash: new SyncHook(["chunk"]), /** @type {SyncHook} */ afterHash: new SyncHook([]), /** @type {SyncHook<any>} */ recordHash: new SyncHook(["records"]), /** @type {SyncHook<Compilation, any>} */ record: new SyncHook(["compilation", "records"]), /** @type {SyncHook} */ beforeModuleAssets: new SyncHook([]), /** @type {SyncBailHook} */ shouldGenerateChunkAssets: new SyncBailHook([]), /** @type {SyncHook} */ beforeChunkAssets: new SyncHook([]), /** @type {SyncHook<Chunk[]>} */ additionalChunkAssets: new SyncHook(["chunks"]), /** @type {AsyncSeriesHook} */ additionalAssets: new AsyncSeriesHook([]), /** @type {AsyncSeriesHook<Chunk[]>} */ optimizeChunkAssets: new AsyncSeriesHook(["chunks"]), /** @type {SyncHook<Chunk[]>} */ afterOptimizeChunkAssets: new SyncHook(["chunks"]), /** @type {AsyncSeriesHook<CompilationAssets>} */ optimizeAssets: new AsyncSeriesHook(["assets"]), /** @type {SyncHook<CompilationAssets>} */ afterOptimizeAssets: new SyncHook(["assets"]), /** @type {SyncBailHook} */ needAdditionalSeal: new SyncBailHook([]), /** @type {AsyncSeriesHook} */ afterSeal: new AsyncSeriesHook([]), /** @type {SyncHook<Chunk, Hash>} */ chunkHash: new SyncHook(["chunk", "chunkHash"]), /** @type {SyncHook<Module, string>} */ moduleAsset: new SyncHook(["module", "filename"]), /** @type {SyncHook<Chunk, string>} */ chunkAsset: new SyncHook(["chunk", "filename"]), /** @type {SyncWaterfallHook<string, TODO>} */ assetPath: new SyncWaterfallHook(["filename", "data"]), // TODO MainTemplate /** @type {SyncBailHook} */ needAdditionalPass: new SyncBailHook([]), /** @type {SyncHook<Compiler, string, number>} */ childCompiler: new SyncHook([ "childCompiler", "compilerName", "compilerIndex" ]), /** @type {SyncBailHook<string, LogEntry>} */ log: new SyncBailHook(["origin", "logEntry"]), // TODO the following hooks are weirdly located here // TODO move them for webpack 5 /** @type {SyncHook<object, Module>} */ normalModuleLoader: new SyncHook(["loaderContext", "module"]), /** @type {SyncBailHook<Chunk[]>} */ optimizeExtractedChunksBasic: new SyncBailHook(["chunks"]), /** @type {SyncBailHook<Chunk[]>} */ optimizeExtractedChunks: new SyncBailHook(["chunks"]), /** @type {SyncBailHook<Chunk[]>} */ optimizeExtractedChunksAdvanced: new SyncBailHook(["chunks"]), /** @type {SyncHook<Chunk[]>} */ 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 = compiler.options; this.options = 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 = []; /** @type {Map<string, Entrypoint>} */ this.entrypoints = new Map(); /** @type {Chunk[]} */ this.chunks = []; /** @type {ChunkGroup[]} */ this.chunkGroups = []; /** @type {Map<string, ChunkGroup>} */ this.namedChunkGroups = new Map(); /** @type {Map<string, Chunk>} */ this.namedChunks = new Map(); /** @type {Module[]} */ this.modules = []; /** @private @type {Map<string, Module>} */ this._modules = new Map(); this.cache = null; this.records = null; /** @type {string[]} */ this.additionalChunkAssets = []; /** @type {CompilationAssets} */ this.assets = {}; /** @type {Map<string, AssetInfo>} */ this.assetsInfo = new Map(); /** @type {WebpackError[]} */ this.errors = []; /** @type {WebpackError[]} */ this.warnings = []; /** @type {Compilation[]} */ this.children = []; /** @type {Map<string, LogEntry[]>} */ this.logging = new Map(); /** @type {Map<DepConstructor, ModuleFactory>} */ this.dependencyFactories = new Map(); /** @type {Map<DepConstructor, DependencyTemplate>} */ 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<number|string>} */ this.usedChunkIds = null; /** @type {Set<number>} */ this.usedModuleIds = null; /** @type {Map<string, number>=} */ this.fileTimestamps = undefined; /** @type {Map<string, number>=} */ this.contextTimestamps = undefined; /** @type {Set<string>=} */ this.compilationDependencies = undefined; /** @private @type {Map<Module, Callback[]>} */ this._buildingModules = new Map(); /** @private @type {Map<Module, Callback[]>} */ this._rebuildingModules = new Map(); /** @type {Set<string>} */ this.emittedAssets = new Set(); } getStats() { return new Stats(this); } /** * @param {string | (function(): string)} name name of the logger, or function called once to get the logger name * @returns {Logger} a logger with that name */ getLogger(name) { if (!name) { throw new TypeError("Compilation.getLogger(name) called without a name"); } /** @type {LogEntry[] | undefined} */ let logEntries; return new Logger((type, args) => { if (typeof name === "function") { name = name(); if (!name) { throw new TypeError( "Compilation.getLogger(name) called with a function not returning a name" ); } } let trace; switch (type) { case LogType.warn: case LogType.error: case LogType.trace: trace = ErrorHelpers.cutOffLoaderExecution(new Error("Trace").stack) .split("\n") .slice(3); break; } /** @type {LogEntry} */ const logEntry = { time: Date.now(), type, args, trace }; if (this.hooks.log.call(name, logEntry) === undefined) { if (logEntry.type === LogType.profileEnd) { // eslint-disable-next-line node/no-unsupported-features/node-builtins if (typeof console.profileEnd === "function") { // eslint-disable-next-line node/no-unsupported-features/node-builtins console.profileEnd(`[${name}] ${logEntry.args[0]}`); } } if (logEntries === undefined) { logEntries = this.logging.get(name); if (logEntries === undefined) { logEntries = []; this.logging.set(name, logEntries); } } logEntries.push(logEntry); if (logEntry.type === LogType.profile) { // eslint-disable-next-line node/no-unsupported-features/node-builtins if (typeof console.profile === "function") { // eslint-disable-next-line node/no-unsupported-features/node-builtins console.profile(`[${name}] ${logEntry.args[0]}`); } } } }); } /** * @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); } const originalMap = module.dependencies.reduce((map, v, i) => { map.set(v, i); return map; }, new Map()); module.dependencies.sort((a, b) => { const cmp = compareLocations(a.loc, b.loc); if (cmp) return cmp; return originalMap.get(a) - originalMap.get(b); }); 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 (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 (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) { this.hooks.addEntry.call(entry, name); 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) { this.hooks.failedEntry.call(entry, name, err); return callback(err); } if (module) { slot.module = module; } else { const idx = this._preparedEntrypoints.indexOf(slot); if (idx >= 0) { this._preparedEntrypoints.splice(idx, 1); } } this.hooks.succeedEntry.call(entry, name, module); 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(callback) { const modules = this.modules; this.hooks.finishModules.callAsync(modules, err => { if (err) return callback(err); for (let index = 0; index < modules.length; index++) { const module = modules[index]; this.reportDependencyErrorsAndWarnings(module, [module]); } callback(); }); } 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 = {}; this.assetsInfo.clear(); 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); } buildChunkGraph( this, /** @type {Entrypoint[]} */ (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); } /** * * @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<number>} */ 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(); } chunks.sort((a, b) => a.compareTo(b)); } 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 = /** @type {string} */ (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); try { 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 = /** @type {string} */ (chunkHash.digest(hashDigest)); hash.update(chunk.hash); chunk.renderedHash = chunk.hash.substr(0, hashDigestLength); this.hooks.contentHash.call(chunk); } catch (err) { this.errors.push(new ChunkRenderError(chunk, "", err)); } } this.fullHash = /** @type {string} */ (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 = /** @type {string} */ (hash.digest(hashDigest)); this.hash = this.fullHash.substr(0, hashDigestLength); } /** * @param {string} file file name * @param {Source} source asset source * @param {AssetInfo} assetInfo extra asset information * @returns {void} */ emitAsset(file, source, assetInfo = {}) { if (this.assets[file]) { if (!isSourceEqual(this.assets[file], source)) { // TODO webpack 5: make this an error instead this.warnings.push( new WebpackError( `Conflict: Multiple assets emit different content to the same filename ${file}` ) ); this.assets[file] = source; this.assetsInfo.set(file, assetInfo); return; } const oldInfo = this.assetsInfo.get(file); this.assetsInfo.set(file, Object.assign({}, oldInfo, assetInfo)); return; } this.assets[file] = source; this.assetsInfo.set(file, assetInfo); } /** * @param {string} file file name * @param {Source | function(Source): Source} newSourceOrFunction new asset source or function converting old to new * @param {AssetInfo | function(AssetInfo | undefined): AssetInfo} assetInfoUpdateOrFunction new asset info or function converting old to new */ updateAsset( file, newSourceOrFunction, assetInfoUpdateOrFunction = undefined ) { if (!this.assets[file]) { throw new Error( `Called Compilation.updateAsset for not existing filename ${file}` ); } if (typeof newSourceOrFunction === "function") { this.assets[file] = newSourceOrFunction(this.assets[file]); } else { this.assets[file] = newSourceOrFunction; } if (assetInfoUpdateOrFunction !== undefined) { const oldInfo = this.assetsInfo.get(file); if (typeof assetInfoUpdateOrFunction === "function") { this.assetsInfo.set(file, assetInfoUpdateOrFunction(oldInfo || {})); } else { this.assetsInfo.set( file, Object.assign({}, oldInfo, assetInfoUpdateOrFunction) ); } } } getAssets() { /** @type {Asset[]} */ const array = []; for (const assetName of Object.keys(this.assets)) { if (Object.prototype.hasOwnProperty.call(this.assets, assetName)) { array.push({ name: assetName, source: this.assets[assetName], info: this.assetsInfo.get(assetName) || {} }); } } return array; } /** * @param {string} name the name of the asset * @returns {Asset | undefined} the asset or undefined when not found */ getAsset(name) { if (!Object.prototype.hasOwnProperty.call(this.assets, name)) return undefined; return { name, source: this.assets[name], info: this.assetsInfo.get(name) || {} }; } createModuleAssets() { for (let i = 0; i < this.modules.length; i++) { const module = this.modules[i]; if (module.buildInfo.assets) { const assetsInfo = module.buildInfo.assetsInfo; for (const assetName of Object.keys(module.buildInfo.assets)) { const fileName = this.getPath(assetName); this.emitAsset( fileName, module.buildInfo.assets[assetName], assetsInfo ? assetsInfo.get(assetName) : undefined ); this.hooks.moduleAsset.call(module, fileName); } } } } createChunkAssets() { const outputOptions = this.outputOptions; const cachedSourceMap = new Map(); /** @type {Map<string, {hash: string, source: Source, chunk: Chunk}>} */ 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; const pathAndInfo = this.getPathWithInfo( filenameTemplate, fileManifest.pathOptions ); file = pathAndInfo.path; const assetInfo = pathAndInfo.info; // 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 }; } } this.emitAsset(file, source, assetInfo); 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); } /** * @param {string} filename used to get asset path with hash * @param {TODO=} data // TODO: figure out this param type * @returns {{ path: string, info: AssetInfo }} interpolated path and asset info */ getPathWithInfo(filename, data) { data = data || {}; data.hash = data.hash || this.hash; return this.mainTemplate.getAssetPathWithInfo(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<number|string>} */ 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;