You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
9.7 KiB

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