SelfReferencePlugin.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const DescriptionFileUtils = require("./DescriptionFileUtils");
  7. /** @typedef {import("./Resolver")} Resolver */
  8. /** @typedef {import("./Resolver").JsonObject} JsonObject */
  9. /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
  10. /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
  11. const slashCode = "/".charCodeAt(0);
  12. // Sentinel stored in `_nameCache` when the description file either has no
  13. // exports field (so self-reference can't apply) or no string `name`.
  14. const NO_SELF_REF = Symbol("NoSelfRef");
  15. module.exports = class SelfReferencePlugin {
  16. /**
  17. * @param {string | ResolveStepHook} source source
  18. * @param {string | string[]} fieldNamePath name path
  19. * @param {string | ResolveStepHook} target target
  20. */
  21. constructor(source, fieldNamePath, target) {
  22. this.source = source;
  23. this.target = target;
  24. this.fieldName = fieldNamePath;
  25. // Self-reference needs both an exports field and a `"name"` string.
  26. // Both are stable per description-file content, so cache the decision
  27. // in one WeakMap: the resolved name when self-reference is possible,
  28. // or `NO_SELF_REF` when it isn't. This skips the two per-resolve
  29. // `DescriptionFileUtils.getField` walks for hot packages.
  30. /** @type {WeakMap<JsonObject, string | typeof NO_SELF_REF>} */
  31. this._nameCache = new WeakMap();
  32. }
  33. /**
  34. * @param {Resolver} resolver the resolver
  35. * @returns {void}
  36. */
  37. apply(resolver) {
  38. const target = resolver.ensureHook(this.target);
  39. resolver
  40. .getHook(this.source)
  41. .tapAsync("SelfReferencePlugin", (request, resolveContext, callback) => {
  42. if (!request.descriptionFileData) return callback();
  43. const req = request.request;
  44. if (!req) return callback();
  45. const { descriptionFileData } = request;
  46. let name = this._nameCache.get(descriptionFileData);
  47. if (name === undefined) {
  48. // Feature is only enabled when an exports field is present
  49. const exportsField = DescriptionFileUtils.getField(
  50. descriptionFileData,
  51. this.fieldName,
  52. );
  53. if (!exportsField) {
  54. this._nameCache.set(descriptionFileData, NO_SELF_REF);
  55. return callback();
  56. }
  57. const rawName = DescriptionFileUtils.getField(
  58. descriptionFileData,
  59. "name",
  60. );
  61. if (typeof rawName !== "string") {
  62. this._nameCache.set(descriptionFileData, NO_SELF_REF);
  63. return callback();
  64. }
  65. name = rawName;
  66. this._nameCache.set(descriptionFileData, name);
  67. } else if (name === NO_SELF_REF) {
  68. return callback();
  69. }
  70. if (
  71. req.startsWith(name) &&
  72. (req.length === name.length ||
  73. req.charCodeAt(name.length) === slashCode)
  74. ) {
  75. const remainingRequest = `.${req.slice(name.length)}`;
  76. /** @type {ResolveRequest} */
  77. const obj = {
  78. ...request,
  79. request: remainingRequest,
  80. path: /** @type {string} */ (request.descriptionFileRoot),
  81. relativePath: ".",
  82. };
  83. resolver.doResolve(
  84. target,
  85. obj,
  86. "self reference",
  87. resolveContext,
  88. callback,
  89. );
  90. } else {
  91. return callback();
  92. }
  93. });
  94. }
  95. };