index.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.pnpPlugin = void 0;
  4. const tslib_1 = require("tslib");
  5. const fs = tslib_1.__importStar(require("fs"));
  6. const path_1 = tslib_1.__importDefault(require("path"));
  7. const matchAll = /()/;
  8. const defaultExtensions = [`.tsx`, `.ts`, `.jsx`, `.mjs`, `.cjs`, `.js`, `.css`, `.json`];
  9. // Reference: https://github.com/evanw/esbuild/blob/537195ae84bee1510fac14235906d588084c39cd/pkg/api/api_impl.go#L366-L388
  10. function parseExternals(externals) {
  11. return externals.map(external => {
  12. // ESBuild's validation pass runs before this function is called so there's no need to assert that there's a single wildcard
  13. const wildcardIdx = external.indexOf(`*`);
  14. if (wildcardIdx !== -1)
  15. return { prefix: external.slice(0, wildcardIdx), suffix: external.slice(wildcardIdx + 1) };
  16. return external;
  17. });
  18. }
  19. function isExternal(path, externals) {
  20. for (const external of externals) {
  21. if (typeof external === `object`) {
  22. // Reference: https://github.com/evanw/esbuild/blob/537195ae84bee1510fac14235906d588084c39cd/internal/resolver/resolver.go#L372-L381
  23. if (path.length >= external.prefix.length + external.suffix.length
  24. && path.startsWith(external.prefix)
  25. && path.endsWith(external.suffix)) {
  26. return true;
  27. }
  28. }
  29. else {
  30. if (path === external)
  31. return true;
  32. // If the module "foo" has been marked as external, we also want to treat
  33. // paths into that module such as "foo/bar" as external too.
  34. // References:
  35. // - https://github.com/evanw/esbuild/blob/537195ae84bee1510fac14235906d588084c39cd/internal/resolver/resolver.go#L651-L652
  36. // - https://github.com/evanw/esbuild/blob/537195ae84bee1510fac14235906d588084c39cd/internal/resolver/resolver.go#L1688-L1691
  37. if (!external.startsWith(`/`) && !external.startsWith(`./`) && !external.startsWith(`../`) && external !== `.` && external !== `..`) {
  38. if (path.startsWith(`${external}/`)) {
  39. return true;
  40. }
  41. }
  42. }
  43. }
  44. return false;
  45. }
  46. async function defaultOnLoad(args) {
  47. return {
  48. contents: await fs.promises.readFile(args.path),
  49. loader: `default`,
  50. // For regular imports in the `file` namespace, resolveDir is the directory the
  51. // file being resolved lives in. For all other virtual modules, this defaults to
  52. // empty string: ""
  53. // A sensible value for pnp imports is the same as the `file` namespace, as pnp
  54. // still resolves to files on disk (in the cache).
  55. resolveDir: path_1.default.dirname(args.path),
  56. };
  57. }
  58. async function defaultOnResolve(args, { resolvedPath, error, watchFiles }) {
  59. const problems = error ? [{ text: error.message }] : [];
  60. // Sometimes dynamic resolve calls might be wrapped in a try / catch,
  61. // but ESBuild neither skips them nor does it provide a way for us to tell.
  62. // Because of that, we downgrade all errors to warnings in these situations.
  63. // Issue: https://github.com/evanw/esbuild/issues/1127
  64. let mergeWith;
  65. switch (args.kind) {
  66. case `require-call`:
  67. case `require-resolve`:
  68. case `dynamic-import`:
  69. {
  70. mergeWith = { warnings: problems };
  71. }
  72. break;
  73. default:
  74. {
  75. mergeWith = { errors: problems };
  76. }
  77. break;
  78. }
  79. if (resolvedPath !== null) {
  80. return { namespace: `pnp`, path: resolvedPath, watchFiles };
  81. }
  82. else {
  83. return { external: true, ...mergeWith, watchFiles };
  84. }
  85. }
  86. function pnpPlugin({ baseDir = process.cwd(), extensions = defaultExtensions, filter = matchAll, onResolve = defaultOnResolve, onLoad = defaultOnLoad, } = {}) {
  87. return {
  88. name: `@yarnpkg/esbuild-plugin-pnp`,
  89. setup(build) {
  90. var _a, _b;
  91. const { findPnpApi } = require(`module`);
  92. if (typeof findPnpApi === `undefined`)
  93. return;
  94. const externals = parseExternals((_a = build.initialOptions.external) !== null && _a !== void 0 ? _a : []);
  95. const platform = (_b = build.initialOptions.platform) !== null && _b !== void 0 ? _b : `browser`;
  96. const isPlatformNode = platform === `node`;
  97. // Reference: https://github.com/evanw/esbuild/blob/537195ae84bee1510fac14235906d588084c39cd/internal/resolver/resolver.go#L238-L253
  98. const conditionsDefault = new Set(build.initialOptions.conditions);
  99. conditionsDefault.add(`default`);
  100. if (platform === `browser` || platform === `node`)
  101. conditionsDefault.add(platform);
  102. const conditionsImport = new Set(conditionsDefault);
  103. conditionsImport.add(`import`);
  104. const conditionsRequire = new Set(conditionsDefault);
  105. conditionsRequire.add(`require`);
  106. build.onResolve({ filter }, args => {
  107. var _a, _b;
  108. if (isExternal(args.path, externals))
  109. return { external: true };
  110. // Reference: https://github.com/evanw/esbuild/blob/537195ae84bee1510fac14235906d588084c39cd/internal/resolver/resolver.go#L1495-L1502
  111. let conditions = conditionsDefault;
  112. if (args.kind === `dynamic-import` || args.kind === `import-statement`)
  113. conditions = conditionsImport;
  114. else if (args.kind === `require-call` || args.kind === `require-resolve`)
  115. conditions = conditionsRequire;
  116. // The entry point resolution uses an empty string
  117. const effectiveImporter = args.resolveDir
  118. ? `${args.resolveDir}/`
  119. : args.importer
  120. ? args.importer
  121. : `${baseDir}/`;
  122. const pnpApi = findPnpApi(effectiveImporter);
  123. if (!pnpApi)
  124. // Path isn't controlled by PnP so delegate to the next resolver in the chain
  125. return undefined;
  126. let path = null;
  127. let error;
  128. try {
  129. path = pnpApi.resolveRequest(args.path, effectiveImporter, {
  130. conditions,
  131. considerBuiltins: isPlatformNode,
  132. extensions,
  133. });
  134. }
  135. catch (e) {
  136. error = e;
  137. }
  138. const watchFiles = [pnpApi.resolveRequest(`pnpapi`, null)];
  139. if (path) {
  140. const locator = pnpApi.findPackageLocator(path);
  141. if (locator) {
  142. const info = pnpApi.getPackageInformation(locator);
  143. if ((info === null || info === void 0 ? void 0 : info.linkType) === `SOFT`) {
  144. watchFiles.push((_b = (_a = pnpApi.resolveVirtual) === null || _a === void 0 ? void 0 : _a.call(pnpApi, path)) !== null && _b !== void 0 ? _b : path);
  145. }
  146. }
  147. }
  148. return onResolve(args, { resolvedPath: path, error, watchFiles });
  149. });
  150. // We register on the build to prevent ESBuild from reading the files
  151. // itself, since it wouldn't know how to access the files from within
  152. // the zip archives.
  153. if (build.onLoad !== null) {
  154. build.onLoad({ filter }, onLoad);
  155. }
  156. },
  157. };
  158. }
  159. exports.pnpPlugin = pnpPlugin;