UseEffectRulePlugin.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const util = require("util");
  7. /** @typedef {import("../../declarations/WebpackOptions").Falsy} Falsy */
  8. /** @typedef {import("../../declarations/WebpackOptions").RuleSetLoader} RuleSetLoader */
  9. /** @typedef {import("../../declarations/WebpackOptions").RuleSetLoaderOptions} RuleSetLoaderOptions */
  10. /** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */
  11. /** @typedef {import("../../declarations/WebpackOptions").RuleSetUse} RuleSetUse */
  12. /** @typedef {import("../../declarations/WebpackOptions").RuleSetUseItem} RuleSetUseItem */
  13. /** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */
  14. /** @typedef {import("./RuleSetCompiler").Effect} Effect */
  15. /** @typedef {import("./RuleSetCompiler").EffectData} EffectData */
  16. /** @typedef {import("./RuleSetCompiler").EffectUseType} EffectUseType */
  17. const PLUGIN_NAME = "UseEffectRulePlugin";
  18. class UseEffectRulePlugin {
  19. /**
  20. * Applies the plugin by registering its hooks on the compiler.
  21. * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler
  22. * @returns {void}
  23. */
  24. apply(ruleSetCompiler) {
  25. ruleSetCompiler.hooks.rule.tap(
  26. PLUGIN_NAME,
  27. (path, rule, unhandledProperties, result, references) => {
  28. /**
  29. * Processes the provided property.
  30. * @param {keyof RuleSetRule} property property
  31. * @param {string} correctProperty correct property
  32. */
  33. const conflictWith = (property, correctProperty) => {
  34. if (unhandledProperties.has(property)) {
  35. throw ruleSetCompiler.error(
  36. `${path}.${property}`,
  37. rule[property],
  38. `A Rule must not have a '${property}' property when it has a '${correctProperty}' property`
  39. );
  40. }
  41. };
  42. if (unhandledProperties.has("use")) {
  43. unhandledProperties.delete("use");
  44. unhandledProperties.delete("enforce");
  45. conflictWith("loader", "use");
  46. conflictWith("options", "use");
  47. const use = /** @type {RuleSetUse} */ (rule.use);
  48. const enforce = rule.enforce;
  49. const type =
  50. /** @type {EffectUseType} */
  51. (enforce ? `use-${enforce}` : "use");
  52. /**
  53. * Returns effect.
  54. * @param {string} path options path
  55. * @param {string} defaultIdent default ident when none is provided
  56. * @param {RuleSetUseItem} item user provided use value
  57. * @returns {(Effect | ((effectData: EffectData) => Effect[]))} effect
  58. */
  59. const useToEffect = (path, defaultIdent, item) => {
  60. if (typeof item === "function") {
  61. return (data) =>
  62. useToEffectsWithoutIdent(
  63. path,
  64. /** @type {RuleSetUseItem | RuleSetUseItem[]} */
  65. (item(data))
  66. );
  67. }
  68. return useToEffectRaw(path, defaultIdent, item);
  69. };
  70. /**
  71. * Returns effect.
  72. * @param {string} path options path
  73. * @param {string} defaultIdent default ident when none is provided
  74. * @param {Exclude<NonNullable<RuleSetUseItem>, EXPECTED_FUNCTION>} item user provided use value
  75. * @returns {Effect} effect
  76. */
  77. const useToEffectRaw = (path, defaultIdent, item) => {
  78. if (typeof item === "string") {
  79. return {
  80. type,
  81. value: {
  82. loader: item,
  83. options: undefined,
  84. ident: undefined
  85. }
  86. };
  87. }
  88. const loader = /** @type {string} */ (item.loader);
  89. const options = item.options;
  90. let ident = item.ident;
  91. if (options && typeof options === "object") {
  92. if (!ident) ident = defaultIdent;
  93. references.set(ident, options);
  94. }
  95. if (typeof options === "string") {
  96. util.deprecate(
  97. () => {},
  98. `Using a string as loader options is deprecated (${path}.options)`,
  99. "DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING"
  100. )();
  101. }
  102. return {
  103. type: enforce ? `use-${enforce}` : "use",
  104. value: {
  105. loader,
  106. options,
  107. ident
  108. }
  109. };
  110. };
  111. /**
  112. * Use to effects without ident.
  113. * @param {string} path options path
  114. * @param {RuleSetUseItem | (Falsy | RuleSetUseItem)[]} items user provided use value
  115. * @returns {Effect[]} effects
  116. */
  117. const useToEffectsWithoutIdent = (path, items) => {
  118. if (Array.isArray(items)) {
  119. return items.filter(Boolean).map((item, idx) =>
  120. useToEffectRaw(
  121. `${path}[${idx}]`,
  122. "[[missing ident]]",
  123. /** @type {Exclude<RuleSetUseItem, EXPECTED_FUNCTION>} */
  124. (item)
  125. )
  126. );
  127. }
  128. return [
  129. useToEffectRaw(
  130. path,
  131. "[[missing ident]]",
  132. /** @type {Exclude<RuleSetUseItem, EXPECTED_FUNCTION>} */
  133. (items)
  134. )
  135. ];
  136. };
  137. /**
  138. * Returns effects.
  139. * @param {string} path current path
  140. * @param {RuleSetUse} items user provided use value
  141. * @returns {(Effect | ((effectData: EffectData) => Effect[]))[]} effects
  142. */
  143. const useToEffects = (path, items) => {
  144. if (Array.isArray(items)) {
  145. return items.filter(Boolean).map((item, idx) => {
  146. const subPath = `${path}[${idx}]`;
  147. return useToEffect(
  148. subPath,
  149. subPath,
  150. /** @type {RuleSetUseItem} */
  151. (item)
  152. );
  153. });
  154. }
  155. return [
  156. useToEffect(path, path, /** @type {RuleSetUseItem} */ (items))
  157. ];
  158. };
  159. if (typeof use === "function") {
  160. result.effects.push((data) =>
  161. useToEffectsWithoutIdent(`${path}.use`, use(data))
  162. );
  163. } else {
  164. for (const effect of useToEffects(`${path}.use`, use)) {
  165. result.effects.push(effect);
  166. }
  167. }
  168. }
  169. if (unhandledProperties.has("loader")) {
  170. unhandledProperties.delete("loader");
  171. unhandledProperties.delete("options");
  172. unhandledProperties.delete("enforce");
  173. const loader = /** @type {RuleSetLoader} */ (rule.loader);
  174. const options = rule.options;
  175. const enforce = rule.enforce;
  176. if (loader.includes("!")) {
  177. throw ruleSetCompiler.error(
  178. `${path}.loader`,
  179. loader,
  180. "Exclamation mark separated loader lists has been removed in favor of the 'use' property with arrays"
  181. );
  182. }
  183. if (loader.includes("?")) {
  184. throw ruleSetCompiler.error(
  185. `${path}.loader`,
  186. loader,
  187. "Query arguments on 'loader' has been removed in favor of the 'options' property"
  188. );
  189. }
  190. if (typeof options === "string") {
  191. util.deprecate(
  192. () => {},
  193. `Using a string as loader options is deprecated (${path}.options)`,
  194. "DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING"
  195. )();
  196. }
  197. const ident =
  198. options && typeof options === "object" ? path : undefined;
  199. if (ident) {
  200. references.set(
  201. ident,
  202. /** @type {RuleSetLoaderOptions} */
  203. (options)
  204. );
  205. }
  206. result.effects.push({
  207. type: enforce ? `use-${enforce}` : "use",
  208. value: {
  209. loader,
  210. options,
  211. ident
  212. }
  213. });
  214. }
  215. }
  216. );
  217. }
  218. }
  219. module.exports = UseEffectRulePlugin;