UseEffectRulePlugin.js 5.2 KB

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