| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601 | /*	MIT License http://www.opensource.org/licenses/mit-license.php	Author Tobias Koppers @sokra*/"use strict";const RuntimeGlobals = require("./RuntimeGlobals");const WebpackError = require("./WebpackError");const ConstDependency = require("./dependencies/ConstDependency");const BasicEvaluatedExpression = require("./javascript/BasicEvaluatedExpression");const {	evaluateToString,	toConstantDependency} = require("./javascript/JavascriptParserHelpers");const createHash = require("./util/createHash");/** @typedef {import("estree").Expression} Expression *//** @typedef {import("./Compiler")} Compiler *//** @typedef {import("./NormalModule")} NormalModule *//** @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 *//** * @typedef {Object} RuntimeValueOptions * @property {string[]=} fileDependencies * @property {string[]=} contextDependencies * @property {string[]=} missingDependencies * @property {string[]=} buildDependencies * @property {string|function(): string=} version */class RuntimeValue {	/**	 * @param {function({ module: NormalModule, key: string, readonly version: string | undefined }): CodeValuePrimitive} fn generator function	 * @param {true | string[] | RuntimeValueOptions=} options options	 */	constructor(fn, options) {		this.fn = fn;		if (Array.isArray(options)) {			options = {				fileDependencies: options			};		}		this.options = options || {};	}	get fileDependencies() {		return this.options === true ? true : this.options.fileDependencies;	}	/**	 * @param {JavascriptParser} parser the parser	 * @param {Map<string, string | Set<string>>} valueCacheVersions valueCacheVersions	 * @param {string} key the defined key	 * @returns {CodeValuePrimitive} code	 */	exec(parser, valueCacheVersions, key) {		const buildInfo = parser.state.module.buildInfo;		if (this.options === true) {			buildInfo.cacheable = false;		} else {			if (this.options.fileDependencies) {				for (const dep of this.options.fileDependencies) {					buildInfo.fileDependencies.add(dep);				}			}			if (this.options.contextDependencies) {				for (const dep of this.options.contextDependencies) {					buildInfo.contextDependencies.add(dep);				}			}			if (this.options.missingDependencies) {				for (const dep of this.options.missingDependencies) {					buildInfo.missingDependencies.add(dep);				}			}			if (this.options.buildDependencies) {				for (const dep of this.options.buildDependencies) {					buildInfo.buildDependencies.add(dep);				}			}		}		return this.fn({			module: parser.state.module,			key,			get version() {				return /** @type {string} */ (					valueCacheVersions.get(VALUE_DEP_PREFIX + key)				);			}		});	}	getCacheVersion() {		return this.options === true			? undefined			: (typeof this.options.version === "function"					? this.options.version()					: this.options.version) || "unset";	}}/** * @param {any[]|{[k: string]: any}} obj obj * @param {JavascriptParser} parser Parser * @param {Map<string, string | Set<string>>} valueCacheVersions valueCacheVersions * @param {string} key the defined key * @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,	valueCacheVersions,	key,	runtimeTemplate,	asiSafe) => {	let code;	let arr = Array.isArray(obj);	if (arr) {		code = `[${obj			.map(code =>				toCode(code, parser, valueCacheVersions, key, runtimeTemplate, null)			)			.join(",")}]`;	} else {		code = `{${Object.keys(obj)			.map(key => {				const code = obj[key];				return (					JSON.stringify(key) +					":" +					toCode(code, parser, valueCacheVersions, key, runtimeTemplate, null)				);			})			.join(",")}}`;	}	switch (asiSafe) {		case null:			return code;		case true:			return arr ? code : `(${code})`;		case false:			return arr ? `;${code}` : `;(${code})`;		default:			return `/*#__PURE__*/Object(${code})`;	}};/** * Convert code to a string that evaluates * @param {CodeValue} code Code to evaluate * @param {JavascriptParser} parser Parser * @param {Map<string, string | Set<string>>} valueCacheVersions valueCacheVersions * @param {string} key the defined key * @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,	valueCacheVersions,	key,	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, valueCacheVersions, key),			parser,			valueCacheVersions,			key,			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,			valueCacheVersions,			key,			runtimeTemplate,			asiSafe		);	}	if (typeof code === "bigint") {		return runtimeTemplate.supportsBigIntLiteral()			? `${code}n`			: `BigInt("${code}")`;	}	return code + "";};const toCacheVersion = code => {	if (code === null) {		return "null";	}	if (code === undefined) {		return "undefined";	}	if (Object.is(code, -0)) {		return "-0";	}	if (code instanceof RuntimeValue) {		return code.getCacheVersion();	}	if (code instanceof RegExp && code.toString) {		return code.toString();	}	if (typeof code === "function" && code.toString) {		return "(" + code.toString() + ")";	}	if (typeof code === "object") {		const items = Object.keys(code).map(key => ({			key,			value: toCacheVersion(code[key])		}));		if (items.some(({ value }) => value === undefined)) return undefined;		return `{${items.map(({ key, value }) => `${key}: ${value}`).join(", ")}}`;	}	if (typeof code === "bigint") {		return `${code}n`;	}	return code + "";};const VALUE_DEP_PREFIX = "webpack/DefinePlugin ";const VALUE_DEP_MAIN = "webpack/DefinePlugin_hash";class DefinePlugin {	/**	 * Create a new define plugin	 * @param {Record<string, CodeValue>} definitions A map of global object definitions	 */	constructor(definitions) {		this.definitions = definitions;	}	/**	 * @param {function({ module: NormalModule, key: string, readonly version: string | undefined }): CodeValuePrimitive} fn generator function	 * @param {true | string[] | RuntimeValueOptions=} options options	 * @returns {RuntimeValue} runtime value	 */	static runtimeValue(fn, options) {		return new RuntimeValue(fn, options);	}	/**	 * 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;				const mainHash = createHash(compilation.outputOptions.hashFunction);				mainHash.update(					/** @type {string} */ (						compilation.valueCacheVersions.get(VALUE_DEP_MAIN)					) || ""				);				/**				 * Handler				 * @param {JavascriptParser} parser Parser				 * @returns {void}				 */				const handler = parser => {					const mainValue = compilation.valueCacheVersions.get(VALUE_DEP_MAIN);					parser.hooks.program.tap("DefinePlugin", () => {						const { buildInfo } = parser.state.module;						if (!buildInfo.valueDependencies)							buildInfo.valueDependencies = new Map();						buildInfo.valueDependencies.set(VALUE_DEP_MAIN, mainValue);					});					const addValueDependency = key => {						const { buildInfo } = parser.state.module;						buildInfo.valueDependencies.set(							VALUE_DEP_PREFIX + key,							compilation.valueCacheVersions.get(VALUE_DEP_PREFIX + key)						);					};					const withValueDependency =						(key, fn) =>						(...args) => {							addValueDependency(key);							return fn(...args);						};					/**					 * 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", () => {								addValueDependency(key);								return true;							});						});					};					/**					 * Apply Code					 * @param {string} key Key					 * @param {CodeValue} code Code					 * @returns {void}					 */					const applyDefine = (key, code) => {						const originalKey = key;						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", () => {								addValueDependency(originalKey);								return true;							});							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;									addValueDependency(originalKey);									recurse = true;									const res = parser.evaluate(										toCode(											code,											parser,											compilation.valueCacheVersions,											key,											runtimeTemplate,											null										)									);									recurse = false;									res.setRange(expr.range);									return res;								});							parser.hooks.expression.for(key).tap("DefinePlugin", expr => {								addValueDependency(originalKey);								const strCode = toCode(									code,									parser,									compilation.valueCacheVersions,									originalKey,									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;							addValueDependency(originalKey);							const codeCode = toCode(								code,								parser,								compilation.valueCacheVersions,								originalKey,								runtimeTemplate,								null							);							const typeofCode = isTypeof								? codeCode								: "typeof (" + codeCode + ")";							const res = parser.evaluate(typeofCode);							recurseTypeof = false;							res.setRange(expr.range);							return res;						});						parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {							addValueDependency(originalKey);							const codeCode = toCode(								code,								parser,								compilation.valueCacheVersions,								originalKey,								runtimeTemplate,								null							);							const typeofCode = isTypeof								? codeCode								: "typeof (" + codeCode + ")";							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", () => {							addValueDependency(key);							return true;						});						parser.hooks.evaluateIdentifier							.for(key)							.tap("DefinePlugin", expr => {								addValueDependency(key);								return new BasicEvaluatedExpression()									.setTruthy()									.setSideEffects(false)									.setRange(expr.range);							});						parser.hooks.evaluateTypeof							.for(key)							.tap(								"DefinePlugin",								withValueDependency(key, evaluateToString("object"))							);						parser.hooks.expression.for(key).tap("DefinePlugin", expr => {							addValueDependency(key);							const strCode = stringifyObj(								obj,								parser,								compilation.valueCacheVersions,								key,								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",								withValueDependency(									key,									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);				/**				 * Walk definitions				 * @param {Object} definitions Definitions map				 * @param {string} prefix Prefix string				 * @returns {void}				 */				const walkDefinitionsForValues = (definitions, prefix) => {					Object.keys(definitions).forEach(key => {						const code = definitions[key];						const version = toCacheVersion(code);						const name = VALUE_DEP_PREFIX + prefix + key;						mainHash.update("|" + prefix + key);						const oldVersion = compilation.valueCacheVersions.get(name);						if (oldVersion === undefined) {							compilation.valueCacheVersions.set(name, version);						} else if (oldVersion !== version) {							const warning = new WebpackError(								`DefinePlugin\nConflicting values for '${prefix + key}'`							);							warning.details = `'${oldVersion}' !== '${version}'`;							warning.hideStack = true;							compilation.warnings.push(warning);						}						if (							code &&							typeof code === "object" &&							!(code instanceof RuntimeValue) &&							!(code instanceof RegExp)						) {							walkDefinitionsForValues(code, prefix + key + ".");						}					});				};				walkDefinitionsForValues(definitions, "");				compilation.valueCacheVersions.set(					VALUE_DEP_MAIN,					/** @type {string} */ (mainHash.digest("hex").slice(0, 8))				);			}		);	}}module.exports = DefinePlugin;
 |