| /* |
| MIT License http://www.opensource.org/licenses/mit-license.php |
| Author Tobias Koppers @sokra |
| */ |
| "use strict"; |
| |
| const ConstDependency = require("./dependencies/ConstDependency"); |
| const BasicEvaluatedExpression = require("./BasicEvaluatedExpression"); |
| const ParserHelpers = require("./ParserHelpers"); |
| const NullFactory = require("./NullFactory"); |
| |
| /** @typedef {import("./Compiler")} Compiler */ |
| /** @typedef {import("./Parser")} Parser */ |
| /** @typedef {null|undefined|RegExp|Function|string|number} CodeValuePrimitive */ |
| /** @typedef {CodeValuePrimitive|Record<string, CodeValuePrimitive>|RuntimeValue} CodeValue */ |
| |
| class RuntimeValue { |
| constructor(fn, fileDependencies) { |
| this.fn = fn; |
| this.fileDependencies = fileDependencies || []; |
| } |
| |
| exec(parser) { |
| if (this.fileDependencies === true) { |
| parser.state.module.buildInfo.cacheable = false; |
| } else { |
| for (const fileDependency of this.fileDependencies) { |
| parser.state.module.buildInfo.fileDependencies.add(fileDependency); |
| } |
| } |
| |
| return this.fn({ module: parser.state.module }); |
| } |
| } |
| |
| const stringifyObj = (obj, parser) => { |
| return ( |
| "Object({" + |
| Object.keys(obj) |
| .map(key => { |
| const code = obj[key]; |
| return JSON.stringify(key) + ":" + toCode(code, parser); |
| }) |
| .join(",") + |
| "})" |
| ); |
| }; |
| |
| /** |
| * Convert code to a string that evaluates |
| * @param {CodeValue} code Code to evaluate |
| * @param {Parser} parser Parser |
| * @returns {string} code converted to string that evaluates |
| */ |
| const toCode = (code, parser) => { |
| if (code === null) { |
| return "null"; |
| } |
| if (code === undefined) { |
| return "undefined"; |
| } |
| if (code instanceof RuntimeValue) { |
| return toCode(code.exec(parser), parser); |
| } |
| if (code instanceof RegExp && code.toString) { |
| return code.toString(); |
| } |
| if (typeof code === "function" && code.toString) { |
| return "(" + code.toString() + ")"; |
| } |
| if (typeof code === "object") { |
| return stringifyObj(code, parser); |
| } |
| return code + ""; |
| }; |
| |
| class DefinePlugin { |
| /** |
| * Create a new define plugin |
| * @param {Record<string, CodeValue>} definitions A map of global object definitions |
| */ |
| constructor(definitions) { |
| this.definitions = definitions; |
| } |
| |
| static runtimeValue(fn, fileDependencies) { |
| return new RuntimeValue(fn, fileDependencies); |
| } |
| |
| /** |
| * Apply the plugin |
| * @param {Compiler} compiler Webpack compiler |
| * @returns {void} |
| */ |
| apply(compiler) { |
| const definitions = this.definitions; |
| compiler.hooks.compilation.tap( |
| "DefinePlugin", |
| (compilation, { normalModuleFactory }) => { |
| compilation.dependencyFactories.set(ConstDependency, new NullFactory()); |
| compilation.dependencyTemplates.set( |
| ConstDependency, |
| new ConstDependency.Template() |
| ); |
| |
| /** |
| * Handler |
| * @param {Parser} parser Parser |
| * @returns {void} |
| */ |
| const handler = parser => { |
| /** |
| * Walk definitions |
| * @param {Object} definitions Definitions map |
| * @param {string} prefix Prefix string |
| * @returns {void} |
| */ |
| const walkDefinitions = (definitions, prefix) => { |
| Object.keys(definitions).forEach(key => { |
| const code = definitions[key]; |
| if ( |
| code && |
| typeof code === "object" && |
| !(code instanceof RuntimeValue) && |
| !(code instanceof RegExp) |
| ) { |
| walkDefinitions(code, prefix + key + "."); |
| applyObjectDefine(prefix + key, code); |
| return; |
| } |
| applyDefineKey(prefix, key); |
| applyDefine(prefix + key, code); |
| }); |
| }; |
| |
| /** |
| * Apply define key |
| * @param {string} prefix Prefix |
| * @param {string} key Key |
| * @returns {void} |
| */ |
| const applyDefineKey = (prefix, key) => { |
| const splittedKey = key.split("."); |
| splittedKey.slice(1).forEach((_, i) => { |
| const fullKey = prefix + splittedKey.slice(0, i + 1).join("."); |
| parser.hooks.canRename |
| .for(fullKey) |
| .tap("DefinePlugin", ParserHelpers.approve); |
| }); |
| }; |
| |
| /** |
| * Apply Code |
| * @param {string} key Key |
| * @param {CodeValue} code Code |
| * @returns {void} |
| */ |
| const applyDefine = (key, code) => { |
| const isTypeof = /^typeof\s+/.test(key); |
| if (isTypeof) key = key.replace(/^typeof\s+/, ""); |
| let recurse = false; |
| let recurseTypeof = false; |
| if (!isTypeof) { |
| parser.hooks.canRename |
| .for(key) |
| .tap("DefinePlugin", ParserHelpers.approve); |
| parser.hooks.evaluateIdentifier |
| .for(key) |
| .tap("DefinePlugin", expr => { |
| /** |
| * this is needed in case there is a recursion in the DefinePlugin |
| * to prevent an endless recursion |
| * e.g.: new DefinePlugin({ |
| * "a": "b", |
| * "b": "a" |
| * }); |
| */ |
| if (recurse) return; |
| recurse = true; |
| const res = parser.evaluate(toCode(code, parser)); |
| recurse = false; |
| res.setRange(expr.range); |
| return res; |
| }); |
| parser.hooks.expression.for(key).tap("DefinePlugin", expr => { |
| const strCode = toCode(code, parser); |
| if (/__webpack_require__/.test(strCode)) { |
| return ParserHelpers.toConstantDependencyWithWebpackRequire( |
| parser, |
| strCode |
| )(expr); |
| } else { |
| return ParserHelpers.toConstantDependency(parser, strCode)( |
| expr |
| ); |
| } |
| }); |
| } |
| parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => { |
| /** |
| * this is needed in case there is a recursion in the DefinePlugin |
| * to prevent an endless recursion |
| * e.g.: new DefinePlugin({ |
| * "typeof a": "typeof b", |
| * "typeof b": "typeof a" |
| * }); |
| */ |
| if (recurseTypeof) return; |
| recurseTypeof = true; |
| const typeofCode = isTypeof |
| ? toCode(code, parser) |
| : "typeof (" + toCode(code, parser) + ")"; |
| const res = parser.evaluate(typeofCode); |
| recurseTypeof = false; |
| res.setRange(expr.range); |
| return res; |
| }); |
| parser.hooks.typeof.for(key).tap("DefinePlugin", expr => { |
| const typeofCode = isTypeof |
| ? toCode(code, parser) |
| : "typeof (" + toCode(code, parser) + ")"; |
| const res = parser.evaluate(typeofCode); |
| if (!res.isString()) return; |
| return ParserHelpers.toConstantDependency( |
| parser, |
| JSON.stringify(res.string) |
| ).bind(parser)(expr); |
| }); |
| }; |
| |
| /** |
| * Apply Object |
| * @param {string} key Key |
| * @param {Object} obj Object |
| * @returns {void} |
| */ |
| const applyObjectDefine = (key, obj) => { |
| parser.hooks.canRename |
| .for(key) |
| .tap("DefinePlugin", ParserHelpers.approve); |
| parser.hooks.evaluateIdentifier |
| .for(key) |
| .tap("DefinePlugin", expr => |
| new BasicEvaluatedExpression().setTruthy().setRange(expr.range) |
| ); |
| parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => { |
| return ParserHelpers.evaluateToString("object")(expr); |
| }); |
| parser.hooks.expression.for(key).tap("DefinePlugin", expr => { |
| const strCode = stringifyObj(obj, parser); |
| |
| if (/__webpack_require__/.test(strCode)) { |
| return ParserHelpers.toConstantDependencyWithWebpackRequire( |
| parser, |
| strCode |
| )(expr); |
| } else { |
| return ParserHelpers.toConstantDependency(parser, strCode)( |
| expr |
| ); |
| } |
| }); |
| parser.hooks.typeof.for(key).tap("DefinePlugin", expr => { |
| return ParserHelpers.toConstantDependency( |
| parser, |
| JSON.stringify("object") |
| )(expr); |
| }); |
| }; |
| |
| walkDefinitions(definitions, ""); |
| }; |
| |
| normalModuleFactory.hooks.parser |
| .for("javascript/auto") |
| .tap("DefinePlugin", handler); |
| normalModuleFactory.hooks.parser |
| .for("javascript/dynamic") |
| .tap("DefinePlugin", handler); |
| normalModuleFactory.hooks.parser |
| .for("javascript/esm") |
| .tap("DefinePlugin", handler); |
| } |
| ); |
| } |
| } |
| module.exports = DefinePlugin; |