ImportsFieldPlugin.js 4.7 KB

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