AliasPlugin.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const forEachBail = require("./forEachBail");
  7. const { PathType, getType } = require("./util/path");
  8. /** @typedef {import("./Resolver")} Resolver */
  9. /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
  10. /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
  11. /** @typedef {string | Array<string> | false} Alias */
  12. /** @typedef {{alias: Alias, name: string, onlyModule?: boolean}} AliasOption */
  13. module.exports = class AliasPlugin {
  14. /**
  15. * @param {string | ResolveStepHook} source source
  16. * @param {AliasOption | Array<AliasOption>} options options
  17. * @param {string | ResolveStepHook} target target
  18. */
  19. constructor(source, options, target) {
  20. this.source = source;
  21. this.options = Array.isArray(options) ? options : [options];
  22. this.target = target;
  23. }
  24. /**
  25. * @param {Resolver} resolver the resolver
  26. * @returns {void}
  27. */
  28. apply(resolver) {
  29. const target = resolver.ensureHook(this.target);
  30. /**
  31. * @param {string} maybeAbsolutePath path
  32. * @returns {null|string} absolute path with slash ending
  33. */
  34. const getAbsolutePathWithSlashEnding = (maybeAbsolutePath) => {
  35. const type = getType(maybeAbsolutePath);
  36. if (type === PathType.AbsolutePosix || type === PathType.AbsoluteWin) {
  37. return resolver.join(maybeAbsolutePath, "_").slice(0, -1);
  38. }
  39. return null;
  40. };
  41. /**
  42. * @param {string} path path
  43. * @param {string} maybeSubPath sub path
  44. * @returns {boolean} true, if path is sub path
  45. */
  46. const isSubPath = (path, maybeSubPath) => {
  47. const absolutePath = getAbsolutePathWithSlashEnding(maybeSubPath);
  48. if (!absolutePath) return false;
  49. return path.startsWith(absolutePath);
  50. };
  51. resolver
  52. .getHook(this.source)
  53. .tapAsync("AliasPlugin", (request, resolveContext, callback) => {
  54. const innerRequest = request.request || request.path;
  55. if (!innerRequest) return callback();
  56. forEachBail(
  57. this.options,
  58. (item, callback) => {
  59. /** @type {boolean} */
  60. let shouldStop = false;
  61. const matchRequest =
  62. innerRequest === item.name ||
  63. (!item.onlyModule &&
  64. (request.request
  65. ? innerRequest.startsWith(`${item.name}/`)
  66. : isSubPath(innerRequest, item.name)));
  67. const splitName = item.name.split("*");
  68. const matchWildcard = !item.onlyModule && splitName.length === 2;
  69. if (matchRequest || matchWildcard) {
  70. /**
  71. * @param {Alias} alias alias
  72. * @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback
  73. * @returns {void}
  74. */
  75. const resolveWithAlias = (alias, callback) => {
  76. if (alias === false) {
  77. /** @type {ResolveRequest} */
  78. const ignoreObj = {
  79. ...request,
  80. path: false,
  81. };
  82. if (typeof resolveContext.yield === "function") {
  83. resolveContext.yield(ignoreObj);
  84. return callback(null, null);
  85. }
  86. return callback(null, ignoreObj);
  87. }
  88. let newRequestStr;
  89. const [prefix, suffix] = splitName;
  90. if (
  91. matchWildcard &&
  92. innerRequest.startsWith(prefix) &&
  93. innerRequest.endsWith(suffix)
  94. ) {
  95. const match = innerRequest.slice(
  96. prefix.length,
  97. innerRequest.length - suffix.length,
  98. );
  99. newRequestStr = item.alias.toString().replace("*", match);
  100. }
  101. if (
  102. matchRequest &&
  103. innerRequest !== alias &&
  104. !innerRequest.startsWith(`${alias}/`)
  105. ) {
  106. /** @type {string} */
  107. const remainingRequest = innerRequest.slice(item.name.length);
  108. newRequestStr = alias + remainingRequest;
  109. }
  110. if (newRequestStr !== undefined) {
  111. shouldStop = true;
  112. /** @type {ResolveRequest} */
  113. const obj = {
  114. ...request,
  115. request: newRequestStr,
  116. fullySpecified: false,
  117. };
  118. return resolver.doResolve(
  119. target,
  120. obj,
  121. `aliased with mapping '${item.name}': '${alias}' to '${
  122. newRequestStr
  123. }'`,
  124. resolveContext,
  125. (err, result) => {
  126. if (err) return callback(err);
  127. if (result) return callback(null, result);
  128. return callback();
  129. },
  130. );
  131. }
  132. return callback();
  133. };
  134. /**
  135. * @param {(null | Error)=} err error
  136. * @param {(null | ResolveRequest)=} result result
  137. * @returns {void}
  138. */
  139. const stoppingCallback = (err, result) => {
  140. if (err) return callback(err);
  141. if (result) return callback(null, result);
  142. // Don't allow other aliasing or raw request
  143. if (shouldStop) return callback(null, null);
  144. return callback();
  145. };
  146. if (Array.isArray(item.alias)) {
  147. return forEachBail(
  148. item.alias,
  149. resolveWithAlias,
  150. stoppingCallback,
  151. );
  152. }
  153. return resolveWithAlias(item.alias, stoppingCallback);
  154. }
  155. return callback();
  156. },
  157. callback,
  158. );
  159. });
  160. }
  161. };