Skip to content

Commit

Permalink
feat: new cases
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Apr 20, 2023
1 parent 8471f90 commit 04cd599
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 25 deletions.
76 changes: 53 additions & 23 deletions src/ast/nodes/ImportExpression.ts
Expand Up @@ -12,7 +12,10 @@ import { findFirstOccurrenceOutsideComment, type RenderOptions } from '../../uti
import type { InclusionContext } from '../ExecutionContext';
import type ChildScope from '../scopes/ChildScope';
import type NamespaceVariable from '../variables/NamespaceVariable';
import type ArrowFunctionExpression from './ArrowFunctionExpression';
import type AwaitExpression from './AwaitExpression';
import type CallExpression from './CallExpression';
import type FunctionDeclaration from './FunctionDeclaration';
import type Identifier from './Identifier';
import type MemberExpression from './MemberExpression';
import type * as NodeType from './NodeType';
Expand Down Expand Up @@ -55,41 +58,55 @@ export default class ImportExpression extends NodeBase {
*
* - `const { foo } = await import('bar')`.
* - `(await import('bar')).foo`
* - `import('bar').then(({ foo }) => {})`
*
* Returns undefined if it's not deterministic.
*/
getDeterministicImportedNames(): string[] | undefined {
if (this.parent?.type !== 'AwaitExpression') return;
if (this.parent?.type === 'AwaitExpression') {
const awaitExpression = this.parent as AwaitExpression;

const awaitExpression = this.parent as AwaitExpression;
// Case 1: const { foo } = await import('bar')
if (awaitExpression.parent?.type === 'VariableDeclarator') {
const variableDeclarator = awaitExpression.parent as VariableDeclarator;
if (variableDeclarator.id?.type !== 'ObjectPattern') return;

// Case 1: const { foo } = await import('bar')
if (awaitExpression.parent?.type === 'VariableDeclarator') {
const variableDeclarator = awaitExpression.parent as VariableDeclarator;
if (variableDeclarator.id?.type !== 'ObjectPattern') return;

const objectPattern = variableDeclarator.id as ObjectPattern;
return getDeterministicObjectDestructure(variableDeclarator.id as ObjectPattern);
}
// Case 2: (await import('bar')).foo
else {
if (awaitExpression.parent?.type !== 'MemberExpression') return;

const variables: string[] = [];
const memberExpression = awaitExpression.parent as MemberExpression;
if (memberExpression.computed || memberExpression.property.type !== 'Identifier') return;

for (const property of objectPattern.properties) {
if (property.type === 'RestElement') return;
if (property.computed) return;
if (property.key.type !== 'Identifier') return;
variables.push((property.key as Identifier).name);
return [(memberExpression.property as Identifier).name];
}

return variables;
}
// Case 2: (await import('bar')).foo
else {
if (awaitExpression.parent?.type !== 'MemberExpression') return;
// Case 3: import('bar').then(({ foo }) => {})
else if (this.parent?.type === 'MemberExpression') {
const memberExpression = this.parent as MemberExpression;
if (
memberExpression.property.type !== 'Identifier' ||
(memberExpression.property as Identifier).name !== 'then' ||
memberExpression.parent?.type !== 'CallExpression'
)
return;

const callExpression = memberExpression.parent as CallExpression;

if (
callExpression.arguments.length !== 1 ||
!['ArrowFunctionExpression', 'FunctionDeclaration'].includes(
callExpression.arguments[0].type
)
)
return;

const memberExpression = awaitExpression.parent as MemberExpression;
if (memberExpression.computed) return;
if (memberExpression.property.type !== 'Identifier') return;
const callback = callExpression.arguments[0] as ArrowFunctionExpression | FunctionDeclaration;
if (callback.params.length !== 1 || callback.params[0].type !== 'ObjectPattern') return;

return [(memberExpression.property as Identifier).name];
return getDeterministicObjectDestructure(callback.params[0] as ObjectPattern);
}
}

Expand Down Expand Up @@ -339,6 +356,19 @@ function getInteropHelper(
: null;
}

function getDeterministicObjectDestructure(objectPattern: ObjectPattern): string[] | undefined {
const variables: string[] = [];

for (const property of objectPattern.properties) {
if (property.type === 'RestElement') return;
if (property.computed) return;
if (property.key.type !== 'Identifier') return;
variables.push((property.key as Identifier).name);
}

return variables;
}

const accessedImportGlobals: Record<string, string[]> = {
amd: ['require'],
cjs: ['require'],
Expand Down
@@ -1,5 +1,5 @@
module.exports = {
description: 'treeshakes dynamic imports when the target is statically known',
description: 'treeshakes dynamic imports when the target is deterministic',
options: {
output: {
inlineDynamicImports: true
Expand Down
Expand Up @@ -4,6 +4,9 @@ async function entry() {
(await Promise.resolve().then(function () { return sub2; })).bar3();
await Promise.resolve().then(function () { return sub2; });
await Promise.resolve().then(function () { return sub2; });
Promise.resolve().then(function () { return sub2; }).then(({ baz3 }) => {
console.log(baz3);
});

// bail out
await Promise.resolve().then(function () { return bail1$1; });
Expand Down
5 changes: 4 additions & 1 deletion test/form/samples/treeshake-static-dynamic-import/main.js
Expand Up @@ -4,8 +4,11 @@ export async function entry() {

// multiple
;(await import('./sub2.js')).bar3()
const { foo3, baz3 } = await import('./sub2.js');
const { foo3 } = await import('./sub2.js');
const { foo4 } = await import('./sub2.js');
import('./sub2.js').then(({ baz3 }) => {
console.log(baz3);
})

// bail out
const { bail1 } = await import('./bail1.js');
Expand Down

0 comments on commit 04cd599

Please sign in to comment.