webpack/lib/ContextModuleFactory.js

482 lines
14 KiB
JavaScript
Raw Normal View History

2013-01-31 01:49:25 +08:00
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
2018-07-30 23:08:51 +08:00
2017-05-11 03:36:20 +08:00
"use strict";
2018-02-11 12:27:09 +08:00
const asyncLib = require("neo-async");
2018-06-26 14:27:44 +08:00
const { AsyncSeriesWaterfallHook, SyncWaterfallHook } = require("tapable");
2017-05-11 03:36:20 +08:00
const ContextModule = require("./ContextModule");
const ModuleFactory = require("./ModuleFactory");
2017-05-11 03:36:20 +08:00
const ContextElementDependency = require("./dependencies/ContextElementDependency");
2021-04-23 19:51:39 +08:00
const LazySet = require("./util/LazySet");
const { cachedSetProperty } = require("./util/cleverMerge");
const { createFakeHook } = require("./util/deprecation");
const { join } = require("./util/fs");
2017-05-11 03:36:20 +08:00
2020-06-10 19:31:01 +08:00
/** @typedef {import("./ContextModule").ContextModuleOptions} ContextModuleOptions */
/** @typedef {import("./ContextModule").ResolveDependenciesCallback} ResolveDependenciesCallback */
/** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
/** @typedef {import("./ModuleFactory").ModuleFactoryCallback} ModuleFactoryCallback */
/** @typedef {import("./ResolverFactory")} ResolverFactory */
/** @typedef {import("./dependencies/ContextDependency")} ContextDependency */
2024-08-07 23:22:25 +08:00
/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */
/**
* @template T
* @typedef {import("./util/deprecation").FakeHook<T>} FakeHook<T>
*/
/** @typedef {import("./util/fs").IStats} IStats */
2020-06-10 19:31:01 +08:00
/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
2024-08-07 23:22:25 +08:00
/** @typedef {{ context: string, request: string }} ContextAlternativeRequest */
const EMPTY_RESOLVE_OPTIONS = {};
2020-11-29 03:04:11 +08:00
module.exports = class ContextModuleFactory extends ModuleFactory {
/**
* @param {ResolverFactory} resolverFactory resolverFactory
*/
constructor(resolverFactory) {
super();
2024-08-07 23:22:25 +08:00
/** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[], ContextModuleOptions]>} */
const alternativeRequests = new AsyncSeriesWaterfallHook([
"modules",
"options"
]);
2018-07-30 20:25:40 +08:00
this.hooks = Object.freeze({
2018-12-09 19:54:17 +08:00
/** @type {AsyncSeriesWaterfallHook<[TODO]>} */
2017-11-28 16:54:24 +08:00
beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
2018-12-09 19:54:17 +08:00
/** @type {AsyncSeriesWaterfallHook<[TODO]>} */
2017-11-28 16:54:24 +08:00
afterResolve: new AsyncSeriesWaterfallHook(["data"]),
2018-12-09 19:54:17 +08:00
/** @type {SyncWaterfallHook<[string[]]>} */
2017-11-28 16:54:24 +08:00
contextModuleFiles: new SyncWaterfallHook(["files"]),
2024-08-07 23:22:25 +08:00
/** @type {FakeHook<Pick<AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>, "tap" | "tapAsync" | "tapPromise" | "name">>} */
alternatives: createFakeHook(
{
name: "alternatives",
2024-08-07 23:22:25 +08:00
/** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["intercept"]} */
intercept: interceptor => {
throw new Error(
"Intercepting fake hook ContextModuleFactory.hooks.alternatives is not possible, use ContextModuleFactory.hooks.alternativeRequests instead"
);
},
2024-08-07 23:22:25 +08:00
/** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["tap"]} */
tap: (options, fn) => {
alternativeRequests.tap(options, fn);
},
2024-08-07 23:22:25 +08:00
/** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["tapAsync"]} */
tapAsync: (options, fn) => {
alternativeRequests.tapAsync(options, (items, _options, callback) =>
fn(items, callback)
);
},
2024-08-07 23:22:25 +08:00
/** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["tapPromise"]} */
tapPromise: (options, fn) => {
alternativeRequests.tapPromise(options, fn);
}
},
"ContextModuleFactory.hooks.alternatives has deprecated in favor of ContextModuleFactory.hooks.alternativeRequests with an additional options argument.",
"DEP_WEBPACK_CONTEXT_MODULE_FACTORY_ALTERNATIVES"
),
alternativeRequests
2018-07-30 20:25:40 +08:00
});
this.resolverFactory = resolverFactory;
2017-05-11 03:36:20 +08:00
}
/**
* @param {ModuleFactoryCreateData} data data object
* @param {ModuleFactoryCallback} callback callback
* @returns {void}
*/
2017-05-11 03:36:20 +08:00
create(data, callback) {
const context = data.context;
const dependencies = data.dependencies;
const resolveOptions = data.resolveOptions;
const dependency = /** @type {ContextDependency} */ (dependencies[0]);
2021-04-23 19:51:39 +08:00
const fileDependencies = new LazySet();
const missingDependencies = new LazySet();
const contextDependencies = new LazySet();
2018-02-25 09:00:20 +08:00
this.hooks.beforeResolve.callAsync(
{
2024-07-31 04:09:42 +08:00
context,
dependencies,
layer: data.contextInfo.issuerLayer,
resolveOptions,
fileDependencies,
missingDependencies,
contextDependencies,
...dependency.options
},
2018-02-25 09:00:20 +08:00
(err, beforeResolveResult) => {
2019-07-05 06:41:30 +08:00
if (err) {
return callback(err, {
fileDependencies,
missingDependencies,
contextDependencies
});
}
2018-02-25 09:00:20 +08:00
// Ignored
2019-07-05 06:41:30 +08:00
if (!beforeResolveResult) {
return callback(null, {
fileDependencies,
missingDependencies,
contextDependencies
});
}
2018-02-25 09:00:20 +08:00
const context = beforeResolveResult.context;
const request = beforeResolveResult.request;
const resolveOptions = beforeResolveResult.resolveOptions;
2024-07-31 09:56:53 +08:00
let loaders;
let resource;
let loadersPrefix = "";
2018-02-25 09:00:20 +08:00
const idx = request.lastIndexOf("!");
if (idx >= 0) {
let loadersRequest = request.slice(0, idx + 1);
2018-02-25 09:00:20 +08:00
let i;
for (
i = 0;
i < loadersRequest.length && loadersRequest[i] === "!";
i++
) {
2018-02-25 09:00:20 +08:00
loadersPrefix += "!";
}
loadersRequest = loadersRequest
.slice(i)
2018-02-25 09:00:20 +08:00
.replace(/!+$/, "")
.replace(/!!+/g, "!");
2024-08-02 02:36:27 +08:00
loaders = loadersRequest === "" ? [] : loadersRequest.split("!");
resource = request.slice(idx + 1);
2018-02-25 09:00:20 +08:00
} else {
loaders = [];
resource = request;
2017-05-11 03:36:20 +08:00
}
2018-02-25 09:00:20 +08:00
const contextResolver = this.resolverFactory.get(
"context",
dependencies.length > 0
? cachedSetProperty(
resolveOptions || EMPTY_RESOLVE_OPTIONS,
"dependencyType",
dependencies[0].category
2024-07-31 04:54:55 +08:00
)
: resolveOptions
2018-02-25 09:00:20 +08:00
);
const loaderResolver = this.resolverFactory.get("loader");
2018-02-25 09:00:20 +08:00
asyncLib.parallel(
[
callback => {
2024-08-07 23:22:25 +08:00
const results = /** @type ResolveRequest[] */ ([]);
2024-08-09 23:42:37 +08:00
/**
* @param {ResolveRequest} obj obj
* @returns {void}
*/
const yield_ = obj => {
results.push(obj);
};
2018-02-25 09:00:20 +08:00
contextResolver.resolve(
{},
context,
resource,
2019-07-05 06:41:30 +08:00
{
fileDependencies,
missingDependencies,
contextDependencies,
yield: yield_
2019-07-05 06:41:30 +08:00
},
err => {
2018-02-25 09:00:20 +08:00
if (err) return callback(err);
callback(null, results);
2018-02-25 09:00:20 +08:00
}
);
},
callback => {
asyncLib.map(
loaders,
(loader, callback) => {
loaderResolver.resolve(
{},
context,
loader,
2019-07-05 06:41:30 +08:00
{
fileDependencies,
missingDependencies,
contextDependencies
},
2018-02-25 09:00:20 +08:00
(err, result) => {
if (err) return callback(err);
2024-08-07 23:22:25 +08:00
callback(null, /** @type {string} */ (result));
2018-02-25 09:00:20 +08:00
}
);
},
callback
);
}
],
(err, result) => {
2019-07-05 06:41:30 +08:00
if (err) {
return callback(err, {
fileDependencies,
missingDependencies,
contextDependencies
});
}
2024-08-07 23:22:25 +08:00
let [contextResult, loaderResult] =
/** @type {[ResolveRequest[], string[]]} */ (result);
if (contextResult.length > 1) {
const first = contextResult[0];
contextResult = contextResult.filter(r => r.path);
if (contextResult.length === 0) contextResult.push(first);
}
2018-02-25 09:00:20 +08:00
this.hooks.afterResolve.callAsync(
{
addon:
loadersPrefix +
loaderResult.join("!") +
(loaderResult.length > 0 ? "!" : ""),
resource:
contextResult.length > 1
? contextResult.map(r => r.path)
: contextResult[0].path,
resolveDependencies: this.resolveDependencies.bind(this),
resourceQuery: contextResult[0].query,
resourceFragment: contextResult[0].fragment,
...beforeResolveResult
},
2018-02-25 09:00:20 +08:00
(err, result) => {
2019-07-05 06:41:30 +08:00
if (err) {
return callback(err, {
fileDependencies,
missingDependencies,
contextDependencies
});
}
2018-02-25 09:00:20 +08:00
// Ignored
2019-07-05 06:41:30 +08:00
if (!result) {
return callback(null, {
fileDependencies,
missingDependencies,
contextDependencies
});
}
2018-02-25 09:00:20 +08:00
return callback(null, {
module: new ContextModule(result.resolveDependencies, result),
fileDependencies,
missingDependencies,
contextDependencies
});
2018-02-25 09:00:20 +08:00
}
);
}
);
}
);
2017-05-11 03:36:20 +08:00
}
2013-02-04 19:34:20 +08:00
2020-06-10 19:31:01 +08:00
/**
* @param {InputFileSystem} fs file system
* @param {ContextModuleOptions} options options
* @param {ResolveDependenciesCallback} callback callback function
* @returns {void}
*/
2017-10-14 07:51:01 +08:00
resolveDependencies(fs, options, callback) {
const cmf = this;
2020-06-10 19:31:01 +08:00
const {
resource,
resourceQuery,
2020-07-03 23:03:15 +08:00
resourceFragment,
2020-06-10 19:31:01 +08:00
recursive,
regExp,
include,
exclude,
referencedExports,
category,
2024-03-16 00:59:30 +08:00
typePrefix,
attributes
2020-06-10 19:31:01 +08:00
} = options;
2018-02-25 09:00:20 +08:00
if (!regExp || !resource) return callback(null, []);
2024-08-07 23:22:25 +08:00
/**
* @param {string} ctx context
* @param {string} directory directory
* @param {Set<string>} visited visited
* @param {ResolveDependenciesCallback} callback callback
*/
const addDirectoryChecked = (ctx, directory, visited, callback) => {
2024-08-07 23:22:25 +08:00
/** @type {NonNullable<InputFileSystem["realpath"]>} */
(fs.realpath)(directory, (err, _realPath) => {
if (err) return callback(err);
2024-08-07 23:22:25 +08:00
const realPath = /** @type {string} */ (_realPath);
if (visited.has(realPath)) return callback(null, []);
2024-08-07 23:22:25 +08:00
/** @type {Set<string> | undefined} */
let recursionStack;
addDirectory(
ctx,
directory,
(_, dir, callback) => {
if (recursionStack === undefined) {
recursionStack = new Set(visited);
recursionStack.add(realPath);
}
addDirectoryChecked(ctx, dir, recursionStack, callback);
},
callback
);
});
};
2024-08-07 23:22:25 +08:00
/**
* @param {string} ctx context
* @param {string} directory directory
* @param {(context: string, subResource: string, callback: () => void) => void} addSubDirectory addSubDirectoryFn
2024-08-07 23:22:25 +08:00
* @param {ResolveDependenciesCallback} callback callback
*/
const addDirectory = (ctx, directory, addSubDirectory, callback) => {
2017-08-11 13:52:25 +08:00
fs.readdir(directory, (err, files) => {
2018-02-25 09:00:20 +08:00
if (err) return callback(err);
const processedFiles = cmf.hooks.contextModuleFiles.call(
/** @type {string[]} */ (files).map(file => file.normalize("NFC"))
);
if (!processedFiles || processedFiles.length === 0) {
return callback(null, []);
}
2018-02-25 09:00:20 +08:00
asyncLib.map(
processedFiles.filter(p => p.indexOf(".") !== 0),
2018-02-26 10:43:37 +08:00
(segment, callback) => {
const subResource = join(fs, directory, segment);
2018-02-25 09:00:20 +08:00
2025-07-08 22:46:17 +08:00
if (!exclude || !exclude.test(subResource)) {
2024-08-07 23:22:25 +08:00
fs.stat(subResource, (err, _stat) => {
2018-02-25 09:00:20 +08:00
if (err) {
if (err.code === "ENOENT") {
// ENOENT is ok here because the file may have been deleted between
// the readdir and stat calls.
return callback();
}
2024-07-31 04:21:27 +08:00
return callback(err);
2017-10-14 07:51:01 +08:00
}
2013-02-04 19:34:20 +08:00
2024-08-07 23:22:25 +08:00
const stat = /** @type {IStats} */ (_stat);
2018-02-25 09:00:20 +08:00
if (stat.isDirectory()) {
if (!recursive) return callback();
addSubDirectory(ctx, subResource, callback);
2018-02-25 09:00:20 +08:00
} else if (
stat.isFile() &&
2025-07-08 22:46:17 +08:00
(!include || include.test(subResource))
2018-02-25 09:00:20 +08:00
) {
2024-08-07 23:22:25 +08:00
/** @type {{ context: string, request: string }} */
2018-02-25 09:00:20 +08:00
const obj = {
context: ctx,
2024-07-31 10:39:30 +08:00
request: `.${subResource.slice(ctx.length).replace(/\\/g, "/")}`
2018-02-25 09:00:20 +08:00
};
this.hooks.alternativeRequests.callAsync(
2018-02-25 09:00:20 +08:00
[obj],
options,
2018-02-25 09:00:20 +08:00
(err, alternatives) => {
if (err) return callback(err);
2024-08-07 23:22:25 +08:00
callback(
null,
/** @type {ContextAlternativeRequest[]} */
(alternatives)
.filter(obj =>
regExp.test(/** @type {string} */ (obj.request))
)
.map(obj => {
const dep = new ContextElementDependency(
`${obj.request}${resourceQuery}${resourceFragment}`,
obj.request,
typePrefix,
/** @type {string} */
(category),
referencedExports,
2025-04-26 01:43:01 +08:00
obj.context,
2024-08-07 23:22:25 +08:00
attributes
);
dep.optional = true;
return dep;
})
);
2018-02-25 09:00:20 +08:00
}
);
} else {
callback();
}
2018-02-25 09:00:20 +08:00
});
} else {
callback();
}
2018-02-25 09:00:20 +08:00
},
(err, result) => {
if (err) return callback(err);
if (!result) return callback(null, []);
2020-03-13 00:51:26 +08:00
const flattenedResult = [];
for (const item of result) {
2020-03-13 00:51:26 +08:00
if (item) flattenedResult.push(...item);
}
2020-03-13 00:51:26 +08:00
callback(null, flattenedResult);
2018-02-25 09:00:20 +08:00
}
);
2017-08-11 13:52:25 +08:00
});
2017-11-08 18:32:05 +08:00
};
2024-08-07 23:22:25 +08:00
/**
* @param {string} ctx context
* @param {string} dir dir
* @param {ResolveDependenciesCallback} callback callback
* @returns {void}
*/
const addSubDirectory = (ctx, dir, callback) =>
addDirectory(ctx, dir, addSubDirectory, callback);
2024-08-07 23:22:25 +08:00
/**
* @param {string} resource resource
* @param {ResolveDependenciesCallback} callback callback
*/
const visitResource = (resource, callback) => {
if (typeof fs.realpath === "function") {
addDirectoryChecked(resource, resource, new Set(), callback);
} else {
addDirectory(resource, resource, addSubDirectory, callback);
}
};
if (typeof resource === "string") {
visitResource(resource, callback);
} else {
2024-08-07 23:22:25 +08:00
asyncLib.map(resource, visitResource, (err, _result) => {
if (err) return callback(err);
2024-08-07 23:22:25 +08:00
const result = /** @type {ContextElementDependency[][]} */ (_result);
// result dependencies should have unique userRequest
// ordered by resolve result
2024-08-07 23:22:25 +08:00
/** @type {Set<string>} */
const temp = new Set();
2024-08-07 23:22:25 +08:00
/** @type {ContextElementDependency[]} */
const res = [];
for (let i = 0; i < result.length; i++) {
const inner = result[i];
for (const el of inner) {
if (temp.has(el.userRequest)) continue;
res.push(el);
temp.add(el.userRequest);
}
}
callback(null, res);
});
}
2017-05-11 03:36:20 +08:00
}
2013-01-31 01:49:25 +08:00
};