diff --git a/lib/Compilation.js b/lib/Compilation.js index 1e68fa5fa..d9c74c9ad 100644 --- a/lib/Compilation.js +++ b/lib/Compilation.js @@ -808,37 +808,44 @@ class Compilation { } } - // This is nested so we need to allow one additional task - this.processDependenciesQueue.increaseParallelism(); + if (sortedDependencies.length === 0) { + callback(); + return; + } - asyncLib.forEach( - sortedDependencies, - (item, callback) => { - this.handleModuleCreation( - { - factory: item.factory, - dependencies: item.dependencies, - originModule: module - }, - 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 && this.bail) { - // eslint-disable-next-line no-self-assign - err.stack = err.stack; - return callback(err); + process.nextTick(() => { + // This is nested so we need to allow one additional task + this.processDependenciesQueue.increaseParallelism(); + + asyncLib.forEach( + sortedDependencies, + (item, callback) => { + this.handleModuleCreation( + { + factory: item.factory, + dependencies: item.dependencies, + originModule: module + }, + 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 && this.bail) { + // eslint-disable-next-line no-self-assign + err.stack = err.stack; + return callback(err); + } + callback(); } - callback(); - } - ); - }, - err => { - this.processDependenciesQueue.decreaseParallelism(); + ); + }, + err => { + this.processDependenciesQueue.decreaseParallelism(); - return callback(err); - } - ); + return callback(err); + } + ); + }); } /** diff --git a/lib/Compiler.js b/lib/Compiler.js index a057f44b2..80eeb67cf 100644 --- a/lib/Compiler.js +++ b/lib/Compiler.js @@ -566,16 +566,18 @@ class Compiler { this.hooks.make.callAsync(compilation, err => { if (err) return callback(err); - compilation.finish(err => { - if (err) return callback(err); - - compilation.seal(err => { + process.nextTick(() => { + compilation.finish(err => { if (err) return callback(err); - this.hooks.afterCompile.callAsync(compilation, err => { + compilation.seal(err => { if (err) return callback(err); - return callback(null, compilation); + this.hooks.afterCompile.callAsync(compilation, err => { + if (err) return callback(err); + + return callback(null, compilation); + }); }); }); }); diff --git a/lib/NormalModuleFactory.js b/lib/NormalModuleFactory.js index f59b2fdf1..47c50e0a3 100644 --- a/lib/NormalModuleFactory.js +++ b/lib/NormalModuleFactory.js @@ -58,6 +58,14 @@ const loaderToIdent = data => { return data.loader + "?" + JSON.stringify(data.options); }; +const stringifyLoadersAndResource = (loaders, resource) => { + let str = ""; + for (const loader of loaders) { + str += loaderToIdent(loader) + "!"; + } + return str + resource; +}; + const identToLoaderRequest = resultString => { const idx = resultString.indexOf("?"); if (idx >= 0) { @@ -75,6 +83,18 @@ const identToLoaderRequest = resultString => { } }; +const needCalls = (times, callback) => { + return err => { + if (--times === 0) { + return callback(err); + } + if (err && times > 0) { + times = NaN; + return callback(err); + } + }; +}; + // TODO webpack 6 remove const deprecationChangedHookMessage = name => `NormalModuleFactory.${name} is no longer a waterfall hook, but a bailing hook instead. ` + @@ -196,25 +216,33 @@ class NormalModuleFactory extends ModuleFactory { const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request); if (matchResourceMatch) { matchResource = matchResourceMatch[1]; - if (/^\.\.?\//.test(matchResource)) { - matchResource = path.join(context, matchResource); + if (matchResource.charCodeAt(0) === 46) { + // 46 === ".", 47 === "/" + const secondChar = matchResource.charCodeAt(1); + if ( + secondChar === 47 || + (secondChar === 46 && matchResource.charCodeAt(2) === 47) + ) { + // if matchResources startsWith ../ or ./ + matchResource = path.join(context, matchResource); + } } requestWithoutMatchResource = request.substr( matchResourceMatch[0].length ); } - const noPreAutoLoaders = requestWithoutMatchResource.startsWith("-!"); - const noAutoLoaders = - noPreAutoLoaders || requestWithoutMatchResource.startsWith("!"); - const noPrePostAutoLoaders = requestWithoutMatchResource.startsWith( - "!!" - ); + const firstChar = requestWithoutMatchResource.charCodeAt(0); + const secondChar = requestWithoutMatchResource.charCodeAt(1); + const noPreAutoLoaders = firstChar === 45 && secondChar === 33; // startsWith "-!" + const noAutoLoaders = noPreAutoLoaders || firstChar === 33; // startsWith "!" + const noPrePostAutoLoaders = firstChar === 33 && secondChar === 33; // startsWith "!!"; const rawElements = requestWithoutMatchResource - .replace(/^-?!+/, "") - .replace(/!!+/g, "!") - .split("!"); - const resource = rawElements.pop(); + .slice( + noPreAutoLoaders || noPrePostAutoLoaders ? 2 : noAutoLoaders ? 1 : 0 + ) + .split(/!+/); + const unresolvedResource = rawElements.pop(); const elements = rawElements.map(identToLoaderRequest); const resolveContext = { @@ -224,192 +252,197 @@ class NormalModuleFactory extends ModuleFactory { contextDependencies }; - asyncLib.parallel( - [ - callback => - this.resolveRequestArray( - contextInfo, - context, - elements, - loaderResolver, - resolveContext, - callback - ), - callback => { - if (resource === "" || resource[0] === "?") { - return callback(null, { - resource - }); - } + /** @type {string | false} */ + let resource; + let resourceResolveData; + let loaders; - normalResolver.resolve( - contextInfo, - context, - resource, - resolveContext, - (err, resource, resourceResolveData) => { - if (err) return callback(err); + const continueCallback = needCalls(2, err => { + if (err) return callback(err); - // TODO remove this when enhanced-resolve supports fileDependencies - if (resource) { - fileDependencies.add(resource); - } - - callback(null, { - resourceResolveData, - resource - }); - } - ); - } - ], - (err, results) => { - if (err) return callback(err); - let loaders = results[0]; - const resourceResolveData = results[1].resourceResolveData; - const resource = results[1].resource; - - // translate option idents - try { - for (const item of loaders) { - if ( - typeof item.options === "string" && - item.options[0] === "?" - ) { - const ident = item.options.substr(1); - item.options = this.ruleSet.findOptionsByIdent(ident); - item.ident = ident; - } - } - } catch (e) { - return callback(e); - } - - if (resource === false) { - // ignored - return callback( - null, - new RawModule( - "/* (ignored) */", - `ignored|${request}`, - `${request} (ignored)` - ) - ); - } - - const userRequest = - (matchResource !== undefined ? `${matchResource}!=!` : "") + - loaders - .map(loaderToIdent) - .concat([resource]) - .join("!"); - - let resourcePath = - matchResource !== undefined ? matchResource : resource; - let resourceQuery = ""; - const queryIndex = resourcePath.indexOf("?"); - if (queryIndex >= 0) { - resourceQuery = resourcePath.substr(queryIndex); - resourcePath = resourcePath.substr(0, queryIndex); - } - - const result = this.ruleSet.exec({ - resource: resourcePath, - realResource: - matchResource !== undefined - ? resource.replace(/\?.*/, "") - : resourcePath, - resourceQuery, - issuer: contextInfo.issuer, - compiler: contextInfo.compiler - }); - const settings = {}; - const useLoadersPost = []; - const useLoaders = []; - const useLoadersPre = []; - for (const r of result) { - if (r.type === "use") { - if (r.enforce === "post" && !noPrePostAutoLoaders) { - useLoadersPost.push(r.value); - } else if ( - r.enforce === "pre" && - !noPreAutoLoaders && - !noPrePostAutoLoaders - ) { - useLoadersPre.push(r.value); - } else if ( - !r.enforce && - !noAutoLoaders && - !noPrePostAutoLoaders - ) { - useLoaders.push(r.value); - } - } else if ( - typeof r.value === "object" && - r.value !== null && - typeof settings[r.type] === "object" && - settings[r.type] !== null - ) { - settings[r.type] = cachedMerge(settings[r.type], r.value); - } else { - settings[r.type] = r.value; + // translate option idents + try { + for (const item of loaders) { + if (typeof item.options === "string" && item.options[0] === "?") { + const ident = item.options.substr(1); + item.options = this.ruleSet.findOptionsByIdent(ident); + item.ident = ident; } } - asyncLib.parallel( - [ - this.resolveRequestArray.bind( - this, - contextInfo, - this.context, - useLoadersPost, - loaderResolver, - resolveContext - ), - this.resolveRequestArray.bind( - this, - contextInfo, - this.context, - useLoaders, - loaderResolver, - resolveContext - ), - this.resolveRequestArray.bind( - this, - contextInfo, - this.context, - useLoadersPre, - loaderResolver, - resolveContext - ) - ], - (err, results) => { - if (err) { - return callback(err); - } - loaders = results[0].concat(loaders, results[1], results[2]); - const type = settings.type; - const resolveOptions = settings.resolve; - Object.assign(data.createData, { - request: loaders - .map(loaderToIdent) - .concat([resource]) - .join("!"), - userRequest, - rawRequest: request, - loaders, - resource, - matchResource, - resourceResolveData, - settings, - type, - parser: this.getParser(type, settings.parser), - generator: this.getGenerator(type, settings.generator), - resolveOptions - }); - callback(); - } + } catch (e) { + return callback(e); + } + + if (resource === false) { + // ignored + return callback( + null, + new RawModule( + "/* (ignored) */", + `ignored|${request}`, + `${request} (ignored)` + ) ); } + + const userRequest = + (matchResource !== undefined ? `${matchResource}!=!` : "") + + stringifyLoadersAndResource(loaders, resource); + + let resourcePath = + matchResource !== undefined ? matchResource : resource; + let resourceQuery = ""; + const queryIndex = resourcePath.indexOf("?"); + if (queryIndex >= 0) { + resourceQuery = resourcePath.substr(queryIndex); + resourcePath = resourcePath.substr(0, queryIndex); + } + + const result = this.ruleSet.exec({ + resource: resourcePath, + realResource: + matchResource !== undefined + ? resource.replace(/\?.*/, "") + : resourcePath, + resourceQuery, + issuer: contextInfo.issuer, + compiler: contextInfo.compiler + }); + const settings = {}; + const useLoadersPost = []; + const useLoaders = []; + const useLoadersPre = []; + for (const r of result) { + if (r.type === "use") { + if (r.enforce === "post" && !noPrePostAutoLoaders) { + useLoadersPost.push(r.value); + } else if ( + r.enforce === "pre" && + !noPreAutoLoaders && + !noPrePostAutoLoaders + ) { + useLoadersPre.push(r.value); + } else if ( + !r.enforce && + !noAutoLoaders && + !noPrePostAutoLoaders + ) { + useLoaders.push(r.value); + } + } else if ( + typeof r.value === "object" && + r.value !== null && + typeof settings[r.type] === "object" && + settings[r.type] !== null + ) { + settings[r.type] = cachedMerge(settings[r.type], r.value); + } else { + settings[r.type] = r.value; + } + } + + let postLoaders, normalLoaders, preLoaders; + + const continueCallback = needCalls(3, err => { + if (err) { + return callback(err); + } + const allLoaders = postLoaders; + for (const loader of loaders) allLoaders.push(loader); + for (const loader of normalLoaders) allLoaders.push(loader); + for (const loader of preLoaders) allLoaders.push(loader); + const type = settings.type; + const resolveOptions = settings.resolve; + Object.assign(data.createData, { + request: stringifyLoadersAndResource(allLoaders, resource), + userRequest, + rawRequest: request, + loaders: allLoaders, + resource, + matchResource, + resourceResolveData, + settings, + type, + parser: this.getParser(type, settings.parser), + generator: this.getGenerator(type, settings.generator), + resolveOptions + }); + callback(); + }); + this.resolveRequestArray( + contextInfo, + this.context, + useLoadersPost, + loaderResolver, + resolveContext, + (err, result) => { + postLoaders = result; + continueCallback(err); + } + ); + this.resolveRequestArray( + contextInfo, + this.context, + useLoaders, + loaderResolver, + resolveContext, + (err, result) => { + normalLoaders = result; + continueCallback(err); + } + ); + this.resolveRequestArray( + contextInfo, + this.context, + useLoadersPre, + loaderResolver, + resolveContext, + (err, result) => { + preLoaders = result; + continueCallback(err); + } + ); + }); + + this.resolveRequestArray( + contextInfo, + context, + elements, + loaderResolver, + resolveContext, + (err, result) => { + if (err) return continueCallback(err); + loaders = result; + continueCallback(); + } + ); + + if ( + unresolvedResource === "" || + unresolvedResource.charCodeAt(0) === 63 + ) { + // 63 === "?" + resource = unresolvedResource; + return continueCallback(); + } + + normalResolver.resolve( + contextInfo, + context, + unresolvedResource, + resolveContext, + (err, resolvedResource, resolvedResourceResolveData) => { + if (err) return continueCallback(err); + + // TODO remove this when enhanced-resolve supports fileDependencies + if (resolvedResource) { + fileDependencies.add(resolvedResource); + } + + resource = resolvedResource; + resourceResolveData = resolvedResourceResolveData; + continueCallback(); + } ); } ); @@ -484,7 +517,7 @@ class NormalModuleFactory extends ModuleFactory { resolveContext, callback ) { - if (array.length === 0) return callback(null, []); + if (array.length === 0) return callback(null, array); asyncLib.map( array, (item, callback) => { @@ -503,7 +536,7 @@ class NormalModuleFactory extends ModuleFactory { contextInfo, context, item.loader + "-loader", - {}, + resolveContext, err2 => { if (!err2) { err.message = @@ -521,18 +554,15 @@ class NormalModuleFactory extends ModuleFactory { } if (err) return callback(err); - const optionsOnly = item.options - ? { - options: item.options - } - : undefined; - - const resolved = Object.assign( - {}, - item, - identToLoaderRequest(result), - optionsOnly - ); + const parsedResult = identToLoaderRequest(result); + const resolved = { + loader: parsedResult.loader, + options: + item.options === undefined + ? parsedResult.options + : item.options, + ident: item.options === undefined ? undefined : item.ident + }; // TODO remove this when enhanced-resolve supports fileDependencies if (resolved.loader) {