ContextReplacementPlugin.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const ContextElementDependency = require("./dependencies/ContextElementDependency");
  7. const { isAbsolute, join } = require("./util/fs");
  8. /** @import Compiler from "./Compiler" */
  9. /** @import { ContextModuleOptions } from "./ContextModule" */
  10. /**
  11. * @import {
  12. * BeforeContextResolveData,
  13. * AfterContextResolveData
  14. * } from "./ContextModuleFactory"
  15. */
  16. /** @import { InputFileSystem } from "./util/fs" */
  17. /** @typedef {Record<string, string>} NewContentCreateContextMap */
  18. const PLUGIN_NAME = "ContextReplacementPlugin";
  19. class ContextReplacementPlugin {
  20. /**
  21. * Creates an instance of ContextReplacementPlugin.
  22. * @param {RegExp} resourceRegExp A regular expression that determines which files will be selected
  23. * @param {(string | ((context: BeforeContextResolveData | AfterContextResolveData) => void) | RegExp | boolean)=} newContentResource A new resource to replace the match
  24. * @param {(boolean | NewContentCreateContextMap | RegExp)=} newContentRecursive If true, all subdirectories are searched for matches
  25. * @param {RegExp=} newContentRegExp A regular expression that determines which files will be selected
  26. */
  27. constructor(
  28. resourceRegExp,
  29. newContentResource,
  30. newContentRecursive,
  31. newContentRegExp
  32. ) {
  33. /** @type {RegExp} */
  34. this.resourceRegExp = resourceRegExp;
  35. // new webpack.ContextReplacementPlugin(/selector/, (context) => { /* Logic */ });
  36. if (typeof newContentResource === "function") {
  37. this.newContentCallback = newContentResource;
  38. }
  39. // new ContextReplacementPlugin(/selector/, './folder', { './request': './request' });
  40. else if (
  41. typeof newContentResource === "string" &&
  42. typeof newContentRecursive === "object"
  43. ) {
  44. /** @type {string | undefined} */
  45. this.newContentResource = newContentResource;
  46. /**
  47. * Stores new content create context map.
  48. * @param {InputFileSystem} fs input file system
  49. * @param {(err: null | Error, newContentRecursive: NewContentCreateContextMap) => void} callback callback
  50. */
  51. this.newContentCreateContextMap = (fs, callback) => {
  52. callback(
  53. null,
  54. /** @type {NewContentCreateContextMap} */ (newContentRecursive)
  55. );
  56. };
  57. }
  58. // new ContextReplacementPlugin(/selector/, './folder', (context) => { /* Logic */ });
  59. else if (
  60. typeof newContentResource === "string" &&
  61. typeof newContentRecursive === "function"
  62. ) {
  63. this.newContentResource = newContentResource;
  64. this.newContentCreateContextMap = newContentRecursive;
  65. } else {
  66. // new webpack.ContextReplacementPlugin(/selector/, false, /reg-exp/);
  67. if (typeof newContentResource !== "string") {
  68. newContentRegExp = /** @type {RegExp} */ (newContentRecursive);
  69. newContentRecursive = /** @type {boolean} */ (newContentResource);
  70. newContentResource = undefined;
  71. }
  72. // new webpack.ContextReplacementPlugin(/selector/, /de|fr|hu/);
  73. if (typeof newContentRecursive !== "boolean") {
  74. newContentRegExp = /** @type {RegExp} */ (newContentRecursive);
  75. newContentRecursive = undefined;
  76. }
  77. // new webpack.ContextReplacementPlugin(/selector/, './folder', false, /selector/);
  78. this.newContentResource =
  79. /** @type {string | undefined} */
  80. (newContentResource);
  81. /** @type {boolean | undefined} */
  82. this.newContentRecursive =
  83. /** @type {boolean | undefined} */
  84. (newContentRecursive);
  85. /** @type {RegExp | undefined} */
  86. this.newContentRegExp =
  87. /** @type {RegExp | undefined} */
  88. (newContentRegExp);
  89. }
  90. }
  91. /**
  92. * Applies the plugin by registering its hooks on the compiler.
  93. * @param {Compiler} compiler the compiler instance
  94. * @returns {void}
  95. */
  96. apply(compiler) {
  97. const resourceRegExp = this.resourceRegExp;
  98. const newContentCallback = this.newContentCallback;
  99. const newContentResource = this.newContentResource;
  100. const newContentRecursive = this.newContentRecursive;
  101. const newContentRegExp = this.newContentRegExp;
  102. const newContentCreateContextMap = this.newContentCreateContextMap;
  103. compiler.hooks.contextModuleFactory.tap(PLUGIN_NAME, (cmf) => {
  104. cmf.hooks.beforeResolve.tap(PLUGIN_NAME, (result) => {
  105. if (!result) return;
  106. if (resourceRegExp.test(result.request)) {
  107. if (newContentResource !== undefined) {
  108. result.request = newContentResource;
  109. }
  110. if (newContentRecursive !== undefined) {
  111. result.recursive = newContentRecursive;
  112. }
  113. if (newContentRegExp !== undefined) {
  114. result.regExp = newContentRegExp;
  115. }
  116. if (typeof newContentCallback === "function") {
  117. newContentCallback(result);
  118. } else {
  119. for (const d of result.dependencies) {
  120. if (d.critical) d.critical = false;
  121. }
  122. }
  123. }
  124. return result;
  125. });
  126. cmf.hooks.afterResolve.tap(PLUGIN_NAME, (result) => {
  127. if (!result) return;
  128. const isMatchResourceRegExp = () => {
  129. if (Array.isArray(result.resource)) {
  130. return result.resource.some((item) => resourceRegExp.test(item));
  131. }
  132. return resourceRegExp.test(result.resource);
  133. };
  134. if (isMatchResourceRegExp()) {
  135. if (newContentResource !== undefined) {
  136. if (isAbsolute(newContentResource)) {
  137. result.resource = newContentResource;
  138. } else {
  139. const rootPath =
  140. typeof result.resource === "string"
  141. ? result.resource
  142. : /** @type {string} */
  143. (result.resource.find((item) => resourceRegExp.test(item)));
  144. result.resource = join(
  145. /** @type {InputFileSystem} */
  146. (compiler.inputFileSystem),
  147. rootPath,
  148. newContentResource
  149. );
  150. }
  151. }
  152. if (newContentRecursive !== undefined) {
  153. result.recursive = newContentRecursive;
  154. }
  155. if (newContentRegExp !== undefined) {
  156. result.regExp = newContentRegExp;
  157. }
  158. if (typeof newContentCreateContextMap === "function") {
  159. result.resolveDependencies =
  160. createResolveDependenciesFromContextMap(
  161. newContentCreateContextMap
  162. );
  163. }
  164. if (typeof newContentCallback === "function") {
  165. const origResource = result.resource;
  166. newContentCallback(result);
  167. if (result.resource !== origResource) {
  168. const newResource = Array.isArray(result.resource)
  169. ? result.resource
  170. : [result.resource];
  171. for (let i = 0; i < newResource.length; i++) {
  172. if (!isAbsolute(newResource[i])) {
  173. // When the function changed it to an relative path
  174. newResource[i] = join(
  175. /** @type {InputFileSystem} */
  176. (compiler.inputFileSystem),
  177. origResource[i],
  178. newResource[i]
  179. );
  180. }
  181. }
  182. result.resource = newResource;
  183. }
  184. } else {
  185. for (const d of result.dependencies) {
  186. if (d.critical) d.critical = false;
  187. }
  188. }
  189. }
  190. return result;
  191. });
  192. });
  193. }
  194. }
  195. /**
  196. * Creates a resolve dependencies from context map.
  197. * @param {(fs: InputFileSystem, callback: (err: null | Error, map: NewContentCreateContextMap) => void) => void} createContextMap create context map function
  198. * @returns {(fs: InputFileSystem, options: ContextModuleOptions, callback: (err: null | Error, dependencies?: ContextElementDependency[]) => void) => void} resolve resolve dependencies from context map function
  199. */
  200. const createResolveDependenciesFromContextMap =
  201. (createContextMap) => (fs, options, callback) => {
  202. createContextMap(fs, (err, map) => {
  203. if (err) return callback(err);
  204. const dependencies = Object.keys(map).map(
  205. (key) =>
  206. new ContextElementDependency(
  207. map[key] + options.resourceQuery + options.resourceFragment,
  208. key,
  209. options.typePrefix,
  210. /** @type {string} */
  211. (options.category),
  212. options.referencedExports
  213. )
  214. );
  215. callback(null, dependencies);
  216. });
  217. };
  218. module.exports = ContextReplacementPlugin;