report dependencies from resolver to the compilation

refactor NormalModuleFactory
This commit is contained in:
Tobias Koppers 2019-01-04 19:17:37 +01:00
parent b97bc4bc06
commit ef9e25d8ea
17 changed files with 555 additions and 354 deletions

View File

@ -62,6 +62,7 @@ const { arrayToSetDeprecation } = require("./util/deprecation");
/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("./DependencyTemplate")} DependencyTemplate */
/** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleFactory")} ModuleFactory */
/** @typedef {import("./RuntimeModule")} RuntimeModule */
/** @typedef {import("./Template").RenderManifestEntry} RenderManifestEntry */
/** @typedef {import("./WebpackError")} WebpackError */
@ -99,25 +100,6 @@ const { arrayToSetDeprecation } = require("./util/deprecation");
/** @typedef {new (...args: any[]) => Dependency} DepConstructor */
/** @typedef {Record<string, Source>} CompilationAssets */
/**
* @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} AvailableModulesChunkGroupMapping
* @property {ChunkGroup} chunkGroup
@ -552,8 +534,6 @@ class Compilation {
this.usedChunkIds = null;
/** @type {Set<number>} */
this.usedModuleIds = null;
/** @type {Set<string>=} */
this.compilationDependencies = undefined;
/** @type {boolean} */
this.needAdditionalPass = false;
/** @type {WeakSet<Module>} */
@ -562,6 +542,12 @@ class Compilation {
this._rebuildingModules = new Map();
/** @type {WeakSet<Source>} */
this.emittedAssets = new WeakSet();
/** @type {SortableSet<string>} */
this.fileDependencies = new SortableSet();
/** @type {SortableSet<string>} */
this.contextDependencies = new SortableSet();
/** @type {SortableSet<string>} */
this.missingDependencies = new SortableSet();
}
getStats() {
@ -999,7 +985,23 @@ class Compilation {
: this.compiler.context,
dependencies: dependencies
},
(err, newModule) => {
(err, result) => {
if (result) {
const {
fileDependencies,
contextDependencies,
missingDependencies
} = result;
if (fileDependencies) {
addAllToSet(this.fileDependencies, fileDependencies);
}
if (contextDependencies) {
addAllToSet(this.contextDependencies, contextDependencies);
}
if (missingDependencies) {
addAllToSet(this.missingDependencies, missingDependencies);
}
}
if (err) {
const notFoundError = new ModuleNotFoundError(
originModule,
@ -1008,6 +1010,10 @@ class Compilation {
);
return callback(notFoundError);
}
if (!result) {
return callback();
}
const newModule = result.module;
if (!newModule) {
return callback();
}
@ -2190,10 +2196,6 @@ class Compilation {
}
summarizeDependencies() {
this.fileDependencies = new SortableSet(this.compilationDependencies);
this.contextDependencies = new SortableSet();
this.missingDependencies = new SortableSet();
for (
let indexChildren = 0;
indexChildren < this.children.length;
@ -2207,16 +2209,16 @@ class Compilation {
}
for (const module of this.modules) {
if (module.buildInfo.fileDependencies) {
addAllToSet(this.fileDependencies, module.buildInfo.fileDependencies);
const fileDependencies = module.buildInfo.fileDependencies;
const contextDependencies = module.buildInfo.contextDependencies;
if (fileDependencies) {
addAllToSet(this.fileDependencies, fileDependencies);
}
if (module.buildInfo.contextDependencies) {
addAllToSet(
this.contextDependencies,
module.buildInfo.contextDependencies
);
if (contextDependencies) {
addAllToSet(this.contextDependencies, contextDependencies);
}
}
for (const error of this.errors) {
if (
typeof error.missing === "object" &&
@ -2226,6 +2228,7 @@ class Compilation {
addAllToSet(this.missingDependencies, error.missing);
}
}
this.fileDependencies.sort();
this.contextDependencies.sort();
this.missingDependencies.sort();

View File

@ -40,7 +40,6 @@ const { makePathsRelative } = require("./util/identifier");
* @typedef {Object} CompilationParams
* @property {NormalModuleFactory} normalModuleFactory
* @property {ContextModuleFactory} contextModuleFactory
* @property {Set<string>} compilationDependencies
*/
/**
@ -139,9 +138,9 @@ class Compiler {
this.records = {};
/** @type {Set<string>} */
this.removedFiles = new Set();
/** @type {Map<string, FileSystemInfoEntry>} */
/** @type {Map<string, FileSystemInfoEntry | null>} */
this.fileTimestamps = new Map();
/** @type {Map<string, FileSystemInfoEntry>} */
/** @type {Map<string, FileSystemInfoEntry | null>} */
this.contextTimestamps = new Map();
/** @type {ResolverFactory} */
this.resolverFactory = new ResolverFactory();
@ -522,7 +521,6 @@ class Compiler {
const compilation = this.createCompilation();
compilation.name = this.name;
compilation.records = this.records;
compilation.compilationDependencies = params.compilationDependencies;
this.hooks.thisCompilation.call(compilation, params);
this.hooks.compilation.call(compilation, params);
return compilation;
@ -547,8 +545,7 @@ class Compiler {
newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(),
contextModuleFactory: this.createContextModuleFactory(),
compilationDependencies: new Set()
contextModuleFactory: this.createContextModuleFactory()
};
return params;
}

View File

@ -10,14 +10,19 @@ const path = require("path");
const { AsyncSeriesWaterfallHook, SyncWaterfallHook } = require("tapable");
const ContextModule = require("./ContextModule");
const ModuleFactory = require("./ModuleFactory");
const ContextElementDependency = require("./dependencies/ContextElementDependency");
/** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
/** @typedef {import("./dependencies/ContextDependency")} ContextDependency */
const EMPTY_RESOLVE_OPTIONS = {};
module.exports = class ContextModuleFactory {
module.exports = class ContextModuleFactory extends ModuleFactory {
constructor(resolverFactory) {
super();
this.hooks = Object.freeze({
/** @type {AsyncSeriesWaterfallHook<TODO>} */
beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
@ -31,17 +36,28 @@ module.exports = class ContextModuleFactory {
this.resolverFactory = resolverFactory;
}
/**
* @param {ModuleFactoryCreateData} data data object
* @param {function(Error=, ModuleFactoryResult=): void} callback callback
* @returns {void}
*/
create(data, callback) {
const context = data.context;
const dependencies = data.dependencies;
const resolveOptions = data.resolveOptions;
const dependency = dependencies[0];
const dependency = /** @type {ContextDependency} */ (dependencies[0]);
const fileDependencies = new Set();
const missingDependencies = new Set();
const contextDependencies = new Set();
this.hooks.beforeResolve.callAsync(
Object.assign(
{
context: context,
dependencies: dependencies,
resolveOptions
resolveOptions,
fileDependencies,
missingDependencies,
contextDependencies
},
dependency.options
),
@ -147,10 +163,12 @@ module.exports = class ContextModuleFactory {
// Ignored
if (!result) return callback();
return callback(
null,
new ContextModule(result.resolveDependencies, result)
);
return callback(null, {
module: new ContextModule(result.resolveDependencies, result),
fileDependencies,
missingDependencies,
contextDependencies
});
}
);
}

View File

@ -28,9 +28,9 @@ class DelegatedModuleFactoryPlugin {
apply(normalModuleFactory) {
const scope = this.options.scope;
if (scope) {
normalModuleFactory.hooks.factory.tap(
normalModuleFactory.hooks.factorize.tapAsync(
"DelegatedModuleFactoryPlugin",
factory => (data, callback) => {
(data, callback) => {
const dependency = data.dependencies[0];
const request = dependency.request;
if (request && request.indexOf(scope + "/") === 0) {
@ -67,7 +67,7 @@ class DelegatedModuleFactoryPlugin {
}
}
}
return factory(data, callback);
return callback();
}
);
} else {

View File

@ -6,17 +6,31 @@
"use strict";
const DllModule = require("./DllModule");
const ModuleFactory = require("./ModuleFactory");
class DllModuleFactory {
/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
/** @typedef {import("./dependencies/DllEntryDependency")} DllEntryDependency */
class DllModuleFactory extends ModuleFactory {
constructor() {
super();
this.hooks = Object.freeze({});
}
/**
* @param {ModuleFactoryCreateData} data data object
* @param {function(Error=, ModuleFactoryResult=): void} callback callback
* @returns {void}
*/
create(data, callback) {
const dependency = data.dependencies[0];
callback(
null,
new DllModule(data.context, dependency.dependencies, dependency.name)
);
const dependency = /** @type {DllEntryDependency} */ (data.dependencies[0]);
callback(null, {
module: new DllModule(
data.context,
dependency.dependencies,
dependency.name
)
});
}
}

View File

@ -27,6 +27,8 @@ class DllReferencePlugin {
constructor(options) {
validateOptions(schema, options, "Dll Reference Plugin");
this.options = options;
/** @type {WeakMap<Object, {path: string, data: DllReferencePluginOptionsManifest?, error: Error?}>} */
this._compilationData = new WeakMap();
}
apply(compiler) {
@ -50,15 +52,17 @@ class DllReferencePlugin {
if ("manifest" in this.options) {
const manifest = this.options.manifest;
if (typeof manifest === "string") {
params.compilationDependencies.add(manifest);
compiler.inputFileSystem.readFile(manifest, (err, result) => {
if (err) return callback(err);
const data = {
path: manifest,
data: undefined,
error: undefined
};
// Catch errors parsing the manifest so that blank
// or malformed manifest files don't kill the process.
try {
params["dll reference " + manifest] = parseJson(
result.toString("utf-8")
);
data.data = parseJson(result.toString("utf-8"));
} catch (e) {
// Store the error in the params so that it can
// be added as a compilation error later on.
@ -67,10 +71,9 @@ class DllReferencePlugin {
manifest,
compiler.root
);
params[
"dll reference parse error " + manifest
] = new DllManifestError(manifestPath, e.message);
data.error = new DllManifestError(manifestPath, e.message);
}
this._compilationData.set(params, data);
return callback();
});
return;
@ -89,16 +92,14 @@ class DllReferencePlugin {
let manifestParameter = this.options.manifest;
let manifest;
if (typeof manifestParameter === "string") {
const data = this._compilationData.get(params);
// If there was an error parsing the manifest
// file, exit now because the error will be added
// as a compilation error in the "compilation" hook.
if (params["dll reference parse error " + manifestParameter]) {
if (data.error) {
return;
}
manifest =
/** @type {DllReferencePluginOptionsManifest} */ (params[
"dll reference " + manifestParameter
]);
manifest = data.data;
} else {
manifest = manifestParameter;
}
@ -131,12 +132,13 @@ class DllReferencePlugin {
if ("manifest" in this.options) {
let manifest = this.options.manifest;
if (typeof manifest === "string") {
const data = this._compilationData.get(params);
// If there was an error parsing the manifest file, add the
// error as a compilation error to make the compilation fail.
let e = params["dll reference parse error " + manifest];
if (e) {
compilation.errors.push(e);
if (data.error) {
compilation.errors.push(data.error);
}
compilation.fileDependencies.add(manifest);
}
}
}

View File

@ -15,9 +15,9 @@ class ExternalModuleFactoryPlugin {
apply(normalModuleFactory) {
const globalType = this.type;
normalModuleFactory.hooks.factory.tap(
normalModuleFactory.hooks.factorize.tapAsync(
"ExternalModuleFactoryPlugin",
factory => (data, callback) => {
(data, callback) => {
const context = data.context;
const dependency = data.dependencies[0];
@ -30,7 +30,7 @@ class ExternalModuleFactoryPlugin {
const handleExternal = (value, type, callback) => {
if (value === false) {
// Not externals, fallback to original factory
return factory(data, callback);
return callback();
}
/** @type {string} */
let externalConfig;
@ -126,11 +126,7 @@ class ExternalModuleFactoryPlugin {
callback();
};
handleExternals(this.externals, (err, module) => {
if (err) return callback(err);
if (!module) return handleExternal(false, undefined, callback);
return callback(null, module);
});
handleExternals(this.externals, callback);
}
);
}

View File

@ -26,7 +26,9 @@ const applyMtime = mtime => {
class FileSystemInfo {
constructor(fs) {
this.fs = fs;
/** @type {Map<string, FileSystemInfoEntry | null>} */
this._fileTimestamps = new Map();
/** @type {Map<string, FileSystemInfoEntry | null>} */
this._contextTimestamps = new Map();
this.fileTimestampQueue = new AsyncQueue({
name: "file timestamp",
@ -41,7 +43,7 @@ class FileSystemInfo {
}
/**
* @param {Map<string, FileSystemInfoEntry>} map timestamps
* @param {Map<string, FileSystemInfoEntry | null>} map timestamps
* @returns {void}
*/
addFileTimestamps(map) {
@ -51,7 +53,7 @@ class FileSystemInfo {
}
/**
* @param {Map<string, FileSystemInfoEntry>} map timestamps
* @param {Map<string, FileSystemInfoEntry | null>} map timestamps
* @returns {void}
*/
addContextTimestamps(map) {

View File

@ -10,6 +10,7 @@ const schema = require("../schemas/plugins/IgnorePlugin.json");
/** @typedef {import("../declarations/plugins/IgnorePlugin").IgnorePluginOptions} IgnorePluginOptions */
/** @typedef {import("./Compiler")} Compiler */
/** @typedef {import("./NormalModuleFactory").ResolveData} ResolveData */
class IgnorePlugin {
/**
@ -27,44 +28,40 @@ class IgnorePlugin {
* Note that if "contextRegExp" is given, both the "resourceRegExp"
* and "contextRegExp" have to match.
*
* @param {TODO} result result
* @returns {TODO|null} returns result or null if result should be ignored
* @param {ResolveData} resolveData resolve data
* @returns {false|undefined} returns false when the request should be ignored, otherwise undefined
*/
checkIgnore(result) {
if (!result) return result;
checkIgnore(resolveData) {
if (
"checkResource" in this.options &&
this.options.checkResource &&
this.options.checkResource(result.request, result.context)
this.options.checkResource(resolveData.request, resolveData.context)
) {
// TODO webpack 5 remove checkContext, as checkResource already gets context
if ("checkContext" in this.options && this.options.checkContext) {
if (this.options.checkContext(result.context)) {
return null;
if (this.options.checkContext(resolveData.context)) {
return false;
}
} else {
return null;
return false;
}
}
if (
"resourceRegExp" in this.options &&
this.options.resourceRegExp &&
this.options.resourceRegExp.test(result.request)
this.options.resourceRegExp.test(resolveData.request)
) {
if ("contextRegExp" in this.options && this.options.contextRegExp) {
// if "contextRegExp" is given,
// both the "resourceRegExp" and "contextRegExp" have to match.
if (this.options.contextRegExp.test(result.context)) {
return null;
if (this.options.contextRegExp.test(resolveData.context)) {
return false;
}
} else {
return null;
return false;
}
}
return result;
}
/**

44
lib/ModuleFactory.js Normal file
View File

@ -0,0 +1,44 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./Module")} Module */
/**
* @typedef {Object} ModuleFactoryResult
* @property {Module=} module the created module or unset if no module was created
* @property {Set<string>=} fileDependencies
* @property {Set<string>=} contextDependencies
* @property {Set<string>=} missingDependencies
*/
/**
* @typedef {Object} ModuleFactoryCreateDataContextInfo
* @property {string} issuer
* @property {string} compiler
*/
/**
* @typedef {Object} ModuleFactoryCreateData
* @property {ModuleFactoryCreateDataContextInfo} contextInfo
* @property {any=} resolveOptions
* @property {string} context
* @property {Dependency[]} dependencies
*/
class ModuleFactory {
/**
* @param {ModuleFactoryCreateData} data data object
* @param {function(Error=, ModuleFactoryResult=): void} callback callback
* @returns {void}
*/
create(data, callback) {
throw new Error("ModuleFactory.create must be overridden");
}
}
module.exports = ModuleFactory;

View File

@ -8,17 +8,36 @@
const asyncLib = require("neo-async");
const path = require("path");
const {
AsyncSeriesWaterfallHook,
AsyncSeriesBailHook,
SyncWaterfallHook,
SyncBailHook,
SyncHook,
HookMap
} = require("tapable");
const Module = require("./Module");
const ModuleFactory = require("./ModuleFactory");
const NormalModule = require("./NormalModule");
const RawModule = require("./RawModule");
const RuleSet = require("./RuleSet");
const cachedMerge = require("./util/cachedMerge");
/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
/** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */
/**
* @typedef {Object} ResolveData
* @property {ModuleFactoryCreateData["contextInfo"]} contextInfo
* @property {ModuleFactoryCreateData["resolveOptions"]} resolveOptions
* @property {string} context
* @property {string} request
* @property {ModuleDependency[]} dependencies
* @property {Object} createData
* @property {Set<string>} fileDependencies
* @property {Set<string>} missingDependencies
* @property {Set<string>} contextDependencies
*/
const EMPTY_RESOLVE_OPTIONS = {};
const MATCH_RESOURCE_REGEX = /^([^!]+)!=!/;
@ -56,17 +75,30 @@ const identToLoaderRequest = resultString => {
}
};
// TODO webpack 6 remove
const deprecationChangedHookMessage = name =>
`NormalModuleFactory.${name} is no longer a waterfall hook, but a bailing hook instead. ` +
"Do not return the passed object, but modify it instead. " +
"Returning false will ignore the request and results in no module created.";
const dependencyCache = new WeakMap();
class NormalModuleFactory {
class NormalModuleFactory extends ModuleFactory {
constructor(context, resolverFactory, options) {
super();
this.hooks = Object.freeze({
resolver: new SyncWaterfallHook(["resolver"]),
factory: new SyncWaterfallHook(["factory"]),
beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
afterResolve: new AsyncSeriesWaterfallHook(["data"]),
createModule: new SyncBailHook(["data"]),
module: new SyncWaterfallHook(["module", "data"]),
/** @type {AsyncSeriesBailHook<ResolveData>} */
resolve: new AsyncSeriesBailHook(["resolveData"]),
/** @type {AsyncSeriesBailHook<ResolveData>} */
factorize: new AsyncSeriesBailHook(["resolveData"]),
/** @type {AsyncSeriesBailHook<ResolveData>} */
beforeResolve: new AsyncSeriesBailHook(["resolveData"]),
/** @type {AsyncSeriesBailHook<ResolveData>} */
afterResolve: new AsyncSeriesBailHook(["resolveData"]),
/** @type {SyncBailHook<ResolveData>} */
createModule: new SyncBailHook(["resolveData"]),
/** @type {SyncWaterfallHook<Module, ResolveData["createData"], ResolveData>} */
module: new SyncWaterfallHook(["module", "createData", "resolveData"]),
createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
createGenerator: new HookMap(
@ -86,232 +118,282 @@ class NormalModuleFactory {
this.context = context || "";
this.parserCache = Object.create(null);
this.generatorCache = Object.create(null);
this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {
let resolver = this.hooks.resolver.call(null);
// Ignored
if (!resolver) return callback();
resolver(result, (err, data) => {
if (err) return callback(err);
// Ignored
if (!data) return callback();
// direct module
if (typeof data.source === "function") return callback(null, data);
this.hooks.afterResolve.callAsync(data, (err, result) => {
this.hooks.factorize.tapAsync(
/** @type {TODO} */ ({
name: "NormalModuleFactory",
stage: 100
}),
(resolveData, callback) => {
this.hooks.resolve.callAsync(resolveData, (err, result) => {
if (err) return callback(err);
// Ignored
if (!result) return callback();
if (result === false) return callback();
let createdModule = this.hooks.createModule.call(result);
if (!createdModule) {
if (!result.request) {
return callback(new Error("Empty dependency (no request)"));
// direct module
if (result instanceof Module) return callback(null, result);
if (typeof result === "object")
throw new Error(
deprecationChangedHookMessage("resolve") +
" Returning a Module object will result in this module used as result."
);
this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
if (err) return callback(err);
if (typeof result === "object")
throw new Error(deprecationChangedHookMessage("afterResolve"));
// Ignored
if (result === false) return callback();
const createData = resolveData.createData;
let createdModule = this.hooks.createModule.call(createData);
if (!createdModule) {
if (!resolveData.request) {
return callback(new Error("Empty dependency (no request)"));
}
createdModule = new NormalModule(createData);
}
createdModule = new NormalModule(result);
}
createdModule = this.hooks.module.call(
createdModule,
createData,
resolveData
);
createdModule = this.hooks.module.call(createdModule, result);
return callback(null, createdModule);
return callback(null, createdModule);
});
});
});
});
this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {
const contextInfo = data.contextInfo;
const context = data.context;
const request = data.request;
const loaderResolver = this.getResolver("loader");
const normalResolver = this.getResolver("normal", data.resolveOptions);
let matchResource = undefined;
let requestWithoutMatchResource = request;
const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request);
if (matchResourceMatch) {
matchResource = matchResourceMatch[1];
if (/^\.\.?\//.test(matchResource)) {
matchResource = path.join(context, matchResource);
}
requestWithoutMatchResource = request.substr(
matchResourceMatch[0].length
);
}
);
this.hooks.resolve.tapAsync(
/** @type {TODO} */ ({
name: "NormalModuleFactory",
stage: 100
}),
(data, callback) => {
const {
contextInfo,
context,
request,
resolveOptions,
fileDependencies,
missingDependencies,
contextDependencies
} = data;
const noPreAutoLoaders = requestWithoutMatchResource.startsWith("-!");
const noAutoLoaders =
noPreAutoLoaders || requestWithoutMatchResource.startsWith("!");
const noPrePostAutoLoaders = requestWithoutMatchResource.startsWith("!!");
let elements = requestWithoutMatchResource
.replace(/^-?!+/, "")
.replace(/!!+/g, "!")
.split("!");
let resource = elements.pop();
elements = elements.map(identToLoaderRequest);
const loaderResolver = this.getResolver("loader");
const normalResolver = this.getResolver("normal", resolveOptions);
asyncLib.parallel(
[
callback =>
this.resolveRequestArray(
contextInfo,
context,
elements,
loaderResolver,
callback
),
callback => {
if (resource === "" || resource[0] === "?") {
return callback(null, {
resource
});
}
/** @type {string} */
let matchResource = undefined;
/** @type {string} */
let requestWithoutMatchResource = request;
const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request);
if (matchResourceMatch) {
matchResource = matchResourceMatch[1];
if (/^\.\.?\//.test(matchResource)) {
matchResource = path.join(context, matchResource);
}
requestWithoutMatchResource = request.substr(
matchResourceMatch[0].length
);
}
normalResolver.resolve(
contextInfo,
context,
resource,
{},
(err, resource, resourceResolveData) => {
if (err) return callback(err);
callback(null, {
resourceResolveData,
const noPreAutoLoaders = requestWithoutMatchResource.startsWith("-!");
const noAutoLoaders =
noPreAutoLoaders || requestWithoutMatchResource.startsWith("!");
const noPrePostAutoLoaders = requestWithoutMatchResource.startsWith(
"!!"
);
const rawElements = requestWithoutMatchResource
.replace(/^-?!+/, "")
.replace(/!!+/g, "!")
.split("!");
const resource = rawElements.pop();
const elements = rawElements.map(identToLoaderRequest);
const resolveContext = {
fileDependencies,
missing: missingDependencies,
missingDependencies,
contextDependencies
};
asyncLib.parallel(
[
callback =>
this.resolveRequestArray(
contextInfo,
context,
elements,
loaderResolver,
resolveContext,
callback
),
callback => {
if (resource === "" || resource[0] === "?") {
return callback(null, {
resource
});
}
);
}
],
(err, results) => {
if (err) return callback(err);
let loaders = results[0];
const resourceResolveData = results[1].resourceResolveData;
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;
normalResolver.resolve(
contextInfo,
context,
resource,
resolveContext,
(err, resource, resourceResolveData) => {
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;
}
}
} 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;
}
}
asyncLib.parallel(
[
this.resolveRequestArray.bind(
this,
contextInfo,
this.context,
useLoadersPost,
loaderResolver
),
this.resolveRequestArray.bind(
this,
contextInfo,
this.context,
useLoaders,
loaderResolver
),
this.resolveRequestArray.bind(
this,
contextInfo,
this.context,
useLoadersPre,
loaderResolver
)
],
(err, results) => {
if (err) return callback(err);
loaders = results[0].concat(loaders, results[1], results[2]);
process.nextTick(() => {
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;
callback(null, {
context: context,
Object.assign(data.createData, {
request: loaders
.map(loaderToIdent)
.concat([resource])
.join("!"),
dependencies: data.dependencies,
userRequest,
rawRequest: request,
loaders,
@ -324,59 +406,84 @@ class NormalModuleFactory {
generator: this.getGenerator(type, settings.generator),
resolveOptions
});
});
}
);
}
);
});
callback();
}
);
}
);
}
);
}
/**
* @param {ModuleFactoryCreateData} data data object
* @param {function(Error=, ModuleFactoryResult=): void} callback callback
* @returns {void}
*/
create(data, callback) {
const dependencies = data.dependencies;
const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies);
if (this.unsafeCache) {
const cacheEntry = dependencyCache.get(dependencies[0]);
if (cacheEntry) return callback(null, cacheEntry);
}
const context = data.context || this.context;
const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;
const request = dependencies[0].request;
const contextInfo = data.contextInfo || {};
this.hooks.beforeResolve.callAsync(
{
contextInfo,
resolveOptions,
context,
request,
dependencies
},
(err, result) => {
const dependency = dependencies[0];
const request = dependency.request;
const contextInfo = data.contextInfo;
const fileDependencies = new Set();
const missingDependencies = new Set();
const contextDependencies = new Set();
/** @type {ResolveData} */
const resolveData = {
contextInfo,
resolveOptions,
context,
request,
dependencies,
fileDependencies,
missingDependencies,
contextDependencies,
createData: {}
};
this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
if (err) return callback(err);
// Ignored
if (result === false) return callback();
if (typeof result === "object")
throw new Error(deprecationChangedHookMessage("beforeResolve"));
this.hooks.factorize.callAsync(resolveData, (err, module) => {
if (err) return callback(err);
// Ignored
if (!result) return callback();
const factoryResult = {
module,
fileDependencies,
missingDependencies,
contextDependencies
};
const factory = this.hooks.factory.call(null);
// Ignored
if (!factory) return callback();
factory(result, (err, module) => {
if (err) return callback(err);
if (this.unsafeCache && module && this.cachePredicate(module)) {
for (const d of dependencies) {
dependencyCache.set(d, module);
}
if (this.unsafeCache && module && this.cachePredicate(module)) {
for (const d of dependencies) {
dependencyCache.set(d, factoryResult);
}
}
callback(null, module);
});
}
);
callback(null, factoryResult);
});
});
}
resolveRequestArray(contextInfo, context, array, resolver, callback) {
resolveRequestArray(
contextInfo,
context,
array,
resolver,
resolveContext,
callback
) {
if (array.length === 0) return callback(null, []);
asyncLib.map(
array,
@ -385,7 +492,7 @@ class NormalModuleFactory {
contextInfo,
context,
item.loader,
{},
resolveContext,
(err, result) => {
if (
err &&
@ -419,10 +526,20 @@ class NormalModuleFactory {
options: item.options
}
: undefined;
return callback(
null,
Object.assign({}, item, identToLoaderRequest(result), optionsOnly)
const resolved = Object.assign(
{},
item,
identToLoaderRequest(result),
optionsOnly
);
// TODO remove this when enhanced-resolve supports fileDependencies
if (resolved.loader) {
resolveContext.fileDependencies.add(resolved.loader);
}
return callback(null, resolved);
}
);
},

View File

@ -33,7 +33,6 @@ class NormalModuleReplacementPlugin {
"NormalModuleReplacementPlugin",
nmf => {
nmf.hooks.beforeResolve.tap("NormalModuleReplacementPlugin", result => {
if (!result) return;
if (resourceRegExp.test(result.request)) {
if (typeof newResource === "function") {
newResource(result);
@ -44,13 +43,13 @@ class NormalModuleReplacementPlugin {
return result;
});
nmf.hooks.afterResolve.tap("NormalModuleReplacementPlugin", result => {
if (!result) return;
if (resourceRegExp.test(result.resource)) {
const createData = result.createData;
if (resourceRegExp.test(createData.resource)) {
if (typeof newResource === "function") {
newResource(result);
} else {
result.resource = path.resolve(
path.dirname(result.resource),
createData.resource = path.resolve(
path.dirname(createData.resource),
newResource
);
}

View File

@ -5,7 +5,17 @@
"use strict";
class NullFactory {
const ModuleFactory = require("./ModuleFactory");
/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
class NullFactory extends ModuleFactory {
/**
* @param {ModuleFactoryCreateData} data data object
* @param {function(Error=, ModuleFactoryResult=): void} callback callback
* @returns {void}
*/
create(data, callback) {
return callback();
}

View File

@ -23,7 +23,7 @@ const Stats = require("./Stats");
const toFileSystemInfoEntryMap = timestamps => {
const map = new Map();
for (const [key, ts] of timestamps) {
map.set(key, { safeTime: ts });
map.set(key, ts ? { safeTime: ts } : null);
}
return map;
};

View File

@ -72,6 +72,7 @@ class ResolverCachePlugin {
const contextDependencies = new Set(
newResolveContext.contextDependencies
);
// TODO remove this when enhanced-resolve supports fileDependencies
if (result && result.path) {
fileDependencies.add(result.path);
}

View File

@ -52,6 +52,7 @@ class SideEffectsFlagPlugin {
} else if (data.settings.sideEffects === true) {
module.factoryMeta.sideEffectFree = false;
}
return module;
});
});
compiler.hooks.compilation.tap("SideEffectsFlagPlugin", compilation => {

View File

@ -88,7 +88,7 @@ describe("StatsTestCases", () => {
if (/error$/.test(testName)) {
expect(stats.hasErrors()).toBe(true);
} else if (stats.hasErrors()) {
return done(new Error(stats.toJson().errors.join("\n\n")));
return done(new Error(stats.toString({ all: false, errors: true })));
}
let toStringOptions = {
context: path.join(base, testName),