UseEffectRulePlugin.js 5.7 KB

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