UseEffectRulePlugin.js 6.5 KB

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