/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const util = require("util"); /** @typedef {import("../../declarations/WebpackOptions").Falsy} Falsy */ /** @typedef {import("../../declarations/WebpackOptions").RuleSetLoader} RuleSetLoader */ /** @typedef {import("../../declarations/WebpackOptions").RuleSetLoaderOptions} RuleSetLoaderOptions */ /** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ /** @typedef {import("../../declarations/WebpackOptions").RuleSetUse} RuleSetUse */ /** @typedef {import("../../declarations/WebpackOptions").RuleSetUseItem} RuleSetUseItem */ /** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */ /** @typedef {import("./RuleSetCompiler").Effect} Effect */ /** @typedef {import("./RuleSetCompiler").EffectData} EffectData */ class UseEffectRulePlugin { /** * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler * @returns {void} */ apply(ruleSetCompiler) { ruleSetCompiler.hooks.rule.tap( "UseEffectRulePlugin", (path, rule, unhandledProperties, result, references) => { /** * @param {keyof RuleSetRule} property property * @param {string} correctProperty correct property */ const conflictWith = (property, correctProperty) => { if (unhandledProperties.has(property)) { throw ruleSetCompiler.error( `${path}.${property}`, rule[property], `A Rule must not have a '${property}' property when it has a '${correctProperty}' property` ); } }; if (unhandledProperties.has("use")) { unhandledProperties.delete("use"); unhandledProperties.delete("enforce"); conflictWith("loader", "use"); conflictWith("options", "use"); const use = /** @type {RuleSetUse} */ (rule.use); const enforce = rule.enforce; const type = enforce ? `use-${enforce}` : "use"; /** * @param {string} path options path * @param {string} defaultIdent default ident when none is provided * @param {RuleSetUseItem} item user provided use value * @returns {(Effect | ((effectData: EffectData) => Effect[]))} effect */ const useToEffect = (path, defaultIdent, item) => { if (typeof item === "function") { return data => useToEffectsWithoutIdent( path, /** @type {RuleSetUseItem | RuleSetUseItem[]} */ (item(data)) ); } return useToEffectRaw(path, defaultIdent, item); }; /** * @param {string} path options path * @param {string} defaultIdent default ident when none is provided * @param {Exclude, EXPECTED_FUNCTION>} item user provided use value * @returns {Effect} effect */ const useToEffectRaw = (path, defaultIdent, item) => { if (typeof item === "string") { return { type, value: { loader: item, options: undefined, ident: undefined } }; } const loader = item.loader; const options = item.options; let ident = item.ident; if (options && typeof options === "object") { if (!ident) ident = defaultIdent; references.set(ident, options); } if (typeof options === "string") { util.deprecate( () => {}, `Using a string as loader options is deprecated (${path}.options)`, "DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING" )(); } return { type: enforce ? `use-${enforce}` : "use", value: { loader, options, ident } }; }; /** * @param {string} path options path * @param {RuleSetUseItem | (Falsy | RuleSetUseItem)[]} items user provided use value * @returns {Effect[]} effects */ const useToEffectsWithoutIdent = (path, items) => { if (Array.isArray(items)) { return items.filter(Boolean).map((item, idx) => useToEffectRaw( `${path}[${idx}]`, "[[missing ident]]", /** @type {Exclude} */ (item) ) ); } return [ useToEffectRaw( path, "[[missing ident]]", /** @type {Exclude} */ (items) ) ]; }; /** * @param {string} path current path * @param {RuleSetUse} items user provided use value * @returns {(Effect | ((effectData: EffectData) => Effect[]))[]} effects */ const useToEffects = (path, items) => { if (Array.isArray(items)) { return items.filter(Boolean).map((item, idx) => { const subPath = `${path}[${idx}]`; return useToEffect( subPath, subPath, /** @type {RuleSetUseItem} */ (item) ); }); } return [ useToEffect(path, path, /** @type {RuleSetUseItem} */ (items)) ]; }; if (typeof use === "function") { result.effects.push(data => useToEffectsWithoutIdent(`${path}.use`, use(data)) ); } else { for (const effect of useToEffects(`${path}.use`, use)) { result.effects.push(effect); } } } if (unhandledProperties.has("loader")) { unhandledProperties.delete("loader"); unhandledProperties.delete("options"); unhandledProperties.delete("enforce"); const loader = /** @type {RuleSetLoader} */ (rule.loader); const options = rule.options; const enforce = rule.enforce; if (loader.includes("!")) { throw ruleSetCompiler.error( `${path}.loader`, loader, "Exclamation mark separated loader lists has been removed in favor of the 'use' property with arrays" ); } if (loader.includes("?")) { throw ruleSetCompiler.error( `${path}.loader`, loader, "Query arguments on 'loader' has been removed in favor of the 'options' property" ); } if (typeof options === "string") { util.deprecate( () => {}, `Using a string as loader options is deprecated (${path}.options)`, "DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING" )(); } const ident = options && typeof options === "object" ? path : undefined; references.set( /** @type {TODO} */ (ident), /** @type {RuleSetLoaderOptions} */ (options) ); result.effects.push({ type: enforce ? `use-${enforce}` : "use", value: { loader, options, ident } }); } } ); } } module.exports = UseEffectRulePlugin;