mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			348 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			348 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
/*
 | 
						|
	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
						|
	Author Tobias Koppers @sokra
 | 
						|
*/
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
const RuntimeGlobals = require("./RuntimeGlobals");
 | 
						|
const ConstDependency = require("./dependencies/ConstDependency");
 | 
						|
const BasicEvaluatedExpression = require("./javascript/BasicEvaluatedExpression");
 | 
						|
const {
 | 
						|
	approve,
 | 
						|
	evaluateToString,
 | 
						|
	toConstantDependency
 | 
						|
} = require("./javascript/JavascriptParserHelpers");
 | 
						|
 | 
						|
/** @typedef {import("estree").Expression} Expression */
 | 
						|
/** @typedef {import("./Compiler")} Compiler */
 | 
						|
/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
 | 
						|
/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
 | 
						|
 | 
						|
/** @typedef {null|undefined|RegExp|Function|string|number|boolean|bigint|undefined} CodeValuePrimitive */
 | 
						|
/** @typedef {RecursiveArrayOrRecord<CodeValuePrimitive|RuntimeValue>} CodeValue */
 | 
						|
 | 
						|
class RuntimeValue {
 | 
						|
	constructor(fn, fileDependencies) {
 | 
						|
		this.fn = fn;
 | 
						|
		this.fileDependencies = fileDependencies || [];
 | 
						|
	}
 | 
						|
 | 
						|
	exec(parser) {
 | 
						|
		const buildInfo = parser.state.module.buildInfo;
 | 
						|
		if (this.fileDependencies === true) {
 | 
						|
			buildInfo.cacheable = false;
 | 
						|
		} else {
 | 
						|
			for (const fileDependency of this.fileDependencies) {
 | 
						|
				buildInfo.fileDependencies.add(fileDependency);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return this.fn({ module: parser.state.module });
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {any[]|{[k: string]: any}} obj obj
 | 
						|
 * @param {JavascriptParser} parser Parser
 | 
						|
 * @param {RuntimeTemplate} runtimeTemplate the runtime template
 | 
						|
 * @param {boolean|undefined|null=} asiSafe asi safe (undefined: unknown, null: unneeded)
 | 
						|
 * @returns {string} code converted to string that evaluates
 | 
						|
 */
 | 
						|
const stringifyObj = (obj, parser, runtimeTemplate, asiSafe) => {
 | 
						|
	let code;
 | 
						|
	let arr = Array.isArray(obj);
 | 
						|
	if (arr) {
 | 
						|
		code = `[${obj
 | 
						|
			.map(code => toCode(code, parser, runtimeTemplate, null))
 | 
						|
			.join(",")}]`;
 | 
						|
	} else {
 | 
						|
		code = `{${Object.keys(obj)
 | 
						|
			.map(key => {
 | 
						|
				const code = obj[key];
 | 
						|
				return (
 | 
						|
					JSON.stringify(key) +
 | 
						|
					":" +
 | 
						|
					toCode(code, parser, runtimeTemplate, null)
 | 
						|
				);
 | 
						|
			})
 | 
						|
			.join(",")}}`;
 | 
						|
	}
 | 
						|
 | 
						|
	switch (asiSafe) {
 | 
						|
		case null:
 | 
						|
			return code;
 | 
						|
		case true:
 | 
						|
			return arr ? code : `(${code})`;
 | 
						|
		case false:
 | 
						|
			return arr ? `;${code}` : `;(${code})`;
 | 
						|
		default:
 | 
						|
			return `Object(${code})`;
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Convert code to a string that evaluates
 | 
						|
 * @param {CodeValue} code Code to evaluate
 | 
						|
 * @param {JavascriptParser} parser Parser
 | 
						|
 * @param {RuntimeTemplate} runtimeTemplate the runtime template
 | 
						|
 * @param {boolean|undefined|null=} asiSafe asi safe (undefined: unknown, null: unneeded)
 | 
						|
 * @returns {string} code converted to string that evaluates
 | 
						|
 */
 | 
						|
const toCode = (code, parser, runtimeTemplate, asiSafe) => {
 | 
						|
	if (code === null) {
 | 
						|
		return "null";
 | 
						|
	}
 | 
						|
	if (code === undefined) {
 | 
						|
		return "undefined";
 | 
						|
	}
 | 
						|
	if (Object.is(code, -0)) {
 | 
						|
		return "-0";
 | 
						|
	}
 | 
						|
	if (code instanceof RuntimeValue) {
 | 
						|
		return toCode(code.exec(parser), parser, runtimeTemplate, asiSafe);
 | 
						|
	}
 | 
						|
	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, runtimeTemplate, asiSafe);
 | 
						|
	}
 | 
						|
	if (typeof code === "bigint") {
 | 
						|
		return runtimeTemplate.supportsBigIntLiteral()
 | 
						|
			? `${code}n`
 | 
						|
			: `BigInt("${code}")`;
 | 
						|
	}
 | 
						|
	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 the compiler instance
 | 
						|
	 * @returns {void}
 | 
						|
	 */
 | 
						|
	apply(compiler) {
 | 
						|
		const definitions = this.definitions;
 | 
						|
		compiler.hooks.compilation.tap(
 | 
						|
			"DefinePlugin",
 | 
						|
			(compilation, { normalModuleFactory }) => {
 | 
						|
				compilation.dependencyTemplates.set(
 | 
						|
					ConstDependency,
 | 
						|
					new ConstDependency.Template()
 | 
						|
				);
 | 
						|
				const { runtimeTemplate } = compilation;
 | 
						|
 | 
						|
				/**
 | 
						|
				 * Handler
 | 
						|
				 * @param {JavascriptParser} 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", 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", 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, runtimeTemplate, null)
 | 
						|
									);
 | 
						|
									recurse = false;
 | 
						|
									res.setRange(expr.range);
 | 
						|
									return res;
 | 
						|
								});
 | 
						|
							parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
 | 
						|
								const strCode = toCode(
 | 
						|
									code,
 | 
						|
									parser,
 | 
						|
									runtimeTemplate,
 | 
						|
									!parser.isAsiPosition(expr.range[0])
 | 
						|
								);
 | 
						|
								if (/__webpack_require__\s*(!?\.)/.test(strCode)) {
 | 
						|
									return toConstantDependency(parser, strCode, [
 | 
						|
										RuntimeGlobals.require
 | 
						|
									])(expr);
 | 
						|
								} else if (/__webpack_require__/.test(strCode)) {
 | 
						|
									return toConstantDependency(parser, strCode, [
 | 
						|
										RuntimeGlobals.requireScope
 | 
						|
									])(expr);
 | 
						|
								} else {
 | 
						|
									return 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, runtimeTemplate, null)
 | 
						|
								: "typeof (" +
 | 
						|
								  toCode(code, parser, runtimeTemplate, null) +
 | 
						|
								  ")";
 | 
						|
							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, runtimeTemplate, null)
 | 
						|
								: "typeof (" +
 | 
						|
								  toCode(code, parser, runtimeTemplate, null) +
 | 
						|
								  ")";
 | 
						|
							const res = parser.evaluate(typeofCode);
 | 
						|
							if (!res.isString()) return;
 | 
						|
							return 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", approve);
 | 
						|
						parser.hooks.evaluateIdentifier
 | 
						|
							.for(key)
 | 
						|
							.tap("DefinePlugin", expr =>
 | 
						|
								new BasicEvaluatedExpression()
 | 
						|
									.setTruthy()
 | 
						|
									.setSideEffects(false)
 | 
						|
									.setRange(expr.range)
 | 
						|
							);
 | 
						|
						parser.hooks.evaluateTypeof
 | 
						|
							.for(key)
 | 
						|
							.tap("DefinePlugin", evaluateToString("object"));
 | 
						|
						parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
 | 
						|
							const strCode = stringifyObj(
 | 
						|
								obj,
 | 
						|
								parser,
 | 
						|
								runtimeTemplate,
 | 
						|
								!parser.isAsiPosition(expr.range[0])
 | 
						|
							);
 | 
						|
 | 
						|
							if (/__webpack_require__\s*(!?\.)/.test(strCode)) {
 | 
						|
								return toConstantDependency(parser, strCode, [
 | 
						|
									RuntimeGlobals.require
 | 
						|
								])(expr);
 | 
						|
							} else if (/__webpack_require__/.test(strCode)) {
 | 
						|
								return toConstantDependency(parser, strCode, [
 | 
						|
									RuntimeGlobals.requireScope
 | 
						|
								])(expr);
 | 
						|
							} else {
 | 
						|
								return toConstantDependency(parser, strCode)(expr);
 | 
						|
							}
 | 
						|
						});
 | 
						|
						parser.hooks.typeof
 | 
						|
							.for(key)
 | 
						|
							.tap(
 | 
						|
								"DefinePlugin",
 | 
						|
								toConstantDependency(parser, JSON.stringify("object"))
 | 
						|
							);
 | 
						|
					};
 | 
						|
 | 
						|
					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;
 |