source: frontend/node_modules/webpack/lib/ConstPlugin.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 17.1 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const {
9 JAVASCRIPT_MODULE_TYPE_AUTO,
10 JAVASCRIPT_MODULE_TYPE_DYNAMIC,
11 JAVASCRIPT_MODULE_TYPE_ESM
12} = require("./ModuleTypeConstants");
13const CachedConstDependency = require("./dependencies/CachedConstDependency");
14const ConstDependency = require("./dependencies/ConstDependency");
15const { evaluateToString } = require("./javascript/JavascriptParserHelpers");
16const { parseResource } = require("./util/identifier");
17
18/** @typedef {import("estree").AssignmentProperty} AssignmentProperty */
19/** @typedef {import("estree").Expression} Expression */
20/** @typedef {import("estree").Identifier} Identifier */
21/** @typedef {import("estree").Pattern} Pattern */
22/** @typedef {import("estree").SourceLocation} SourceLocation */
23/** @typedef {import("estree").Statement} Statement */
24/** @typedef {import("estree").Super} Super */
25/** @typedef {import("estree").VariableDeclaration} VariableDeclaration */
26/** @typedef {import("./Compiler")} Compiler */
27/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
28/** @typedef {import("./javascript/JavascriptParser").Range} Range */
29
30/** @typedef {Set<string>} Declarations */
31
32/**
33 * Collect declaration.
34 * @param {Declarations} declarations set of declarations
35 * @param {Identifier | Pattern} pattern pattern to collect declarations from
36 */
37const collectDeclaration = (declarations, pattern) => {
38 const stack = [pattern];
39 while (stack.length > 0) {
40 const node = /** @type {Pattern} */ (stack.pop());
41 switch (node.type) {
42 case "Identifier":
43 declarations.add(node.name);
44 break;
45 case "ArrayPattern":
46 for (const element of node.elements) {
47 if (element) {
48 stack.push(element);
49 }
50 }
51 break;
52 case "AssignmentPattern":
53 stack.push(node.left);
54 break;
55 case "ObjectPattern":
56 for (const property of node.properties) {
57 stack.push(/** @type {AssignmentProperty} */ (property).value);
58 }
59 break;
60 case "RestElement":
61 stack.push(node.argument);
62 break;
63 }
64 }
65};
66
67/**
68 * Gets hoisted declarations.
69 * @param {Statement} branch branch to get hoisted declarations from
70 * @param {boolean} includeFunctionDeclarations whether to include function declarations
71 * @returns {string[]} hoisted declarations
72 */
73const getHoistedDeclarations = (branch, includeFunctionDeclarations) => {
74 /** @type {Declarations} */
75 const declarations = new Set();
76 /** @type {(Statement | null | undefined)[]} */
77 const stack = [branch];
78 while (stack.length > 0) {
79 const node = stack.pop();
80 // Some node could be `null` or `undefined`.
81 if (!node) continue;
82 switch (node.type) {
83 // Walk through control statements to look for hoisted declarations.
84 // Some branches are skipped since they do not allow declarations.
85 case "BlockStatement":
86 for (const stmt of node.body) {
87 stack.push(stmt);
88 }
89 break;
90 case "IfStatement":
91 stack.push(node.consequent);
92 stack.push(node.alternate);
93 break;
94 case "ForStatement":
95 stack.push(/** @type {VariableDeclaration} */ (node.init));
96 stack.push(node.body);
97 break;
98 case "ForInStatement":
99 case "ForOfStatement":
100 stack.push(/** @type {VariableDeclaration} */ (node.left));
101 stack.push(node.body);
102 break;
103 case "DoWhileStatement":
104 case "WhileStatement":
105 case "LabeledStatement":
106 stack.push(node.body);
107 break;
108 case "SwitchStatement":
109 for (const cs of node.cases) {
110 for (const consequent of cs.consequent) {
111 stack.push(consequent);
112 }
113 }
114 break;
115 case "TryStatement":
116 stack.push(node.block);
117 if (node.handler) {
118 stack.push(node.handler.body);
119 }
120 stack.push(node.finalizer);
121 break;
122 case "FunctionDeclaration":
123 if (includeFunctionDeclarations) {
124 collectDeclaration(declarations, /** @type {Identifier} */ (node.id));
125 }
126 break;
127 case "VariableDeclaration":
128 if (node.kind === "var") {
129 for (const decl of node.declarations) {
130 collectDeclaration(declarations, decl.id);
131 }
132 }
133 break;
134 }
135 }
136 return [...declarations];
137};
138
139const PLUGIN_NAME = "ConstPlugin";
140
141class ConstPlugin {
142 /**
143 * Applies the plugin by registering its hooks on the compiler.
144 * @param {Compiler} compiler the compiler instance
145 * @returns {void}
146 */
147 apply(compiler) {
148 const cachedParseResource = parseResource.bindCache(compiler.root);
149 compiler.hooks.compilation.tap(
150 PLUGIN_NAME,
151 (compilation, { normalModuleFactory }) => {
152 compilation.dependencyTemplates.set(
153 ConstDependency,
154 new ConstDependency.Template()
155 );
156
157 compilation.dependencyTemplates.set(
158 CachedConstDependency,
159 new CachedConstDependency.Template()
160 );
161
162 /**
163 * Handles the hook callback for this code path.
164 * @param {JavascriptParser} parser the parser
165 */
166 const handler = (parser) => {
167 parser.hooks.terminate.tap(PLUGIN_NAME, (_statement) => true);
168 parser.hooks.statementIf.tap(PLUGIN_NAME, (statement) => {
169 if (parser.scope.isAsmJs) return;
170 const param = parser.evaluateExpression(statement.test);
171 const bool = param.asBool();
172 if (typeof bool === "boolean") {
173 if (!param.couldHaveSideEffects()) {
174 const dep = new ConstDependency(
175 `${bool}`,
176 /** @type {Range} */ (param.range)
177 );
178 dep.loc = /** @type {SourceLocation} */ (statement.loc);
179 parser.state.module.addPresentationalDependency(dep);
180 } else {
181 parser.walkExpression(statement.test);
182 }
183 const branchToRemove = bool
184 ? statement.alternate
185 : statement.consequent;
186 if (branchToRemove) {
187 this.eliminateUnusedStatement(parser, branchToRemove, true);
188 }
189 return bool;
190 }
191 });
192 parser.hooks.unusedStatement.tap(PLUGIN_NAME, (statement) => {
193 if (
194 parser.scope.isAsmJs ||
195 // Check top level scope here again
196 parser.scope.topLevelScope === true
197 ) {
198 return;
199 }
200 this.eliminateUnusedStatement(parser, statement, false);
201 return true;
202 });
203 parser.hooks.expressionConditionalOperator.tap(
204 PLUGIN_NAME,
205 (expression) => {
206 if (parser.scope.isAsmJs) return;
207 const param = parser.evaluateExpression(expression.test);
208 const bool = param.asBool();
209 if (typeof bool === "boolean") {
210 if (!param.couldHaveSideEffects()) {
211 const dep = new ConstDependency(
212 ` ${bool}`,
213 /** @type {Range} */ (param.range)
214 );
215 dep.loc = /** @type {SourceLocation} */ (expression.loc);
216 parser.state.module.addPresentationalDependency(dep);
217 } else {
218 parser.walkExpression(expression.test);
219 }
220 // Expressions do not hoist.
221 // It is safe to remove the dead branch.
222 //
223 // Given the following code:
224 //
225 // false ? someExpression() : otherExpression();
226 //
227 // the generated code is:
228 //
229 // false ? 0 : otherExpression();
230 //
231 const branchToRemove = bool
232 ? expression.alternate
233 : expression.consequent;
234 const dep = new ConstDependency(
235 "0",
236 /** @type {Range} */ (branchToRemove.range)
237 );
238 dep.loc = /** @type {SourceLocation} */ (branchToRemove.loc);
239 parser.state.module.addPresentationalDependency(dep);
240 return bool;
241 }
242 }
243 );
244 parser.hooks.expressionLogicalOperator.tap(
245 PLUGIN_NAME,
246 (expression) => {
247 if (parser.scope.isAsmJs) return;
248 if (
249 expression.operator === "&&" ||
250 expression.operator === "||"
251 ) {
252 const param = parser.evaluateExpression(expression.left);
253 const bool = param.asBool();
254 if (typeof bool === "boolean") {
255 // Expressions do not hoist.
256 // It is safe to remove the dead branch.
257 //
258 // ------------------------------------------
259 //
260 // Given the following code:
261 //
262 // falsyExpression() && someExpression();
263 //
264 // the generated code is:
265 //
266 // falsyExpression() && false;
267 //
268 // ------------------------------------------
269 //
270 // Given the following code:
271 //
272 // truthyExpression() && someExpression();
273 //
274 // the generated code is:
275 //
276 // true && someExpression();
277 //
278 // ------------------------------------------
279 //
280 // Given the following code:
281 //
282 // truthyExpression() || someExpression();
283 //
284 // the generated code is:
285 //
286 // truthyExpression() || false;
287 //
288 // ------------------------------------------
289 //
290 // Given the following code:
291 //
292 // falsyExpression() || someExpression();
293 //
294 // the generated code is:
295 //
296 // false && someExpression();
297 //
298 const keepRight =
299 (expression.operator === "&&" && bool) ||
300 (expression.operator === "||" && !bool);
301
302 if (
303 !param.couldHaveSideEffects() &&
304 (param.isBoolean() || keepRight)
305 ) {
306 // for case like
307 //
308 // return'development'===process.env.NODE_ENV&&'foo'
309 //
310 // we need a space before the bool to prevent result like
311 //
312 // returnfalse&&'foo'
313 //
314 const dep = new ConstDependency(
315 ` ${bool}`,
316 /** @type {Range} */ (param.range)
317 );
318 dep.loc = /** @type {SourceLocation} */ (expression.loc);
319 parser.state.module.addPresentationalDependency(dep);
320 } else {
321 parser.walkExpression(expression.left);
322 }
323 if (!keepRight) {
324 const dep = new ConstDependency(
325 "0",
326 /** @type {Range} */ (expression.right.range)
327 );
328 dep.loc = /** @type {SourceLocation} */ (expression.loc);
329 parser.state.module.addPresentationalDependency(dep);
330 }
331 return keepRight;
332 }
333 } else if (expression.operator === "??") {
334 const param = parser.evaluateExpression(expression.left);
335 const keepRight = param.asNullish();
336 if (typeof keepRight === "boolean") {
337 // ------------------------------------------
338 //
339 // Given the following code:
340 //
341 // nonNullish ?? someExpression();
342 //
343 // the generated code is:
344 //
345 // nonNullish ?? 0;
346 //
347 // ------------------------------------------
348 //
349 // Given the following code:
350 //
351 // nullish ?? someExpression();
352 //
353 // the generated code is:
354 //
355 // null ?? someExpression();
356 //
357 if (!param.couldHaveSideEffects() && keepRight) {
358 // cspell:word returnnull
359 // for case like
360 //
361 // return('development'===process.env.NODE_ENV&&null)??'foo'
362 //
363 // we need a space before the bool to prevent result like
364 //
365 // returnnull??'foo'
366 //
367 const dep = new ConstDependency(
368 " null",
369 /** @type {Range} */ (param.range)
370 );
371 dep.loc = /** @type {SourceLocation} */ (expression.loc);
372 parser.state.module.addPresentationalDependency(dep);
373 } else {
374 const dep = new ConstDependency(
375 "0",
376 /** @type {Range} */ (expression.right.range)
377 );
378 dep.loc = /** @type {SourceLocation} */ (expression.loc);
379 parser.state.module.addPresentationalDependency(dep);
380 parser.walkExpression(expression.left);
381 }
382
383 return keepRight;
384 }
385 }
386 }
387 );
388 parser.hooks.optionalChaining.tap(PLUGIN_NAME, (expr) => {
389 /** @type {Expression[]} */
390 const optionalExpressionsStack = [];
391 /** @type {Expression | Super} */
392 let next = expr.expression;
393
394 while (
395 next.type === "MemberExpression" ||
396 next.type === "CallExpression"
397 ) {
398 if (next.type === "MemberExpression") {
399 if (next.optional) {
400 // SuperNode can not be optional
401 optionalExpressionsStack.push(
402 /** @type {Expression} */ (next.object)
403 );
404 }
405 next = next.object;
406 } else {
407 if (next.optional) {
408 // SuperNode can not be optional
409 optionalExpressionsStack.push(
410 /** @type {Expression} */ (next.callee)
411 );
412 }
413 next = next.callee;
414 }
415 }
416
417 while (optionalExpressionsStack.length) {
418 const expression = optionalExpressionsStack.pop();
419 const evaluated = parser.evaluateExpression(
420 /** @type {Expression} */ (expression)
421 );
422
423 if (evaluated.asNullish()) {
424 // ------------------------------------------
425 //
426 // Given the following code:
427 //
428 // nullishMemberChain?.a.b();
429 //
430 // the generated code is:
431 //
432 // undefined;
433 //
434 // ------------------------------------------
435 //
436 const dep = new ConstDependency(
437 " undefined",
438 /** @type {Range} */ (expr.range)
439 );
440 dep.loc = /** @type {SourceLocation} */ (expr.loc);
441 parser.state.module.addPresentationalDependency(dep);
442 return true;
443 }
444 }
445 });
446 parser.hooks.evaluateIdentifier
447 .for("__resourceQuery")
448 .tap(PLUGIN_NAME, (expr) => {
449 if (parser.scope.isAsmJs) return;
450 if (!parser.state.module) return;
451 return evaluateToString(
452 cachedParseResource(parser.state.module.resource).query
453 )(expr);
454 });
455 parser.hooks.expression
456 .for("__resourceQuery")
457 .tap(PLUGIN_NAME, (expr) => {
458 if (parser.scope.isAsmJs) return;
459 if (!parser.state.module) return;
460 const dep = new CachedConstDependency(
461 JSON.stringify(
462 cachedParseResource(parser.state.module.resource).query
463 ),
464 /** @type {Range} */ (expr.range),
465 "__resourceQuery"
466 );
467 dep.loc = /** @type {SourceLocation} */ (expr.loc);
468 parser.state.module.addPresentationalDependency(dep);
469 return true;
470 });
471
472 parser.hooks.evaluateIdentifier
473 .for("__resourceFragment")
474 .tap(PLUGIN_NAME, (expr) => {
475 if (parser.scope.isAsmJs) return;
476 if (!parser.state.module) return;
477 return evaluateToString(
478 cachedParseResource(parser.state.module.resource).fragment
479 )(expr);
480 });
481 parser.hooks.expression
482 .for("__resourceFragment")
483 .tap(PLUGIN_NAME, (expr) => {
484 if (parser.scope.isAsmJs) return;
485 if (!parser.state.module) return;
486 const dep = new CachedConstDependency(
487 JSON.stringify(
488 cachedParseResource(parser.state.module.resource).fragment
489 ),
490 /** @type {Range} */ (expr.range),
491 "__resourceFragment"
492 );
493 dep.loc = /** @type {SourceLocation} */ (expr.loc);
494 parser.state.module.addPresentationalDependency(dep);
495 return true;
496 });
497 };
498
499 normalModuleFactory.hooks.parser
500 .for(JAVASCRIPT_MODULE_TYPE_AUTO)
501 .tap(PLUGIN_NAME, handler);
502 normalModuleFactory.hooks.parser
503 .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
504 .tap(PLUGIN_NAME, handler);
505 normalModuleFactory.hooks.parser
506 .for(JAVASCRIPT_MODULE_TYPE_ESM)
507 .tap(PLUGIN_NAME, handler);
508 }
509 );
510 }
511
512 /**
513 * Eliminate an unused statement.
514 * @param {JavascriptParser} parser the parser
515 * @param {Statement} statement the statement to remove
516 * @param {boolean} alwaysInBlock whether to always generate curly brackets
517 * @returns {void}
518 */
519 eliminateUnusedStatement(parser, statement, alwaysInBlock) {
520 // Before removing the unused branch, the hoisted declarations
521 // must be collected.
522 //
523 // Given the following code:
524 //
525 // if (true) f() else g()
526 // if (false) {
527 // function f() {}
528 // const g = function g() {}
529 // if (someTest) {
530 // let a = 1
531 // var x, {y, z} = obj
532 // }
533 // } else {
534 // …
535 // }
536 //
537 // the generated code is:
538 //
539 // if (true) f() else {}
540 // if (false) {
541 // var f, x, y, z; (in loose mode)
542 // var x, y, z; (in strict mode)
543 // } else {
544 // …
545 // }
546 //
547 // NOTE: When code runs in strict mode, `var` declarations
548 // are hoisted but `function` declarations don't.
549 //
550 const declarations = parser.scope.isStrict
551 ? getHoistedDeclarations(statement, false)
552 : getHoistedDeclarations(statement, true);
553
554 const inBlock = alwaysInBlock || statement.type === "BlockStatement";
555
556 let replacement = inBlock ? "{" : "";
557 replacement +=
558 declarations.length > 0 ? ` var ${declarations.join(", ")}; ` : "";
559 replacement += inBlock ? "}" : "";
560
561 const dep = new ConstDependency(
562 `// removed by dead control flow\n${replacement}`,
563 /** @type {Range} */ (statement.range)
564 );
565 dep.loc = /** @type {SourceLocation} */ (statement.loc);
566 parser.state.module.addPresentationalDependency(dep);
567 }
568}
569
570module.exports = ConstPlugin;
Note: See TracBrowser for help on using the repository browser.