PnpPlugin.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  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 {function(string, string, object): string | null} resolveToUnqualified
  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[0];
  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. return callback();
  92. }
  93. return callback(/** @type {Error} */ (error));
  94. }
  95. if (resolution === packageName) return callback();
  96. if (apiResolution && resolveContext.fileDependencies) {
  97. resolveContext.fileDependencies.add(apiResolution);
  98. }
  99. /** @type {ResolveRequest} */
  100. const obj = {
  101. ...request,
  102. path: resolution,
  103. request: innerRequest,
  104. ignoreSymlinks: true,
  105. fullySpecified: request.fullySpecified && innerRequest !== "."
  106. };
  107. resolver.doResolve(
  108. target,
  109. obj,
  110. `resolved by pnp to ${resolution}`,
  111. resolveContext,
  112. (err, result) => {
  113. if (err) return callback(err);
  114. if (result) return callback(null, result);
  115. // Skip alternatives
  116. return callback(null, null);
  117. }
  118. );
  119. });
  120. }
  121. };