PnpPlugin.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Maël Nison @arcanis
  4. */
  5. "use strict";
  6. /** @typedef {import("./Resolver")} Resolver */
  7. /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
  8. /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
  9. /**
  10. * @typedef {object} PnpApiImpl
  11. * @property {(packageName: string, issuer: string, options: { considerBuiltins: boolean }) => string | null} resolveToUnqualified resolve to unqualified
  12. */
  13. module.exports = class PnpPlugin {
  14. /**
  15. * @param {string | ResolveStepHook} source source
  16. * @param {PnpApiImpl} pnpApi pnpApi
  17. * @param {string | ResolveStepHook} target target
  18. * @param {string | ResolveStepHook} alternateTarget alternateTarget
  19. */
  20. constructor(source, pnpApi, target, alternateTarget) {
  21. this.source = source;
  22. this.pnpApi = pnpApi;
  23. this.target = target;
  24. this.alternateTarget = alternateTarget;
  25. }
  26. /**
  27. * @param {Resolver} resolver the resolver
  28. * @returns {void}
  29. */
  30. apply(resolver) {
  31. /** @type {ResolveStepHook} */
  32. const target = resolver.ensureHook(this.target);
  33. const alternateTarget = resolver.ensureHook(this.alternateTarget);
  34. resolver
  35. .getHook(this.source)
  36. .tapAsync("PnpPlugin", (request, resolveContext, callback) => {
  37. const req = request.request;
  38. if (!req) return callback();
  39. // The trailing slash indicates to PnP that this value is a folder rather than a file
  40. const issuer = `${request.path}/`;
  41. const packageMatch = /^(@[^/]+\/)?[^/]+/.exec(req);
  42. if (!packageMatch) return callback();
  43. const [packageName] = packageMatch;
  44. const innerRequest = `.${req.slice(packageName.length)}`;
  45. /** @type {string|undefined|null} */
  46. let resolution;
  47. /** @type {string|undefined|null} */
  48. let apiResolution;
  49. try {
  50. resolution = this.pnpApi.resolveToUnqualified(packageName, issuer, {
  51. considerBuiltins: false,
  52. });
  53. if (resolution === null) {
  54. // This is either not a PnP managed issuer or it's a Node builtin
  55. // Try to continue resolving with our alternatives
  56. resolver.doResolve(
  57. alternateTarget,
  58. request,
  59. "issuer is not managed by a pnpapi",
  60. resolveContext,
  61. (err, result) => {
  62. if (err) return callback(err);
  63. if (result) return callback(null, result);
  64. // Skip alternatives
  65. return callback(null, null);
  66. },
  67. );
  68. return;
  69. }
  70. if (resolveContext.fileDependencies) {
  71. apiResolution = this.pnpApi.resolveToUnqualified("pnpapi", issuer, {
  72. considerBuiltins: false,
  73. });
  74. }
  75. } catch (/** @type {unknown} */ error) {
  76. if (
  77. /** @type {Error & { code: string }} */
  78. (error).code === "MODULE_NOT_FOUND" &&
  79. /** @type {Error & { pnpCode: string }} */
  80. (error).pnpCode === "UNDECLARED_DEPENDENCY"
  81. ) {
  82. // This is not a PnP managed dependency.
  83. // Try to continue resolving with our alternatives
  84. if (resolveContext.log) {
  85. resolveContext.log("request is not managed by the pnpapi");
  86. for (const line of /** @type {Error} */ (error).message
  87. .split("\n")
  88. .filter(Boolean)) {
  89. resolveContext.log(` ${line}`);
  90. }
  91. }
  92. return callback();
  93. }
  94. return callback(/** @type {Error} */ (error));
  95. }
  96. if (resolution === packageName) return callback();
  97. if (apiResolution && resolveContext.fileDependencies) {
  98. resolveContext.fileDependencies.add(apiResolution);
  99. }
  100. /** @type {ResolveRequest} */
  101. const obj = {
  102. ...request,
  103. path: resolution,
  104. request: innerRequest,
  105. ignoreSymlinks: true,
  106. fullySpecified: request.fullySpecified && innerRequest !== ".",
  107. };
  108. resolver.doResolve(
  109. target,
  110. obj,
  111. `resolved by pnp to ${resolution}`,
  112. resolveContext,
  113. (err, result) => {
  114. if (err) return callback(err);
  115. if (result) return callback(null, result);
  116. // Skip alternatives
  117. return callback(null, null);
  118. },
  119. );
  120. });
  121. }
  122. };