Projekt

Obecné

Profil

Stáhnout (7.92 KB) Statistiky
| Větev: | Revize:
1
/*
2
	MIT License http://www.opensource.org/licenses/mit-license.php
3
	Author Tobias Koppers @sokra
4
*/
5
"use strict";
6

    
7
const ConstDependency = require("./dependencies/ConstDependency");
8
const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
9
const ParserHelpers = require("./ParserHelpers");
10
const NullFactory = require("./NullFactory");
11

    
12
/** @typedef {import("./Compiler")} Compiler */
13
/** @typedef {import("./Parser")} Parser */
14
/** @typedef {null|undefined|RegExp|Function|string|number} CodeValuePrimitive */
15
/** @typedef {CodeValuePrimitive|Record<string, CodeValuePrimitive>|RuntimeValue} CodeValue */
16

    
17
class RuntimeValue {
18
	constructor(fn, fileDependencies) {
19
		this.fn = fn;
20
		this.fileDependencies = fileDependencies || [];
21
	}
22

    
23
	exec(parser) {
24
		if (this.fileDependencies === true) {
25
			parser.state.module.buildInfo.cacheable = false;
26
		} else {
27
			for (const fileDependency of this.fileDependencies) {
28
				parser.state.module.buildInfo.fileDependencies.add(fileDependency);
29
			}
30
		}
31

    
32
		return this.fn({ module: parser.state.module });
33
	}
34
}
35

    
36
const stringifyObj = (obj, parser) => {
37
	return (
38
		"Object({" +
39
		Object.keys(obj)
40
			.map(key => {
41
				const code = obj[key];
42
				return JSON.stringify(key) + ":" + toCode(code, parser);
43
			})
44
			.join(",") +
45
		"})"
46
	);
47
};
48

    
49
/**
50
 * Convert code to a string that evaluates
51
 * @param {CodeValue} code Code to evaluate
52
 * @param {Parser} parser Parser
53
 * @returns {string} code converted to string that evaluates
54
 */
55
const toCode = (code, parser) => {
56
	if (code === null) {
57
		return "null";
58
	}
59
	if (code === undefined) {
60
		return "undefined";
61
	}
62
	if (code instanceof RuntimeValue) {
63
		return toCode(code.exec(parser), parser);
64
	}
65
	if (code instanceof RegExp && code.toString) {
66
		return code.toString();
67
	}
68
	if (typeof code === "function" && code.toString) {
69
		return "(" + code.toString() + ")";
70
	}
71
	if (typeof code === "object") {
72
		return stringifyObj(code, parser);
73
	}
74
	return code + "";
75
};
76

    
77
class DefinePlugin {
78
	/**
79
	 * Create a new define plugin
80
	 * @param {Record<string, CodeValue>} definitions A map of global object definitions
81
	 */
82
	constructor(definitions) {
83
		this.definitions = definitions;
84
	}
85

    
86
	static runtimeValue(fn, fileDependencies) {
87
		return new RuntimeValue(fn, fileDependencies);
88
	}
89

    
90
	/**
91
	 * Apply the plugin
92
	 * @param {Compiler} compiler Webpack compiler
93
	 * @returns {void}
94
	 */
95
	apply(compiler) {
96
		const definitions = this.definitions;
97
		compiler.hooks.compilation.tap(
98
			"DefinePlugin",
99
			(compilation, { normalModuleFactory }) => {
100
				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
101
				compilation.dependencyTemplates.set(
102
					ConstDependency,
103
					new ConstDependency.Template()
104
				);
105

    
106
				/**
107
				 * Handler
108
				 * @param {Parser} parser Parser
109
				 * @returns {void}
110
				 */
111
				const handler = parser => {
112
					/**
113
					 * Walk definitions
114
					 * @param {Object} definitions Definitions map
115
					 * @param {string} prefix Prefix string
116
					 * @returns {void}
117
					 */
118
					const walkDefinitions = (definitions, prefix) => {
119
						Object.keys(definitions).forEach(key => {
120
							const code = definitions[key];
121
							if (
122
								code &&
123
								typeof code === "object" &&
124
								!(code instanceof RuntimeValue) &&
125
								!(code instanceof RegExp)
126
							) {
127
								walkDefinitions(code, prefix + key + ".");
128
								applyObjectDefine(prefix + key, code);
129
								return;
130
							}
131
							applyDefineKey(prefix, key);
132
							applyDefine(prefix + key, code);
133
						});
134
					};
135

    
136
					/**
137
					 * Apply define key
138
					 * @param {string} prefix Prefix
139
					 * @param {string} key Key
140
					 * @returns {void}
141
					 */
142
					const applyDefineKey = (prefix, key) => {
143
						const splittedKey = key.split(".");
144
						splittedKey.slice(1).forEach((_, i) => {
145
							const fullKey = prefix + splittedKey.slice(0, i + 1).join(".");
146
							parser.hooks.canRename
147
								.for(fullKey)
148
								.tap("DefinePlugin", ParserHelpers.approve);
149
						});
150
					};
151

    
152
					/**
153
					 * Apply Code
154
					 * @param {string} key Key
155
					 * @param {CodeValue} code Code
156
					 * @returns {void}
157
					 */
158
					const applyDefine = (key, code) => {
159
						const isTypeof = /^typeof\s+/.test(key);
160
						if (isTypeof) key = key.replace(/^typeof\s+/, "");
161
						let recurse = false;
162
						let recurseTypeof = false;
163
						if (!isTypeof) {
164
							parser.hooks.canRename
165
								.for(key)
166
								.tap("DefinePlugin", ParserHelpers.approve);
167
							parser.hooks.evaluateIdentifier
168
								.for(key)
169
								.tap("DefinePlugin", expr => {
170
									/**
171
									 * this is needed in case there is a recursion in the DefinePlugin
172
									 * to prevent an endless recursion
173
									 * e.g.: new DefinePlugin({
174
									 * "a": "b",
175
									 * "b": "a"
176
									 * });
177
									 */
178
									if (recurse) return;
179
									recurse = true;
180
									const res = parser.evaluate(toCode(code, parser));
181
									recurse = false;
182
									res.setRange(expr.range);
183
									return res;
184
								});
185
							parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
186
								const strCode = toCode(code, parser);
187
								if (/__webpack_require__/.test(strCode)) {
188
									return ParserHelpers.toConstantDependencyWithWebpackRequire(
189
										parser,
190
										strCode
191
									)(expr);
192
								} else {
193
									return ParserHelpers.toConstantDependency(
194
										parser,
195
										strCode
196
									)(expr);
197
								}
198
							});
199
						}
200
						parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {
201
							/**
202
							 * this is needed in case there is a recursion in the DefinePlugin
203
							 * to prevent an endless recursion
204
							 * e.g.: new DefinePlugin({
205
							 * "typeof a": "typeof b",
206
							 * "typeof b": "typeof a"
207
							 * });
208
							 */
209
							if (recurseTypeof) return;
210
							recurseTypeof = true;
211
							const typeofCode = isTypeof
212
								? toCode(code, parser)
213
								: "typeof (" + toCode(code, parser) + ")";
214
							const res = parser.evaluate(typeofCode);
215
							recurseTypeof = false;
216
							res.setRange(expr.range);
217
							return res;
218
						});
219
						parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {
220
							const typeofCode = isTypeof
221
								? toCode(code, parser)
222
								: "typeof (" + toCode(code, parser) + ")";
223
							const res = parser.evaluate(typeofCode);
224
							if (!res.isString()) return;
225
							return ParserHelpers.toConstantDependency(
226
								parser,
227
								JSON.stringify(res.string)
228
							).bind(parser)(expr);
229
						});
230
					};
231

    
232
					/**
233
					 * Apply Object
234
					 * @param {string} key Key
235
					 * @param {Object} obj Object
236
					 * @returns {void}
237
					 */
238
					const applyObjectDefine = (key, obj) => {
239
						parser.hooks.canRename
240
							.for(key)
241
							.tap("DefinePlugin", ParserHelpers.approve);
242
						parser.hooks.evaluateIdentifier
243
							.for(key)
244
							.tap("DefinePlugin", expr =>
245
								new BasicEvaluatedExpression().setTruthy().setRange(expr.range)
246
							);
247
						parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {
248
							return ParserHelpers.evaluateToString("object")(expr);
249
						});
250
						parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
251
							const strCode = stringifyObj(obj, parser);
252

    
253
							if (/__webpack_require__/.test(strCode)) {
254
								return ParserHelpers.toConstantDependencyWithWebpackRequire(
255
									parser,
256
									strCode
257
								)(expr);
258
							} else {
259
								return ParserHelpers.toConstantDependency(
260
									parser,
261
									strCode
262
								)(expr);
263
							}
264
						});
265
						parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {
266
							return ParserHelpers.toConstantDependency(
267
								parser,
268
								JSON.stringify("object")
269
							)(expr);
270
						});
271
					};
272

    
273
					walkDefinitions(definitions, "");
274
				};
275

    
276
				normalModuleFactory.hooks.parser
277
					.for("javascript/auto")
278
					.tap("DefinePlugin", handler);
279
				normalModuleFactory.hooks.parser
280
					.for("javascript/dynamic")
281
					.tap("DefinePlugin", handler);
282
				normalModuleFactory.hooks.parser
283
					.for("javascript/esm")
284
					.tap("DefinePlugin", handler);
285
			}
286
		);
287
	}
288
}
289
module.exports = DefinePlugin;
(25-25/144)