ContextReplacementPlugin.js 6.8 KB

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