AliasPlugin.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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 '" +
  122. item.name +
  123. "': '" +
  124. alias +
  125. "' to '" +
  126. newRequestStr +
  127. "'",
  128. resolveContext,
  129. (err, result) => {
  130. if (err) return callback(err);
  131. if (result) return callback(null, result);
  132. return callback();
  133. }
  134. );
  135. }
  136. return callback();
  137. };
  138. /**
  139. * @param {null|Error} [err] error
  140. * @param {null|ResolveRequest} [result] result
  141. * @returns {void}
  142. */
  143. const stoppingCallback = (err, result) => {
  144. if (err) return callback(err);
  145. if (result) return callback(null, result);
  146. // Don't allow other aliasing or raw request
  147. if (shouldStop) return callback(null, null);
  148. return callback();
  149. };
  150. if (Array.isArray(item.alias)) {
  151. return forEachBail(
  152. item.alias,
  153. resolveWithAlias,
  154. stoppingCallback
  155. );
  156. } else {
  157. return resolveWithAlias(item.alias, stoppingCallback);
  158. }
  159. }
  160. return callback();
  161. },
  162. callback
  163. );
  164. });
  165. }
  166. };