| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const asyncLib = require("neo-async");
- const { AsyncSeriesWaterfallHook, SyncWaterfallHook } = require("tapable");
- const ContextModule = require("./ContextModule");
- const ModuleFactory = require("./ModuleFactory");
- const ContextElementDependency = require("./dependencies/ContextElementDependency");
- const LazySet = require("./util/LazySet");
- const { cachedSetProperty } = require("./util/cleverMerge");
- const { createFakeHook } = require("./util/deprecation");
- const { join } = require("./util/fs");
- const {
- globPatternBaseReachesDir,
- globUserRequest,
- isNonExhaustiveImportMetaGlobSkippedDir,
- resolveContextModuleGlobPattern
- } = require("./util/globUtils");
- /** @import { ResolvedContextModuleGlobPattern } from "./util/globUtils" */
- /** @typedef {(context: string, subResource: string, callback: () => void, resolvedGlobPatterns?: ResolvedContextModuleGlobPattern[]) => void} AddSubDirectoryFn */
- /** @import { ResolveRequest } from "enhanced-resolve" */
- /** @import { FileSystemDependencies } from "./Compilation" */
- /**
- * @import {
- * ContextModuleOptions,
- * ResolveDependenciesCallback,
- * ContextOptions
- * } from "./ContextModule"
- */
- /**
- * @import {
- * ModuleFactoryCreateData,
- * ModuleFactoryCallback
- * } from "./ModuleFactory"
- */
- /** @import ResolverFactory from "./ResolverFactory" */
- /** @import ContextDependency from "./dependencies/ContextDependency" */
- /**
- * Defines the shared type used by this module.
- * @template T
- * @typedef {import("./util/deprecation").FakeHook<T>} FakeHook<T>
- */
- /** @import { IStats, InputFileSystem } from "./util/fs" */
- /** @typedef {{ context: string, request: string }} ContextAlternativeRequest */
- /**
- * Defines the context resolve data type used by this module.
- * @typedef {object} ContextResolveData
- * @property {string} context
- * @property {string} request
- * @property {ModuleFactoryCreateData["resolveOptions"]} resolveOptions
- * @property {FileSystemDependencies} fileDependencies
- * @property {FileSystemDependencies} missingDependencies
- * @property {FileSystemDependencies} contextDependencies
- * @property {ContextDependency[]} dependencies
- */
- /** @typedef {ContextResolveData & ContextOptions} BeforeContextResolveData */
- /** @typedef {BeforeContextResolveData & { resource: string | string[], resourceQuery: string | undefined, resourceFragment: string | undefined, resolveDependencies: ContextModuleFactory["resolveDependencies"] }} AfterContextResolveData */
- const EMPTY_RESOLVE_OPTIONS = {};
- /**
- * Strips the query and fragment the elements of the context carry themselves, so
- * the remainder can be joined with an element request again.
- * @param {string | undefined} request the request of the context as written by the user
- * @param {string | undefined} resourceQuery query of the resolved context
- * @param {string | undefined} resourceFragment fragment of the resolved context
- * @returns {string | undefined} the request without query and fragment
- */
- const getContextRequest = (request, resourceQuery, resourceFragment) => {
- if (request === undefined) return undefined;
- const suffix = (resourceQuery || "") + (resourceFragment || "");
- return suffix && request.endsWith(suffix)
- ? request.slice(0, -suffix.length)
- : request;
- };
- class ContextModuleFactory extends ModuleFactory {
- /**
- * Creates an instance of ContextModuleFactory.
- * @param {ResolverFactory} resolverFactory resolverFactory
- */
- constructor(resolverFactory) {
- super();
- /** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[], ContextModuleOptions]>} */
- const alternativeRequests = new AsyncSeriesWaterfallHook([
- "modules",
- "options"
- ]);
- this.hooks = Object.freeze({
- /** @type {AsyncSeriesWaterfallHook<[BeforeContextResolveData], BeforeContextResolveData | false | void>} */
- beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
- /** @type {AsyncSeriesWaterfallHook<[AfterContextResolveData], AfterContextResolveData | false | void>} */
- afterResolve: new AsyncSeriesWaterfallHook(["data"]),
- /** @type {SyncWaterfallHook<[string[]]>} */
- contextModuleFiles: new SyncWaterfallHook(["files"]),
- /** @type {FakeHook<Pick<AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>, "tap" | "tapAsync" | "tapPromise" | "name">>} */
- alternatives: createFakeHook(
- {
- name: "alternatives",
- /** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["intercept"]} */
- intercept: (interceptor) => {
- throw new Error(
- "Intercepting fake hook ContextModuleFactory.hooks.alternatives is not possible, use ContextModuleFactory.hooks.alternativeRequests instead"
- );
- },
- /** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["tap"]} */
- tap: (options, fn) => {
- alternativeRequests.tap(options, fn);
- },
- /** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["tapAsync"]} */
- tapAsync: (options, fn) => {
- alternativeRequests.tapAsync(options, (items, _options, callback) =>
- fn(items, callback)
- );
- },
- /** @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
- });
- /** @type {ResolverFactory} */
- this.resolverFactory = resolverFactory;
- }
- /**
- * Processes the provided data.
- * @param {ModuleFactoryCreateData} data data object
- * @param {ModuleFactoryCallback} callback callback
- * @returns {void}
- */
- create(data, callback) {
- const context = data.context;
- const dependencies = /** @type {ContextDependency[]} */ (data.dependencies);
- const resolveOptions = data.resolveOptions;
- const dependency = dependencies[0];
- /** @type {FileSystemDependencies} */
- const fileDependencies = new LazySet();
- /** @type {FileSystemDependencies} */
- const missingDependencies = new LazySet();
- /** @type {FileSystemDependencies} */
- const contextDependencies = new LazySet();
- this.hooks.beforeResolve.callAsync(
- {
- context,
- dependencies,
- layer: data.contextInfo.issuerLayer,
- resolveOptions,
- fileDependencies,
- missingDependencies,
- contextDependencies,
- ...dependency.options
- },
- (err, beforeResolveResult) => {
- if (err) {
- return callback(err, {
- fileDependencies,
- missingDependencies,
- contextDependencies
- });
- }
- // Ignored
- if (!beforeResolveResult) {
- return callback(null, {
- fileDependencies,
- missingDependencies,
- contextDependencies
- });
- }
- const context = beforeResolveResult.context;
- const request = beforeResolveResult.request;
- const resolveOptions = beforeResolveResult.resolveOptions;
- /** @type {undefined | string[]} */
- let loaders;
- /** @type {undefined | string} */
- let resource;
- let loadersPrefix = "";
- const idx = request.lastIndexOf("!");
- if (idx >= 0) {
- let loadersRequest = request.slice(0, idx + 1);
- /** @type {number} */
- let i;
- for (
- i = 0;
- i < loadersRequest.length && loadersRequest[i] === "!";
- i++
- ) {
- loadersPrefix += "!";
- }
- loadersRequest = loadersRequest
- .slice(i)
- .replace(/!+$/, "")
- .replace(/!{2,}/g, "!");
- loaders = loadersRequest === "" ? [] : loadersRequest.split("!");
- resource = request.slice(idx + 1);
- } else {
- loaders = [];
- resource = request;
- }
- const contextResolver = this.resolverFactory.get(
- "context",
- dependencies.length > 0
- ? cachedSetProperty(
- resolveOptions || EMPTY_RESOLVE_OPTIONS,
- "dependencyType",
- dependencies[0].category
- )
- : resolveOptions
- );
- const loaderResolver = this.resolverFactory.get("loader");
- asyncLib.parallel(
- [
- (callback) => {
- const results = /** @type {ResolveRequest[]} */ ([]);
- /**
- * Processes the provided obj.
- * @param {ResolveRequest} obj obj
- * @returns {void}
- */
- const yield_ = (obj) => {
- results.push(obj);
- };
- contextResolver.resolve(
- {},
- context,
- resource,
- {
- fileDependencies,
- missingDependencies,
- contextDependencies,
- yield: yield_
- },
- (err) => {
- if (err) return callback(err);
- callback(null, results);
- }
- );
- },
- (callback) => {
- asyncLib.map(
- loaders,
- (loader, callback) => {
- loaderResolver.resolve(
- {},
- context,
- loader,
- {
- fileDependencies,
- missingDependencies,
- contextDependencies
- },
- (err, result) => {
- if (err) return callback(err);
- callback(null, result);
- }
- );
- },
- callback
- );
- }
- ],
- (err, result) => {
- if (err) {
- return callback(err, {
- fileDependencies,
- missingDependencies,
- contextDependencies
- });
- }
- 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);
- }
- this.hooks.afterResolve.callAsync(
- {
- addon:
- loadersPrefix +
- loaderResult.join("!") +
- (loaderResult.length > 0 ? "!" : ""),
- resource:
- contextResult.length > 1
- ? /** @type {string[]} */ (contextResult.map((r) => r.path))
- : /** @type {string} */ (contextResult[0].path),
- resolveDependencies: this.resolveDependencies.bind(this),
- resourceQuery: contextResult[0].query,
- resourceFragment: contextResult[0].fragment,
- ...beforeResolveResult
- },
- (err, result) => {
- if (err) {
- return callback(err, {
- fileDependencies,
- missingDependencies,
- contextDependencies
- });
- }
- // Ignored
- if (!result) {
- return callback(null, {
- fileDependencies,
- missingDependencies,
- contextDependencies
- });
- }
- return callback(null, {
- module: new ContextModule(result.resolveDependencies, result),
- fileDependencies,
- missingDependencies,
- contextDependencies
- });
- }
- );
- }
- );
- }
- );
- }
- /**
- * Resolves dependencies.
- * @param {InputFileSystem} fs file system
- * @param {ContextModuleOptions} options options
- * @param {ResolveDependenciesCallback} callback callback function
- * @returns {void}
- */
- resolveDependencies(fs, options, callback) {
- const cmf = this;
- const {
- resource,
- resourceQuery,
- resourceFragment,
- recursive,
- regExp,
- patterns,
- requestContext,
- exhaustive,
- caseSensitive,
- include,
- exclude,
- referencedExports,
- category,
- typePrefix,
- attributes
- } = options;
- const isImportMetaGlob = Boolean(patterns && requestContext);
- if ((!regExp && !isImportMetaGlob) || !resource) return callback(null, []);
- // the request the user wrote (`#configs`, `./dir`, …) before it was resolved
- // to a directory — elements keep it so they can report an original request
- const contextRequest = getContextRequest(
- options.request,
- resourceQuery,
- resourceFragment
- );
- /**
- * Adds directory checked.
- * @param {string} ctx context
- * @param {string} directory directory
- * @param {Set<string>} visited visited
- * @param {ResolveDependenciesCallback} callback callback
- * @param {ResolvedContextModuleGlobPattern[] | undefined} resolvedGlobPatterns resolved glob patterns
- */
- const addDirectoryChecked = (
- ctx,
- directory,
- visited,
- callback,
- resolvedGlobPatterns
- ) => {
- /** @type {NonNullable<InputFileSystem["realpath"]>} */
- (fs.realpath)(directory, (err, _realPath) => {
- if (err) return callback(err);
- const realPath = /** @type {string} */ (_realPath);
- if (visited.has(realPath)) return callback(null, []);
- /** @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,
- resolvedGlobPatterns
- );
- },
- callback,
- resolvedGlobPatterns
- );
- });
- };
- /**
- * Adds the provided ctx to the context module factory.
- * @param {string} ctx context
- * @param {string} directory directory
- * @param {AddSubDirectoryFn} addSubDirectory addSubDirectoryFn
- * @param {ResolveDependenciesCallback} callback callback
- * @param {ResolvedContextModuleGlobPattern[] | undefined} resolvedGlobPatterns resolved glob patterns
- * @returns {void}
- */
- const addDirectory = (
- ctx,
- directory,
- addSubDirectory,
- callback,
- resolvedGlobPatterns
- ) => {
- fs.readdir(directory, (err, files) => {
- 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, []);
- }
- /** @type {ContextAlternativeRequest[]} */
- const fileObjs = [];
- /** @type {ContextElementDependency[]} */
- const globDeps = [];
- /** @type {Set<string>} */
- const globUserRequests = new Set();
- asyncLib.map(
- isImportMetaGlob
- ? processedFiles
- : processedFiles.filter((p) => p.indexOf(".") !== 0),
- (segment, callback) => {
- const subResource = join(fs, directory, segment);
- if (!exclude || !exclude.test(subResource)) {
- fs.stat(subResource, (err, _stat) => {
- 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();
- }
- return callback(err);
- }
- const stat = /** @type {IStats} */ (_stat);
- if (stat.isDirectory()) {
- if (!recursive) return callback();
- if (
- isImportMetaGlob &&
- !exhaustive &&
- isNonExhaustiveImportMetaGlobSkippedDir(segment) &&
- !(
- resolvedGlobPatterns &&
- globPatternBaseReachesDir(
- resolvedGlobPatterns,
- subResource
- )
- )
- ) {
- return callback();
- }
- addSubDirectory(
- ctx,
- subResource,
- callback,
- resolvedGlobPatterns
- );
- } else if (
- stat.isFile() &&
- (!include || include.test(subResource))
- ) {
- if (
- isImportMetaGlob &&
- patterns &&
- requestContext &&
- resolvedGlobPatterns
- ) {
- const relativePath = `.${subResource
- .slice(ctx.length)
- .replace(/\\/g, "/")}`;
- const exposedUserRequest = globUserRequest(
- resolvedGlobPatterns,
- subResource,
- exhaustive === true,
- caseSensitive !== false
- );
- if (
- exposedUserRequest &&
- !globUserRequests.has(exposedUserRequest)
- ) {
- globUserRequests.add(exposedUserRequest);
- const dep = new ContextElementDependency(
- `${relativePath}${resourceQuery}${resourceFragment}`,
- exposedUserRequest,
- typePrefix,
- /** @type {string} */
- (category),
- referencedExports,
- ctx,
- attributes,
- contextRequest
- );
- dep.optional = true;
- globDeps.push(dep);
- }
- return callback();
- }
- // Collect for a single batched alternativeRequests call
- // per directory below. Calling the hook once per file
- // would pay per-call overhead (closure, resolverFactory
- // lookup, array allocations) for every file in the
- // context — which is the bulk of work on rebuilds.
- fileObjs.push({
- context: ctx,
- request: `.${subResource.slice(ctx.length).replace(/\\/g, "/")}`
- });
- callback();
- } else {
- callback();
- }
- });
- } else {
- callback();
- }
- },
- (err, result) => {
- if (err) return callback(err);
- /** @type {ContextElementDependency[]} */
- const flattenedResult = [];
- if (result) {
- for (const item of result) {
- if (item) flattenedResult.push(...item);
- }
- }
- if (isImportMetaGlob) {
- /** @type {Set<string>} */
- const mergedUserRequests = new Set();
- /** @type {ContextElementDependency[]} */
- const merged = [];
- for (const dep of [...flattenedResult, ...globDeps]) {
- if (mergedUserRequests.has(dep.userRequest)) continue;
- mergedUserRequests.add(dep.userRequest);
- merged.push(dep);
- }
- return callback(null, merged);
- }
- if (fileObjs.length === 0) {
- return callback(null, flattenedResult);
- }
- this.hooks.alternativeRequests.callAsync(
- fileObjs,
- options,
- (err, alternatives) => {
- if (err) return callback(err);
- for (const alt of /** @type {ContextAlternativeRequest[]} */ (
- alternatives
- )) {
- if (
- !(regExp instanceof RegExp) ||
- !regExp.test(/** @type {string} */ (alt.request))
- ) {
- continue;
- }
- const dep = new ContextElementDependency(
- `${alt.request}${resourceQuery}${resourceFragment}`,
- alt.request,
- typePrefix,
- /** @type {string} */
- (category),
- referencedExports,
- alt.context,
- attributes,
- contextRequest
- );
- dep.optional = true;
- flattenedResult.push(dep);
- }
- callback(null, flattenedResult);
- }
- );
- }
- );
- });
- };
- /**
- * Adds sub directory.
- * @param {string} ctx context
- * @param {string} dir dir
- * @param {ResolveDependenciesCallback} callback callback
- * @param {ResolvedContextModuleGlobPattern[] | undefined} resolvedGlobPatterns resolved glob patterns
- * @returns {void}
- */
- const addSubDirectory = (ctx, dir, callback, resolvedGlobPatterns) =>
- addDirectory(ctx, dir, addSubDirectory, callback, resolvedGlobPatterns);
- /**
- * Processes the provided resource.
- * @param {string} resource resource
- * @param {ResolveDependenciesCallback} callback callback
- */
- const visitResource = (resource, callback) => {
- /** @type {ResolvedContextModuleGlobPattern[] | undefined} */
- const resolvedGlobPatterns =
- isImportMetaGlob && patterns && requestContext
- ? patterns.map((pattern) =>
- resolveContextModuleGlobPattern(pattern, requestContext, resource)
- )
- : undefined;
- if (typeof fs.realpath === "function") {
- addDirectoryChecked(
- resource,
- resource,
- /** @type {Set<string>} */
- new Set(),
- callback,
- resolvedGlobPatterns
- );
- } else {
- addDirectory(
- resource,
- resource,
- addSubDirectory,
- callback,
- resolvedGlobPatterns
- );
- }
- };
- if (typeof resource === "string") {
- visitResource(resource, callback);
- } else {
- asyncLib.map(resource, visitResource, (err, _result) => {
- if (err) return callback(err);
- const result = /** @type {ContextElementDependency[][]} */ (_result);
- // result dependencies should have unique userRequest
- // ordered by resolve result
- /** @type {Set<string>} */
- const temp = new Set();
- /** @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);
- });
- }
- }
- }
- module.exports = ContextModuleFactory;
|