ModulesUtils.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const forEachBail = require("./forEachBail");
  7. const { getPathsCached } = require("./getPaths");
  8. /** @typedef {import("./Resolver")} Resolver */
  9. /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
  10. /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
  11. /** @typedef {import("./Resolver").ResolveContext} ResolveContext */
  12. /** @typedef {(err?: null | Error, result?: null | ResolveRequest) => void} InnerCallback */
  13. /**
  14. * Per-(directories-array) cache of the flat `addrs` list produced for a given
  15. * `request.path`. For a fixed directories configuration the fan-out of
  16. * `ancestor × directory` is deterministic per request.path, and many resolves
  17. * share the same starting directory (sibling files in one project, loops over
  18. * a batch of imports, etc.) — caching avoids the `getPaths` regex split plus
  19. * `len(paths) × len(directories)` join calls per resolve.
  20. *
  21. * The outer map is keyed on the directories array reference (plugin-owned,
  22. * stable for the lifetime of the resolver), and the inner map on the
  23. * starting `request.path`. Kept private to this module (rather than hung off
  24. * `resolver.pathCache`) so the pathCache's hidden-class shape is unchanged —
  25. * that avoids perturbing the interpreter-mode IC state for the
  26. * `resolver.pathCache.{join,dirname,basename}.fn(...)` accesses that run on
  27. * every resolve, which the CodSpeed instruction-count harness is sensitive to.
  28. * @type {WeakMap<string[], Map<string, string[]>>}
  29. */
  30. const _addrsCacheByDirs = new WeakMap();
  31. /**
  32. * @param {Resolver} resolver resolver
  33. * @param {string[]} directories directories
  34. * @param {ResolveStepHook} target target
  35. * @param {ResolveRequest} request request
  36. * @param {ResolveContext} resolveContext resolve context
  37. * @param {InnerCallback} callback callback
  38. * @returns {void}
  39. */
  40. function modulesResolveHandler(
  41. resolver,
  42. directories,
  43. target,
  44. request,
  45. resolveContext,
  46. callback,
  47. ) {
  48. const fs = resolver.fileSystem;
  49. const requestPath = /** @type {string} */ (request.path);
  50. // Compute-or-reuse the flat `addrs` list. Inlined (rather than a helper
  51. // function) so the cache-hit path — which is the vast majority of
  52. // invocations — stays a single WeakMap + Map lookup with no function-call
  53. // overhead. See `_addrsCacheByDirs` above for caching rationale.
  54. let addrs;
  55. let perPath = _addrsCacheByDirs.get(directories);
  56. if (perPath === undefined) {
  57. perPath = new Map();
  58. _addrsCacheByDirs.set(directories, perPath);
  59. } else {
  60. addrs = perPath.get(requestPath);
  61. }
  62. if (addrs === undefined) {
  63. const { paths } = getPathsCached(fs, requestPath);
  64. const pathsLen = paths.length;
  65. const dirsLen = directories.length;
  66. // Pre-size the flat array rather than going through `map().reduce()`
  67. // with intermediate arrays + spreads.
  68. // eslint-disable-next-line unicorn/no-new-array
  69. addrs = new Array(pathsLen * dirsLen);
  70. let idx = 0;
  71. const joinFn = resolver.pathCache.join.fn;
  72. for (let pi = 0; pi < pathsLen; pi++) {
  73. const pathItem = paths[pi];
  74. for (let di = 0; di < dirsLen; di++) {
  75. addrs[idx++] = joinFn(pathItem, directories[di]);
  76. }
  77. }
  78. perPath.set(requestPath, addrs);
  79. }
  80. // Hoist the dot-prefixed request out of the per-addr iterator. `addrs`
  81. // can have up to `paths.length × directories.length` entries (e.g. 36
  82. // for an 8-deep source dir × 4-module config), and concatenating the
  83. // same `./${request.request}` string on every iteration is wasted
  84. // work — it's constant for the whole fan-out.
  85. const relRequest = `./${request.request}`;
  86. forEachBail(
  87. addrs,
  88. /**
  89. * @param {string} addr addr
  90. * @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
  91. * @returns {void}
  92. */
  93. (addr, callback) => {
  94. fs.stat(addr, (err, stat) => {
  95. if (!err && stat && stat.isDirectory()) {
  96. /** @type {ResolveRequest} */
  97. const obj = {
  98. ...request,
  99. path: addr,
  100. request: relRequest,
  101. module: false,
  102. };
  103. const message = `looking for modules in ${addr}`;
  104. return resolver.doResolve(
  105. target,
  106. obj,
  107. message,
  108. resolveContext,
  109. callback,
  110. );
  111. }
  112. if (resolveContext.log) {
  113. resolveContext.log(`${addr} doesn't exist or is not a directory`);
  114. }
  115. if (resolveContext.missingDependencies) {
  116. resolveContext.missingDependencies.add(addr);
  117. }
  118. return callback();
  119. });
  120. },
  121. callback,
  122. );
  123. }
  124. module.exports = {
  125. modulesResolveHandler,
  126. };