source: frontend/node_modules/webpack/lib/dependencies/HarmonyImportDependencyParserPlugin.js

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

Fix frontend appearance

  • Property mode set to 100644
File size: 19.4 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 HotModuleReplacementPlugin = require("../HotModuleReplacementPlugin");
9const WebpackError = require("../errors/WebpackError");
10const {
11 VariableInfo,
12 getImportAttributes
13} = require("../javascript/JavascriptParser");
14const InnerGraph = require("../optimize/InnerGraph");
15const AppendOnlyStackedSet = require("../util/AppendOnlyStackedSet");
16const ConstDependency = require("./ConstDependency");
17const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
18const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
19const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
20const HarmonyExports = require("./HarmonyExports");
21const {
22 ExportPresenceModes,
23 getNonOptionalPart
24} = require("./HarmonyImportDependency");
25const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
26const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
27const { ImportPhaseUtils, createGetImportPhase } = require("./ImportPhase");
28
29/** @typedef {import("estree").Expression} Expression */
30/** @typedef {import("estree").PrivateIdentifier} PrivateIdentifier */
31/** @typedef {import("estree").Identifier} Identifier */
32/** @typedef {import("estree").MemberExpression} MemberExpression */
33/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
34/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
35/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
36/** @typedef {import("../javascript/JavascriptParser").ExportAllDeclaration} ExportAllDeclaration */
37/** @typedef {import("../javascript/JavascriptParser").ExportNamedDeclaration} ExportNamedDeclaration */
38/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */
39/** @typedef {import("../javascript/JavascriptParser").ImportDeclaration} ImportDeclaration */
40/** @typedef {import("../javascript/JavascriptParser").Range} Range */
41/** @typedef {import("../javascript/JavascriptParser").Members} Members */
42/** @typedef {import("../javascript/JavascriptParser").MembersOptionals} MembersOptionals */
43/** @typedef {import("./HarmonyImportDependency").Ids} Ids */
44/** @typedef {import("./HarmonyImportDependency").ExportPresenceMode} ExportPresenceMode */
45/** @typedef {import("./ImportPhase").ImportPhaseType} ImportPhaseType */
46
47/**
48 * Defines the harmony specifier guards type used by this module.
49 * @typedef {object} HarmonySpecifierGuards
50 * @property {AppendOnlyStackedSet<string> | undefined} guards
51 */
52
53/** @typedef {Map<string, Set<string>>} Guards Map of import root to guarded member keys */
54
55const harmonySpecifierTag = Symbol("harmony import");
56const harmonySpecifierGuardTag = Symbol("harmony import guard");
57
58/**
59 * Defines the harmony settings type used by this module.
60 * @typedef {object} HarmonySettings
61 * @property {Ids} ids
62 * @property {string} source
63 * @property {number} sourceOrder
64 * @property {string} name
65 * @property {boolean} await
66 * @property {ImportAttributes=} attributes
67 * @property {ImportPhaseType} phase
68 */
69
70const PLUGIN_NAME = "HarmonyImportDependencyParserPlugin";
71
72/**
73 * Gets in operator harmony import info.
74 * @param {JavascriptParser} parser the parser
75 * @param {PrivateIdentifier | Expression} left left expression
76 * @param {Expression} right right expression
77 * @returns {{ leftPart: string, members: Members, settings: HarmonySettings } | undefined} info
78 */
79const getInOperatorHarmonyImportInfo = (parser, left, right) => {
80 const leftPartEvaluated = parser.evaluateExpression(left);
81 if (leftPartEvaluated.couldHaveSideEffects()) return;
82 /** @type {string | undefined} */
83 const leftPart = leftPartEvaluated.asString();
84 if (!leftPart) return;
85
86 const rightPart = parser.evaluateExpression(right);
87 if (!rightPart.isIdentifier()) return;
88
89 const rootInfo = rightPart.rootInfo;
90 const root =
91 typeof rootInfo === "string"
92 ? rootInfo
93 : rootInfo instanceof VariableInfo
94 ? rootInfo.name
95 : undefined;
96 if (!root) return;
97
98 const settings = /** @type {HarmonySettings | undefined} */ (
99 parser.getTagData(root, harmonySpecifierTag)
100 );
101 if (!settings) {
102 return;
103 }
104
105 return {
106 leftPart,
107 members: /** @type {(() => Members)} */ (rightPart.getMembers)(),
108 settings
109 };
110};
111
112module.exports = class HarmonyImportDependencyParserPlugin {
113 /**
114 * Creates an instance of HarmonyImportDependencyParserPlugin.
115 * @param {JavascriptParserOptions} options options
116 */
117 constructor(options) {
118 this.options = options;
119 /** @type {ExportPresenceMode} */
120 this.exportPresenceMode = ExportPresenceModes.resolveFromOptions(
121 options.importExportsPresence,
122 options
123 );
124 this.strictThisContextOnImports = options.strictThisContextOnImports;
125 }
126
127 /**
128 * Gets export presence mode.
129 * @param {JavascriptParser} parser the parser
130 * @param {HarmonySettings} settings settings
131 * @param {Ids} ids ids
132 * @returns {ExportPresenceMode} exportPresenceMode
133 */
134 getExportPresenceMode(parser, settings, ids) {
135 // Guards only apply to namespace imports
136 if (settings.ids.length) return this.exportPresenceMode;
137
138 const harmonySettings = /** @type {HarmonySettings=} */ (
139 parser.currentTagData
140 );
141 if (!harmonySettings) return this.exportPresenceMode;
142
143 const data = /** @type {HarmonySpecifierGuards=} */ (
144 parser.getTagData(harmonySettings.name, harmonySpecifierGuardTag)
145 );
146
147 if (data && data.guards && data.guards.has(ids[0])) {
148 return ExportPresenceModes.NONE;
149 }
150
151 return this.exportPresenceMode;
152 }
153
154 /**
155 * Applies the plugin by registering its hooks on the compiler.
156 * @param {JavascriptParser} parser the parser
157 * @returns {void}
158 */
159 apply(parser) {
160 const getImportPhase = createGetImportPhase(
161 this.options.deferImport,
162 this.options.sourceImport
163 );
164
165 /**
166 * Gets non optional member chain.
167 * @param {MemberExpression} node member expression
168 * @param {number} count count
169 * @returns {Expression} member expression
170 */
171 function getNonOptionalMemberChain(node, count) {
172 while (count--) node = /** @type {MemberExpression} */ (node.object);
173 return node;
174 }
175
176 parser.hooks.isPure.for("Identifier").tap(PLUGIN_NAME, (expression) => {
177 const expr = /** @type {Identifier} */ (expression);
178 if (
179 parser.isVariableDefined(expr.name) ||
180 parser.getTagData(expr.name, harmonySpecifierTag)
181 ) {
182 return true;
183 }
184 });
185 parser.hooks.import.tap(PLUGIN_NAME, (statement, source) => {
186 parser.state.lastHarmonyImportOrder =
187 (parser.state.lastHarmonyImportOrder || 0) + 1;
188 const clearDep = new ConstDependency(
189 parser.isAsiPosition(/** @type {Range} */ (statement.range)[0])
190 ? ";"
191 : "",
192 /** @type {Range} */ (statement.range)
193 );
194 clearDep.loc = /** @type {DependencyLocation} */ (statement.loc);
195 parser.state.module.addPresentationalDependency(clearDep);
196 parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]);
197 const attributes = getImportAttributes(statement);
198 const phase = getImportPhase(parser, statement);
199 if (
200 ImportPhaseUtils.isDefer(phase) &&
201 (statement.specifiers.length !== 1 ||
202 statement.specifiers[0].type !== "ImportNamespaceSpecifier")
203 ) {
204 const error = new WebpackError(
205 "Deferred import can only be used with `import * as namespace from '...'` syntax."
206 );
207 error.loc = statement.loc || undefined;
208 parser.state.current.addError(error);
209 }
210
211 const sideEffectDep = new HarmonyImportSideEffectDependency(
212 /** @type {string} */ (source),
213 parser.state.lastHarmonyImportOrder,
214 phase,
215 attributes
216 );
217 sideEffectDep.loc = /** @type {DependencyLocation} */ (statement.loc);
218 parser.state.module.addDependency(sideEffectDep);
219 return true;
220 });
221 parser.hooks.importSpecifier.tap(
222 PLUGIN_NAME,
223 (statement, source, id, name) => {
224 const ids = id === null ? [] : [id];
225 const phase = getImportPhase(parser, statement);
226 parser.tagVariable(
227 name,
228 harmonySpecifierTag,
229 /** @type {HarmonySettings} */ ({
230 name,
231 source,
232 ids,
233 sourceOrder: parser.state.lastHarmonyImportOrder,
234 attributes: getImportAttributes(statement),
235 phase
236 })
237 );
238 return true;
239 }
240 );
241 parser.hooks.binaryExpression.tap(PLUGIN_NAME, (expression) => {
242 if (expression.operator !== "in") return;
243 const info = getInOperatorHarmonyImportInfo(
244 parser,
245 expression.left,
246 expression.right
247 );
248 if (!info) return;
249
250 const { leftPart, members, settings } = info;
251 const dep = new HarmonyEvaluatedImportSpecifierDependency(
252 settings.source,
253 settings.sourceOrder,
254 [...settings.ids, ...members, leftPart],
255 settings.name,
256 /** @type {Range} */ (expression.range),
257 settings.attributes,
258 "in"
259 );
260 dep.directImport = members.length === 0;
261 dep.asiSafe = !parser.isAsiPosition(
262 /** @type {Range} */ (expression.range)[0]
263 );
264 dep.loc = /** @type {DependencyLocation} */ (expression.loc);
265 parser.state.module.addDependency(dep);
266 InnerGraph.onUsage(parser.state, (e) => (dep.usedByExports = e));
267 return true;
268 });
269 parser.hooks.collectDestructuringAssignmentProperties.tap(
270 PLUGIN_NAME,
271 (expr) => {
272 const nameInfo = parser.getNameForExpression(expr);
273 if (
274 nameInfo &&
275 nameInfo.rootInfo instanceof VariableInfo &&
276 nameInfo.rootInfo.name &&
277 parser.getTagData(nameInfo.rootInfo.name, harmonySpecifierTag)
278 ) {
279 return true;
280 }
281 }
282 );
283 parser.hooks.expression
284 .for(harmonySpecifierTag)
285 .tap(PLUGIN_NAME, (expr) => {
286 const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
287
288 const dep = new HarmonyImportSpecifierDependency(
289 settings.source,
290 settings.sourceOrder,
291 settings.ids,
292 settings.name,
293 /** @type {Range} */
294 (expr.range),
295 this.exportPresenceMode,
296 settings.phase,
297 settings.attributes,
298 []
299 );
300 dep.referencedPropertiesInDestructuring =
301 parser.destructuringAssignmentPropertiesFor(expr);
302 dep.shorthand = parser.scope.inShorthand;
303 dep.directImport = true;
304 dep.asiSafe = !parser.isAsiPosition(
305 /** @type {Range} */ (expr.range)[0]
306 );
307 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
308 dep.call = parser.scope.inTaggedTemplateTag;
309 parser.state.module.addDependency(dep);
310 InnerGraph.onUsage(parser.state, (e) => (dep.usedByExports = e));
311 return true;
312 });
313 parser.hooks.expressionMemberChain
314 .for(harmonySpecifierTag)
315 .tap(
316 PLUGIN_NAME,
317 (expression, members, membersOptionals, memberRanges) => {
318 const settings =
319 /** @type {HarmonySettings} */
320 (parser.currentTagData);
321 const nonOptionalMembers = getNonOptionalPart(
322 members,
323 membersOptionals
324 );
325 /** @type {Range[]} */
326 const ranges = memberRanges.slice(
327 0,
328 memberRanges.length - (members.length - nonOptionalMembers.length)
329 );
330 const expr =
331 nonOptionalMembers !== members
332 ? getNonOptionalMemberChain(
333 expression,
334 members.length - nonOptionalMembers.length
335 )
336 : expression;
337 const ids = [...settings.ids, ...nonOptionalMembers];
338 const dep = new HarmonyImportSpecifierDependency(
339 settings.source,
340 settings.sourceOrder,
341 ids,
342 settings.name,
343 /** @type {Range} */
344 (expr.range),
345 this.getExportPresenceMode(parser, settings, ids),
346 settings.phase,
347 settings.attributes,
348 ranges
349 );
350 dep.referencedPropertiesInDestructuring =
351 parser.destructuringAssignmentPropertiesFor(expr);
352 dep.asiSafe = !parser.isAsiPosition(
353 /** @type {Range} */
354 (expr.range)[0]
355 );
356 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
357 parser.state.module.addDependency(dep);
358 InnerGraph.onUsage(parser.state, (e) => (dep.usedByExports = e));
359 return true;
360 }
361 );
362 parser.hooks.callMemberChain
363 .for(harmonySpecifierTag)
364 .tap(
365 PLUGIN_NAME,
366 (expression, members, membersOptionals, memberRanges) => {
367 const { arguments: args } = expression;
368 const callee = /** @type {MemberExpression} */ (expression.callee);
369 const settings = /** @type {HarmonySettings} */ (
370 parser.currentTagData
371 );
372 const nonOptionalMembers = getNonOptionalPart(
373 members,
374 membersOptionals
375 );
376 /** @type {Range[]} */
377 const ranges = memberRanges.slice(
378 0,
379 memberRanges.length - (members.length - nonOptionalMembers.length)
380 );
381 const expr =
382 nonOptionalMembers !== members
383 ? getNonOptionalMemberChain(
384 callee,
385 members.length - nonOptionalMembers.length
386 )
387 : callee;
388 const ids = [...settings.ids, ...nonOptionalMembers];
389 const dep = new HarmonyImportSpecifierDependency(
390 settings.source,
391 settings.sourceOrder,
392 ids,
393 settings.name,
394 /** @type {Range} */ (expr.range),
395 this.getExportPresenceMode(parser, settings, ids),
396 settings.phase,
397 settings.attributes,
398 ranges
399 );
400 dep.directImport = members.length === 0;
401 dep.call = true;
402 dep.asiSafe = !parser.isAsiPosition(
403 /** @type {Range} */ (expr.range)[0]
404 );
405 // only in case when we strictly follow the spec we need a special case here
406 dep.namespaceObjectAsContext =
407 members.length > 0 &&
408 /** @type {boolean} */ (this.strictThisContextOnImports);
409 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
410 parser.state.module.addDependency(dep);
411 if (args) parser.walkExpressions(args);
412 InnerGraph.onUsage(parser.state, (e) => (dep.usedByExports = e));
413 return true;
414 }
415 );
416 // Per the TC39 import-defer spec, [[Set]] on a Module Namespace
417 // Exotic Object returns false without triggering evaluation. The
418 // default expressionMemberChain path produces `<importVar>.a.foo`
419 // whose `.a` getter eagerly requires (and thus evaluates) the
420 // deferred module. For top-level `ns.foo = value`, walk only the
421 // bare `ns` identifier so it gets replaced with the deferred
422 // namespace proxy (whose set trap returns false), and leave the
423 // `.foo = value` part as plain code.
424 parser.hooks.assignMemberChain
425 .for(harmonySpecifierTag)
426 .tap(PLUGIN_NAME, (expression, members) => {
427 const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
428 if (!ImportPhaseUtils.isDefer(settings.phase)) return;
429 if (expression.operator !== "=") return;
430 if (members.length !== 1) return;
431 const left = /** @type {MemberExpression} */ (expression.left);
432 if (left.object.type !== "Identifier") return;
433 parser.walkExpression(expression.right);
434 parser.walkExpression(left.object);
435 return true;
436 });
437 const { hotAcceptCallback, hotAcceptWithoutCallback } =
438 HotModuleReplacementPlugin.getParserHooks(parser);
439 hotAcceptCallback.tap(PLUGIN_NAME, (expr, requests) => {
440 if (!HarmonyExports.isEnabled(parser.state)) {
441 // This is not a harmony module, skip it
442 return;
443 }
444 const dependencies = requests.map((request) => {
445 const dep = new HarmonyAcceptImportDependency(request);
446 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
447 parser.state.module.addDependency(dep);
448 return dep;
449 });
450 if (dependencies.length > 0) {
451 const dep = new HarmonyAcceptDependency(
452 /** @type {Range} */
453 (expr.range),
454 dependencies,
455 true
456 );
457 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
458 parser.state.module.addDependency(dep);
459 }
460 });
461 hotAcceptWithoutCallback.tap(PLUGIN_NAME, (expr, requests) => {
462 if (!HarmonyExports.isEnabled(parser.state)) {
463 // This is not a harmony module, skip it
464 return;
465 }
466 const dependencies = requests.map((request) => {
467 const dep = new HarmonyAcceptImportDependency(request);
468 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
469 parser.state.module.addDependency(dep);
470 return dep;
471 });
472 if (dependencies.length > 0) {
473 const dep = new HarmonyAcceptDependency(
474 /** @type {Range} */
475 (expr.range),
476 dependencies,
477 false
478 );
479 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
480 parser.state.module.addDependency(dep);
481 }
482 });
483
484 /**
485 * Processes the provided guard.
486 * @param {Guards} guards guards
487 * @param {() => void} walk walk callback
488 * @returns {void}
489 */
490 const withGuards = (guards, walk) => {
491 const applyGuards = () => {
492 /** @type {(() => void)[]} */
493 const restoreFns = [];
494
495 for (const [rootName, members] of guards) {
496 const previous = parser.getVariableInfo(rootName);
497 const exist = /** @type {HarmonySpecifierGuards=} */ (
498 parser.getTagData(rootName, harmonySpecifierGuardTag)
499 );
500
501 const mergedGuards =
502 exist && exist.guards
503 ? exist.guards.createChild()
504 : new AppendOnlyStackedSet();
505
506 for (const memberKey of members) mergedGuards.add(memberKey);
507 parser.tagVariable(rootName, harmonySpecifierGuardTag, {
508 guards: mergedGuards
509 });
510 restoreFns.push(() => {
511 parser.setVariable(rootName, previous);
512 });
513 }
514
515 return () => {
516 for (const restore of restoreFns) {
517 restore();
518 }
519 };
520 };
521
522 const restore = applyGuards();
523 try {
524 walk();
525 } finally {
526 restore();
527 }
528 };
529
530 if (this.exportPresenceMode !== ExportPresenceModes.NONE) {
531 parser.hooks.collectGuards.tap(PLUGIN_NAME, (expression) => {
532 if (parser.scope.isAsmJs) return;
533 /** @type {Guards} */
534 const guards = new Map();
535
536 /**
537 * Processes the provided expression.
538 * @param {Expression} expression expression
539 * @param {boolean} needTruthy need to be truthy
540 */
541 const collect = (expression, needTruthy) => {
542 if (
543 expression.type === "UnaryExpression" &&
544 expression.operator === "!"
545 ) {
546 collect(expression.argument, !needTruthy);
547 return;
548 } else if (expression.type === "LogicalExpression" && needTruthy) {
549 if (expression.operator === "&&") {
550 collect(expression.left, true);
551 collect(expression.right, true);
552 } else if (expression.operator === "||") {
553 const leftEvaluation = parser.evaluateExpression(expression.left);
554 const leftBool = leftEvaluation.asBool();
555 if (leftBool === false) {
556 collect(expression.right, true);
557 }
558 } else if (expression.operator === "??") {
559 const leftEvaluation = parser.evaluateExpression(expression.left);
560 const leftNullish = leftEvaluation.asNullish();
561 if (leftNullish === true) {
562 collect(expression.right, true);
563 }
564 }
565 return;
566 }
567 if (!needTruthy) return;
568
569 // Direct `"x" in ns` guards
570 if (
571 expression.type === "BinaryExpression" &&
572 expression.operator === "in"
573 ) {
574 if (expression.right.type !== "Identifier") {
575 return;
576 }
577 const info = getInOperatorHarmonyImportInfo(
578 parser,
579 expression.left,
580 expression.right
581 );
582 if (!info) return;
583
584 const { settings, leftPart, members } = info;
585 // Only direct namespace guards
586 if (members.length > 0) return;
587 const guarded = guards.get(settings.name);
588 if (guarded) {
589 guarded.add(leftPart);
590 return;
591 }
592
593 guards.set(settings.name, new Set([leftPart]));
594 }
595 };
596
597 collect(expression, true);
598
599 if (guards.size === 0) return;
600 return (walk) => {
601 withGuards(guards, walk);
602 };
603 });
604 }
605 }
606};
607
608module.exports.harmonySpecifierGuardTag = harmonySpecifierGuardTag;
609module.exports.harmonySpecifierTag = harmonySpecifierTag;
Note: See TracBrowser for help on using the repository browser.