PnpPlugin.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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} 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. */
  19. constructor(source, pnpApi, target) {
  20. this.source = source;
  21. this.pnpApi = pnpApi;
  22. this.target = target;
  23. }
  24. /**
  25. * @param {Resolver} resolver the resolver
  26. * @returns {void}
  27. */
  28. apply(resolver) {
  29. /** @type {ResolveStepHook} */
  30. const target = resolver.ensureHook(this.target);
  31. resolver
  32. .getHook(this.source)
  33. .tapAsync("PnpPlugin", (request, resolveContext, callback) => {
  34. const req = request.request;
  35. if (!req) return callback();
  36. // The trailing slash indicates to PnP that this value is a folder rather than a file
  37. const issuer = `${request.path}/`;
  38. const packageMatch = /^(@[^/]+\/)?[^/]+/.exec(req);
  39. if (!packageMatch) return callback();
  40. const packageName = packageMatch[0];
  41. const innerRequest = `.${req.slice(packageName.length)}`;
  42. /** @type {string|undefined} */
  43. let resolution;
  44. /** @type {string|undefined} */
  45. let apiResolution;
  46. try {
  47. resolution = this.pnpApi.resolveToUnqualified(packageName, issuer, {
  48. considerBuiltins: false
  49. });
  50. if (resolveContext.fileDependencies) {
  51. apiResolution = this.pnpApi.resolveToUnqualified("pnpapi", issuer, {
  52. considerBuiltins: false
  53. });
  54. }
  55. } catch (/** @type {unknown} */ error) {
  56. if (
  57. /** @type {Error & { code: string }} */
  58. (error).code === "MODULE_NOT_FOUND" &&
  59. /** @type {Error & { pnpCode: string }} */
  60. (error).pnpCode === "UNDECLARED_DEPENDENCY"
  61. ) {
  62. // This is not a PnP managed dependency.
  63. // Try to continue resolving with our alternatives
  64. if (resolveContext.log) {
  65. resolveContext.log(`request is not managed by the pnpapi`);
  66. for (const line of /** @type {Error} */ (error).message
  67. .split("\n")
  68. .filter(Boolean))
  69. resolveContext.log(` ${line}`);
  70. }
  71. return callback();
  72. }
  73. return callback(/** @type {Error} */ (error));
  74. }
  75. if (resolution === packageName) return callback();
  76. if (apiResolution && resolveContext.fileDependencies) {
  77. resolveContext.fileDependencies.add(apiResolution);
  78. }
  79. /** @type {ResolveRequest} */
  80. const obj = {
  81. ...request,
  82. path: resolution,
  83. request: innerRequest,
  84. ignoreSymlinks: true,
  85. fullySpecified: request.fullySpecified && innerRequest !== "."
  86. };
  87. resolver.doResolve(
  88. target,
  89. obj,
  90. `resolved by pnp to ${resolution}`,
  91. resolveContext,
  92. (err, result) => {
  93. if (err) return callback(err);
  94. if (result) return callback(null, result);
  95. // Skip alternatives
  96. return callback(null, null);
  97. }
  98. );
  99. });
  100. }
  101. };