| /* |
| MIT License http://www.opensource.org/licenses/mit-license.php |
| Author Tobias Koppers @sokra |
| */ |
| "use strict"; |
| |
| const Resolver = require("./Resolver"); |
| |
| const SyncAsyncFileSystemDecorator = require("./SyncAsyncFileSystemDecorator"); |
| |
| const ParsePlugin = require("./ParsePlugin"); |
| const DescriptionFilePlugin = require("./DescriptionFilePlugin"); |
| const NextPlugin = require("./NextPlugin"); |
| const TryNextPlugin = require("./TryNextPlugin"); |
| const ModuleKindPlugin = require("./ModuleKindPlugin"); |
| const FileKindPlugin = require("./FileKindPlugin"); |
| const JoinRequestPlugin = require("./JoinRequestPlugin"); |
| const ModulesInHierachicDirectoriesPlugin = require("./ModulesInHierachicDirectoriesPlugin"); |
| const ModulesInRootPlugin = require("./ModulesInRootPlugin"); |
| const AliasPlugin = require("./AliasPlugin"); |
| const AliasFieldPlugin = require("./AliasFieldPlugin"); |
| const ConcordExtensionsPlugin = require("./ConcordExtensionsPlugin"); |
| const ConcordMainPlugin = require("./ConcordMainPlugin"); |
| const ConcordModulesPlugin = require("./ConcordModulesPlugin"); |
| const DirectoryExistsPlugin = require("./DirectoryExistsPlugin"); |
| const FileExistsPlugin = require("./FileExistsPlugin"); |
| const SymlinkPlugin = require("./SymlinkPlugin"); |
| const MainFieldPlugin = require("./MainFieldPlugin"); |
| const UseFilePlugin = require("./UseFilePlugin"); |
| const AppendPlugin = require("./AppendPlugin"); |
| const RootPlugin = require("./RootPlugin"); |
| const RestrictionsPlugin = require("./RestrictionsPlugin"); |
| const ResultPlugin = require("./ResultPlugin"); |
| const ModuleAppendPlugin = require("./ModuleAppendPlugin"); |
| const UnsafeCachePlugin = require("./UnsafeCachePlugin"); |
| |
| exports.createResolver = function(options) { |
| //// OPTIONS //// |
| |
| // A list of directories to resolve modules from, can be absolute path or folder name |
| let modules = options.modules || ["node_modules"]; |
| |
| // A list of description files to read from |
| const descriptionFiles = options.descriptionFiles || ["package.json"]; |
| |
| // A list of additional resolve plugins which should be applied |
| // The slice is there to create a copy, because otherwise pushing into plugins |
| // changes the original options.plugins array, causing duplicate plugins |
| const plugins = (options.plugins && options.plugins.slice()) || []; |
| |
| // A list of main fields in description files |
| let mainFields = options.mainFields || ["main"]; |
| |
| // A list of alias fields in description files |
| const aliasFields = options.aliasFields || []; |
| |
| // A list of main files in directories |
| const mainFiles = options.mainFiles || ["index"]; |
| |
| // A list of extensions which should be tried for files |
| let extensions = options.extensions || [".js", ".json", ".node"]; |
| |
| // Enforce that a extension from extensions must be used |
| const enforceExtension = options.enforceExtension || false; |
| |
| // A list of module extensions which should be tried for modules |
| let moduleExtensions = options.moduleExtensions || []; |
| |
| // Enforce that a extension from moduleExtensions must be used |
| const enforceModuleExtension = options.enforceModuleExtension || false; |
| |
| // A list of module alias configurations or an object which maps key to value |
| let alias = options.alias || []; |
| |
| // Resolve symlinks to their symlinked location |
| const symlinks = |
| typeof options.symlinks !== "undefined" ? options.symlinks : true; |
| |
| // Resolve to a context instead of a file |
| const resolveToContext = options.resolveToContext || false; |
| |
| // A list of root paths |
| const roots = options.roots || []; |
| |
| // Ignore errors happening when resolving roots |
| const ignoreRootsErrors = options.ignoreRootsErrors || false; |
| |
| // Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots |
| const preferAbsolute = options.preferAbsolute || false; |
| |
| const restrictions = options.restrictions || []; |
| |
| // Use this cache object to unsafely cache the successful requests |
| let unsafeCache = options.unsafeCache || false; |
| |
| // Whether or not the unsafeCache should include request context as part of the cache key. |
| const cacheWithContext = |
| typeof options.cacheWithContext !== "undefined" |
| ? options.cacheWithContext |
| : true; |
| |
| // Enable concord description file instructions |
| const enableConcord = options.concord || false; |
| |
| // A function which decides whether a request should be cached or not. |
| // an object is passed with `path` and `request` properties. |
| const cachePredicate = |
| options.cachePredicate || |
| function() { |
| return true; |
| }; |
| |
| // The file system which should be used |
| const fileSystem = options.fileSystem; |
| |
| // Use only the sync constiants of the file system calls |
| const useSyncFileSystemCalls = options.useSyncFileSystemCalls; |
| |
| // A prepared Resolver to which the plugins are attached |
| let resolver = options.resolver; |
| |
| //// options processing //// |
| |
| if (!resolver) { |
| resolver = new Resolver( |
| useSyncFileSystemCalls |
| ? new SyncAsyncFileSystemDecorator(fileSystem) |
| : fileSystem |
| ); |
| } |
| |
| extensions = [].concat(extensions); |
| moduleExtensions = [].concat(moduleExtensions); |
| |
| modules = mergeFilteredToArray([].concat(modules), item => { |
| return !isAbsolutePath(item); |
| }); |
| |
| mainFields = mainFields.map(item => { |
| if (typeof item === "string" || Array.isArray(item)) { |
| item = { |
| name: item, |
| forceRelative: true |
| }; |
| } |
| return item; |
| }); |
| |
| if (typeof alias === "object" && !Array.isArray(alias)) { |
| alias = Object.keys(alias).map(key => { |
| let onlyModule = false; |
| let obj = alias[key]; |
| if (/\$$/.test(key)) { |
| onlyModule = true; |
| key = key.substr(0, key.length - 1); |
| } |
| if (typeof obj === "string") { |
| obj = { |
| alias: obj |
| }; |
| } |
| obj = Object.assign( |
| { |
| name: key, |
| onlyModule: onlyModule |
| }, |
| obj |
| ); |
| return obj; |
| }); |
| } |
| |
| if (unsafeCache && typeof unsafeCache !== "object") { |
| unsafeCache = {}; |
| } |
| |
| //// pipeline //// |
| |
| resolver.ensureHook("resolve"); |
| resolver.ensureHook("parsedResolve"); |
| resolver.ensureHook("describedResolve"); |
| resolver.ensureHook("rawModule"); |
| resolver.ensureHook("module"); |
| resolver.ensureHook("relative"); |
| resolver.ensureHook("describedRelative"); |
| resolver.ensureHook("directory"); |
| resolver.ensureHook("existingDirectory"); |
| resolver.ensureHook("undescribedRawFile"); |
| resolver.ensureHook("rawFile"); |
| resolver.ensureHook("file"); |
| resolver.ensureHook("existingFile"); |
| resolver.ensureHook("resolved"); |
| |
| // resolve |
| if (unsafeCache) { |
| plugins.push( |
| new UnsafeCachePlugin( |
| "resolve", |
| cachePredicate, |
| unsafeCache, |
| cacheWithContext, |
| "new-resolve" |
| ) |
| ); |
| plugins.push(new ParsePlugin("new-resolve", "parsed-resolve")); |
| } else { |
| plugins.push(new ParsePlugin("resolve", "parsed-resolve")); |
| } |
| |
| // parsed-resolve |
| plugins.push( |
| new DescriptionFilePlugin( |
| "parsed-resolve", |
| descriptionFiles, |
| "described-resolve" |
| ) |
| ); |
| plugins.push(new NextPlugin("after-parsed-resolve", "described-resolve")); |
| |
| // described-resolve |
| if (alias.length > 0) |
| plugins.push(new AliasPlugin("described-resolve", alias, "resolve")); |
| if (enableConcord) { |
| plugins.push(new ConcordModulesPlugin("described-resolve", {}, "resolve")); |
| } |
| aliasFields.forEach(item => { |
| plugins.push(new AliasFieldPlugin("described-resolve", item, "resolve")); |
| }); |
| plugins.push(new ModuleKindPlugin("after-described-resolve", "raw-module")); |
| if (preferAbsolute) { |
| plugins.push(new JoinRequestPlugin("after-described-resolve", "relative")); |
| } |
| roots.forEach(root => { |
| plugins.push( |
| new RootPlugin( |
| "after-described-resolve", |
| root, |
| "relative", |
| ignoreRootsErrors |
| ) |
| ); |
| }); |
| if (!preferAbsolute) { |
| plugins.push(new JoinRequestPlugin("after-described-resolve", "relative")); |
| } |
| |
| // raw-module |
| moduleExtensions.forEach(item => { |
| plugins.push(new ModuleAppendPlugin("raw-module", item, "module")); |
| }); |
| if (!enforceModuleExtension) |
| plugins.push(new TryNextPlugin("raw-module", null, "module")); |
| |
| // module |
| modules.forEach(item => { |
| if (Array.isArray(item)) |
| plugins.push( |
| new ModulesInHierachicDirectoriesPlugin("module", item, "resolve") |
| ); |
| else plugins.push(new ModulesInRootPlugin("module", item, "resolve")); |
| }); |
| |
| // relative |
| plugins.push( |
| new DescriptionFilePlugin( |
| "relative", |
| descriptionFiles, |
| "described-relative" |
| ) |
| ); |
| plugins.push(new NextPlugin("after-relative", "described-relative")); |
| |
| // described-relative |
| plugins.push(new FileKindPlugin("described-relative", "raw-file")); |
| plugins.push( |
| new TryNextPlugin("described-relative", "as directory", "directory") |
| ); |
| |
| // directory |
| plugins.push(new DirectoryExistsPlugin("directory", "existing-directory")); |
| |
| if (resolveToContext) { |
| // existing-directory |
| plugins.push(new NextPlugin("existing-directory", "resolved")); |
| } else { |
| // existing-directory |
| if (enableConcord) { |
| plugins.push(new ConcordMainPlugin("existing-directory", {}, "resolve")); |
| } |
| mainFields.forEach(item => { |
| plugins.push(new MainFieldPlugin("existing-directory", item, "resolve")); |
| }); |
| mainFiles.forEach(item => { |
| plugins.push( |
| new UseFilePlugin("existing-directory", item, "undescribed-raw-file") |
| ); |
| }); |
| |
| // undescribed-raw-file |
| plugins.push( |
| new DescriptionFilePlugin( |
| "undescribed-raw-file", |
| descriptionFiles, |
| "raw-file" |
| ) |
| ); |
| plugins.push(new NextPlugin("after-undescribed-raw-file", "raw-file")); |
| |
| // raw-file |
| if (!enforceExtension) { |
| plugins.push(new TryNextPlugin("raw-file", "no extension", "file")); |
| } |
| if (enableConcord) { |
| plugins.push(new ConcordExtensionsPlugin("raw-file", {}, "file")); |
| } |
| extensions.forEach(item => { |
| plugins.push(new AppendPlugin("raw-file", item, "file")); |
| }); |
| |
| // file |
| if (alias.length > 0) |
| plugins.push(new AliasPlugin("file", alias, "resolve")); |
| if (enableConcord) { |
| plugins.push(new ConcordModulesPlugin("file", {}, "resolve")); |
| } |
| aliasFields.forEach(item => { |
| plugins.push(new AliasFieldPlugin("file", item, "resolve")); |
| }); |
| if (symlinks) plugins.push(new SymlinkPlugin("file", "relative")); |
| plugins.push(new FileExistsPlugin("file", "existing-file")); |
| |
| // existing-file |
| plugins.push(new NextPlugin("existing-file", "resolved")); |
| } |
| |
| // resolved |
| if (restrictions.length > 0) { |
| plugins.push(new RestrictionsPlugin(resolver.hooks.resolved, restrictions)); |
| } |
| plugins.push(new ResultPlugin(resolver.hooks.resolved)); |
| |
| //// RESOLVER //// |
| |
| plugins.forEach(plugin => { |
| plugin.apply(resolver); |
| }); |
| |
| return resolver; |
| }; |
| |
| function mergeFilteredToArray(array, filter) { |
| return array.reduce((array, item) => { |
| if (filter(item)) { |
| const lastElement = array[array.length - 1]; |
| if (Array.isArray(lastElement)) { |
| lastElement.push(item); |
| } else { |
| array.push([item]); |
| } |
| return array; |
| } else { |
| array.push(item); |
| return array; |
| } |
| }, []); |
| } |
| |
| function isAbsolutePath(path) { |
| return /^[A-Z]:|^\//.test(path); |
| } |