123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- /*
- 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");
- /** @typedef {import("./ContextModule").ContextModuleOptions} ContextModuleOptions */
- /** @typedef {import("./ContextModule").ResolveDependenciesCallback} ResolveDependenciesCallback */
- /** @typedef {import("./Module")} Module */
- /** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
- /** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
- /** @typedef {import("./ResolverFactory")} ResolverFactory */
- /** @typedef {import("./dependencies/ContextDependency")} ContextDependency */
- /** @template T @typedef {import("./util/deprecation").FakeHook<T>} FakeHook<T> */
- /** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
- const EMPTY_RESOLVE_OPTIONS = {};
- module.exports = class ContextModuleFactory extends ModuleFactory {
- /**
- * @param {ResolverFactory} resolverFactory resolverFactory
- */
- constructor(resolverFactory) {
- super();
- /** @type {AsyncSeriesWaterfallHook<[TODO[], ContextModuleOptions]>} */
- const alternativeRequests = new AsyncSeriesWaterfallHook([
- "modules",
- "options"
- ]);
- this.hooks = Object.freeze({
- /** @type {AsyncSeriesWaterfallHook<[TODO]>} */
- beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
- /** @type {AsyncSeriesWaterfallHook<[TODO]>} */
- afterResolve: new AsyncSeriesWaterfallHook(["data"]),
- /** @type {SyncWaterfallHook<[string[]]>} */
- contextModuleFiles: new SyncWaterfallHook(["files"]),
- /** @type {FakeHook<Pick<AsyncSeriesWaterfallHook<[TODO[]]>, "tap" | "tapAsync" | "tapPromise" | "name">>} */
- alternatives: createFakeHook(
- {
- name: "alternatives",
- /** @type {AsyncSeriesWaterfallHook<[TODO[]]>["intercept"]} */
- intercept: interceptor => {
- throw new Error(
- "Intercepting fake hook ContextModuleFactory.hooks.alternatives is not possible, use ContextModuleFactory.hooks.alternativeRequests instead"
- );
- },
- /** @type {AsyncSeriesWaterfallHook<[TODO[]]>["tap"]} */
- tap: (options, fn) => {
- alternativeRequests.tap(options, fn);
- },
- /** @type {AsyncSeriesWaterfallHook<[TODO[]]>["tapAsync"]} */
- tapAsync: (options, fn) => {
- alternativeRequests.tapAsync(options, (items, _options, callback) =>
- fn(items, callback)
- );
- },
- /** @type {AsyncSeriesWaterfallHook<[TODO[]]>["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
- });
- 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 = /** @type {ContextDependency} */ (dependencies[0]);
- const fileDependencies = new LazySet();
- const missingDependencies = new LazySet();
- const contextDependencies = new LazySet();
- this.hooks.beforeResolve.callAsync(
- {
- context: context,
- dependencies: dependencies,
- 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;
- let loaders,
- resource,
- loadersPrefix = "";
- const idx = request.lastIndexOf("!");
- if (idx >= 0) {
- let loadersRequest = request.slice(0, idx + 1);
- let i;
- for (
- i = 0;
- i < loadersRequest.length && loadersRequest[i] === "!";
- i++
- ) {
- loadersPrefix += "!";
- }
- loadersRequest = loadersRequest
- .slice(i)
- .replace(/!+$/, "")
- .replace(/!!+/g, "!");
- if (loadersRequest === "") {
- loaders = [];
- } else {
- loaders = 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 = [];
- 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] = 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
- ? contextResult.map(r => r.path)
- : 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
- });
- }
- );
- }
- );
- }
- );
- }
- /**
- * @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,
- include,
- exclude,
- referencedExports,
- category,
- typePrefix
- } = options;
- if (!regExp || !resource) return callback(null, []);
- const addDirectoryChecked = (ctx, directory, visited, callback) => {
- fs.realpath(directory, (err, realPath) => {
- if (err) return callback(err);
- if (visited.has(realPath)) return callback(null, []);
- let recursionStack;
- addDirectory(
- ctx,
- directory,
- (_, dir, callback) => {
- if (recursionStack === undefined) {
- recursionStack = new Set(visited);
- recursionStack.add(realPath);
- }
- addDirectoryChecked(ctx, dir, recursionStack, callback);
- },
- callback
- );
- });
- };
- const addDirectory = (ctx, directory, addSubDirectory, callback) => {
- 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, []);
- asyncLib.map(
- processedFiles.filter(p => p.indexOf(".") !== 0),
- (segment, callback) => {
- const subResource = join(fs, directory, segment);
- if (!exclude || !subResource.match(exclude)) {
- 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();
- } else {
- return callback(err);
- }
- }
- if (stat.isDirectory()) {
- if (!recursive) return callback();
- addSubDirectory(ctx, subResource, callback);
- } else if (
- stat.isFile() &&
- (!include || subResource.match(include))
- ) {
- const obj = {
- context: ctx,
- request:
- "." + subResource.slice(ctx.length).replace(/\\/g, "/")
- };
- this.hooks.alternativeRequests.callAsync(
- [obj],
- options,
- (err, alternatives) => {
- if (err) return callback(err);
- alternatives = alternatives
- .filter(obj => regExp.test(obj.request))
- .map(obj => {
- const dep = new ContextElementDependency(
- `${obj.request}${resourceQuery}${resourceFragment}`,
- obj.request,
- typePrefix,
- category,
- referencedExports,
- obj.context
- );
- dep.optional = true;
- return dep;
- });
- callback(null, alternatives);
- }
- );
- } else {
- callback();
- }
- });
- } else {
- callback();
- }
- },
- (err, result) => {
- if (err) return callback(err);
- if (!result) return callback(null, []);
- const flattenedResult = [];
- for (const item of result) {
- if (item) flattenedResult.push(...item);
- }
- callback(null, flattenedResult);
- }
- );
- });
- };
- const addSubDirectory = (ctx, dir, callback) =>
- addDirectory(ctx, dir, addSubDirectory, 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 {
- asyncLib.map(resource, visitResource, (err, result) => {
- if (err) return callback(err);
- // result dependencies should have unique userRequest
- // ordered by resolve result
- const temp = new Set();
- 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);
- });
- }
- }
- };
|