Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fff335f

Browse filesBrowse files
TrickyPirenovate[bot]lukastaegert
authored
enhance tree-shaking for dynamic imports (#5954)
* chore(deps): update dependency lint-staged to v16 * Explicitly enable WASM features to fix compilation broken with Rust 1.87.0 * Remove unneeded getrandom * enhance tree-shaking for dynamic imports --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Lukas Taegert-Atkinson <lukas.taegert-atkinson@tngtech.com>
1 parent 9cf84fc commit fff335f
Copy full SHA for fff335f

File tree

7 files changed

+111
-38
lines changed
Filter options

7 files changed

+111
-38
lines changed

‎src/ast/nodes/ImportExpression.ts

Copy file name to clipboardExpand all lines: src/ast/nodes/ImportExpression.ts
+5-31Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ export default class ImportExpression extends NodeBase {
8686
*
8787
* 1. `const { foo } = await import('bar')`.
8888
* 2. `(await import('bar')).foo`
89-
* 3. `import('bar').then((m) => m.foo)`
90-
* 4. `import('bar').then(({ foo }) => {})`
89+
* 3. `import('bar').then(({ foo }) => {})`
9190
*
9291
* Returns empty array if it's side-effect only import.
9392
* Returns undefined if it's not fully deterministic.
@@ -172,37 +171,12 @@ export default class ImportExpression extends NodeBase {
172171
return EMPTY_ARRAY;
173172
}
174173

175-
if (thenCallback.params.length === 1) {
176-
// Promises .then() can only have one argument so only look at first one
177-
const declaration = thenCallback.params[0];
178-
179-
// Case 3: import('bar').then(m => m.foo)
180-
if (declaration instanceof Identifier) {
181-
const starName = declaration.name;
182-
const memberExpression = thenCallback.body;
183-
if (
184-
!(memberExpression instanceof MemberExpression) ||
185-
memberExpression.computed ||
186-
!(memberExpression.property instanceof Identifier)
187-
) {
188-
return;
189-
}
190-
191-
const returnVariable = memberExpression.object;
192-
if (!(returnVariable instanceof Identifier) || returnVariable.name !== starName) {
193-
return;
194-
}
195-
196-
return [memberExpression.property.name];
197-
}
198-
199-
// Case 4: import('bar').then(({ foo }) => {})
200-
if (declaration instanceof ObjectPattern) {
201-
return getDeterministicObjectDestructure(declaration);
202-
}
174+
const declaration = thenCallback.params[0];
175+
if (thenCallback.params.length === 1 && declaration instanceof ObjectPattern) {
176+
return getDeterministicObjectDestructure(declaration);
203177
}
204178

205-
return;
179+
return this.hasUnknownAccessedKey ? undefined : [...this.accessedPropKey];
206180
}
207181
}
208182

‎src/ast/nodes/MemberExpression.ts

Copy file name to clipboardExpand all lines: src/ast/nodes/MemberExpression.ts
+19-1Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
INTERACTION_ASSIGNED,
2020
NODE_INTERACTION_UNKNOWN_ACCESS
2121
} from '../NodeInteractions';
22+
import { isAwaitExpressionNode, isImportExpressionNode } from '../utils/identifyNode';
2223
import { MAX_PATH_DEPTH } from '../utils/limitPathLength';
2324
import {
2425
EMPTY_PATH,
@@ -33,6 +34,7 @@ import {
3334
} from '../utils/PathTracker';
3435
import { UNDEFINED_EXPRESSION } from '../values';
3536
import ExternalVariable from '../variables/ExternalVariable';
37+
import LocalVariable from '../variables/LocalVariable';
3638
import type NamespaceVariable from '../variables/NamespaceVariable';
3739
import type Variable from '../variables/Variable';
3840
import Identifier from './Identifier';
@@ -45,6 +47,7 @@ import {
4547
deoptimizeInteraction,
4648
type ExpressionEntity,
4749
includeInteraction,
50+
includeInteractionWithoutThis,
4851
type LiteralValueOrUnknown,
4952
UNKNOWN_RETURN_EXPRESSION,
5053
UnknownValue
@@ -418,7 +421,22 @@ export default class MemberExpression
418421
if (this.variable) {
419422
this.variable.includeCallArguments(interaction, context);
420423
} else {
421-
includeInteraction(interaction, context);
424+
if (
425+
isImportExpressionNode(this.object) ||
426+
/**
427+
* const c = await import('foo')
428+
* c.foo();
429+
*/
430+
(this.object.variable &&
431+
!this.object.variable.isReassigned &&
432+
this.object.variable instanceof LocalVariable &&
433+
isAwaitExpressionNode(this.object.variable.init) &&
434+
isImportExpressionNode(this.object.variable.init.argument))
435+
) {
436+
includeInteractionWithoutThis(interaction, context);
437+
} else {
438+
includeInteraction(interaction, context);
439+
}
422440
}
423441
}
424442

‎src/ast/nodes/shared/Expression.ts

Copy file name to clipboardExpand all lines: src/ast/nodes/shared/Expression.ts
+9-2Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,17 @@ export const deoptimizeInteraction = (interaction: NodeInteraction) => {
120120
}
121121
};
122122

123-
export const includeInteraction = ({ args }: NodeInteraction, context: InclusionContext) => {
123+
export const includeInteraction = (interaction: NodeInteraction, context: InclusionContext) => {
124124
// We do not re-include the "this" argument as we expect this is already
125125
// re-included at the call site
126-
args[0]?.includePath(UNKNOWN_PATH, context);
126+
interaction.args[0]?.includePath(UNKNOWN_PATH, context);
127+
includeInteractionWithoutThis(interaction, context);
128+
};
129+
130+
export const includeInteractionWithoutThis = (
131+
{ args }: NodeInteraction,
132+
context: InclusionContext
133+
) => {
127134
for (let argumentIndex = 1; argumentIndex < args.length; argumentIndex++) {
128135
const argument = args[argumentIndex];
129136
if (argument) {

‎src/ast/utils/identifyNode.ts

Copy file name to clipboard
+32-3Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,43 @@
1+
import type ArrowFunctionExpression from '../nodes/ArrowFunctionExpression';
2+
import type AwaitExpression from '../nodes/AwaitExpression';
3+
import type CallExpression from '../nodes/CallExpression';
4+
import type Identifier from '../nodes/Identifier';
5+
import type ImportExpression from '../nodes/ImportExpression';
6+
import type MemberExpression from '../nodes/MemberExpression';
17
import * as nodeType from '../nodes/NodeType';
28
import type ObjectExpression from '../nodes/ObjectExpression';
39
import type Property from '../nodes/Property';
410
import type { ExpressionEntity } from '../nodes/shared/Expression';
511
import type { ExpressionNode } from '../nodes/shared/Node';
612
import { NodeBase } from '../nodes/shared/Node';
7-
813
export function isObjectExpressionNode(node: ExpressionEntity): node is ObjectExpression {
914
return node instanceof NodeBase && node.type === nodeType.ObjectExpression;
1015
}
1116

12-
export function isPropertyNode(node: ExpressionNode): node is Property {
13-
return node.type === nodeType.Property;
17+
export function isPropertyNode(node: unknown): node is Property {
18+
return node instanceof NodeBase && node.type === nodeType.Property;
19+
}
20+
21+
export function isArrowFunctionExpressionNode(node: unknown): node is ArrowFunctionExpression {
22+
return node instanceof NodeBase && node.type === nodeType.ArrowFunctionExpression;
23+
}
24+
25+
export function isCallExpressionNode(node: unknown): node is CallExpression {
26+
return node instanceof NodeBase && node.type === nodeType.CallExpression;
27+
}
28+
29+
export function isMemberExpressionNode(node: ExpressionNode): node is MemberExpression {
30+
return node instanceof NodeBase && node.type === nodeType.MemberExpression;
31+
}
32+
33+
export function isImportExpressionNode(node: unknown): node is ImportExpression {
34+
return node instanceof NodeBase && node.type === nodeType.ImportExpression;
35+
}
36+
37+
export function isAwaitExpressionNode(node: unknown): node is AwaitExpression {
38+
return node instanceof NodeBase && node.type === nodeType.AwaitExpression;
39+
}
40+
41+
export function isIdentifierNode(node: unknown): node is Identifier {
42+
return node instanceof NodeBase && node.type === nodeType.Identifier;
1443
}

‎src/ast/variables/LocalVariable.ts

Copy file name to clipboardExpand all lines: src/ast/variables/LocalVariable.ts
+24-1Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ import {
2222
} from '../nodes/shared/Expression';
2323
import type { Node } from '../nodes/shared/Node';
2424
import type { VariableKind } from '../nodes/shared/VariableKinds';
25+
import {
26+
isArrowFunctionExpressionNode,
27+
isCallExpressionNode,
28+
isIdentifierNode,
29+
isImportExpressionNode,
30+
isMemberExpressionNode
31+
} from '../utils/identifyNode';
2532
import { limitConcatenatedPathDepth, MAX_PATH_DEPTH } from '../utils/limitPathLength';
2633
import type { IncludedPathTracker } from '../utils/PathTracker';
2734
import {
@@ -49,7 +56,7 @@ export default class LocalVariable extends Variable {
4956
constructor(
5057
name: string,
5158
declarator: Identifier | ExportDefaultDeclaration | null,
52-
private init: ExpressionEntity,
59+
public init: ExpressionEntity,
5360
/** if this is non-empty, the actual init is this path of this.init */
5461
protected initPath: ObjectPath,
5562
context: AstContext,
@@ -223,6 +230,22 @@ export default class LocalVariable extends Variable {
223230
if (node.type === NodeType.Program) break;
224231
node = node.parent as Node;
225232
}
233+
/**
234+
* import('foo').then(m => {
235+
* console.log(m.foo)
236+
* })
237+
*/
238+
if (
239+
this.kind === 'parameter' &&
240+
isArrowFunctionExpressionNode(declaration.parent) &&
241+
isCallExpressionNode(declaration.parent.parent) &&
242+
isMemberExpressionNode(declaration.parent.parent.callee) &&
243+
isIdentifierNode(declaration.parent.parent.callee.property) &&
244+
declaration.parent.parent.callee.property.name === 'then' &&
245+
isImportExpressionNode(declaration.parent.parent.callee.object)
246+
) {
247+
declaration.parent.parent.callee.object.includePath(path);
248+
}
226249
}
227250
// We need to make sure we include the correct path of the init
228251
if (path.length > 0) {

‎test/form/samples/treeshake-deterministic-dynamic-import/_expected.js

Copy file name to clipboardExpand all lines: test/form/samples/treeshake-deterministic-dynamic-import/_expected.js
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ async function entry() {
2323
const { foo2 } = await Promise.resolve().then(function () { return sub2; });
2424
const { foo3 } = await Promise.resolve().then(function () { return sub2; });
2525
Promise.resolve().then(function () { return sub2; }).then((m) => m.baz2);
26+
Promise.resolve().then(function () { return sub2; }).then(m=>{
27+
function f(m){
28+
console.log(m.baz2);
29+
}
30+
f(m);
31+
});
2632
Promise.resolve().then(function () { return sub2; }).then(({ baz2 }) => baz2);
2733
Promise.resolve().then(function () { return sub2; }).then(function({ reexported }) { });
2834

@@ -65,6 +71,11 @@ async function entry() {
6571
{
6672
const [name11] = await Promise.resolve().then(function () { return bail11$1; });
6773
}
74+
75+
{
76+
const sub2$1 = await Promise.resolve().then(function () { return sub2; });
77+
sub2$1.foo2();
78+
}
6879
}
6980

7081
function foo1() {

‎test/form/samples/treeshake-deterministic-dynamic-import/main.js

Copy file name to clipboardExpand all lines: test/form/samples/treeshake-deterministic-dynamic-import/main.js
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ export async function entry() {
88
const { foo2 } = await import('./sub2.js');
99
const { foo3 } = await import('./sub2.js');
1010
import('./sub2.js').then((m) => m.baz2)
11+
import('./sub2.js').then(m=>{
12+
function f(m){
13+
console.log(m.baz2)
14+
}
15+
f(m);
16+
})
1117
import('./sub2.js').then(({ baz2 }) => baz2)
1218
import('./sub2.js').then(function({ reexported }) { reexported })
1319

@@ -50,4 +56,9 @@ export async function entry() {
5056
{
5157
const [name11] = await import('./bail-11.js');
5258
}
59+
60+
{
61+
const sub2 = await import('./sub2.js');
62+
sub2.foo2();
63+
}
5364
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.