ContextReplacementPlugin.js 7.9 KB

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