UseEffectRulePlugin.js 6.5 KB

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