source: imaps-frontend/node_modules/webpack/lib/ConstPlugin.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

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