resolver.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. const path = require(`path`);
  2. /**
  3. * return source path given a locator
  4. * @param {*} sourceLocator
  5. * @returns
  6. */
  7. function getSourceLocation(sourceLocator, pnpapi) {
  8. if (!sourceLocator) return null;
  9. const sourceInformation = pnpapi.getPackageInformation(sourceLocator);
  10. if (!sourceInformation)
  11. throw new Error(`Couldn't find the package to use as resolution source`);
  12. if (!sourceInformation.packageLocation)
  13. throw new Error(
  14. `The package to use as resolution source seem to not have been installed - maybe it's a devDependency not installed in prod?`
  15. );
  16. return sourceInformation.packageLocation.replace(/\/?$/, `/`);
  17. }
  18. /**
  19. *
  20. * @param {*} sourceLocator
  21. * @param {*} filter
  22. * @returns
  23. */
  24. function makeResolver(opts) {
  25. const { sourceLocator, filter, pnpapi } = opts || {};
  26. const sourceLocation = getSourceLocation(sourceLocator, pnpapi);
  27. return (resolver) => {
  28. const BACKWARD_PATH = /^\.\.([\\\/]|$)/;
  29. const resolvedHook = resolver.ensureHook(`resolve`);
  30. // Prevents the SymlinkPlugin from kicking in. We need the symlinks to be preserved because that's how we deal with peer dependencies ambiguities.
  31. resolver.getHook(`file`).intercept({
  32. register: (tapInfo) => {
  33. return tapInfo.name !== `SymlinkPlugin`
  34. ? tapInfo
  35. : Object.assign({}, tapInfo, {
  36. fn: (request, resolveContext, callback) => {
  37. callback();
  38. },
  39. });
  40. },
  41. });
  42. resolver
  43. .getHook(`after-module`)
  44. .tapAsync(`PnpResolver`, (request, resolveContext, callback) => {
  45. // rethrow pnp errors if we have any for this request
  46. return callback(
  47. resolveContext.pnpErrors &&
  48. resolveContext.pnpErrors.get(request.context.issuer)
  49. );
  50. });
  51. // Register a plugin that will resolve bare imports into the package location on the filesystem before leaving the rest of the resolution to Webpack
  52. resolver
  53. .getHook(`before-module`)
  54. .tapAsync(`PnpResolver`, (requestContext, resolveContext, callback) => {
  55. let request = requestContext.request;
  56. let issuer = requestContext.context.issuer;
  57. // When using require.context, issuer seems to be false (cf https://github.com/webpack/webpack-dev-server/blob/d0725c98fb752d8c0b1e8c9067e526e22b5f5134/client-src/default/index.js#L94)
  58. if (!issuer) {
  59. issuer = `${requestContext.path}/`;
  60. // We only support issuer when they're absolute paths. I'm not sure the opposite can ever happen, but better check here.
  61. } else if (!path.isAbsolute(issuer)) {
  62. throw new Error(
  63. `Cannot successfully resolve this dependency - issuer not supported (${issuer})`
  64. );
  65. }
  66. if (filter) {
  67. const relative = path.relative(filter, issuer);
  68. if (path.isAbsolute(relative) || BACKWARD_PATH.test(relative)) {
  69. return callback(null);
  70. }
  71. }
  72. let resolutionIssuer = sourceLocation || issuer;
  73. let resolution;
  74. try {
  75. resolution = pnpapi.resolveToUnqualified(request, resolutionIssuer, {
  76. considerBuiltins: false,
  77. });
  78. } catch (error) {
  79. if (resolveContext.missingDependencies)
  80. resolveContext.missingDependencies.add(requestContext.path);
  81. if (resolveContext.log) resolveContext.log(error.message);
  82. resolveContext.pnpErrors = resolveContext.pnpErrors || new Map();
  83. resolveContext.pnpErrors.set(issuer, error);
  84. return callback();
  85. }
  86. resolver.doResolve(
  87. resolvedHook,
  88. Object.assign({}, requestContext, {
  89. request: resolution,
  90. }),
  91. null,
  92. resolveContext,
  93. callback
  94. );
  95. });
  96. };
  97. }
  98. module.exports.makeResolver = makeResolver;