ImportsFieldPlugin.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Ivan Kopeykin @vankop
  4. */
  5. "use strict";
  6. const path = require("path");
  7. const DescriptionFileUtils = require("./DescriptionFileUtils");
  8. const forEachBail = require("./forEachBail");
  9. const { processImportsField } = require("./util/entrypoints");
  10. const { parseIdentifier } = require("./util/identifier");
  11. /** @typedef {import("./Resolver")} Resolver */
  12. /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
  13. /** @typedef {import("./util/entrypoints").FieldProcessor} FieldProcessor */
  14. /** @typedef {import("./util/entrypoints").ImportsField} ImportsField */
  15. const dotCode = ".".charCodeAt(0);
  16. module.exports = class ImportsFieldPlugin {
  17. /**
  18. * @param {string | ResolveStepHook} source source
  19. * @param {Set<string>} conditionNames condition names
  20. * @param {string | string[]} fieldNamePath name path
  21. * @param {string | ResolveStepHook} targetFile target file
  22. * @param {string | ResolveStepHook} targetPackage target package
  23. */
  24. constructor(
  25. source,
  26. conditionNames,
  27. fieldNamePath,
  28. targetFile,
  29. targetPackage
  30. ) {
  31. this.source = source;
  32. this.targetFile = targetFile;
  33. this.targetPackage = targetPackage;
  34. this.conditionNames = conditionNames;
  35. this.fieldName = fieldNamePath;
  36. /** @type {WeakMap<any, FieldProcessor>} */
  37. this.fieldProcessorCache = new WeakMap();
  38. }
  39. /**
  40. * @param {Resolver} resolver the resolver
  41. * @returns {void}
  42. */
  43. apply(resolver) {
  44. const targetFile = resolver.ensureHook(this.targetFile);
  45. const targetPackage = resolver.ensureHook(this.targetPackage);
  46. resolver
  47. .getHook(this.source)
  48. .tapAsync("ImportsFieldPlugin", (request, resolveContext, callback) => {
  49. // When there is no description file, abort
  50. if (!request.descriptionFilePath || request.request === undefined) {
  51. return callback();
  52. }
  53. const remainingRequest =
  54. request.request + request.query + request.fragment;
  55. /** @type {ImportsField|null} */
  56. const importsField = DescriptionFileUtils.getField(
  57. request.descriptionFileData,
  58. this.fieldName
  59. );
  60. if (!importsField) return callback();
  61. if (request.directory) {
  62. return callback(
  63. new Error(
  64. `Resolving to directories is not possible with the imports field (request was ${remainingRequest}/)`
  65. )
  66. );
  67. }
  68. let paths;
  69. try {
  70. // We attach the cache to the description file instead of the importsField value
  71. // because we use a WeakMap and the importsField could be a string too.
  72. // Description file is always an object when exports field can be accessed.
  73. let fieldProcessor = this.fieldProcessorCache.get(
  74. request.descriptionFileData
  75. );
  76. if (fieldProcessor === undefined) {
  77. fieldProcessor = processImportsField(importsField);
  78. this.fieldProcessorCache.set(
  79. request.descriptionFileData,
  80. fieldProcessor
  81. );
  82. }
  83. paths = fieldProcessor(remainingRequest, this.conditionNames);
  84. } catch (err) {
  85. if (resolveContext.log) {
  86. resolveContext.log(
  87. `Imports field in ${request.descriptionFilePath} can't be processed: ${err}`
  88. );
  89. }
  90. return callback(err);
  91. }
  92. if (paths.length === 0) {
  93. return callback(
  94. new Error(
  95. `Package import ${remainingRequest} is not imported from package ${request.descriptionFileRoot} (see imports field in ${request.descriptionFilePath})`
  96. )
  97. );
  98. }
  99. forEachBail(
  100. paths,
  101. (p, callback) => {
  102. const parsedIdentifier = parseIdentifier(p);
  103. if (!parsedIdentifier) return callback();
  104. const [path_, query, fragment] = parsedIdentifier;
  105. switch (path_.charCodeAt(0)) {
  106. // should be relative
  107. case dotCode: {
  108. const obj = {
  109. ...request,
  110. request: undefined,
  111. path: path.join(
  112. /** @type {string} */ (request.descriptionFileRoot),
  113. path_
  114. ),
  115. relativePath: path_,
  116. query,
  117. fragment
  118. };
  119. resolver.doResolve(
  120. targetFile,
  121. obj,
  122. "using imports field: " + p,
  123. resolveContext,
  124. callback
  125. );
  126. break;
  127. }
  128. // package resolving
  129. default: {
  130. const obj = {
  131. ...request,
  132. request: path_,
  133. relativePath: path_,
  134. fullySpecified: true,
  135. query,
  136. fragment
  137. };
  138. resolver.doResolve(
  139. targetPackage,
  140. obj,
  141. "using imports field: " + p,
  142. resolveContext,
  143. callback
  144. );
  145. }
  146. }
  147. },
  148. (err, result) => callback(err, result || null)
  149. );
  150. });
  151. }
  152. };