123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const versions = require("process").versions;
- const Resolver = require("./Resolver");
- const { getType, PathType } = require("./util/path");
- const SyncAsyncFileSystemDecorator = require("./SyncAsyncFileSystemDecorator");
- const AliasFieldPlugin = require("./AliasFieldPlugin");
- const AliasPlugin = require("./AliasPlugin");
- const AppendPlugin = require("./AppendPlugin");
- const ConditionalPlugin = require("./ConditionalPlugin");
- const DescriptionFilePlugin = require("./DescriptionFilePlugin");
- const DirectoryExistsPlugin = require("./DirectoryExistsPlugin");
- const ExportsFieldPlugin = require("./ExportsFieldPlugin");
- const ExtensionAliasPlugin = require("./ExtensionAliasPlugin");
- const FileExistsPlugin = require("./FileExistsPlugin");
- const ImportsFieldPlugin = require("./ImportsFieldPlugin");
- const JoinRequestPartPlugin = require("./JoinRequestPartPlugin");
- const JoinRequestPlugin = require("./JoinRequestPlugin");
- const MainFieldPlugin = require("./MainFieldPlugin");
- const ModulesInHierarchicalDirectoriesPlugin = require("./ModulesInHierarchicalDirectoriesPlugin");
- const ModulesInRootPlugin = require("./ModulesInRootPlugin");
- const NextPlugin = require("./NextPlugin");
- const ParsePlugin = require("./ParsePlugin");
- const PnpPlugin = require("./PnpPlugin");
- const RestrictionsPlugin = require("./RestrictionsPlugin");
- const ResultPlugin = require("./ResultPlugin");
- const RootsPlugin = require("./RootsPlugin");
- const SelfReferencePlugin = require("./SelfReferencePlugin");
- const SymlinkPlugin = require("./SymlinkPlugin");
- const TryNextPlugin = require("./TryNextPlugin");
- const UnsafeCachePlugin = require("./UnsafeCachePlugin");
- const UseFilePlugin = require("./UseFilePlugin");
- /** @typedef {import("./AliasPlugin").AliasOption} AliasOptionEntry */
- /** @typedef {import("./ExtensionAliasPlugin").ExtensionAliasOption} ExtensionAliasOption */
- /** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
- /** @typedef {import("./Resolver").FileSystem} FileSystem */
- /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
- /** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
- /** @typedef {string|string[]|false} AliasOptionNewRequest */
- /** @typedef {{[k: string]: AliasOptionNewRequest}} AliasOptions */
- /** @typedef {{[k: string]: string|string[] }} ExtensionAliasOptions */
- /** @typedef {{apply: function(Resolver): void} | function(this: Resolver, Resolver): void} Plugin */
- /**
- * @typedef {Object} UserResolveOptions
- * @property {(AliasOptions | AliasOptionEntry[])=} alias A list of module alias configurations or an object which maps key to value
- * @property {(AliasOptions | AliasOptionEntry[])=} fallback A list of module alias configurations or an object which maps key to value, applied only after modules option
- * @property {ExtensionAliasOptions=} extensionAlias An object which maps extension to extension aliases
- * @property {(string | string[])[]=} aliasFields A list of alias fields in description files
- * @property {(function(ResolveRequest): boolean)=} cachePredicate A function which decides whether a request should be cached or not. An object is passed with at least `path` and `request` properties.
- * @property {boolean=} cacheWithContext Whether or not the unsafeCache should include request context as part of the cache key.
- * @property {string[]=} descriptionFiles A list of description files to read from
- * @property {string[]=} conditionNames A list of exports field condition names.
- * @property {boolean=} enforceExtension Enforce that a extension from extensions must be used
- * @property {(string | string[])[]=} exportsFields A list of exports fields in description files
- * @property {(string | string[])[]=} importsFields A list of imports fields in description files
- * @property {string[]=} extensions A list of extensions which should be tried for files
- * @property {FileSystem} fileSystem The file system which should be used
- * @property {(object | boolean)=} unsafeCache Use this cache object to unsafely cache the successful requests
- * @property {boolean=} symlinks Resolve symlinks to their symlinked location
- * @property {Resolver=} resolver A prepared Resolver to which the plugins are attached
- * @property {string[] | string=} modules A list of directories to resolve modules from, can be absolute path or folder name
- * @property {(string | string[] | {name: string | string[], forceRelative: boolean})[]=} mainFields A list of main fields in description files
- * @property {string[]=} mainFiles A list of main files in directories
- * @property {Plugin[]=} plugins A list of additional resolve plugins which should be applied
- * @property {PnpApi | null=} pnpApi A PnP API that should be used - null is "never", undefined is "auto"
- * @property {string[]=} roots A list of root paths
- * @property {boolean=} fullySpecified The request is already fully specified and no extensions or directories are resolved for it
- * @property {boolean=} resolveToContext Resolve to a context instead of a file
- * @property {(string|RegExp)[]=} restrictions A list of resolve restrictions
- * @property {boolean=} useSyncFileSystemCalls Use only the sync constraints of the file system calls
- * @property {boolean=} preferRelative Prefer to resolve module requests as relative requests before falling back to modules
- * @property {boolean=} preferAbsolute Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots
- */
- /**
- * @typedef {Object} ResolveOptions
- * @property {AliasOptionEntry[]} alias
- * @property {AliasOptionEntry[]} fallback
- * @property {Set<string | string[]>} aliasFields
- * @property {ExtensionAliasOption[]} extensionAlias
- * @property {(function(ResolveRequest): boolean)} cachePredicate
- * @property {boolean} cacheWithContext
- * @property {Set<string>} conditionNames A list of exports field condition names.
- * @property {string[]} descriptionFiles
- * @property {boolean} enforceExtension
- * @property {Set<string | string[]>} exportsFields
- * @property {Set<string | string[]>} importsFields
- * @property {Set<string>} extensions
- * @property {FileSystem} fileSystem
- * @property {object | false} unsafeCache
- * @property {boolean} symlinks
- * @property {Resolver=} resolver
- * @property {Array<string | string[]>} modules
- * @property {{name: string[], forceRelative: boolean}[]} mainFields
- * @property {Set<string>} mainFiles
- * @property {Plugin[]} plugins
- * @property {PnpApi | null} pnpApi
- * @property {Set<string>} roots
- * @property {boolean} fullySpecified
- * @property {boolean} resolveToContext
- * @property {Set<string|RegExp>} restrictions
- * @property {boolean} preferRelative
- * @property {boolean} preferAbsolute
- */
- /**
- * @param {PnpApi | null=} option option
- * @returns {PnpApi | null} processed option
- */
- function processPnpApiOption(option) {
- if (
- option === undefined &&
- /** @type {NodeJS.ProcessVersions & {pnp: string}} */ versions.pnp
- ) {
- // @ts-ignore
- return require("pnpapi"); // eslint-disable-line node/no-missing-require
- }
- return option || null;
- }
- /**
- * @param {AliasOptions | AliasOptionEntry[] | undefined} alias alias
- * @returns {AliasOptionEntry[]} normalized aliases
- */
- function normalizeAlias(alias) {
- return typeof alias === "object" && !Array.isArray(alias) && alias !== null
- ? Object.keys(alias).map(key => {
- /** @type {AliasOptionEntry} */
- const obj = { name: key, onlyModule: false, alias: alias[key] };
- if (/\$$/.test(key)) {
- obj.onlyModule = true;
- obj.name = key.substr(0, key.length - 1);
- }
- return obj;
- })
- : /** @type {Array<AliasOptionEntry>} */ (alias) || [];
- }
- /**
- * @param {UserResolveOptions} options input options
- * @returns {ResolveOptions} output options
- */
- function createOptions(options) {
- const mainFieldsSet = new Set(options.mainFields || ["main"]);
- const mainFields = [];
- for (const item of mainFieldsSet) {
- if (typeof item === "string") {
- mainFields.push({
- name: [item],
- forceRelative: true
- });
- } else if (Array.isArray(item)) {
- mainFields.push({
- name: item,
- forceRelative: true
- });
- } else {
- mainFields.push({
- name: Array.isArray(item.name) ? item.name : [item.name],
- forceRelative: item.forceRelative
- });
- }
- }
- return {
- alias: normalizeAlias(options.alias),
- fallback: normalizeAlias(options.fallback),
- aliasFields: new Set(options.aliasFields),
- cachePredicate:
- options.cachePredicate ||
- function () {
- return true;
- },
- cacheWithContext:
- typeof options.cacheWithContext !== "undefined"
- ? options.cacheWithContext
- : true,
- exportsFields: new Set(options.exportsFields || ["exports"]),
- importsFields: new Set(options.importsFields || ["imports"]),
- conditionNames: new Set(options.conditionNames),
- descriptionFiles: Array.from(
- new Set(options.descriptionFiles || ["package.json"])
- ),
- enforceExtension:
- options.enforceExtension === undefined
- ? options.extensions && options.extensions.includes("")
- ? true
- : false
- : options.enforceExtension,
- extensions: new Set(options.extensions || [".js", ".json", ".node"]),
- extensionAlias: options.extensionAlias
- ? Object.keys(options.extensionAlias).map(k => ({
- extension: k,
- alias: /** @type {ExtensionAliasOptions} */ (options.extensionAlias)[
- k
- ]
- }))
- : [],
- fileSystem: options.useSyncFileSystemCalls
- ? new SyncAsyncFileSystemDecorator(
- /** @type {SyncFileSystem} */ (
- /** @type {unknown} */ (options.fileSystem)
- )
- )
- : options.fileSystem,
- unsafeCache:
- options.unsafeCache && typeof options.unsafeCache !== "object"
- ? {}
- : options.unsafeCache || false,
- symlinks: typeof options.symlinks !== "undefined" ? options.symlinks : true,
- resolver: options.resolver,
- modules: mergeFilteredToArray(
- Array.isArray(options.modules)
- ? options.modules
- : options.modules
- ? [options.modules]
- : ["node_modules"],
- item => {
- const type = getType(item);
- return type === PathType.Normal || type === PathType.Relative;
- }
- ),
- mainFields,
- mainFiles: new Set(options.mainFiles || ["index"]),
- plugins: options.plugins || [],
- pnpApi: processPnpApiOption(options.pnpApi),
- roots: new Set(options.roots || undefined),
- fullySpecified: options.fullySpecified || false,
- resolveToContext: options.resolveToContext || false,
- preferRelative: options.preferRelative || false,
- preferAbsolute: options.preferAbsolute || false,
- restrictions: new Set(options.restrictions)
- };
- }
- /**
- * @param {UserResolveOptions} options resolve options
- * @returns {Resolver} created resolver
- */
- exports.createResolver = function (options) {
- const normalizedOptions = createOptions(options);
- const {
- alias,
- fallback,
- aliasFields,
- cachePredicate,
- cacheWithContext,
- conditionNames,
- descriptionFiles,
- enforceExtension,
- exportsFields,
- extensionAlias,
- importsFields,
- extensions,
- fileSystem,
- fullySpecified,
- mainFields,
- mainFiles,
- modules,
- plugins: userPlugins,
- pnpApi,
- resolveToContext,
- preferRelative,
- preferAbsolute,
- symlinks,
- unsafeCache,
- resolver: customResolver,
- restrictions,
- roots
- } = normalizedOptions;
- const plugins = userPlugins.slice();
- const resolver = customResolver
- ? customResolver
- : new Resolver(fileSystem, normalizedOptions);
- //// pipeline ////
- resolver.ensureHook("resolve");
- resolver.ensureHook("internalResolve");
- resolver.ensureHook("newInternalResolve");
- resolver.ensureHook("parsedResolve");
- resolver.ensureHook("describedResolve");
- resolver.ensureHook("rawResolve");
- resolver.ensureHook("normalResolve");
- resolver.ensureHook("internal");
- resolver.ensureHook("rawModule");
- resolver.ensureHook("module");
- resolver.ensureHook("resolveAsModule");
- resolver.ensureHook("undescribedResolveInPackage");
- resolver.ensureHook("resolveInPackage");
- resolver.ensureHook("resolveInExistingDirectory");
- resolver.ensureHook("relative");
- resolver.ensureHook("describedRelative");
- resolver.ensureHook("directory");
- resolver.ensureHook("undescribedExistingDirectory");
- resolver.ensureHook("existingDirectory");
- resolver.ensureHook("undescribedRawFile");
- resolver.ensureHook("rawFile");
- resolver.ensureHook("file");
- resolver.ensureHook("finalFile");
- resolver.ensureHook("existingFile");
- resolver.ensureHook("resolved");
- // TODO remove in next major
- // cspell:word Interal
- // Backward-compat
- resolver.hooks.newInteralResolve = resolver.hooks.newInternalResolve;
- // resolve
- for (const { source, resolveOptions } of [
- { source: "resolve", resolveOptions: { fullySpecified } },
- { source: "internal-resolve", resolveOptions: { fullySpecified: false } }
- ]) {
- if (unsafeCache) {
- plugins.push(
- new UnsafeCachePlugin(
- source,
- cachePredicate,
- unsafeCache,
- cacheWithContext,
- `new-${source}`
- )
- );
- plugins.push(
- new ParsePlugin(`new-${source}`, resolveOptions, "parsed-resolve")
- );
- } else {
- plugins.push(new ParsePlugin(source, resolveOptions, "parsed-resolve"));
- }
- }
- // parsed-resolve
- plugins.push(
- new DescriptionFilePlugin(
- "parsed-resolve",
- descriptionFiles,
- false,
- "described-resolve"
- )
- );
- plugins.push(new NextPlugin("after-parsed-resolve", "described-resolve"));
- // described-resolve
- plugins.push(new NextPlugin("described-resolve", "raw-resolve"));
- if (fallback.length > 0) {
- plugins.push(
- new AliasPlugin("described-resolve", fallback, "internal-resolve")
- );
- }
- // raw-resolve
- if (alias.length > 0) {
- plugins.push(new AliasPlugin("raw-resolve", alias, "internal-resolve"));
- }
- aliasFields.forEach(item => {
- plugins.push(new AliasFieldPlugin("raw-resolve", item, "internal-resolve"));
- });
- extensionAlias.forEach(item =>
- plugins.push(
- new ExtensionAliasPlugin("raw-resolve", item, "normal-resolve")
- )
- );
- plugins.push(new NextPlugin("raw-resolve", "normal-resolve"));
- // normal-resolve
- if (preferRelative) {
- plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
- }
- plugins.push(
- new ConditionalPlugin(
- "after-normal-resolve",
- { module: true },
- "resolve as module",
- false,
- "raw-module"
- )
- );
- plugins.push(
- new ConditionalPlugin(
- "after-normal-resolve",
- { internal: true },
- "resolve as internal import",
- false,
- "internal"
- )
- );
- if (preferAbsolute) {
- plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
- }
- if (roots.size > 0) {
- plugins.push(new RootsPlugin("after-normal-resolve", roots, "relative"));
- }
- if (!preferRelative && !preferAbsolute) {
- plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
- }
- // internal
- importsFields.forEach(importsField => {
- plugins.push(
- new ImportsFieldPlugin(
- "internal",
- conditionNames,
- importsField,
- "relative",
- "internal-resolve"
- )
- );
- });
- // raw-module
- exportsFields.forEach(exportsField => {
- plugins.push(
- new SelfReferencePlugin("raw-module", exportsField, "resolve-as-module")
- );
- });
- modules.forEach(item => {
- if (Array.isArray(item)) {
- if (item.includes("node_modules") && pnpApi) {
- plugins.push(
- new ModulesInHierarchicalDirectoriesPlugin(
- "raw-module",
- item.filter(i => i !== "node_modules"),
- "module"
- )
- );
- plugins.push(
- new PnpPlugin("raw-module", pnpApi, "undescribed-resolve-in-package")
- );
- } else {
- plugins.push(
- new ModulesInHierarchicalDirectoriesPlugin(
- "raw-module",
- item,
- "module"
- )
- );
- }
- } else {
- plugins.push(new ModulesInRootPlugin("raw-module", item, "module"));
- }
- });
- // module
- plugins.push(new JoinRequestPartPlugin("module", "resolve-as-module"));
- // resolve-as-module
- if (!resolveToContext) {
- plugins.push(
- new ConditionalPlugin(
- "resolve-as-module",
- { directory: false, request: "." },
- "single file module",
- true,
- "undescribed-raw-file"
- )
- );
- }
- plugins.push(
- new DirectoryExistsPlugin(
- "resolve-as-module",
- "undescribed-resolve-in-package"
- )
- );
- // undescribed-resolve-in-package
- plugins.push(
- new DescriptionFilePlugin(
- "undescribed-resolve-in-package",
- descriptionFiles,
- false,
- "resolve-in-package"
- )
- );
- plugins.push(
- new NextPlugin("after-undescribed-resolve-in-package", "resolve-in-package")
- );
- // resolve-in-package
- exportsFields.forEach(exportsField => {
- plugins.push(
- new ExportsFieldPlugin(
- "resolve-in-package",
- conditionNames,
- exportsField,
- "relative"
- )
- );
- });
- plugins.push(
- new NextPlugin("resolve-in-package", "resolve-in-existing-directory")
- );
- // resolve-in-existing-directory
- plugins.push(
- new JoinRequestPlugin("resolve-in-existing-directory", "relative")
- );
- // relative
- plugins.push(
- new DescriptionFilePlugin(
- "relative",
- descriptionFiles,
- true,
- "described-relative"
- )
- );
- plugins.push(new NextPlugin("after-relative", "described-relative"));
- // described-relative
- if (resolveToContext) {
- plugins.push(new NextPlugin("described-relative", "directory"));
- } else {
- plugins.push(
- new ConditionalPlugin(
- "described-relative",
- { directory: false },
- null,
- true,
- "raw-file"
- )
- );
- plugins.push(
- new ConditionalPlugin(
- "described-relative",
- { fullySpecified: false },
- "as directory",
- true,
- "directory"
- )
- );
- }
- // directory
- plugins.push(
- new DirectoryExistsPlugin("directory", "undescribed-existing-directory")
- );
- if (resolveToContext) {
- // undescribed-existing-directory
- plugins.push(new NextPlugin("undescribed-existing-directory", "resolved"));
- } else {
- // undescribed-existing-directory
- plugins.push(
- new DescriptionFilePlugin(
- "undescribed-existing-directory",
- descriptionFiles,
- false,
- "existing-directory"
- )
- );
- mainFiles.forEach(item => {
- plugins.push(
- new UseFilePlugin(
- "undescribed-existing-directory",
- item,
- "undescribed-raw-file"
- )
- );
- });
- // described-existing-directory
- mainFields.forEach(item => {
- plugins.push(
- new MainFieldPlugin(
- "existing-directory",
- item,
- "resolve-in-existing-directory"
- )
- );
- });
- mainFiles.forEach(item => {
- plugins.push(
- new UseFilePlugin("existing-directory", item, "undescribed-raw-file")
- );
- });
- // undescribed-raw-file
- plugins.push(
- new DescriptionFilePlugin(
- "undescribed-raw-file",
- descriptionFiles,
- true,
- "raw-file"
- )
- );
- plugins.push(new NextPlugin("after-undescribed-raw-file", "raw-file"));
- // raw-file
- plugins.push(
- new ConditionalPlugin(
- "raw-file",
- { fullySpecified: true },
- null,
- false,
- "file"
- )
- );
- if (!enforceExtension) {
- plugins.push(new TryNextPlugin("raw-file", "no extension", "file"));
- }
- extensions.forEach(item => {
- plugins.push(new AppendPlugin("raw-file", item, "file"));
- });
- // file
- if (alias.length > 0)
- plugins.push(new AliasPlugin("file", alias, "internal-resolve"));
- aliasFields.forEach(item => {
- plugins.push(new AliasFieldPlugin("file", item, "internal-resolve"));
- });
- plugins.push(new NextPlugin("file", "final-file"));
- // final-file
- plugins.push(new FileExistsPlugin("final-file", "existing-file"));
- // existing-file
- if (symlinks)
- plugins.push(new SymlinkPlugin("existing-file", "existing-file"));
- plugins.push(new NextPlugin("existing-file", "resolved"));
- }
- // resolved
- if (restrictions.size > 0) {
- plugins.push(new RestrictionsPlugin(resolver.hooks.resolved, restrictions));
- }
- plugins.push(new ResultPlugin(resolver.hooks.resolved));
- //// RESOLVER ////
- for (const plugin of plugins) {
- if (typeof plugin === "function") {
- plugin.call(resolver, resolver);
- } else {
- plugin.apply(resolver);
- }
- }
- return resolver;
- };
- /**
- * Merging filtered elements
- * @param {string[]} array source array
- * @param {function(string): boolean} filter predicate
- * @returns {Array<string | string[]>} merge result
- */
- function mergeFilteredToArray(array, filter) {
- /** @type {Array<string | string[]>} */
- const result = [];
- const set = new Set(array);
- for (const item of set) {
- if (filter(item)) {
- const lastElement =
- result.length > 0 ? result[result.length - 1] : undefined;
- if (Array.isArray(lastElement)) {
- lastElement.push(item);
- } else {
- result.push([item]);
- }
- } else {
- result.push(item);
- }
- }
- return result;
- }
|