ResolverFactory.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Resolver = require("./Resolver");
  7. const SyncAsyncFileSystemDecorator = require("./SyncAsyncFileSystemDecorator");
  8. const ParsePlugin = require("./ParsePlugin");
  9. const DescriptionFilePlugin = require("./DescriptionFilePlugin");
  10. const NextPlugin = require("./NextPlugin");
  11. const TryNextPlugin = require("./TryNextPlugin");
  12. const ModuleKindPlugin = require("./ModuleKindPlugin");
  13. const FileKindPlugin = require("./FileKindPlugin");
  14. const JoinRequestPlugin = require("./JoinRequestPlugin");
  15. const ModulesInHierachicDirectoriesPlugin = require("./ModulesInHierachicDirectoriesPlugin");
  16. const ModulesInRootPlugin = require("./ModulesInRootPlugin");
  17. const AliasPlugin = require("./AliasPlugin");
  18. const AliasFieldPlugin = require("./AliasFieldPlugin");
  19. const ConcordExtensionsPlugin = require("./ConcordExtensionsPlugin");
  20. const ConcordMainPlugin = require("./ConcordMainPlugin");
  21. const ConcordModulesPlugin = require("./ConcordModulesPlugin");
  22. const DirectoryExistsPlugin = require("./DirectoryExistsPlugin");
  23. const FileExistsPlugin = require("./FileExistsPlugin");
  24. const SymlinkPlugin = require("./SymlinkPlugin");
  25. const MainFieldPlugin = require("./MainFieldPlugin");
  26. const UseFilePlugin = require("./UseFilePlugin");
  27. const AppendPlugin = require("./AppendPlugin");
  28. const RootPlugin = require("./RootPlugin");
  29. const RestrictionsPlugin = require("./RestrictionsPlugin");
  30. const ResultPlugin = require("./ResultPlugin");
  31. const ModuleAppendPlugin = require("./ModuleAppendPlugin");
  32. const UnsafeCachePlugin = require("./UnsafeCachePlugin");
  33. exports.createResolver = function(options) {
  34. //// OPTIONS ////
  35. // A list of directories to resolve modules from, can be absolute path or folder name
  36. let modules = options.modules || ["node_modules"];
  37. // A list of description files to read from
  38. const descriptionFiles = options.descriptionFiles || ["package.json"];
  39. // A list of additional resolve plugins which should be applied
  40. // The slice is there to create a copy, because otherwise pushing into plugins
  41. // changes the original options.plugins array, causing duplicate plugins
  42. const plugins = (options.plugins && options.plugins.slice()) || [];
  43. // A list of main fields in description files
  44. let mainFields = options.mainFields || ["main"];
  45. // A list of alias fields in description files
  46. const aliasFields = options.aliasFields || [];
  47. // A list of main files in directories
  48. const mainFiles = options.mainFiles || ["index"];
  49. // A list of extensions which should be tried for files
  50. let extensions = options.extensions || [".js", ".json", ".node"];
  51. // Enforce that a extension from extensions must be used
  52. const enforceExtension = options.enforceExtension || false;
  53. // A list of module extensions which should be tried for modules
  54. let moduleExtensions = options.moduleExtensions || [];
  55. // Enforce that a extension from moduleExtensions must be used
  56. const enforceModuleExtension = options.enforceModuleExtension || false;
  57. // A list of module alias configurations or an object which maps key to value
  58. let alias = options.alias || [];
  59. // Resolve symlinks to their symlinked location
  60. const symlinks =
  61. typeof options.symlinks !== "undefined" ? options.symlinks : true;
  62. // Resolve to a context instead of a file
  63. const resolveToContext = options.resolveToContext || false;
  64. // A list of root paths
  65. const roots = options.roots || [];
  66. // Ignore errors happening when resolving roots
  67. const ignoreRootsErrors = options.ignoreRootsErrors || false;
  68. // Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots
  69. const preferAbsolute = options.preferAbsolute || false;
  70. const restrictions = options.restrictions || [];
  71. // Use this cache object to unsafely cache the successful requests
  72. let unsafeCache = options.unsafeCache || false;
  73. // Whether or not the unsafeCache should include request context as part of the cache key.
  74. const cacheWithContext =
  75. typeof options.cacheWithContext !== "undefined"
  76. ? options.cacheWithContext
  77. : true;
  78. // Enable concord description file instructions
  79. const enableConcord = options.concord || false;
  80. // A function which decides whether a request should be cached or not.
  81. // an object is passed with `path` and `request` properties.
  82. const cachePredicate =
  83. options.cachePredicate ||
  84. function() {
  85. return true;
  86. };
  87. // The file system which should be used
  88. const fileSystem = options.fileSystem;
  89. // Use only the sync constiants of the file system calls
  90. const useSyncFileSystemCalls = options.useSyncFileSystemCalls;
  91. // A prepared Resolver to which the plugins are attached
  92. let resolver = options.resolver;
  93. //// options processing ////
  94. if (!resolver) {
  95. resolver = new Resolver(
  96. useSyncFileSystemCalls
  97. ? new SyncAsyncFileSystemDecorator(fileSystem)
  98. : fileSystem
  99. );
  100. }
  101. extensions = [].concat(extensions);
  102. moduleExtensions = [].concat(moduleExtensions);
  103. modules = mergeFilteredToArray([].concat(modules), item => {
  104. return !isAbsolutePath(item);
  105. });
  106. mainFields = mainFields.map(item => {
  107. if (typeof item === "string" || Array.isArray(item)) {
  108. item = {
  109. name: item,
  110. forceRelative: true
  111. };
  112. }
  113. return item;
  114. });
  115. if (typeof alias === "object" && !Array.isArray(alias)) {
  116. alias = Object.keys(alias).map(key => {
  117. let onlyModule = false;
  118. let obj = alias[key];
  119. if (/\$$/.test(key)) {
  120. onlyModule = true;
  121. key = key.substr(0, key.length - 1);
  122. }
  123. if (typeof obj === "string") {
  124. obj = {
  125. alias: obj
  126. };
  127. }
  128. obj = Object.assign(
  129. {
  130. name: key,
  131. onlyModule: onlyModule
  132. },
  133. obj
  134. );
  135. return obj;
  136. });
  137. }
  138. if (unsafeCache && typeof unsafeCache !== "object") {
  139. unsafeCache = {};
  140. }
  141. //// pipeline ////
  142. resolver.ensureHook("resolve");
  143. resolver.ensureHook("parsedResolve");
  144. resolver.ensureHook("describedResolve");
  145. resolver.ensureHook("rawModule");
  146. resolver.ensureHook("module");
  147. resolver.ensureHook("relative");
  148. resolver.ensureHook("describedRelative");
  149. resolver.ensureHook("directory");
  150. resolver.ensureHook("existingDirectory");
  151. resolver.ensureHook("undescribedRawFile");
  152. resolver.ensureHook("rawFile");
  153. resolver.ensureHook("file");
  154. resolver.ensureHook("existingFile");
  155. resolver.ensureHook("resolved");
  156. // resolve
  157. if (unsafeCache) {
  158. plugins.push(
  159. new UnsafeCachePlugin(
  160. "resolve",
  161. cachePredicate,
  162. unsafeCache,
  163. cacheWithContext,
  164. "new-resolve"
  165. )
  166. );
  167. plugins.push(new ParsePlugin("new-resolve", "parsed-resolve"));
  168. } else {
  169. plugins.push(new ParsePlugin("resolve", "parsed-resolve"));
  170. }
  171. // parsed-resolve
  172. plugins.push(
  173. new DescriptionFilePlugin(
  174. "parsed-resolve",
  175. descriptionFiles,
  176. "described-resolve"
  177. )
  178. );
  179. plugins.push(new NextPlugin("after-parsed-resolve", "described-resolve"));
  180. // described-resolve
  181. if (alias.length > 0)
  182. plugins.push(new AliasPlugin("described-resolve", alias, "resolve"));
  183. if (enableConcord) {
  184. plugins.push(new ConcordModulesPlugin("described-resolve", {}, "resolve"));
  185. }
  186. aliasFields.forEach(item => {
  187. plugins.push(new AliasFieldPlugin("described-resolve", item, "resolve"));
  188. });
  189. plugins.push(new ModuleKindPlugin("after-described-resolve", "raw-module"));
  190. if (preferAbsolute) {
  191. plugins.push(new JoinRequestPlugin("after-described-resolve", "relative"));
  192. }
  193. roots.forEach(root => {
  194. plugins.push(
  195. new RootPlugin(
  196. "after-described-resolve",
  197. root,
  198. "relative",
  199. ignoreRootsErrors
  200. )
  201. );
  202. });
  203. if (!preferAbsolute) {
  204. plugins.push(new JoinRequestPlugin("after-described-resolve", "relative"));
  205. }
  206. // raw-module
  207. moduleExtensions.forEach(item => {
  208. plugins.push(new ModuleAppendPlugin("raw-module", item, "module"));
  209. });
  210. if (!enforceModuleExtension)
  211. plugins.push(new TryNextPlugin("raw-module", null, "module"));
  212. // module
  213. modules.forEach(item => {
  214. if (Array.isArray(item))
  215. plugins.push(
  216. new ModulesInHierachicDirectoriesPlugin("module", item, "resolve")
  217. );
  218. else plugins.push(new ModulesInRootPlugin("module", item, "resolve"));
  219. });
  220. // relative
  221. plugins.push(
  222. new DescriptionFilePlugin(
  223. "relative",
  224. descriptionFiles,
  225. "described-relative"
  226. )
  227. );
  228. plugins.push(new NextPlugin("after-relative", "described-relative"));
  229. // described-relative
  230. plugins.push(new FileKindPlugin("described-relative", "raw-file"));
  231. plugins.push(
  232. new TryNextPlugin("described-relative", "as directory", "directory")
  233. );
  234. // directory
  235. plugins.push(new DirectoryExistsPlugin("directory", "existing-directory"));
  236. if (resolveToContext) {
  237. // existing-directory
  238. plugins.push(new NextPlugin("existing-directory", "resolved"));
  239. } else {
  240. // existing-directory
  241. if (enableConcord) {
  242. plugins.push(new ConcordMainPlugin("existing-directory", {}, "resolve"));
  243. }
  244. mainFields.forEach(item => {
  245. plugins.push(new MainFieldPlugin("existing-directory", item, "resolve"));
  246. });
  247. mainFiles.forEach(item => {
  248. plugins.push(
  249. new UseFilePlugin("existing-directory", item, "undescribed-raw-file")
  250. );
  251. });
  252. // undescribed-raw-file
  253. plugins.push(
  254. new DescriptionFilePlugin(
  255. "undescribed-raw-file",
  256. descriptionFiles,
  257. "raw-file"
  258. )
  259. );
  260. plugins.push(new NextPlugin("after-undescribed-raw-file", "raw-file"));
  261. // raw-file
  262. if (!enforceExtension) {
  263. plugins.push(new TryNextPlugin("raw-file", "no extension", "file"));
  264. }
  265. if (enableConcord) {
  266. plugins.push(new ConcordExtensionsPlugin("raw-file", {}, "file"));
  267. }
  268. extensions.forEach(item => {
  269. plugins.push(new AppendPlugin("raw-file", item, "file"));
  270. });
  271. // file
  272. if (alias.length > 0)
  273. plugins.push(new AliasPlugin("file", alias, "resolve"));
  274. if (enableConcord) {
  275. plugins.push(new ConcordModulesPlugin("file", {}, "resolve"));
  276. }
  277. aliasFields.forEach(item => {
  278. plugins.push(new AliasFieldPlugin("file", item, "resolve"));
  279. });
  280. if (symlinks) plugins.push(new SymlinkPlugin("file", "relative"));
  281. plugins.push(new FileExistsPlugin("file", "existing-file"));
  282. // existing-file
  283. plugins.push(new NextPlugin("existing-file", "resolved"));
  284. }
  285. // resolved
  286. if (restrictions.length > 0) {
  287. plugins.push(new RestrictionsPlugin(resolver.hooks.resolved, restrictions));
  288. }
  289. plugins.push(new ResultPlugin(resolver.hooks.resolved));
  290. //// RESOLVER ////
  291. plugins.forEach(plugin => {
  292. plugin.apply(resolver);
  293. });
  294. return resolver;
  295. };
  296. function mergeFilteredToArray(array, filter) {
  297. return array.reduce((array, item) => {
  298. if (filter(item)) {
  299. const lastElement = array[array.length - 1];
  300. if (Array.isArray(lastElement)) {
  301. lastElement.push(item);
  302. } else {
  303. array.push([item]);
  304. }
  305. return array;
  306. } else {
  307. array.push(item);
  308. return array;
  309. }
  310. }, []);
  311. }
  312. function isAbsolutePath(path) {
  313. return /^[A-Z]:|^\//.test(path);
  314. }