Projekt

Obecné

Profil

Stáhnout (9.79 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
const ConstDependency = require("./dependencies/ConstDependency");
7
const NullFactory = require("./NullFactory");
8
const ParserHelpers = require("./ParserHelpers");
9

    
10
const getQuery = request => {
11
	const i = request.indexOf("?");
12
	return i !== -1 ? request.substr(i) : "";
13
};
14

    
15
const collectDeclaration = (declarations, pattern) => {
16
	const stack = [pattern];
17
	while (stack.length > 0) {
18
		const node = stack.pop();
19
		switch (node.type) {
20
			case "Identifier":
21
				declarations.add(node.name);
22
				break;
23
			case "ArrayPattern":
24
				for (const element of node.elements) {
25
					if (element) {
26
						stack.push(element);
27
					}
28
				}
29
				break;
30
			case "AssignmentPattern":
31
				stack.push(node.left);
32
				break;
33
			case "ObjectPattern":
34
				for (const property of node.properties) {
35
					stack.push(property.value);
36
				}
37
				break;
38
			case "RestElement":
39
				stack.push(node.argument);
40
				break;
41
		}
42
	}
43
};
44

    
45
const getHoistedDeclarations = (branch, includeFunctionDeclarations) => {
46
	const declarations = new Set();
47
	const stack = [branch];
48
	while (stack.length > 0) {
49
		const node = stack.pop();
50
		// Some node could be `null` or `undefined`.
51
		if (!node) continue;
52
		switch (node.type) {
53
			// Walk through control statements to look for hoisted declarations.
54
			// Some branches are skipped since they do not allow declarations.
55
			case "BlockStatement":
56
				for (const stmt of node.body) {
57
					stack.push(stmt);
58
				}
59
				break;
60
			case "IfStatement":
61
				stack.push(node.consequent);
62
				stack.push(node.alternate);
63
				break;
64
			case "ForStatement":
65
				stack.push(node.init);
66
				stack.push(node.body);
67
				break;
68
			case "ForInStatement":
69
			case "ForOfStatement":
70
				stack.push(node.left);
71
				stack.push(node.body);
72
				break;
73
			case "DoWhileStatement":
74
			case "WhileStatement":
75
			case "LabeledStatement":
76
				stack.push(node.body);
77
				break;
78
			case "SwitchStatement":
79
				for (const cs of node.cases) {
80
					for (const consequent of cs.consequent) {
81
						stack.push(consequent);
82
					}
83
				}
84
				break;
85
			case "TryStatement":
86
				stack.push(node.block);
87
				if (node.handler) {
88
					stack.push(node.handler.body);
89
				}
90
				stack.push(node.finalizer);
91
				break;
92
			case "FunctionDeclaration":
93
				if (includeFunctionDeclarations) {
94
					collectDeclaration(declarations, node.id);
95
				}
96
				break;
97
			case "VariableDeclaration":
98
				if (node.kind === "var") {
99
					for (const decl of node.declarations) {
100
						collectDeclaration(declarations, decl.id);
101
					}
102
				}
103
				break;
104
		}
105
	}
106
	return Array.from(declarations);
107
};
108

    
109
class ConstPlugin {
110
	apply(compiler) {
111
		compiler.hooks.compilation.tap(
112
			"ConstPlugin",
113
			(compilation, { normalModuleFactory }) => {
114
				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
115
				compilation.dependencyTemplates.set(
116
					ConstDependency,
117
					new ConstDependency.Template()
118
				);
119

    
120
				const handler = parser => {
121
					parser.hooks.statementIf.tap("ConstPlugin", statement => {
122
						if (parser.scope.isAsmJs) return;
123
						const param = parser.evaluateExpression(statement.test);
124
						const bool = param.asBool();
125
						if (typeof bool === "boolean") {
126
							if (statement.test.type !== "Literal") {
127
								const dep = new ConstDependency(`${bool}`, param.range);
128
								dep.loc = statement.loc;
129
								parser.state.current.addDependency(dep);
130
							}
131
							const branchToRemove = bool
132
								? statement.alternate
133
								: statement.consequent;
134
							if (branchToRemove) {
135
								// Before removing the dead branch, the hoisted declarations
136
								// must be collected.
137
								//
138
								// Given the following code:
139
								//
140
								//     if (true) f() else g()
141
								//     if (false) {
142
								//       function f() {}
143
								//       const g = function g() {}
144
								//       if (someTest) {
145
								//         let a = 1
146
								//         var x, {y, z} = obj
147
								//       }
148
								//     } else {
149
								//       …
150
								//     }
151
								//
152
								// the generated code is:
153
								//
154
								//     if (true) f() else {}
155
								//     if (false) {
156
								//       var f, x, y, z;   (in loose mode)
157
								//       var x, y, z;      (in strict mode)
158
								//     } else {
159
								//       …
160
								//     }
161
								//
162
								// NOTE: When code runs in strict mode, `var` declarations
163
								// are hoisted but `function` declarations don't.
164
								//
165
								let declarations;
166
								if (parser.scope.isStrict) {
167
									// If the code runs in strict mode, variable declarations
168
									// using `var` must be hoisted.
169
									declarations = getHoistedDeclarations(branchToRemove, false);
170
								} else {
171
									// Otherwise, collect all hoisted declaration.
172
									declarations = getHoistedDeclarations(branchToRemove, true);
173
								}
174
								let replacement;
175
								if (declarations.length > 0) {
176
									replacement = `{ var ${declarations.join(", ")}; }`;
177
								} else {
178
									replacement = "{}";
179
								}
180
								const dep = new ConstDependency(
181
									replacement,
182
									branchToRemove.range
183
								);
184
								dep.loc = branchToRemove.loc;
185
								parser.state.current.addDependency(dep);
186
							}
187
							return bool;
188
						}
189
					});
190
					parser.hooks.expressionConditionalOperator.tap(
191
						"ConstPlugin",
192
						expression => {
193
							if (parser.scope.isAsmJs) return;
194
							const param = parser.evaluateExpression(expression.test);
195
							const bool = param.asBool();
196
							if (typeof bool === "boolean") {
197
								if (expression.test.type !== "Literal") {
198
									const dep = new ConstDependency(` ${bool}`, param.range);
199
									dep.loc = expression.loc;
200
									parser.state.current.addDependency(dep);
201
								}
202
								// Expressions do not hoist.
203
								// It is safe to remove the dead branch.
204
								//
205
								// Given the following code:
206
								//
207
								//   false ? someExpression() : otherExpression();
208
								//
209
								// the generated code is:
210
								//
211
								//   false ? undefined : otherExpression();
212
								//
213
								const branchToRemove = bool
214
									? expression.alternate
215
									: expression.consequent;
216
								const dep = new ConstDependency(
217
									"undefined",
218
									branchToRemove.range
219
								);
220
								dep.loc = branchToRemove.loc;
221
								parser.state.current.addDependency(dep);
222
								return bool;
223
							}
224
						}
225
					);
226
					parser.hooks.expressionLogicalOperator.tap(
227
						"ConstPlugin",
228
						expression => {
229
							if (parser.scope.isAsmJs) return;
230
							if (
231
								expression.operator === "&&" ||
232
								expression.operator === "||"
233
							) {
234
								const param = parser.evaluateExpression(expression.left);
235
								const bool = param.asBool();
236
								if (typeof bool === "boolean") {
237
									// Expressions do not hoist.
238
									// It is safe to remove the dead branch.
239
									//
240
									// ------------------------------------------
241
									//
242
									// Given the following code:
243
									//
244
									//   falsyExpression() && someExpression();
245
									//
246
									// the generated code is:
247
									//
248
									//   falsyExpression() && false;
249
									//
250
									// ------------------------------------------
251
									//
252
									// Given the following code:
253
									//
254
									//   truthyExpression() && someExpression();
255
									//
256
									// the generated code is:
257
									//
258
									//   true && someExpression();
259
									//
260
									// ------------------------------------------
261
									//
262
									// Given the following code:
263
									//
264
									//   truthyExpression() || someExpression();
265
									//
266
									// the generated code is:
267
									//
268
									//   truthyExpression() || false;
269
									//
270
									// ------------------------------------------
271
									//
272
									// Given the following code:
273
									//
274
									//   falsyExpression() || someExpression();
275
									//
276
									// the generated code is:
277
									//
278
									//   false && someExpression();
279
									//
280
									const keepRight =
281
										(expression.operator === "&&" && bool) ||
282
										(expression.operator === "||" && !bool);
283

    
284
									if (param.isBoolean() || keepRight) {
285
										// for case like
286
										//
287
										//   return'development'===process.env.NODE_ENV&&'foo'
288
										//
289
										// we need a space before the bool to prevent result like
290
										//
291
										//   returnfalse&&'foo'
292
										//
293
										const dep = new ConstDependency(` ${bool}`, param.range);
294
										dep.loc = expression.loc;
295
										parser.state.current.addDependency(dep);
296
									} else {
297
										parser.walkExpression(expression.left);
298
									}
299
									if (!keepRight) {
300
										const dep = new ConstDependency(
301
											"false",
302
											expression.right.range
303
										);
304
										dep.loc = expression.loc;
305
										parser.state.current.addDependency(dep);
306
									}
307
									return keepRight;
308
								}
309
							}
310
						}
311
					);
312
					parser.hooks.evaluateIdentifier
313
						.for("__resourceQuery")
314
						.tap("ConstPlugin", expr => {
315
							if (parser.scope.isAsmJs) return;
316
							if (!parser.state.module) return;
317
							return ParserHelpers.evaluateToString(
318
								getQuery(parser.state.module.resource)
319
							)(expr);
320
						});
321
					parser.hooks.expression
322
						.for("__resourceQuery")
323
						.tap("ConstPlugin", () => {
324
							if (parser.scope.isAsmJs) return;
325
							if (!parser.state.module) return;
326
							parser.state.current.addVariable(
327
								"__resourceQuery",
328
								JSON.stringify(getQuery(parser.state.module.resource))
329
							);
330
							return true;
331
						});
332
				};
333

    
334
				normalModuleFactory.hooks.parser
335
					.for("javascript/auto")
336
					.tap("ConstPlugin", handler);
337
				normalModuleFactory.hooks.parser
338
					.for("javascript/dynamic")
339
					.tap("ConstPlugin", handler);
340
				normalModuleFactory.hooks.parser
341
					.for("javascript/esm")
342
					.tap("ConstPlugin", handler);
343
			}
344
		);
345
	}
346
}
347

    
348
module.exports = ConstPlugin;
(21-21/145)