webpack/lib/NormalModuleFactory.js

1361 lines
40 KiB
JavaScript
Raw Normal View History

2013-01-31 01:49:25 +08:00
/*
2018-07-30 23:08:51 +08:00
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
2017-04-04 05:28:08 +08:00
"use strict";
2021-08-06 00:47:24 +08:00
const { getContext } = require("loader-runner");
2018-02-11 12:27:09 +08:00
const asyncLib = require("neo-async");
const {
AsyncSeriesBailHook,
2025-07-03 17:06:45 +08:00
HookMap,
SyncBailHook,
SyncHook,
2025-07-03 17:06:45 +08:00
SyncWaterfallHook
} = require("tapable");
const ChunkGraph = require("./ChunkGraph");
const Module = require("./Module");
const ModuleFactory = require("./ModuleFactory");
const ModuleGraph = require("./ModuleGraph");
const { JAVASCRIPT_MODULE_TYPE_AUTO } = require("./ModuleTypeConstants");
2017-04-04 05:28:08 +08:00
const NormalModule = require("./NormalModule");
const BasicEffectRulePlugin = require("./rules/BasicEffectRulePlugin");
const BasicMatcherRulePlugin = require("./rules/BasicMatcherRulePlugin");
const ObjectMatcherRulePlugin = require("./rules/ObjectMatcherRulePlugin");
const RuleSetCompiler = require("./rules/RuleSetCompiler");
const UseEffectRulePlugin = require("./rules/UseEffectRulePlugin");
const LazySet = require("./util/LazySet");
2020-07-03 20:45:49 +08:00
const { getScheme } = require("./util/URLAbsoluteSpecifier");
const { cachedCleverMerge, cachedSetProperty } = require("./util/cleverMerge");
const { join } = require("./util/fs");
const {
parseResource,
parseResourceWithoutFragment
} = require("./util/identifier");
/** @typedef {import("../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */
2022-02-15 22:37:57 +08:00
/** @typedef {import("../declarations/WebpackOptions").RuleSetRule} RuleSetRule */
/** @typedef {import("./Generator")} Generator */
/** @typedef {import("./ModuleFactory").ModuleFactoryCallback} ModuleFactoryCallback */
/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
2024-02-17 01:39:12 +08:00
/** @typedef {import("./ModuleFactory").ModuleFactoryCreateDataContextInfo} ModuleFactoryCreateDataContextInfo */
/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
2024-02-17 01:39:12 +08:00
/** @typedef {import("./NormalModule").GeneratorOptions} GeneratorOptions */
/** @typedef {import("./NormalModule").LoaderItem} LoaderItem */
2022-02-15 22:37:57 +08:00
/** @typedef {import("./NormalModule").NormalModuleCreateData} NormalModuleCreateData */
2024-02-17 01:39:12 +08:00
/** @typedef {import("./NormalModule").ParserOptions} ParserOptions */
2020-08-03 02:09:36 +08:00
/** @typedef {import("./Parser")} Parser */
/** @typedef {import("./ResolverFactory")} ResolverFactory */
2024-02-17 01:39:12 +08:00
/** @typedef {import("./ResolverFactory").ResolveContext} ResolveContext */
/** @typedef {import("./ResolverFactory").ResolveRequest} ResolveRequest */
/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */
/** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */
2025-04-07 21:09:05 +08:00
/** @typedef {import("./javascript/JavascriptParser").ImportAttributes} ImportAttributes */
2025-03-27 08:07:25 +08:00
/** @typedef {import("./rules/RuleSetCompiler").RuleSetRules} RuleSetRules */
/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
/** @typedef {import("./util/identifier").AssociatedObjectForCache} AssociatedObjectForCache */
2025-08-22 06:40:41 +08:00
/**
* @template T
* @typedef {import("./Compiler").Callback<T>} Callback
*/
2024-10-02 05:18:10 +08:00
/** @typedef {Pick<RuleSetRule, 'type' | 'sideEffects' | 'parser' | 'generator' | 'resolve' | 'layer'>} ModuleSettings */
/** @typedef {Partial<NormalModuleCreateData & { settings: ModuleSettings }>} CreateData */
2022-02-15 22:37:57 +08:00
/**
2024-06-11 21:09:50 +08:00
* @typedef {object} ResolveData
* @property {ModuleFactoryCreateData["contextInfo"]} contextInfo
* @property {ModuleFactoryCreateData["resolveOptions"]} resolveOptions
* @property {string} context
* @property {string} request
2025-04-07 21:09:05 +08:00
* @property {ImportAttributes | undefined} assertions
* @property {ModuleDependency[]} dependencies
* @property {string} dependencyType
2022-02-15 22:37:57 +08:00
* @property {CreateData} createData
* @property {LazySet<string>} fileDependencies
* @property {LazySet<string>} missingDependencies
* @property {LazySet<string>} contextDependencies
2024-10-23 08:01:04 +08:00
* @property {Module=} ignoredModule
* @property {boolean} cacheable allow to use the unsafe cache
*/
2020-07-03 20:45:49 +08:00
/**
2024-06-11 21:09:50 +08:00
* @typedef {object} ResourceData
2020-07-03 20:45:49 +08:00
* @property {string} resource
2024-02-17 01:39:12 +08:00
* @property {string=} path
* @property {string=} query
* @property {string=} fragment
2021-08-06 00:47:24 +08:00
* @property {string=} context
2020-07-03 20:45:49 +08:00
*/
2025-05-13 21:03:58 +08:00
/**
* @typedef {object} ResourceSchemeData
* @property {string=} mimetype mime type of the resource
* @property {string=} parameters additional parameters for the resource
* @property {"base64" | false=} encoding encoding of the resource
* @property {string=} encodedContent encoded content of the resource
*/
/** @typedef {ResourceData & { data: ResourceSchemeData & Partial<ResolveRequest> }} ResourceDataWithData */
2020-07-03 20:45:49 +08:00
2024-02-17 01:39:12 +08:00
/**
2024-06-11 21:09:50 +08:00
* @typedef {object} ParsedLoaderRequest
* @property {string} loader loader
* @property {string|undefined} options options
*/
const EMPTY_RESOLVE_OPTIONS = {};
2024-02-17 01:39:12 +08:00
/** @type {ParserOptions} */
const EMPTY_PARSER_OPTIONS = {};
2024-02-17 01:39:12 +08:00
/** @type {GeneratorOptions} */
const EMPTY_GENERATOR_OPTIONS = {};
2024-02-17 01:39:12 +08:00
/** @type {ParsedLoaderRequest[]} */
const EMPTY_ELEMENTS = [];
const MATCH_RESOURCE_REGEX = /^([^!]+)!=!/;
const LEADING_DOT_EXTENSION_REGEX = /^[^.]/;
2024-02-17 01:39:12 +08:00
/**
* @param {LoaderItem} data data
* @returns {string} ident
*/
const loaderToIdent = (data) => {
if (!data.options) {
return data.loader;
}
if (typeof data.options === "string") {
2024-07-31 10:39:30 +08:00
return `${data.loader}?${data.options}`;
}
if (typeof data.options !== "object") {
throw new Error("loader options must be string or object");
}
if (data.ident) {
2024-07-31 10:39:30 +08:00
return `${data.loader}??${data.ident}`;
}
2024-07-31 10:39:30 +08:00
return `${data.loader}?${JSON.stringify(data.options)}`;
2017-11-08 18:32:05 +08:00
};
2013-01-31 01:49:25 +08:00
2024-02-17 01:39:12 +08:00
/**
* @param {LoaderItem[]} loaders loaders
* @param {string} resource resource
* @returns {string} stringified loaders and resource
*/
const stringifyLoadersAndResource = (loaders, resource) => {
let str = "";
for (const loader of loaders) {
2024-07-31 10:39:30 +08:00
str += `${loaderToIdent(loader)}!`;
}
return str + resource;
};
2024-02-17 01:39:12 +08:00
/**
* @param {number} times times
* @param {(err?: null | Error) => void} callback callback
* @returns {(err?: null | Error) => void} callback
*/
const needCalls = (times, callback) => (err) => {
2024-07-31 11:31:11 +08:00
if (--times === 0) {
return callback(err);
}
if (err && times > 0) {
2024-08-02 02:36:27 +08:00
times = Number.NaN;
2024-07-31 11:31:11 +08:00
return callback(err);
}
};
2024-02-17 01:39:12 +08:00
/**
* @template T
* @template O
* @param {T} globalOptions global options
* @param {string} type type
* @param {O} localOptions local options
* @returns {T & O | T | O} result
*/
const mergeGlobalOptions = (globalOptions, type, localOptions) => {
const parts = type.split("/");
let result;
let current = "";
for (const part of parts) {
current = current ? `${current}/${part}` : part;
2024-10-24 04:30:31 +08:00
const options =
/** @type {T} */
(globalOptions[/** @type {keyof T} */ (current)]);
if (typeof options === "object") {
2024-08-02 02:36:27 +08:00
result =
result === undefined ? options : cachedCleverMerge(result, options);
}
}
if (result === undefined) {
return localOptions;
}
2024-07-31 04:21:27 +08:00
return cachedCleverMerge(result, localOptions);
};
// TODO webpack 6 remove
2024-02-17 01:39:12 +08:00
/**
2025-03-11 10:03:41 +08:00
* @template {import("tapable").Hook<EXPECTED_ANY, EXPECTED_ANY>} T
2024-02-17 01:39:12 +08:00
* @param {string} name name
2025-03-11 10:03:41 +08:00
* @param {T} hook hook
2024-02-17 01:39:12 +08:00
* @returns {string} result
*/
const deprecationChangedHookMessage = (name, hook) => {
const names = hook.taps.map((tapped) => tapped.name).join(", ");
return (
`NormalModuleFactory.${name} (${names}) 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 ruleSetCompiler = new RuleSetCompiler([
new BasicMatcherRulePlugin("test", "resource"),
new BasicMatcherRulePlugin("scheme"),
2020-05-18 15:00:28 +08:00
new BasicMatcherRulePlugin("mimetype"),
2020-08-05 05:42:29 +08:00
new BasicMatcherRulePlugin("dependency"),
new BasicMatcherRulePlugin("include", "resource"),
new BasicMatcherRulePlugin("exclude", "resource", true),
new BasicMatcherRulePlugin("resource"),
new BasicMatcherRulePlugin("resourceQuery"),
2020-07-03 23:03:15 +08:00
new BasicMatcherRulePlugin("resourceFragment"),
new BasicMatcherRulePlugin("realResource"),
new BasicMatcherRulePlugin("issuer"),
new BasicMatcherRulePlugin("compiler"),
new BasicMatcherRulePlugin("issuerLayer"),
new ObjectMatcherRulePlugin("assert", "assertions", (value) => {
2024-10-24 04:30:31 +08:00
if (value) {
2025-04-07 21:09:05 +08:00
return (
/** @type {ImportAttributes} */ (value)._isLegacyAssert !== undefined
);
2024-10-24 04:30:31 +08:00
}
return false;
}),
new ObjectMatcherRulePlugin("with", "assertions", (value) => {
2024-10-24 04:30:31 +08:00
if (value) {
2025-04-07 21:09:05 +08:00
return !(/** @type {ImportAttributes} */ (value)._isLegacyAssert);
2024-10-24 04:30:31 +08:00
}
return false;
}),
new ObjectMatcherRulePlugin("descriptionData"),
new BasicEffectRulePlugin("type"),
new BasicEffectRulePlugin("sideEffects"),
new BasicEffectRulePlugin("parser"),
new BasicEffectRulePlugin("resolve"),
2019-11-16 00:27:36 +08:00
new BasicEffectRulePlugin("generator"),
new BasicEffectRulePlugin("layer"),
new UseEffectRulePlugin()
]);
class NormalModuleFactory extends ModuleFactory {
/**
2024-06-11 21:09:50 +08:00
* @param {object} param params
* @param {string=} param.context context
* @param {InputFileSystem} param.fs file system
* @param {ResolverFactory} param.resolverFactory resolverFactory
* @param {ModuleOptions} param.options options
* @param {AssociatedObjectForCache} param.associatedObjectForCache an object to which the cache will be attached
* @param {boolean=} param.layers enable layers
*/
constructor({
context,
fs,
resolverFactory,
options,
associatedObjectForCache,
layers = false
}) {
super();
2018-07-30 20:25:40 +08:00
this.hooks = Object.freeze({
2022-02-15 22:37:57 +08:00
/** @type {AsyncSeriesBailHook<[ResolveData], Module | false | void>} */
resolve: new AsyncSeriesBailHook(["resolveData"]),
2020-07-03 20:45:49 +08:00
/** @type {HookMap<AsyncSeriesBailHook<[ResourceDataWithData, ResolveData], true | void>>} */
resolveForScheme: new HookMap(
() => new AsyncSeriesBailHook(["resourceData", "resolveData"])
),
/** @type {HookMap<AsyncSeriesBailHook<[ResourceDataWithData, ResolveData], true | void>>} */
resolveInScheme: new HookMap(
() => new AsyncSeriesBailHook(["resourceData", "resolveData"])
),
2024-03-18 01:15:44 +08:00
/** @type {AsyncSeriesBailHook<[ResolveData], Module | undefined>} */
factorize: new AsyncSeriesBailHook(["resolveData"]),
2022-02-15 22:37:57 +08:00
/** @type {AsyncSeriesBailHook<[ResolveData], false | void>} */
beforeResolve: new AsyncSeriesBailHook(["resolveData"]),
2022-02-15 22:37:57 +08:00
/** @type {AsyncSeriesBailHook<[ResolveData], false | void>} */
afterResolve: new AsyncSeriesBailHook(["resolveData"]),
2025-03-11 10:03:41 +08:00
/** @type {AsyncSeriesBailHook<[CreateData, ResolveData], Module | void>} */
createModule: new AsyncSeriesBailHook(["createData", "resolveData"]),
2025-03-11 10:03:41 +08:00
/** @type {SyncWaterfallHook<[Module, CreateData, ResolveData]>} */
module: new SyncWaterfallHook(["module", "createData", "resolveData"]),
2024-10-24 22:09:39 +08:00
/** @type {HookMap<SyncBailHook<[ParserOptions], Parser | void>>} */
2017-11-28 16:54:24 +08:00
createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
2024-02-17 01:39:12 +08:00
/** @type {HookMap<SyncBailHook<[TODO, ParserOptions], void>>} */
2017-11-28 16:54:24 +08:00
parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
2024-10-24 22:09:39 +08:00
/** @type {HookMap<SyncBailHook<[GeneratorOptions], Generator | void>>} */
2018-02-25 09:00:20 +08:00
createGenerator: new HookMap(
() => new SyncBailHook(["generatorOptions"])
),
2024-02-17 01:39:12 +08:00
/** @type {HookMap<SyncBailHook<[TODO, GeneratorOptions], void>>} */
2018-02-25 09:00:20 +08:00
generator: new HookMap(
() => new SyncHook(["generator", "generatorOptions"])
),
2025-07-09 18:59:21 +08:00
/** @type {HookMap<SyncBailHook<[CreateData, ResolveData], Module | void>>} */
2023-04-25 21:59:17 +08:00
createModuleClass: new HookMap(
() => new SyncBailHook(["createData", "resolveData"])
2018-02-25 09:00:20 +08:00
)
2018-07-30 20:25:40 +08:00
});
this.resolverFactory = resolverFactory;
this.ruleSet = ruleSetCompiler.compile([
{
2025-03-27 08:07:25 +08:00
rules: /** @type {RuleSetRules} */ (options.defaultRules)
},
{
2025-03-27 08:07:25 +08:00
rules: /** @type {RuleSetRules} */ (options.rules)
}
]);
2017-04-04 05:28:08 +08:00
this.context = context || "";
this.fs = fs;
this._globalParserOptions = options.parser;
this._globalGeneratorOptions = options.generator;
/** @type {Map<string, WeakMap<ParserOptions, Parser>>} */
this.parserCache = new Map();
/** @type {Map<string, WeakMap<GeneratorOptions, Generator>>} */
this.generatorCache = new Map();
/** @type {Set<Module>} */
this._restoredUnsafeCacheEntries = new Set();
2025-05-13 21:03:58 +08:00
/** @type {(resource: string) => import("./util/identifier").ParsedResource} */
const cacheParseResource = parseResource.bindCache(
associatedObjectForCache
);
const cachedParseResourceWithoutFragment =
parseResourceWithoutFragment.bindCache(associatedObjectForCache);
this._parseResourceWithoutFragment = cachedParseResourceWithoutFragment;
this.hooks.factorize.tapAsync(
{
name: "NormalModuleFactory",
stage: 100
},
(resolveData, callback) => {
this.hooks.resolve.callAsync(resolveData, (err, result) => {
if (err) return callback(err);
// Ignored
if (result === false) return callback();
// direct module
if (result instanceof Module) return callback(null, result);
if (typeof result === "object") {
throw new Error(
2024-07-31 10:39:30 +08:00
`${deprecationChangedHookMessage(
"resolve",
this.hooks.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);
2016-02-10 02:32:50 +08:00
if (typeof result === "object") {
throw new Error(
deprecationChangedHookMessage(
"afterResolve",
this.hooks.afterResolve
)
);
}
2015-05-17 00:27:59 +08:00
// Ignored
if (result === false) return callback();
2015-05-17 00:27:59 +08:00
const createData = resolveData.createData;
2017-04-04 05:28:08 +08:00
this.hooks.createModule.callAsync(
createData,
resolveData,
(err, createdModule) => {
if (!createdModule) {
if (!resolveData.request) {
return callback(new Error("Empty dependency (no request)"));
}
2023-04-25 21:59:17 +08:00
// TODO webpack 6 make it required and move javascript/wasm/asset properties to own module
createdModule = this.hooks.createModuleClass
2024-10-25 02:13:59 +08:00
.for(
/** @type {ModuleSettings} */
(createData.settings).type
)
2023-04-25 21:59:17 +08:00
.call(createData, resolveData);
if (!createdModule) {
createdModule = /** @type {Module} */ (
new NormalModule(
2024-10-24 04:30:31 +08:00
/** @type {NormalModuleCreateData} */
(createData)
2023-04-25 21:59:17 +08:00
)
);
}
}
createdModule = this.hooks.module.call(
createdModule,
createData,
resolveData
);
return callback(null, createdModule);
}
);
});
});
}
);
this.hooks.resolve.tapAsync(
{
name: "NormalModuleFactory",
stage: 100
},
(data, callback) => {
const {
contextInfo,
context,
dependencies,
dependencyType,
request,
assertions,
resolveOptions,
fileDependencies,
missingDependencies,
contextDependencies
} = data;
const loaderResolver = this.getResolver("loader");
2020-07-03 20:45:49 +08:00
/** @type {ResourceData | undefined} */
2024-07-31 06:15:03 +08:00
let matchResourceData;
/** @type {string} */
let unresolvedResource;
/** @type {ParsedLoaderRequest[]} */
let elements;
let noPreAutoLoaders = false;
let noAutoLoaders = false;
let noPrePostAutoLoaders = false;
const contextScheme = getScheme(context);
/** @type {string | undefined} */
let scheme = getScheme(request);
if (!scheme) {
/** @type {string} */
let requestWithoutMatchResource = request;
const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request);
if (matchResourceMatch) {
let matchResource = matchResourceMatch[1];
2025-05-13 21:03:58 +08:00
// Check if matchResource starts with ./ or ../
if (matchResource.charCodeAt(0) === 46) {
2025-05-13 21:03:58 +08:00
// 46 is "."
const secondChar = matchResource.charCodeAt(1);
if (
2025-05-13 21:03:58 +08:00
secondChar === 47 || // 47 is "/"
(secondChar === 46 && matchResource.charCodeAt(2) === 47) // "../"
) {
2025-05-13 21:03:58 +08:00
// Resolve relative path against context
matchResource = join(this.fs, context, matchResource);
}
}
2025-03-11 10:03:41 +08:00
matchResourceData = {
2025-05-13 21:03:58 +08:00
...cacheParseResource(matchResource),
resource: matchResource
};
requestWithoutMatchResource = request.slice(
matchResourceMatch[0].length
);
}
scheme = getScheme(requestWithoutMatchResource);
if (!scheme && !contextScheme) {
const firstChar = requestWithoutMatchResource.charCodeAt(0);
const secondChar = requestWithoutMatchResource.charCodeAt(1);
noPreAutoLoaders = firstChar === 45 && secondChar === 33; // startsWith "-!"
noAutoLoaders = noPreAutoLoaders || firstChar === 33; // startsWith "!"
noPrePostAutoLoaders = firstChar === 33 && secondChar === 33; // startsWith "!!";
const rawElements = requestWithoutMatchResource
.slice(
noPreAutoLoaders || noPrePostAutoLoaders
? 2
: noAutoLoaders
2024-07-31 05:43:19 +08:00
? 1
: 0
)
.split(/!+/);
2024-02-17 01:39:12 +08:00
unresolvedResource = /** @type {string} */ (rawElements.pop());
elements = rawElements.map((el) => {
const { path, query } = cachedParseResourceWithoutFragment(el);
return {
loader: path,
options: query ? query.slice(1) : undefined
};
});
scheme = getScheme(unresolvedResource);
} else {
unresolvedResource = requestWithoutMatchResource;
elements = EMPTY_ELEMENTS;
}
} else {
unresolvedResource = request;
elements = EMPTY_ELEMENTS;
}
2024-02-17 01:39:12 +08:00
/** @type {ResolveContext} */
const resolveContext = {
fileDependencies,
missingDependencies,
contextDependencies
};
2020-07-03 20:45:49 +08:00
/** @type {ResourceDataWithData} */
let resourceData;
2024-02-17 01:39:12 +08:00
/** @type {undefined | LoaderItem[]} */
let loaders;
const continueCallback = needCalls(2, (err) => {
if (err) return callback(err);
// translate option idents
try {
2024-02-17 01:39:12 +08:00
for (const item of /** @type {LoaderItem[]} */ (loaders)) {
if (typeof item.options === "string" && item.options[0] === "?") {
const ident = item.options.slice(1);
if (ident === "[[missing ident]]") {
throw new Error(
"No ident is provided by referenced loader. " +
"When using a function for Rule.use in config you need to " +
"provide an 'ident' property for referenced loader options."
);
}
item.options = this.ruleSet.references.get(ident);
if (item.options === undefined) {
throw new Error(
"Invalid ident is provided by referenced loader"
);
}
item.ident = ident;
2018-02-25 09:00:20 +08:00
}
2017-04-04 05:28:08 +08:00
}
2024-07-31 15:37:05 +08:00
} catch (identErr) {
return callback(/** @type {Error} */ (identErr));
}
2020-07-03 20:45:49 +08:00
if (!resourceData) {
// ignored
2025-05-13 21:03:58 +08:00
return callback(null, dependencies[0].createIgnoredModule(context));
}
const userRequest =
2020-07-03 20:45:49 +08:00
(matchResourceData !== undefined
? `${matchResourceData.resource}!=!`
: "") +
2024-02-17 01:39:12 +08:00
stringifyLoadersAndResource(
/** @type {LoaderItem[]} */ (loaders),
resourceData.resource
);
2024-02-17 01:39:12 +08:00
/** @type {ModuleSettings} */
const settings = {};
2025-07-10 21:30:16 +08:00
/** @type {LoaderItem[]} */
const useLoadersPost = [];
2025-07-10 21:30:16 +08:00
/** @type {LoaderItem[]} */
const useLoaders = [];
2025-07-10 21:30:16 +08:00
/** @type {LoaderItem[]} */
const useLoadersPre = [];
// handle .webpack[] suffix
let resource;
let match;
if (
matchResourceData &&
typeof (resource = matchResourceData.resource) === "string" &&
(match = /\.webpack\[([^\]]+)\]$/.exec(resource))
) {
settings.type = match[1];
matchResourceData.resource = matchResourceData.resource.slice(
0,
-settings.type.length - 10
);
} else {
settings.type = JAVASCRIPT_MODULE_TYPE_AUTO;
const resourceDataForRules = matchResourceData || resourceData;
const result = this.ruleSet.exec({
resource: resourceDataForRules.path,
realResource: resourceData.path,
resourceQuery: resourceDataForRules.query,
resourceFragment: resourceDataForRules.fragment,
scheme,
assertions,
mimetype: matchResourceData
? ""
: resourceData.data.mimetype || "",
dependency: dependencyType,
descriptionData: matchResourceData
? undefined
: resourceData.data.descriptionFileData,
issuer: contextInfo.issuer,
compiler: contextInfo.compiler,
issuerLayer: contextInfo.issuerLayer || ""
});
for (const r of result) {
// https://github.com/webpack/webpack/issues/16466
// if a request exists PrePostAutoLoaders, should disable modifying Rule.type
if (r.type === "type" && noPrePostAutoLoaders) {
continue;
}
if (r.type === "use") {
if (!noAutoLoaders && !noPrePostAutoLoaders) {
useLoaders.push(r.value);
}
} else if (r.type === "use-post") {
if (!noPrePostAutoLoaders) {
useLoadersPost.push(r.value);
}
} else if (r.type === "use-pre") {
if (!noPreAutoLoaders && !noPrePostAutoLoaders) {
useLoadersPre.push(r.value);
}
} else if (
typeof r.value === "object" &&
r.value !== null &&
2024-08-09 23:42:37 +08:00
typeof settings[
2025-07-10 21:30:16 +08:00
/** @type {keyof ModuleSettings} */
(r.type)
2024-08-09 23:42:37 +08:00
] === "object" &&
settings[/** @type {keyof ModuleSettings} */ (r.type)] !== null
) {
2025-03-11 22:20:50 +08:00
const type = /** @type {keyof ModuleSettings} */ (r.type);
2025-05-23 21:39:55 +08:00
settings[type] = cachedCleverMerge(settings[type], r.value);
} else {
2025-03-11 22:20:50 +08:00
const type = /** @type {keyof ModuleSettings} */ (r.type);
2025-05-23 21:39:55 +08:00
settings[type] = r.value;
}
2018-02-25 09:00:20 +08:00
}
}
2024-02-17 01:39:12 +08:00
/** @type {undefined | LoaderItem[]} */
let postLoaders;
/** @type {undefined | LoaderItem[]} */
let normalLoaders;
/** @type {undefined | LoaderItem[]} */
let preLoaders;
const continueCallback = needCalls(3, (err) => {
if (err) {
return callback(err);
}
2024-02-17 01:39:12 +08:00
const allLoaders = /** @type {LoaderItem[]} */ (postLoaders);
2020-07-03 20:45:49 +08:00
if (matchResourceData === undefined) {
for (const loader of /** @type {LoaderItem[]} */ (loaders)) {
2024-02-17 01:39:12 +08:00
allLoaders.push(loader);
}
for (const loader of /** @type {LoaderItem[]} */ (
normalLoaders
)) {
2024-02-17 01:39:12 +08:00
allLoaders.push(loader);
}
2019-07-01 17:15:52 +08:00
} else {
for (const loader of /** @type {LoaderItem[]} */ (
normalLoaders
)) {
2024-02-17 01:39:12 +08:00
allLoaders.push(loader);
}
for (const loader of /** @type {LoaderItem[]} */ (loaders)) {
2024-02-17 01:39:12 +08:00
allLoaders.push(loader);
}
2019-07-01 17:15:52 +08:00
}
for (const loader of /** @type {LoaderItem[]} */ (preLoaders)) {
2024-02-17 01:39:12 +08:00
allLoaders.push(loader);
}
2024-07-31 04:09:42 +08:00
const type = /** @type {string} */ (settings.type);
const resolveOptions = settings.resolve;
const layer = settings.layer;
if (layer !== undefined && !layers) {
return callback(
new Error(
"'Rule.layer' is only allowed when 'experiments.layers' is enabled"
)
);
}
try {
Object.assign(data.createData, {
layer:
layer === undefined ? contextInfo.issuerLayer || null : layer,
request: stringifyLoadersAndResource(
allLoaders,
resourceData.resource
),
userRequest,
rawRequest: request,
loaders: allLoaders,
resource: resourceData.resource,
2021-08-06 00:47:24 +08:00
context:
resourceData.context || getContext(resourceData.resource),
matchResource: matchResourceData
? matchResourceData.resource
: undefined,
resourceResolveData: resourceData.data,
settings,
type,
parser: this.getParser(type, settings.parser),
parserOptions: settings.parser,
generator: this.getGenerator(type, settings.generator),
generatorOptions: settings.generator,
resolveOptions
});
2024-07-31 15:37:05 +08:00
} catch (createDataErr) {
return callback(/** @type {Error} */ (createDataErr));
}
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,
contextScheme ? this.context : context,
2024-02-17 01:39:12 +08:00
/** @type {LoaderItem[]} */ (elements),
loaderResolver,
resolveContext,
(err, result) => {
if (err) return continueCallback(err);
loaders = result;
continueCallback();
}
);
2024-02-17 01:39:12 +08:00
/**
* @param {string} context context
*/
const defaultResolve = (context) => {
if (/^($|\?)/.test(unresolvedResource)) {
resourceData = {
2025-05-13 21:03:58 +08:00
...cacheParseResource(unresolvedResource),
resource: unresolvedResource,
2025-05-13 21:03:58 +08:00
data: {}
};
continueCallback();
}
// resource without scheme and with path
else {
const normalResolver = this.getResolver(
"normal",
dependencyType
? cachedSetProperty(
resolveOptions || EMPTY_RESOLVE_OPTIONS,
"dependencyType",
dependencyType
2024-07-31 05:43:19 +08:00
)
: resolveOptions
);
this.resolveResource(
contextInfo,
context,
unresolvedResource,
normalResolver,
resolveContext,
2024-08-09 23:42:37 +08:00
(err, _resolvedResource, resolvedResourceResolveData) => {
if (err) return continueCallback(err);
2024-08-09 23:42:37 +08:00
if (_resolvedResource !== false) {
const resolvedResource =
/** @type {string} */
(_resolvedResource);
resourceData = {
2025-05-13 21:03:58 +08:00
...cacheParseResource(resolvedResource),
resource: resolvedResource,
2024-08-09 23:42:37 +08:00
data:
/** @type {ResolveRequest} */
2025-05-13 21:03:58 +08:00
(resolvedResourceResolveData)
};
}
continueCallback();
}
);
}
};
2020-07-03 20:45:49 +08:00
// resource with scheme
if (scheme) {
resourceData = {
resource: unresolvedResource,
data: {},
path: undefined,
query: undefined,
2021-08-06 00:47:24 +08:00
fragment: undefined,
context: undefined
2020-07-03 20:45:49 +08:00
};
this.hooks.resolveForScheme
.for(scheme)
.callAsync(resourceData, data, (err) => {
2020-07-03 20:45:49 +08:00
if (err) return continueCallback(err);
continueCallback();
});
}
// resource within scheme
else if (contextScheme) {
2020-07-03 20:45:49 +08:00
resourceData = {
resource: unresolvedResource,
data: {},
path: undefined,
query: undefined,
2021-08-06 00:47:24 +08:00
fragment: undefined,
context: undefined
2020-07-03 20:45:49 +08:00
};
this.hooks.resolveInScheme
.for(contextScheme)
.callAsync(resourceData, data, (err, handled) => {
if (err) return continueCallback(err);
if (!handled) return defaultResolve(this.context);
continueCallback();
});
}
// resource without scheme and without path
else {
defaultResolve(context);
}
}
);
2017-04-04 05:28:08 +08:00
}
2013-01-31 01:49:25 +08:00
cleanupForCache() {
for (const module of this._restoredUnsafeCacheEntries) {
ChunkGraph.clearChunkGraphForModule(module);
ModuleGraph.clearModuleGraphForModule(module);
module.cleanupForCache();
}
}
/**
* @param {ModuleFactoryCreateData} data data object
* @param {ModuleFactoryCallback} callback callback
* @returns {void}
*/
2017-04-04 05:28:08 +08:00
create(data, callback) {
const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies);
2017-04-04 16:26:16 +08:00
const context = data.context || this.context;
const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;
const dependency = dependencies[0];
const request = dependency.request;
const assertions = dependency.assertions;
const dependencyType = dependency.category || "";
const contextInfo = data.contextInfo;
const fileDependencies = new LazySet();
const missingDependencies = new LazySet();
const contextDependencies = new LazySet();
/** @type {ResolveData} */
const resolveData = {
contextInfo,
resolveOptions,
context,
request,
assertions,
dependencies,
dependencyType,
fileDependencies,
missingDependencies,
contextDependencies,
createData: {},
cacheable: true
};
this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
2019-07-05 06:41:30 +08:00
if (err) {
return callback(err, {
fileDependencies,
missingDependencies,
contextDependencies,
cacheable: false
2019-07-05 06:41:30 +08:00
});
}
// Ignored
2019-07-05 06:41:30 +08:00
if (result === false) {
2024-10-23 08:01:04 +08:00
/** @type {ModuleFactoryResult} * */
const factoryResult = {
2019-07-05 06:41:30 +08:00
fileDependencies,
missingDependencies,
contextDependencies,
cacheable: resolveData.cacheable
2024-10-23 08:01:04 +08:00
};
if (resolveData.ignoredModule) {
factoryResult.module = resolveData.ignoredModule;
}
return callback(null, factoryResult);
2019-07-05 06:41:30 +08:00
}
if (typeof result === "object") {
throw new Error(
deprecationChangedHookMessage(
"beforeResolve",
this.hooks.beforeResolve
)
);
}
this.hooks.factorize.callAsync(resolveData, (err, module) => {
2019-07-05 06:41:30 +08:00
if (err) {
return callback(err, {
fileDependencies,
missingDependencies,
contextDependencies,
cacheable: false
2019-07-05 06:41:30 +08:00
});
}
2024-10-23 08:01:04 +08:00
/** @type {ModuleFactoryResult} * */
const factoryResult = {
module,
fileDependencies,
missingDependencies,
contextDependencies,
cacheable: resolveData.cacheable
};
2017-04-04 05:28:08 +08:00
callback(null, factoryResult);
});
});
}
2017-04-04 05:28:08 +08:00
2024-02-17 01:39:12 +08:00
/**
* @param {ModuleFactoryCreateDataContextInfo} contextInfo context info
* @param {string} context context
* @param {string} unresolvedResource unresolved resource
* @param {ResolverWithOptions} resolver resolver
* @param {ResolveContext} resolveContext resolver context
2024-02-17 02:03:51 +08:00
* @param {(err: null | Error, res?: string | false, req?: ResolveRequest) => void} callback callback
2024-02-17 01:39:12 +08:00
*/
resolveResource(
contextInfo,
context,
unresolvedResource,
resolver,
resolveContext,
callback
) {
resolver.resolve(
contextInfo,
context,
unresolvedResource,
resolveContext,
(err, resolvedResource, resolvedResourceResolveData) => {
if (err) {
return this._resolveResourceErrorHints(
err,
contextInfo,
context,
unresolvedResource,
resolver,
resolveContext,
(err2, hints) => {
if (err2) {
err.message += `
A fatal error happened during resolving additional hints for this error: ${err2.message}`;
err.stack += `
A fatal error happened during resolving additional hints for this error:
${err2.stack}`;
return callback(err);
}
if (hints && hints.length > 0) {
err.message += `
${hints.join("\n\n")}`;
}
// Check if the extension is missing a leading dot (e.g. "js" instead of ".js")
let appendResolveExtensionsHint = false;
2025-07-03 17:06:45 +08:00
const specifiedExtensions = [...resolver.options.extensions];
const expectedExtensions = specifiedExtensions.map(
(extension) => {
if (LEADING_DOT_EXTENSION_REGEX.test(extension)) {
appendResolveExtensionsHint = true;
return `.${extension}`;
}
return extension;
}
);
if (appendResolveExtensionsHint) {
err.message += `\nDid you miss the leading dot in 'resolve.extensions'? Did you mean '${JSON.stringify(
expectedExtensions
)}' instead of '${JSON.stringify(specifiedExtensions)}'?`;
}
callback(err);
}
);
}
callback(err, resolvedResource, resolvedResourceResolveData);
}
);
}
2024-02-17 01:39:12 +08:00
/**
* @param {Error} error error
* @param {ModuleFactoryCreateDataContextInfo} contextInfo context info
* @param {string} context context
* @param {string} unresolvedResource unresolved resource
* @param {ResolverWithOptions} resolver resolver
* @param {ResolveContext} resolveContext resolver context
* @param {Callback<string[]>} callback callback
* @private
*/
_resolveResourceErrorHints(
error,
contextInfo,
context,
unresolvedResource,
resolver,
resolveContext,
callback
) {
asyncLib.parallel(
[
(callback) => {
if (!resolver.options.fullySpecified) return callback();
resolver
.withOptions({
fullySpecified: false
})
.resolve(
contextInfo,
context,
unresolvedResource,
resolveContext,
(err, resolvedResource) => {
if (!err && resolvedResource) {
const resource = parseResource(resolvedResource).path.replace(
/^.*[\\/]/,
""
);
return callback(
null,
`Did you mean '${resource}'?
BREAKING CHANGE: The request '${unresolvedResource}' failed to resolve only because it was resolved as fully specified
2021-08-06 00:47:24 +08:00
(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.`
);
}
callback();
}
);
},
(callback) => {
if (!resolver.options.enforceExtension) return callback();
resolver
.withOptions({
enforceExtension: false,
extensions: []
})
.resolve(
contextInfo,
context,
unresolvedResource,
resolveContext,
(err, resolvedResource) => {
if (!err && resolvedResource) {
let hint = "";
const match = /(\.[^.]+)(\?|$)/.exec(unresolvedResource);
if (match) {
const fixedRequest = unresolvedResource.replace(
/(\.[^.]+)(\?|$)/,
"$2"
);
2024-08-02 02:36:27 +08:00
hint = resolver.options.extensions.has(match[1])
? `Did you mean '${fixedRequest}'?`
: `Did you mean '${fixedRequest}'? Also note that '${match[1]}' is not in 'resolve.extensions' yet and need to be added for this to work?`;
} else {
2024-07-31 12:23:44 +08:00
hint =
"Did you mean to omit the extension or to remove 'resolve.enforceExtension'?";
}
return callback(
null,
`The request '${unresolvedResource}' failed to resolve only because 'resolve.enforceExtension' was specified.
${hint}
Including the extension in the request is no longer possible. Did you mean to enforce including the extension in requests with 'resolve.extensions: []' instead?`
);
}
callback();
}
);
},
(callback) => {
if (
/^\.\.?\//.test(unresolvedResource) ||
resolver.options.preferRelative
) {
return callback();
}
resolver.resolve(
contextInfo,
context,
`./${unresolvedResource}`,
resolveContext,
(err, resolvedResource) => {
if (err || !resolvedResource) return callback();
const moduleDirectories = resolver.options.modules
.map((m) => (Array.isArray(m) ? m.join(", ") : m))
.join(", ");
callback(
null,
`Did you mean './${unresolvedResource}'?
Requests that should resolve in the current directory need to start with './'.
Requests that start with a name are treated as module requests and resolve within module directories (${moduleDirectories}).
If changing the source code is not an option there is also a resolve options called 'preferRelative' which tries to resolve these kind of requests in the current directory too.`
);
}
);
}
],
(err, hints) => {
if (err) return callback(err);
2024-02-17 01:39:12 +08:00
callback(null, /** @type {string[]} */ (hints).filter(Boolean));
}
);
}
2024-02-17 01:39:12 +08:00
/**
* @param {ModuleFactoryCreateDataContextInfo} contextInfo context info
* @param {string} context context
* @param {LoaderItem[]} array array
* @param {ResolverWithOptions} resolver resolver
* @param {ResolveContext} resolveContext resolve context
* @param {Callback<LoaderItem[]>} callback callback
* @returns {void} result
*/
resolveRequestArray(
contextInfo,
context,
array,
resolver,
resolveContext,
callback
) {
2024-02-17 01:39:12 +08:00
// LoaderItem
if (array.length === 0) return callback(null, array);
2018-02-25 09:00:20 +08:00
asyncLib.map(
array,
2025-08-22 06:40:41 +08:00
/**
* @param {LoaderItem} item item
* @param {Callback<LoaderItem>} callback callback
*/
2018-02-25 09:00:20 +08:00
(item, callback) => {
resolver.resolve(
contextInfo,
context,
item.loader,
resolveContext,
(err, result, resolveRequest) => {
2018-02-25 09:00:20 +08:00
if (
err &&
/^[^/]*$/.test(item.loader) &&
2024-07-31 16:17:46 +08:00
!item.loader.endsWith("-loader")
2018-02-25 09:00:20 +08:00
) {
return resolver.resolve(
contextInfo,
context,
2024-07-31 10:39:30 +08:00
`${item.loader}-loader`,
resolveContext,
(err2) => {
2018-02-25 09:00:20 +08:00
if (!err2) {
err.message =
2024-07-31 10:39:30 +08:00
`${err.message}\n` +
2024-07-31 12:23:44 +08:00
"BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.\n" +
2019-06-09 17:23:42 +08:00
` You need to specify '${item.loader}-loader' instead of '${item.loader}',\n` +
2024-07-31 12:23:44 +08:00
" see https://webpack.js.org/migrate/3/#automatic-loader-module-name-extension-removed";
2018-02-25 09:00:20 +08:00
}
callback(err);
}
);
2017-04-04 05:28:08 +08:00
}
2018-02-25 09:00:20 +08:00
if (err) return callback(err);
2024-08-06 11:08:48 +08:00
const parsedResult = this._parseResourceWithoutFragment(
2025-08-22 06:40:41 +08:00
/** @type {string} */
(result)
2024-08-06 11:08:48 +08:00
);
2023-04-18 20:02:05 +08:00
const type = /\.mjs$/i.test(parsedResult.path)
? "module"
2023-04-18 20:02:05 +08:00
: /\.cjs$/i.test(parsedResult.path)
2024-07-31 05:43:19 +08:00
? "commonjs"
: /** @type {ResolveRequest} */
(resolveRequest).descriptionFileData === undefined
? undefined
2025-08-22 06:40:41 +08:00
: /** @type {string} */
(
/** @type {ResolveRequest} */
(resolveRequest).descriptionFileData.type
);
/** @type {LoaderItem} */
const resolved = {
loader: parsedResult.path,
type,
options:
item.options === undefined
? parsedResult.query
? parsedResult.query.slice(1)
: undefined
: item.options,
2024-02-17 01:39:12 +08:00
ident:
item.options === undefined
? undefined
: /** @type {string} */ (item.ident)
};
2024-02-17 01:39:12 +08:00
2025-08-22 06:40:41 +08:00
return callback(null, resolved);
2018-02-25 09:00:20 +08:00
}
);
},
2025-08-22 06:40:41 +08:00
(err, value) => {
callback(err, /** @type {(LoaderItem)[]} */ (value));
}
2018-02-25 09:00:20 +08:00
);
2017-04-04 05:28:08 +08:00
}
2024-02-17 01:39:12 +08:00
/**
* @param {string} type type
* @param {ParserOptions} parserOptions parser options
* @returns {Parser} parser
*/
getParser(type, parserOptions = EMPTY_PARSER_OPTIONS) {
2019-11-19 01:01:54 +08:00
let cache = this.parserCache.get(type);
2019-11-19 01:01:54 +08:00
if (cache === undefined) {
cache = new WeakMap();
this.parserCache.set(type, cache);
}
2019-11-19 19:17:46 +08:00
let parser = cache.get(parserOptions);
if (parser === undefined) {
parser = this.createParser(type, parserOptions);
cache.set(parserOptions, parser);
}
2019-11-19 19:17:46 +08:00
return parser;
2017-04-04 05:28:08 +08:00
}
2020-08-03 02:09:36 +08:00
/**
* @param {string} type type
2024-02-17 01:39:12 +08:00
* @param {ParserOptions} parserOptions parser options
2020-08-03 02:09:36 +08:00
* @returns {Parser} parser
*/
createParser(type, parserOptions = {}) {
parserOptions = mergeGlobalOptions(
this._globalParserOptions,
type,
parserOptions
);
2017-11-28 16:54:24 +08:00
const parser = this.hooks.createParser.for(type).call(parserOptions);
2018-02-25 09:00:20 +08:00
if (!parser) {
throw new Error(`No parser registered for ${type}`);
2017-10-30 20:56:57 +08:00
}
2017-11-28 16:54:24 +08:00
this.hooks.parser.for(type).call(parser, parserOptions);
return parser;
2017-04-04 05:28:08 +08:00
}
2024-02-17 01:39:12 +08:00
/**
* @param {string} type type of generator
* @param {GeneratorOptions} generatorOptions generator options
* @returns {Generator} generator
*/
getGenerator(type, generatorOptions = EMPTY_GENERATOR_OPTIONS) {
2019-11-19 01:01:54 +08:00
let cache = this.generatorCache.get(type);
2019-11-19 01:01:54 +08:00
if (cache === undefined) {
cache = new WeakMap();
this.generatorCache.set(type, cache);
}
2019-11-19 19:17:46 +08:00
let generator = cache.get(generatorOptions);
if (generator === undefined) {
generator = this.createGenerator(type, generatorOptions);
cache.set(generatorOptions, generator);
}
2019-11-19 19:17:46 +08:00
return generator;
}
2024-02-17 01:39:12 +08:00
/**
* @param {string} type type of generator
* @param {GeneratorOptions} generatorOptions generator options
* @returns {Generator} generator
*/
createGenerator(type, generatorOptions = {}) {
generatorOptions = mergeGlobalOptions(
this._globalGeneratorOptions,
type,
generatorOptions
);
2018-02-25 09:00:20 +08:00
const generator = this.hooks.createGenerator
.for(type)
.call(generatorOptions);
if (!generator) {
throw new Error(`No generator registered for ${type}`);
}
this.hooks.generator.for(type).call(generator, generatorOptions);
return generator;
}
2024-02-17 01:39:12 +08:00
/**
* @param {Parameters<ResolverFactory["get"]>[0]} type type of resolver
* @param {Parameters<ResolverFactory["get"]>[1]=} resolveOptions options
* @returns {ReturnType<ResolverFactory["get"]>} the resolver
*/
getResolver(type, resolveOptions) {
return this.resolverFactory.get(type, resolveOptions);
}
2017-04-04 05:28:08 +08:00
}
module.exports = NormalModuleFactory;