ContextReplacementPlugin.js 6.8 KB

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