UNPKG

webpack

Version:

Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

1,696 lines (1,530 loc) 67.7 kB
/* 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