UseEffectRulePlugin.js 6.5 KB

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