From cbd91c50d0333e3b682fc99d85e28e2016a25cf0 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 19:59:09 +0200 Subject: [PATCH 01/25] chore: init --- src/rollup/types.d.ts | 1 + src/utils/options/normalizeInputOptions.ts | 1 + src/utils/options/options.ts | 3 ++ .../_config.js | 9 +++++ .../_expected.js | 38 +++++++++++++++++++ .../treeshake-static-dynamic-import/main.js | 6 +++ .../treeshake-static-dynamic-import/sub1.js | 9 +++++ .../treeshake-static-dynamic-import/sub2.js | 7 ++++ 8 files changed, 74 insertions(+) create mode 100644 test/form/samples/treeshake-static-dynamic-import/_config.js create mode 100644 test/form/samples/treeshake-static-dynamic-import/_expected.js create mode 100644 test/form/samples/treeshake-static-dynamic-import/main.js create mode 100644 test/form/samples/treeshake-static-dynamic-import/sub1.js create mode 100644 test/form/samples/treeshake-static-dynamic-import/sub2.js diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 94759af04ca..d48604dd3f6 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -479,6 +479,7 @@ export interface NormalizedTreeshakingOptions { manualPureFunctions: readonly string[]; moduleSideEffects: HasModuleSideEffects; propertyReadSideEffects: boolean | 'always'; + staticDynamicImports: boolean; tryCatchDeoptimization: boolean; unknownGlobalSideEffects: boolean; } diff --git a/src/utils/options/normalizeInputOptions.ts b/src/utils/options/normalizeInputOptions.ts index 4090a38f0db..3855c2e57b7 100644 --- a/src/utils/options/normalizeInputOptions.ts +++ b/src/utils/options/normalizeInputOptions.ts @@ -278,6 +278,7 @@ const getTreeshake = (config: InputOptions): NormalizedInputOptions['treeshake'] configWithPreset.propertyReadSideEffects === 'always' ? 'always' : configWithPreset.propertyReadSideEffects !== false, + staticDynamicImports: configWithPreset.staticDynamicImports !== false, tryCatchDeoptimization: configWithPreset.tryCatchDeoptimization !== false, unknownGlobalSideEffects: configWithPreset.unknownGlobalSideEffects !== false }; diff --git a/src/utils/options/options.ts b/src/utils/options/options.ts index 996f623ee09..688bb4e4a02 100644 --- a/src/utils/options/options.ts +++ b/src/utils/options/options.ts @@ -50,6 +50,7 @@ export const treeshakePresets: { manualPureFunctions: EMPTY_ARRAY, moduleSideEffects: () => true, propertyReadSideEffects: true, + staticDynamicImports: false, tryCatchDeoptimization: true, unknownGlobalSideEffects: false }, @@ -59,6 +60,7 @@ export const treeshakePresets: { manualPureFunctions: EMPTY_ARRAY, moduleSideEffects: () => true, propertyReadSideEffects: true, + staticDynamicImports: true, tryCatchDeoptimization: true, unknownGlobalSideEffects: true }, @@ -68,6 +70,7 @@ export const treeshakePresets: { manualPureFunctions: EMPTY_ARRAY, moduleSideEffects: () => false, propertyReadSideEffects: false, + staticDynamicImports: false, tryCatchDeoptimization: false, unknownGlobalSideEffects: false } diff --git a/test/form/samples/treeshake-static-dynamic-import/_config.js b/test/form/samples/treeshake-static-dynamic-import/_config.js new file mode 100644 index 00000000000..1aed137b1d8 --- /dev/null +++ b/test/form/samples/treeshake-static-dynamic-import/_config.js @@ -0,0 +1,9 @@ +module.exports = { + solo: true, + description: 'treeshakes dynamic imports when the target is statically known', + options: { + output: { + inlineDynamicImports: true + } + } +}; diff --git a/test/form/samples/treeshake-static-dynamic-import/_expected.js b/test/form/samples/treeshake-static-dynamic-import/_expected.js new file mode 100644 index 00000000000..deace8fdf91 --- /dev/null +++ b/test/form/samples/treeshake-static-dynamic-import/_expected.js @@ -0,0 +1,38 @@ +async function entry() { + await Promise.resolve().then(function () { return sub1; }); + await Promise.resolve().then(function () { return sub2; }); + + Promise.resolve().then(function () { return sub2; }); // this should make sub2.js not be tree-shaken +} + +function foo1() { + return 'foo1'; +} + +function bar1() { + return 'bar1'; // this should be tree-shaken +} + +console.log('side-effect1'); + +var sub1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + bar1: bar1, + foo1: foo1 +}); + +function foo2() { + return 'foo2'; +} + +function bar2() { + return 'bar2'; +} + +var sub2 = /*#__PURE__*/Object.freeze({ + __proto__: null, + bar2: bar2, + foo2: foo2 +}); + +export { entry }; diff --git a/test/form/samples/treeshake-static-dynamic-import/main.js b/test/form/samples/treeshake-static-dynamic-import/main.js new file mode 100644 index 00000000000..696ce8719ee --- /dev/null +++ b/test/form/samples/treeshake-static-dynamic-import/main.js @@ -0,0 +1,6 @@ +export async function entry() { + const { foo1 } = await import('./sub1.js'); + const { foo2: foo } = await import('./sub2.js'); + + const promise = import('./sub2.js') // this should make sub2.js not be tree-shaken +} diff --git a/test/form/samples/treeshake-static-dynamic-import/sub1.js b/test/form/samples/treeshake-static-dynamic-import/sub1.js new file mode 100644 index 00000000000..13f7de36261 --- /dev/null +++ b/test/form/samples/treeshake-static-dynamic-import/sub1.js @@ -0,0 +1,9 @@ +export function foo1() { + return 'foo1'; +} + +export function bar1() { + return 'bar1'; // this should be tree-shaken +} + +console.log('side-effect1'); diff --git a/test/form/samples/treeshake-static-dynamic-import/sub2.js b/test/form/samples/treeshake-static-dynamic-import/sub2.js new file mode 100644 index 00000000000..790df2055a7 --- /dev/null +++ b/test/form/samples/treeshake-static-dynamic-import/sub2.js @@ -0,0 +1,7 @@ +export function foo2() { + return 'foo2'; +} + +export function bar2() { + return 'bar2'; +} From 70179950e88e84746ed6830f7fca3f154ccb2ddb Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 20:25:46 +0200 Subject: [PATCH 02/25] feat: progress! --- src/Module.ts | 60 ++++++++++++------- src/ast/nodes/ImportExpression.ts | 33 ++++++++++ .../_expected.js | 4 -- 3 files changed, 70 insertions(+), 27 deletions(-) diff --git a/src/Module.ts b/src/Module.ts index c6b1b12663b..2a099ca8a98 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -678,30 +678,43 @@ export default class Module { } includeAllExports(includeNamespaceMembers: boolean): void { + this.includeExportsByNames( + [...this.exports.keys(), ...this.getReexports()], + includeNamespaceMembers + ); + } + + includeAllInBundle(): void { + this.ast!.include(createInclusionContext(), true); + this.includeAllExports(false); + } + + includeExportsByNames(names: string[], includeNamespaceMembers = false): void { if (!this.isExecuted) { markModuleAndImpureDependenciesAsExecuted(this); this.graph.needsTreeshakingPass = true; } - for (const exportName of this.exports.keys()) { - if (includeNamespaceMembers || exportName !== this.info.syntheticNamedExports) { - const variable = this.getVariableForExportName(exportName)[0]!; - variable.deoptimizePath(UNKNOWN_PATH); - if (!variable.included) { - this.includeVariable(variable); - } - } - } + const reexports = this.getReexports(); - for (const name of this.getReexports()) { - const [variable] = this.getVariableForExportName(name); - if (variable) { - variable.deoptimizePath(UNKNOWN_PATH); - if (!variable.included) { - this.includeVariable(variable); + for (const name of names) { + const variable = this.getVariableForExportName(name)[0]!; + if (reexports.includes(name)) { + if (variable) { + variable.deoptimizePath(UNKNOWN_PATH); + if (!variable.included) { + this.includeVariable(variable); + } + if (variable instanceof ExternalVariable) { + variable.module.reexported = true; + } } - if (variable instanceof ExternalVariable) { - variable.module.reexported = true; + } else { + if (includeNamespaceMembers || name !== this.info.syntheticNamedExports) { + variable.deoptimizePath(UNKNOWN_PATH); + if (!variable.included) { + this.includeVariable(variable); + } } } } @@ -711,11 +724,6 @@ export default class Module { } } - includeAllInBundle(): void { - this.ast!.include(createInclusionContext(), true); - this.includeAllExports(false); - } - isIncluded(): boolean | null { // Modules where this.ast is missing have been loaded via this.load and are // not yet fully processed, hence they cannot be included. @@ -1220,7 +1228,13 @@ export default class Module { ).resolution; if (resolution instanceof Module) { resolution.includedDynamicImporters.push(this); - resolution.includeAllExports(true); + const staticVariables = node.getStaticImportedVariables(); + + if (staticVariables) { + resolution.includeExportsByNames(staticVariables); + } else { + resolution.includeAllExports(true); + } } } diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index 81604c4de5b..fc954ae5c92 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -12,8 +12,12 @@ 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 AwaitExpression from './AwaitExpression'; +import type Identifier from './Identifier'; import type * as NodeType from './NodeType'; import type ObjectExpression from './ObjectExpression'; +import type ObjectPattern from './ObjectPattern'; +import type VariableDeclarator from './VariableDeclarator'; import { type ExpressionNode, type GenericEsTreeNode, @@ -45,6 +49,35 @@ export default class ImportExpression extends NodeBase { this.source.bind(); } + /** + * Get imported variables for static usage, + * for example `const { foo } = await import('bar')`. + * + * Returns undefined if not a static import + */ + getStaticImportedVariables(): string[] | undefined { + if (this.parent?.type !== 'AwaitExpression') return; + + const awaitExpression = this.parent as AwaitExpression; + if (awaitExpression.parent?.type !== 'VariableDeclarator') return; + + const variableDeclarator = awaitExpression.parent as VariableDeclarator; + if (variableDeclarator.id?.type !== 'ObjectPattern') return; + + const objectPattern = variableDeclarator.id as ObjectPattern; + + 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; + } + hasEffects(): boolean { return true; } diff --git a/test/form/samples/treeshake-static-dynamic-import/_expected.js b/test/form/samples/treeshake-static-dynamic-import/_expected.js index deace8fdf91..ded974484c7 100644 --- a/test/form/samples/treeshake-static-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-static-dynamic-import/_expected.js @@ -9,10 +9,6 @@ function foo1() { return 'foo1'; } -function bar1() { - return 'bar1'; // this should be tree-shaken -} - console.log('side-effect1'); var sub1 = /*#__PURE__*/Object.freeze({ From aefef264388365d6e36d8f83512a25e4cf741bd8 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 21:14:45 +0200 Subject: [PATCH 03/25] chore: refactor --- src/Module.ts | 67 ++++++++++--------- .../_expected.js | 6 +- .../treeshake-static-dynamic-import/main.js | 6 +- 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/Module.ts b/src/Module.ts index 2a099ca8a98..0f6788581ba 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -678,43 +678,30 @@ export default class Module { } includeAllExports(includeNamespaceMembers: boolean): void { - this.includeExportsByNames( - [...this.exports.keys(), ...this.getReexports()], - includeNamespaceMembers - ); - } - - includeAllInBundle(): void { - this.ast!.include(createInclusionContext(), true); - this.includeAllExports(false); - } - - includeExportsByNames(names: string[], includeNamespaceMembers = false): void { if (!this.isExecuted) { markModuleAndImpureDependenciesAsExecuted(this); this.graph.needsTreeshakingPass = true; } - const reexports = this.getReexports(); + for (const exportName of this.exports.keys()) { + if (includeNamespaceMembers || exportName !== this.info.syntheticNamedExports) { + const variable = this.getVariableForExportName(exportName)[0]!; + variable.deoptimizePath(UNKNOWN_PATH); + if (!variable.included) { + this.includeVariable(variable); + } + } + } - for (const name of names) { - const variable = this.getVariableForExportName(name)[0]!; - if (reexports.includes(name)) { - if (variable) { - variable.deoptimizePath(UNKNOWN_PATH); - if (!variable.included) { - this.includeVariable(variable); - } - if (variable instanceof ExternalVariable) { - variable.module.reexported = true; - } + for (const name of this.getReexports()) { + const [variable] = this.getVariableForExportName(name); + if (variable) { + variable.deoptimizePath(UNKNOWN_PATH); + if (!variable.included) { + this.includeVariable(variable); } - } else { - if (includeNamespaceMembers || name !== this.info.syntheticNamedExports) { - variable.deoptimizePath(UNKNOWN_PATH); - if (!variable.included) { - this.includeVariable(variable); - } + if (variable instanceof ExternalVariable) { + variable.module.reexported = true; } } } @@ -724,6 +711,26 @@ export default class Module { } } + includeAllInBundle(): void { + this.ast!.include(createInclusionContext(), true); + this.includeAllExports(false); + } + + includeExportsByNames(names: string[]): void { + for (const name of names) { + const variable = this.getVariableForExportName(name)[0]; + if (variable) { + variable.deoptimizePath(UNKNOWN_PATH); + if (!variable.included) { + this.includeVariable(variable); + } + if (variable instanceof ExternalVariable) { + variable.module.reexported = true; + } + } + } + } + isIncluded(): boolean | null { // Modules where this.ast is missing have been loaded via this.load and are // not yet fully processed, hence they cannot be included. diff --git a/test/form/samples/treeshake-static-dynamic-import/_expected.js b/test/form/samples/treeshake-static-dynamic-import/_expected.js index ded974484c7..8352ef07b9d 100644 --- a/test/form/samples/treeshake-static-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-static-dynamic-import/_expected.js @@ -1,8 +1,10 @@ async function entry() { - await Promise.resolve().then(function () { return sub1; }); - await Promise.resolve().then(function () { return sub2; }); + const { foo1: foo } = await Promise.resolve().then(function () { return sub1; }); + const { foo2 } = await Promise.resolve().then(function () { return sub2; }); Promise.resolve().then(function () { return sub2; }); // this should make sub2.js not be tree-shaken + + console.log(foo(), foo2()); } function foo1() { diff --git a/test/form/samples/treeshake-static-dynamic-import/main.js b/test/form/samples/treeshake-static-dynamic-import/main.js index 696ce8719ee..9a2621f4ba7 100644 --- a/test/form/samples/treeshake-static-dynamic-import/main.js +++ b/test/form/samples/treeshake-static-dynamic-import/main.js @@ -1,6 +1,8 @@ export async function entry() { - const { foo1 } = await import('./sub1.js'); - const { foo2: foo } = await import('./sub2.js'); + const { foo1: foo } = await import('./sub1.js'); + const { foo2 } = await import('./sub2.js'); const promise = import('./sub2.js') // this should make sub2.js not be tree-shaken + + console.log(foo(), foo2()); } From cd04d7a665a8a77123b6f6f10b89c7e87d0e6752 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 21:28:42 +0200 Subject: [PATCH 04/25] feat: it works! --- src/Module.ts | 7 ++++++- src/ast/variables/NamespaceVariable.ts | 8 ++++---- .../samples/treeshake-static-dynamic-import/_expected.js | 1 - 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Module.ts b/src/Module.ts index 0f6788581ba..920a7002dd2 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -1233,9 +1233,14 @@ export default class Module { resolution: string | Module | ExternalModule | undefined; } ).resolution; + if (resolution instanceof Module) { resolution.includedDynamicImporters.push(this); - const staticVariables = node.getStaticImportedVariables(); + + const staticVariables = + this.options.treeshake && this.options.treeshake.staticDynamicImports + ? node.getStaticImportedVariables() + : undefined; if (staticVariables) { resolution.includeExportsByNames(staticVariables); diff --git a/src/ast/variables/NamespaceVariable.ts b/src/ast/variables/NamespaceVariable.ts index 36226830cbf..011b3088c5a 100644 --- a/src/ast/variables/NamespaceVariable.ts +++ b/src/ast/variables/NamespaceVariable.ts @@ -133,8 +133,9 @@ export default class NamespaceVariable extends Variable { snippets: { _, cnst, getObject, getPropertyAccess, n, s } } = options; const memberVariables = this.getMemberVariables(); - const members: [key: string | null, value: string][] = Object.entries(memberVariables).map( - ([name, original]) => { + const members: [key: string | null, value: string][] = Object.entries(memberVariables) + .filter(([_, variable]) => variable.included) + .map(([name, original]) => { if (this.referencedEarly || original.isReassigned) { return [ null, @@ -143,8 +144,7 @@ export default class NamespaceVariable extends Variable { } return [name, original.getName(getPropertyAccess)]; - } - ); + }); members.unshift([null, `__proto__:${_}null`]); let output = getObject(members, { lineBreakIndent: { base: '', t } }); diff --git a/test/form/samples/treeshake-static-dynamic-import/_expected.js b/test/form/samples/treeshake-static-dynamic-import/_expected.js index 8352ef07b9d..330d2d1c6df 100644 --- a/test/form/samples/treeshake-static-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-static-dynamic-import/_expected.js @@ -15,7 +15,6 @@ console.log('side-effect1'); var sub1 = /*#__PURE__*/Object.freeze({ __proto__: null, - bar1: bar1, foo1: foo1 }); From f0d84c9e32f2dd7354daceea4e794e18f875706e Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 21:40:20 +0200 Subject: [PATCH 05/25] feat: support one more case --- src/Module.ts | 2 +- src/ast/nodes/ImportExpression.ts | 45 ++++++++++++------- .../_expected.js | 34 +++++++++++++- .../treeshake-static-dynamic-import/main.js | 15 ++++++- .../treeshake-static-dynamic-import/sub3.js | 15 +++++++ 5 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 test/form/samples/treeshake-static-dynamic-import/sub3.js diff --git a/src/Module.ts b/src/Module.ts index 920a7002dd2..994cda6587b 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -1239,7 +1239,7 @@ export default class Module { const staticVariables = this.options.treeshake && this.options.treeshake.staticDynamicImports - ? node.getStaticImportedVariables() + ? node.getStaticImportedNames() : undefined; if (staticVariables) { diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index fc954ae5c92..09ddfea5694 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -14,6 +14,7 @@ import type ChildScope from '../scopes/ChildScope'; import type NamespaceVariable from '../variables/NamespaceVariable'; import type AwaitExpression from './AwaitExpression'; import type Identifier from './Identifier'; +import type MemberExpression from './MemberExpression'; import type * as NodeType from './NodeType'; import type ObjectExpression from './ObjectExpression'; import type ObjectPattern from './ObjectPattern'; @@ -50,32 +51,46 @@ export default class ImportExpression extends NodeBase { } /** - * Get imported variables for static usage, - * for example `const { foo } = await import('bar')`. + * Get imported variables for static usage, valid cases are: * - * Returns undefined if not a static import + * - `const { foo } = await import('bar')`. + * - `(await import('bar')).foo` + * + * Returns undefined if it's not deterministic. */ - getStaticImportedVariables(): string[] | undefined { + getStaticImportedNames(): string[] | undefined { if (this.parent?.type !== 'AwaitExpression') return; const awaitExpression = this.parent as AwaitExpression; - if (awaitExpression.parent?.type !== 'VariableDeclarator') return; - 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; - const objectPattern = variableDeclarator.id as ObjectPattern; + const variables: string[] = []; - 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); + } - 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; } + // Case 2: (await import('bar')).foo + else { + if (awaitExpression.parent?.type !== 'MemberExpression') return; - return variables; + const memberExpression = awaitExpression.parent as MemberExpression; + if (memberExpression.computed) return; + if (memberExpression.property.type !== 'Identifier') return; + + return [(memberExpression.property as Identifier).name]; + } } hasEffects(): boolean { diff --git a/test/form/samples/treeshake-static-dynamic-import/_expected.js b/test/form/samples/treeshake-static-dynamic-import/_expected.js index 330d2d1c6df..ceab4e0163b 100644 --- a/test/form/samples/treeshake-static-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-static-dynamic-import/_expected.js @@ -1,10 +1,21 @@ async function entry() { + // simple const { foo1: foo } = await Promise.resolve().then(function () { return sub1; }); + + // fail out const { foo2 } = await Promise.resolve().then(function () { return sub2; }); + Promise.resolve().then(function () { return sub2; }) // this should make sub2.js not be tree-shaken - Promise.resolve().then(function () { return sub2; }); // this should make sub2.js not be tree-shaken + // multiple + ;(await Promise.resolve().then(function () { return sub3; })).bar3(); + const { foo3, baz3 } = await Promise.resolve().then(function () { return sub3; }); - console.log(foo(), foo2()); + console.log([ + foo(), + foo2(), + foo3(), + baz3(), + ]); } function foo1() { @@ -32,4 +43,23 @@ var sub2 = /*#__PURE__*/Object.freeze({ foo2: foo2 }); +function foo3() { + return 'foo3'; +} + +function bar3() { + return 'bar3'; +} + +function baz3() { + return 'baz3'; +} + +var sub3 = /*#__PURE__*/Object.freeze({ + __proto__: null, + bar3: bar3, + baz3: baz3, + foo3: foo3 +}); + export { entry }; diff --git a/test/form/samples/treeshake-static-dynamic-import/main.js b/test/form/samples/treeshake-static-dynamic-import/main.js index 9a2621f4ba7..f915d50c663 100644 --- a/test/form/samples/treeshake-static-dynamic-import/main.js +++ b/test/form/samples/treeshake-static-dynamic-import/main.js @@ -1,8 +1,19 @@ export async function entry() { + // simple const { foo1: foo } = await import('./sub1.js'); - const { foo2 } = await import('./sub2.js'); + // fail out + const { foo2 } = await import('./sub2.js'); const promise = import('./sub2.js') // this should make sub2.js not be tree-shaken - console.log(foo(), foo2()); + // multiple + ;(await import('./sub3')).bar3() + const { foo3, baz3 } = await import('./sub3.js'); + + console.log([ + foo(), + foo2(), + foo3(), + baz3(), + ]); } diff --git a/test/form/samples/treeshake-static-dynamic-import/sub3.js b/test/form/samples/treeshake-static-dynamic-import/sub3.js new file mode 100644 index 00000000000..0db9cc6e49c --- /dev/null +++ b/test/form/samples/treeshake-static-dynamic-import/sub3.js @@ -0,0 +1,15 @@ +export function foo3() { + return 'foo3'; +} + +export function bar3() { + return 'bar3'; +} + +export function baz3() { + return 'baz3'; +} + +export function qux3() { + return 'qux3'; // this should be tree-shaken +} From a1c5639f20c338cee0acddfe39554ff577790b71 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 21:41:45 +0200 Subject: [PATCH 06/25] chore: update --- test/form/samples/treeshake-static-dynamic-import/_config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/form/samples/treeshake-static-dynamic-import/_config.js b/test/form/samples/treeshake-static-dynamic-import/_config.js index 1aed137b1d8..d8fa4163017 100644 --- a/test/form/samples/treeshake-static-dynamic-import/_config.js +++ b/test/form/samples/treeshake-static-dynamic-import/_config.js @@ -1,5 +1,4 @@ module.exports = { - solo: true, description: 'treeshakes dynamic imports when the target is statically known', options: { output: { From d80ef4adc2d6d0a363e6d8156923e98472b13280 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 21:44:41 +0200 Subject: [PATCH 07/25] chore: rename --- src/Module.ts | 2 +- src/rollup/types.d.ts | 2 +- src/utils/options/normalizeInputOptions.ts | 2 +- src/utils/options/options.ts | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Module.ts b/src/Module.ts index 994cda6587b..273b962ab5a 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -1238,7 +1238,7 @@ export default class Module { resolution.includedDynamicImporters.push(this); const staticVariables = - this.options.treeshake && this.options.treeshake.staticDynamicImports + this.options.treeshake && this.options.treeshake.deterministicDynamicImports ? node.getStaticImportedNames() : undefined; diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index d48604dd3f6..ae4caa8d655 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -476,10 +476,10 @@ type TreeshakingPreset = 'smallest' | 'safest' | 'recommended'; export interface NormalizedTreeshakingOptions { annotations: boolean; correctVarValueBeforeDeclaration: boolean; + deterministicDynamicImports: boolean; manualPureFunctions: readonly string[]; moduleSideEffects: HasModuleSideEffects; propertyReadSideEffects: boolean | 'always'; - staticDynamicImports: boolean; tryCatchDeoptimization: boolean; unknownGlobalSideEffects: boolean; } diff --git a/src/utils/options/normalizeInputOptions.ts b/src/utils/options/normalizeInputOptions.ts index 3855c2e57b7..b2bff1190a3 100644 --- a/src/utils/options/normalizeInputOptions.ts +++ b/src/utils/options/normalizeInputOptions.ts @@ -269,6 +269,7 @@ const getTreeshake = (config: InputOptions): NormalizedInputOptions['treeshake'] return { annotations: configWithPreset.annotations !== false, correctVarValueBeforeDeclaration: configWithPreset.correctVarValueBeforeDeclaration === true, + deterministicDynamicImports: configWithPreset.deterministicDynamicImports !== false, manualPureFunctions: (configWithPreset.manualPureFunctions as readonly string[] | undefined) ?? EMPTY_ARRAY, moduleSideEffects: getHasModuleSideEffects( @@ -278,7 +279,6 @@ const getTreeshake = (config: InputOptions): NormalizedInputOptions['treeshake'] configWithPreset.propertyReadSideEffects === 'always' ? 'always' : configWithPreset.propertyReadSideEffects !== false, - staticDynamicImports: configWithPreset.staticDynamicImports !== false, tryCatchDeoptimization: configWithPreset.tryCatchDeoptimization !== false, unknownGlobalSideEffects: configWithPreset.unknownGlobalSideEffects !== false }; diff --git a/src/utils/options/options.ts b/src/utils/options/options.ts index 688bb4e4a02..b7f454ef9a6 100644 --- a/src/utils/options/options.ts +++ b/src/utils/options/options.ts @@ -47,30 +47,30 @@ export const treeshakePresets: { recommended: { annotations: true, correctVarValueBeforeDeclaration: false, + deterministicDynamicImports: false, manualPureFunctions: EMPTY_ARRAY, moduleSideEffects: () => true, propertyReadSideEffects: true, - staticDynamicImports: false, tryCatchDeoptimization: true, unknownGlobalSideEffects: false }, safest: { annotations: true, correctVarValueBeforeDeclaration: true, + deterministicDynamicImports: true, manualPureFunctions: EMPTY_ARRAY, moduleSideEffects: () => true, propertyReadSideEffects: true, - staticDynamicImports: true, tryCatchDeoptimization: true, unknownGlobalSideEffects: true }, smallest: { annotations: true, correctVarValueBeforeDeclaration: false, + deterministicDynamicImports: false, manualPureFunctions: EMPTY_ARRAY, moduleSideEffects: () => false, propertyReadSideEffects: false, - staticDynamicImports: false, tryCatchDeoptimization: false, unknownGlobalSideEffects: false } From efdaf9feb7346d6298fab4894f34b3ccd757a85f Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 21:52:25 +0200 Subject: [PATCH 08/25] chore: rename --- src/Module.ts | 8 ++++---- src/ast/nodes/ImportExpression.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Module.ts b/src/Module.ts index 273b962ab5a..4856eb9daf8 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -1237,13 +1237,13 @@ export default class Module { if (resolution instanceof Module) { resolution.includedDynamicImporters.push(this); - const staticVariables = + const importedNames = this.options.treeshake && this.options.treeshake.deterministicDynamicImports - ? node.getStaticImportedNames() + ? node.getDeterministicImportedNames() : undefined; - if (staticVariables) { - resolution.includeExportsByNames(staticVariables); + if (importedNames) { + resolution.includeExportsByNames(importedNames); } else { resolution.includeAllExports(true); } diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index 09ddfea5694..4c401a84aec 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -51,14 +51,14 @@ export default class ImportExpression extends NodeBase { } /** - * Get imported variables for static usage, valid cases are: + * Get imported variables for deterministic usage, valid cases are: * * - `const { foo } = await import('bar')`. * - `(await import('bar')).foo` * * Returns undefined if it's not deterministic. */ - getStaticImportedNames(): string[] | undefined { + getDeterministicImportedNames(): string[] | undefined { if (this.parent?.type !== 'AwaitExpression') return; const awaitExpression = this.parent as AwaitExpression; From 6aa9614274be999e4f192160829d2c57473337a5 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 21:56:06 +0200 Subject: [PATCH 09/25] feat: add more case --- .../treeshake-static-dynamic-import/_expected.js | 11 ++++++++++- .../samples/treeshake-static-dynamic-import/main.js | 2 ++ .../samples/treeshake-static-dynamic-import/sub3.js | 2 ++ .../samples/treeshake-static-dynamic-import/sub4.js | 13 +++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 test/form/samples/treeshake-static-dynamic-import/sub4.js diff --git a/test/form/samples/treeshake-static-dynamic-import/_expected.js b/test/form/samples/treeshake-static-dynamic-import/_expected.js index ceab4e0163b..cb69604f523 100644 --- a/test/form/samples/treeshake-static-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-static-dynamic-import/_expected.js @@ -9,11 +9,13 @@ async function entry() { // multiple ;(await Promise.resolve().then(function () { return sub3; })).bar3(); const { foo3, baz3 } = await Promise.resolve().then(function () { return sub3; }); + const { foo4 } = await Promise.resolve().then(function () { return sub3; }); console.log([ foo(), foo2(), foo3(), + foo4(), baz3(), ]); } @@ -43,6 +45,12 @@ var sub2 = /*#__PURE__*/Object.freeze({ foo2: foo2 }); +function foo4() { + return 'foo4'; +} + +console.log('side-effect4'); + function foo3() { return 'foo3'; } @@ -59,7 +67,8 @@ var sub3 = /*#__PURE__*/Object.freeze({ __proto__: null, bar3: bar3, baz3: baz3, - foo3: foo3 + foo3: foo3, + foo4: foo4 }); export { entry }; diff --git a/test/form/samples/treeshake-static-dynamic-import/main.js b/test/form/samples/treeshake-static-dynamic-import/main.js index f915d50c663..7ce59d5a353 100644 --- a/test/form/samples/treeshake-static-dynamic-import/main.js +++ b/test/form/samples/treeshake-static-dynamic-import/main.js @@ -9,11 +9,13 @@ export async function entry() { // multiple ;(await import('./sub3')).bar3() const { foo3, baz3 } = await import('./sub3.js'); + const { foo4 } = await import('./sub3.js'); console.log([ foo(), foo2(), foo3(), + foo4(), baz3(), ]); } diff --git a/test/form/samples/treeshake-static-dynamic-import/sub3.js b/test/form/samples/treeshake-static-dynamic-import/sub3.js index 0db9cc6e49c..9761af7f9f9 100644 --- a/test/form/samples/treeshake-static-dynamic-import/sub3.js +++ b/test/form/samples/treeshake-static-dynamic-import/sub3.js @@ -13,3 +13,5 @@ export function baz3() { export function qux3() { return 'qux3'; // this should be tree-shaken } + +export * from './sub4.js'; diff --git a/test/form/samples/treeshake-static-dynamic-import/sub4.js b/test/form/samples/treeshake-static-dynamic-import/sub4.js new file mode 100644 index 00000000000..e0ccc221a0a --- /dev/null +++ b/test/form/samples/treeshake-static-dynamic-import/sub4.js @@ -0,0 +1,13 @@ +export function foo4() { + return 'foo4'; +} + +export function bar4() { + return 'bar4'; // this should be tree-shaken +} + +export function baz4() { + return 'baz4'; // this should be tree-shaken +} + +console.log('side-effect4') From 4b1c430c15e3c71c2c598c2c7a8b9c12e60859b6 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 22:10:42 +0200 Subject: [PATCH 10/25] chore: improve coverage --- .../_config.js | 1 + .../_expected.js | 97 ++++++++++++------- .../treeshake-static-dynamic-import/bail1.js | 7 ++ .../treeshake-static-dynamic-import/bail2.js | 7 ++ .../treeshake-static-dynamic-import/bail3.js | 7 ++ .../treeshake-static-dynamic-import/bail4.js | 7 ++ .../treeshake-static-dynamic-import/main.js | 26 +++-- .../treeshake-static-dynamic-import/sub2.js | 10 ++ .../treeshake-static-dynamic-import/sub3.js | 18 ++-- .../treeshake-static-dynamic-import/sub4.js | 13 --- 10 files changed, 118 insertions(+), 75 deletions(-) create mode 100644 test/form/samples/treeshake-static-dynamic-import/bail1.js create mode 100644 test/form/samples/treeshake-static-dynamic-import/bail2.js create mode 100644 test/form/samples/treeshake-static-dynamic-import/bail3.js create mode 100644 test/form/samples/treeshake-static-dynamic-import/bail4.js delete mode 100644 test/form/samples/treeshake-static-dynamic-import/sub4.js diff --git a/test/form/samples/treeshake-static-dynamic-import/_config.js b/test/form/samples/treeshake-static-dynamic-import/_config.js index d8fa4163017..1aed137b1d8 100644 --- a/test/form/samples/treeshake-static-dynamic-import/_config.js +++ b/test/form/samples/treeshake-static-dynamic-import/_config.js @@ -1,4 +1,5 @@ module.exports = { + solo: true, description: 'treeshakes dynamic imports when the target is statically known', options: { output: { diff --git a/test/form/samples/treeshake-static-dynamic-import/_expected.js b/test/form/samples/treeshake-static-dynamic-import/_expected.js index cb69604f523..292b6b35a2c 100644 --- a/test/form/samples/treeshake-static-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-static-dynamic-import/_expected.js @@ -1,23 +1,19 @@ async function entry() { // simple const { foo1: foo } = await Promise.resolve().then(function () { return sub1; }); +(await Promise.resolve().then(function () { return sub2; })).bar3(); + await Promise.resolve().then(function () { return sub2; }); + await Promise.resolve().then(function () { return sub2; }); - // fail out - const { foo2 } = await Promise.resolve().then(function () { return sub2; }); - Promise.resolve().then(function () { return sub2; }) // this should make sub2.js not be tree-shaken - - // multiple - ;(await Promise.resolve().then(function () { return sub3; })).bar3(); - const { foo3, baz3 } = await Promise.resolve().then(function () { return sub3; }); - const { foo4 } = await Promise.resolve().then(function () { return sub3; }); - - console.log([ - foo(), - foo2(), - foo3(), - foo4(), - baz3(), - ]); + // bail out + await Promise.resolve().then(function () { return bail1$1; }); + Promise.resolve().then(function () { return bail1$1; }); // this should make full1.js not be tree-shaken + + await Promise.resolve().then(function () { return bail2$1; }) + + (await Promise.resolve().then(function () { return bail3$1; }))[foo]; + + await Promise.resolve().then(function () { return bail4$1; }).bail4; } function foo1() { @@ -31,44 +27,71 @@ var sub1 = /*#__PURE__*/Object.freeze({ foo1: foo1 }); -function foo2() { - return 'foo2'; +function foo4() { + return 'foo4'; } -function bar2() { - return 'bar2'; -} +console.log('side-effect4'); var sub2 = /*#__PURE__*/Object.freeze({ __proto__: null, - bar2: bar2, - foo2: foo2 + foo4: foo4 }); -function foo4() { - return 'foo4'; +function bail1() { + return 'bail1'; } -console.log('side-effect4'); +function bailout1() { + return 'bailout1'; +} + +var bail1$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + bail1: bail1, + bailout1: bailout1 +}); -function foo3() { - return 'foo3'; +function bail2() { + return 'bail2'; } -function bar3() { - return 'bar3'; +function bailout2() { + return 'bailout2'; } -function baz3() { - return 'baz3'; +var bail2$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + bail2: bail2, + bailout2: bailout2 +}); + +function bail3() { + return 'bail3'; } -var sub3 = /*#__PURE__*/Object.freeze({ +function bailout3() { + return 'bailout3'; +} + +var bail3$1 = /*#__PURE__*/Object.freeze({ __proto__: null, - bar3: bar3, - baz3: baz3, - foo3: foo3, - foo4: foo4 + bail3: bail3, + bailout3: bailout3 +}); + +function bail4() { + return 'bail4'; +} + +function bailout4() { + return 'bailout4'; +} + +var bail4$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + bail4: bail4, + bailout4: bailout4 }); export { entry }; diff --git a/test/form/samples/treeshake-static-dynamic-import/bail1.js b/test/form/samples/treeshake-static-dynamic-import/bail1.js new file mode 100644 index 00000000000..41d94dfe374 --- /dev/null +++ b/test/form/samples/treeshake-static-dynamic-import/bail1.js @@ -0,0 +1,7 @@ +export function bail1() { + return 'bail1'; +} + +export function bailout1() { + return 'bailout1'; +} diff --git a/test/form/samples/treeshake-static-dynamic-import/bail2.js b/test/form/samples/treeshake-static-dynamic-import/bail2.js new file mode 100644 index 00000000000..47d156b14de --- /dev/null +++ b/test/form/samples/treeshake-static-dynamic-import/bail2.js @@ -0,0 +1,7 @@ +export function bail2() { + return 'bail2'; +} + +export function bailout2() { + return 'bailout2'; +} diff --git a/test/form/samples/treeshake-static-dynamic-import/bail3.js b/test/form/samples/treeshake-static-dynamic-import/bail3.js new file mode 100644 index 00000000000..9e2cfbead08 --- /dev/null +++ b/test/form/samples/treeshake-static-dynamic-import/bail3.js @@ -0,0 +1,7 @@ +export function bail3() { + return 'bail3'; +} + +export function bailout3() { + return 'bailout3'; +} diff --git a/test/form/samples/treeshake-static-dynamic-import/bail4.js b/test/form/samples/treeshake-static-dynamic-import/bail4.js new file mode 100644 index 00000000000..4bf2751bacc --- /dev/null +++ b/test/form/samples/treeshake-static-dynamic-import/bail4.js @@ -0,0 +1,7 @@ +export function bail4() { + return 'bail4'; +} + +export function bailout4() { + return 'bailout4'; +} diff --git a/test/form/samples/treeshake-static-dynamic-import/main.js b/test/form/samples/treeshake-static-dynamic-import/main.js index 7ce59d5a353..fb19970135e 100644 --- a/test/form/samples/treeshake-static-dynamic-import/main.js +++ b/test/form/samples/treeshake-static-dynamic-import/main.js @@ -2,20 +2,18 @@ export async function entry() { // simple const { foo1: foo } = await import('./sub1.js'); - // fail out - const { foo2 } = await import('./sub2.js'); - const promise = import('./sub2.js') // this should make sub2.js not be tree-shaken - // multiple - ;(await import('./sub3')).bar3() - const { foo3, baz3 } = await import('./sub3.js'); - const { foo4 } = await import('./sub3.js'); + ;(await import('./sub2.js')).bar3() + const { foo3, baz3 } = await import('./sub2.js'); + const { foo4 } = await import('./sub2.js'); + + // bail out + const { bail1 } = await import('./bail1.js'); + const promise = import('./bail1.js') // this should make full1.js not be tree-shaken + + const { ...bail2 } = await import('./bail2.js') + + (await import('./bail3.js'))[foo] - console.log([ - foo(), - foo2(), - foo3(), - foo4(), - baz3(), - ]); + await import('./bail4.js').bail4 } diff --git a/test/form/samples/treeshake-static-dynamic-import/sub2.js b/test/form/samples/treeshake-static-dynamic-import/sub2.js index 790df2055a7..28f5f6e59ed 100644 --- a/test/form/samples/treeshake-static-dynamic-import/sub2.js +++ b/test/form/samples/treeshake-static-dynamic-import/sub2.js @@ -5,3 +5,13 @@ export function foo2() { export function bar2() { return 'bar2'; } + +export function baz2() { + return 'baz2'; +} + +export function qux2() { + return 'qux2'; // this should be tree-shaken +} + +export * from './sub3.js'; diff --git a/test/form/samples/treeshake-static-dynamic-import/sub3.js b/test/form/samples/treeshake-static-dynamic-import/sub3.js index 9761af7f9f9..e0ccc221a0a 100644 --- a/test/form/samples/treeshake-static-dynamic-import/sub3.js +++ b/test/form/samples/treeshake-static-dynamic-import/sub3.js @@ -1,17 +1,13 @@ -export function foo3() { - return 'foo3'; +export function foo4() { + return 'foo4'; } -export function bar3() { - return 'bar3'; +export function bar4() { + return 'bar4'; // this should be tree-shaken } -export function baz3() { - return 'baz3'; +export function baz4() { + return 'baz4'; // this should be tree-shaken } -export function qux3() { - return 'qux3'; // this should be tree-shaken -} - -export * from './sub4.js'; +console.log('side-effect4') diff --git a/test/form/samples/treeshake-static-dynamic-import/sub4.js b/test/form/samples/treeshake-static-dynamic-import/sub4.js deleted file mode 100644 index e0ccc221a0a..00000000000 --- a/test/form/samples/treeshake-static-dynamic-import/sub4.js +++ /dev/null @@ -1,13 +0,0 @@ -export function foo4() { - return 'foo4'; -} - -export function bar4() { - return 'bar4'; // this should be tree-shaken -} - -export function baz4() { - return 'baz4'; // this should be tree-shaken -} - -console.log('side-effect4') From 8471f9062569a7b190b92dd7adaa504019570861 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 22:16:23 +0200 Subject: [PATCH 11/25] chore: revert solo flag --- test/form/samples/treeshake-static-dynamic-import/_config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/form/samples/treeshake-static-dynamic-import/_config.js b/test/form/samples/treeshake-static-dynamic-import/_config.js index 1aed137b1d8..d8fa4163017 100644 --- a/test/form/samples/treeshake-static-dynamic-import/_config.js +++ b/test/form/samples/treeshake-static-dynamic-import/_config.js @@ -1,5 +1,4 @@ module.exports = { - solo: true, description: 'treeshakes dynamic imports when the target is statically known', options: { output: { From 913bb4c94466b14623eabbe528dc0af3924efc29 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 20 Apr 2023 23:31:56 +0200 Subject: [PATCH 12/25] chore: refactor --- src/ast/nodes/ImportExpression.ts | 12 +++++++----- .../_config.js | 2 +- .../_expected.js | 0 .../bail1.js | 0 .../bail2.js | 0 .../bail3.js | 0 .../bail4.js | 0 .../main.js | 0 .../sub1.js | 0 .../sub2.js | 0 .../sub3.js | 0 11 files changed, 8 insertions(+), 6 deletions(-) rename test/form/samples/{treeshake-static-dynamic-import => treeshake-deterministic-dynamic-import}/_config.js (50%) rename test/form/samples/{treeshake-static-dynamic-import => treeshake-deterministic-dynamic-import}/_expected.js (100%) rename test/form/samples/{treeshake-static-dynamic-import => treeshake-deterministic-dynamic-import}/bail1.js (100%) rename test/form/samples/{treeshake-static-dynamic-import => treeshake-deterministic-dynamic-import}/bail2.js (100%) rename test/form/samples/{treeshake-static-dynamic-import => treeshake-deterministic-dynamic-import}/bail3.js (100%) rename test/form/samples/{treeshake-static-dynamic-import => treeshake-deterministic-dynamic-import}/bail4.js (100%) rename test/form/samples/{treeshake-static-dynamic-import => treeshake-deterministic-dynamic-import}/main.js (100%) rename test/form/samples/{treeshake-static-dynamic-import => treeshake-deterministic-dynamic-import}/sub1.js (100%) rename test/form/samples/{treeshake-static-dynamic-import => treeshake-deterministic-dynamic-import}/sub2.js (100%) rename test/form/samples/{treeshake-static-dynamic-import => treeshake-deterministic-dynamic-import}/sub3.js (100%) diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index 4c401a84aec..193c39150c7 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -73,9 +73,12 @@ export default class ImportExpression extends NodeBase { const variables: string[] = []; for (const property of objectPattern.properties) { - if (property.type === 'RestElement') return; - if (property.computed) return; - if (property.key.type !== 'Identifier') return; + if ( + property.type === 'RestElement' || + property.computed || + property.key.type !== 'Identifier' + ) + return; variables.push((property.key as Identifier).name); } @@ -86,8 +89,7 @@ export default class ImportExpression extends NodeBase { if (awaitExpression.parent?.type !== 'MemberExpression') return; const memberExpression = awaitExpression.parent as MemberExpression; - if (memberExpression.computed) return; - if (memberExpression.property.type !== 'Identifier') return; + if (memberExpression.computed || memberExpression.property.type !== 'Identifier') return; return [(memberExpression.property as Identifier).name]; } diff --git a/test/form/samples/treeshake-static-dynamic-import/_config.js b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js similarity index 50% rename from test/form/samples/treeshake-static-dynamic-import/_config.js rename to test/form/samples/treeshake-deterministic-dynamic-import/_config.js index d8fa4163017..762f200f152 100644 --- a/test/form/samples/treeshake-static-dynamic-import/_config.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js @@ -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 diff --git a/test/form/samples/treeshake-static-dynamic-import/_expected.js b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js similarity index 100% rename from test/form/samples/treeshake-static-dynamic-import/_expected.js rename to test/form/samples/treeshake-deterministic-dynamic-import/_expected.js diff --git a/test/form/samples/treeshake-static-dynamic-import/bail1.js b/test/form/samples/treeshake-deterministic-dynamic-import/bail1.js similarity index 100% rename from test/form/samples/treeshake-static-dynamic-import/bail1.js rename to test/form/samples/treeshake-deterministic-dynamic-import/bail1.js diff --git a/test/form/samples/treeshake-static-dynamic-import/bail2.js b/test/form/samples/treeshake-deterministic-dynamic-import/bail2.js similarity index 100% rename from test/form/samples/treeshake-static-dynamic-import/bail2.js rename to test/form/samples/treeshake-deterministic-dynamic-import/bail2.js diff --git a/test/form/samples/treeshake-static-dynamic-import/bail3.js b/test/form/samples/treeshake-deterministic-dynamic-import/bail3.js similarity index 100% rename from test/form/samples/treeshake-static-dynamic-import/bail3.js rename to test/form/samples/treeshake-deterministic-dynamic-import/bail3.js diff --git a/test/form/samples/treeshake-static-dynamic-import/bail4.js b/test/form/samples/treeshake-deterministic-dynamic-import/bail4.js similarity index 100% rename from test/form/samples/treeshake-static-dynamic-import/bail4.js rename to test/form/samples/treeshake-deterministic-dynamic-import/bail4.js diff --git a/test/form/samples/treeshake-static-dynamic-import/main.js b/test/form/samples/treeshake-deterministic-dynamic-import/main.js similarity index 100% rename from test/form/samples/treeshake-static-dynamic-import/main.js rename to test/form/samples/treeshake-deterministic-dynamic-import/main.js diff --git a/test/form/samples/treeshake-static-dynamic-import/sub1.js b/test/form/samples/treeshake-deterministic-dynamic-import/sub1.js similarity index 100% rename from test/form/samples/treeshake-static-dynamic-import/sub1.js rename to test/form/samples/treeshake-deterministic-dynamic-import/sub1.js diff --git a/test/form/samples/treeshake-static-dynamic-import/sub2.js b/test/form/samples/treeshake-deterministic-dynamic-import/sub2.js similarity index 100% rename from test/form/samples/treeshake-static-dynamic-import/sub2.js rename to test/form/samples/treeshake-deterministic-dynamic-import/sub2.js diff --git a/test/form/samples/treeshake-static-dynamic-import/sub3.js b/test/form/samples/treeshake-deterministic-dynamic-import/sub3.js similarity index 100% rename from test/form/samples/treeshake-static-dynamic-import/sub3.js rename to test/form/samples/treeshake-deterministic-dynamic-import/sub3.js From 9ac9832613f12082faa0e6c8ec101eaaef97449e Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 21 Apr 2023 01:18:32 +0200 Subject: [PATCH 13/25] feat: support more case --- src/Module.ts | 14 ++- src/ast/nodes/ImportExpression.ts | 77 ++++++++---- .../_config.js | 24 +++- .../_expected.js | 111 +++++++++++------- .../bail1.js | 7 -- .../bail2.js | 7 -- .../bail3.js | 7 -- .../bail4.js | 7 -- .../main.js | 22 ++-- .../sub2.js | 2 + .../sub3.js | 14 +-- 11 files changed, 175 insertions(+), 117 deletions(-) delete mode 100644 test/form/samples/treeshake-deterministic-dynamic-import/bail1.js delete mode 100644 test/form/samples/treeshake-deterministic-dynamic-import/bail2.js delete mode 100644 test/form/samples/treeshake-deterministic-dynamic-import/bail3.js delete mode 100644 test/form/samples/treeshake-deterministic-dynamic-import/bail4.js diff --git a/src/Module.ts b/src/Module.ts index 4856eb9daf8..1bb1c7b441d 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -716,7 +716,7 @@ export default class Module { this.includeAllExports(false); } - includeExportsByNames(names: string[]): void { + includeExportsByNames(names: string[], includeNamespaceMembers: boolean): void { for (const name of names) { const variable = this.getVariableForExportName(name)[0]; if (variable) { @@ -724,11 +724,15 @@ export default class Module { if (!variable.included) { this.includeVariable(variable); } - if (variable instanceof ExternalVariable) { - variable.module.reexported = true; - } + // if (variable instanceof ExternalVariable) { + // variable.module.reexported = true; + // } } } + + if (includeNamespaceMembers) { + this.namespace.setMergedNamespaces(this.includeAndGetAdditionalMergedNamespaces()); + } } isIncluded(): boolean | null { @@ -1243,7 +1247,7 @@ export default class Module { : undefined; if (importedNames) { - resolution.includeExportsByNames(importedNames); + resolution.includeExportsByNames(importedNames, true); } else { resolution.includeAllExports(true); } diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index 193c39150c7..8fca5c0352f 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -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 FunctionExpression from './FunctionExpression'; import type Identifier from './Identifier'; import type MemberExpression from './MemberExpression'; import type * as NodeType from './NodeType'; @@ -55,43 +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' || - property.computed || - 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', 'FunctionExpression'].includes( + callExpression.arguments[0].type + ) + ) + return; - const memberExpression = awaitExpression.parent as MemberExpression; - if (memberExpression.computed || memberExpression.property.type !== 'Identifier') return; + const callback = callExpression.arguments[0] as ArrowFunctionExpression | FunctionExpression; + if (callback.params.length !== 1 || callback.params[0].type !== 'ObjectPattern') return; - return [(memberExpression.property as Identifier).name]; + return getDeterministicObjectDestructure(callback.params[0] as ObjectPattern); } } @@ -346,3 +361,15 @@ const accessedImportGlobals: Record = { cjs: ['require'], system: ['module'] }; + +function getDeterministicObjectDestructure(objectPattern: ObjectPattern): string[] | undefined { + const variables: string[] = []; + + for (const property of objectPattern.properties) { + if (property.type === 'RestElement' || property.computed || property.key.type !== 'Identifier') + return; + variables.push((property.key as Identifier).name); + } + + return variables; +} diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js index 762f200f152..1717f1b4f25 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js @@ -3,6 +3,28 @@ module.exports = { options: { output: { inlineDynamicImports: true - } + }, + plugins: [ + { + resolveId(id) { + if (/bail-(\d+).js$/.test(id)) { + return id; + } + return null; + }, + load(id) { + const match = /bail-(\d+).js$/.exec(id); + if (match) { + return { + code: [ + `export default 'should be included ${match[1]}'`, + `export const named${match[1]} = 'bail${match[1]}';` + ].join('\n') + }; + } + return null; + } + } + ] } }; diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js index 292b6b35a2c..ae725071d15 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js @@ -1,19 +1,25 @@ async function entry() { // simple const { foo1: foo } = await Promise.resolve().then(function () { return sub1; }); -(await Promise.resolve().then(function () { return sub2; })).bar3(); +(await Promise.resolve().then(function () { return sub2; })).bar2(); await Promise.resolve().then(function () { return sub2; }); await Promise.resolve().then(function () { return sub2; }); + Promise.resolve().then(function () { return sub2; }).then(({ baz2 }) => baz2); + Promise.resolve().then(function () { return sub2; }).then(function({ reexported }) { }); // bail out await Promise.resolve().then(function () { return bail1$1; }); - Promise.resolve().then(function () { return bail1$1; }); // this should make full1.js not be tree-shaken + Promise.resolve().then(function () { return bail1$1; }); // this make it bail out await Promise.resolve().then(function () { return bail2$1; }) (await Promise.resolve().then(function () { return bail3$1; }))[foo]; - await Promise.resolve().then(function () { return bail4$1; }).bail4; + await Promise.resolve().then(function () { return bail4$1; }).name4; + + Promise.resolve().then(function () { return bail5$1; }).then(foo); + + await Promise.resolve().then(function () { return bail6$1; }).then(function({ named6, ...args }) { }); } function foo1() { @@ -27,71 +33,90 @@ var sub1 = /*#__PURE__*/Object.freeze({ foo1: foo1 }); -function foo4() { - return 'foo4'; +function foo3() { + return 'foo3'; } -console.log('side-effect4'); +function bar3() { + return 'bar3'; +} -var sub2 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo4: foo4 -}); +console.log('side-effect3'); -function bail1() { - return 'bail1'; +function foo2() { + return 'foo2'; } -function bailout1() { - return 'bailout1'; +function bar2() { + return 'bar2'; } -var bail1$1 = /*#__PURE__*/Object.freeze({ +function baz2() { + return 'baz2'; +} + +var sub2 = /*#__PURE__*/Object.freeze({ __proto__: null, - bail1: bail1, - bailout1: bailout1 + bar2: bar2, + bar3: bar3, + baz2: baz2, + foo2: foo2, + foo3: foo3, + reexported: bar3 }); -function bail2() { - return 'bail2'; -} +var bail1 = 'should be included 1'; +const named1 = 'bail1'; -function bailout2() { - return 'bailout2'; -} +var bail1$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + default: bail1, + named1: named1 +}); + +var bail2 = 'should be included 2'; +const named2 = 'bail2'; var bail2$1 = /*#__PURE__*/Object.freeze({ __proto__: null, - bail2: bail2, - bailout2: bailout2 + default: bail2, + named2: named2 }); -function bail3() { - return 'bail3'; -} - -function bailout3() { - return 'bailout3'; -} +var bail3 = 'should be included 3'; +const named3 = 'bail3'; var bail3$1 = /*#__PURE__*/Object.freeze({ __proto__: null, - bail3: bail3, - bailout3: bailout3 + default: bail3, + named3: named3 }); -function bail4() { - return 'bail4'; -} - -function bailout4() { - return 'bailout4'; -} +var bail4 = 'should be included 4'; +const named4 = 'bail4'; var bail4$1 = /*#__PURE__*/Object.freeze({ __proto__: null, - bail4: bail4, - bailout4: bailout4 + default: bail4, + named4: named4 +}); + +var bail5 = 'should be included 5'; +const named5 = 'bail5'; + +var bail5$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + default: bail5, + named5: named5 +}); + +var bail6 = 'should be included 6'; +const named6 = 'bail6'; + +var bail6$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + default: bail6, + named6: named6 }); export { entry }; diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/bail1.js b/test/form/samples/treeshake-deterministic-dynamic-import/bail1.js deleted file mode 100644 index 41d94dfe374..00000000000 --- a/test/form/samples/treeshake-deterministic-dynamic-import/bail1.js +++ /dev/null @@ -1,7 +0,0 @@ -export function bail1() { - return 'bail1'; -} - -export function bailout1() { - return 'bailout1'; -} diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/bail2.js b/test/form/samples/treeshake-deterministic-dynamic-import/bail2.js deleted file mode 100644 index 47d156b14de..00000000000 --- a/test/form/samples/treeshake-deterministic-dynamic-import/bail2.js +++ /dev/null @@ -1,7 +0,0 @@ -export function bail2() { - return 'bail2'; -} - -export function bailout2() { - return 'bailout2'; -} diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/bail3.js b/test/form/samples/treeshake-deterministic-dynamic-import/bail3.js deleted file mode 100644 index 9e2cfbead08..00000000000 --- a/test/form/samples/treeshake-deterministic-dynamic-import/bail3.js +++ /dev/null @@ -1,7 +0,0 @@ -export function bail3() { - return 'bail3'; -} - -export function bailout3() { - return 'bailout3'; -} diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/bail4.js b/test/form/samples/treeshake-deterministic-dynamic-import/bail4.js deleted file mode 100644 index 4bf2751bacc..00000000000 --- a/test/form/samples/treeshake-deterministic-dynamic-import/bail4.js +++ /dev/null @@ -1,7 +0,0 @@ -export function bail4() { - return 'bail4'; -} - -export function bailout4() { - return 'bailout4'; -} diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/main.js b/test/form/samples/treeshake-deterministic-dynamic-import/main.js index fb19970135e..63b9d557915 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/main.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/main.js @@ -3,17 +3,23 @@ export async function entry() { const { foo1: foo } = await import('./sub1.js'); // multiple - ;(await import('./sub2.js')).bar3() - const { foo3, baz3 } = await import('./sub2.js'); - const { foo4 } = await import('./sub2.js'); + ;(await import('./sub2.js')).bar2() + const { foo2 } = await import('./sub2.js'); + const { foo3 } = await import('./sub2.js'); + import('./sub2.js').then(({ baz2 }) => baz2) + import('./sub2.js').then(function({ reexported }) { reexported }) // bail out - const { bail1 } = await import('./bail1.js'); - const promise = import('./bail1.js') // this should make full1.js not be tree-shaken + const { named1 } = await import('./bail-1.js'); + const promise = import('./bail-1.js') // this make it bail out - const { ...bail2 } = await import('./bail2.js') + const { ...named2 } = await import('./bail-2.js') - (await import('./bail3.js'))[foo] + (await import('./bail-3.js'))[foo] - await import('./bail4.js').bail4 + await import('./bail-4.js').name4 + + import('./bail-5.js').then(foo) + + await import('./bail-6.js').then(function({ named6, ...args }) { }) } diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/sub2.js b/test/form/samples/treeshake-deterministic-dynamic-import/sub2.js index 28f5f6e59ed..4ba13873bfc 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/sub2.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/sub2.js @@ -14,4 +14,6 @@ export function qux2() { return 'qux2'; // this should be tree-shaken } +export { bar3 as reexported } from './sub3.js' + export * from './sub3.js'; diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/sub3.js b/test/form/samples/treeshake-deterministic-dynamic-import/sub3.js index e0ccc221a0a..0e0a3c530fb 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/sub3.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/sub3.js @@ -1,13 +1,13 @@ -export function foo4() { - return 'foo4'; +export function foo3() { + return 'foo3'; } -export function bar4() { - return 'bar4'; // this should be tree-shaken +export function bar3() { + return 'bar3'; } -export function baz4() { - return 'baz4'; // this should be tree-shaken +export function baz3() { + return 'baz3'; // this should be tree-shaken } -console.log('side-effect4') +console.log('side-effect3') From 00a30eb236bf879ab9fa762c87fb3e2516549e80 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 21 Apr 2023 09:15:16 +0200 Subject: [PATCH 14/25] chore: clean up --- src/Module.ts | 7 +++---- src/rollup/types.d.ts | 1 - src/utils/options/normalizeInputOptions.ts | 1 - src/utils/options/options.ts | 3 --- test/form/samples/no-treeshake/_config.js | 3 ++- test/form/samples/no-treeshake/_expected.js | 12 ++++++++++++ test/form/samples/no-treeshake/dynamic-imported.js | 3 +++ test/form/samples/no-treeshake/main.js | 2 ++ 8 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 test/form/samples/no-treeshake/dynamic-imported.js diff --git a/src/Module.ts b/src/Module.ts index 1bb1c7b441d..7c4d16d0df3 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -1241,10 +1241,9 @@ export default class Module { if (resolution instanceof Module) { resolution.includedDynamicImporters.push(this); - const importedNames = - this.options.treeshake && this.options.treeshake.deterministicDynamicImports - ? node.getDeterministicImportedNames() - : undefined; + const importedNames = this.options.treeshake + ? node.getDeterministicImportedNames() + : undefined; if (importedNames) { resolution.includeExportsByNames(importedNames, true); diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index ae4caa8d655..94759af04ca 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -476,7 +476,6 @@ type TreeshakingPreset = 'smallest' | 'safest' | 'recommended'; export interface NormalizedTreeshakingOptions { annotations: boolean; correctVarValueBeforeDeclaration: boolean; - deterministicDynamicImports: boolean; manualPureFunctions: readonly string[]; moduleSideEffects: HasModuleSideEffects; propertyReadSideEffects: boolean | 'always'; diff --git a/src/utils/options/normalizeInputOptions.ts b/src/utils/options/normalizeInputOptions.ts index b2bff1190a3..4090a38f0db 100644 --- a/src/utils/options/normalizeInputOptions.ts +++ b/src/utils/options/normalizeInputOptions.ts @@ -269,7 +269,6 @@ const getTreeshake = (config: InputOptions): NormalizedInputOptions['treeshake'] return { annotations: configWithPreset.annotations !== false, correctVarValueBeforeDeclaration: configWithPreset.correctVarValueBeforeDeclaration === true, - deterministicDynamicImports: configWithPreset.deterministicDynamicImports !== false, manualPureFunctions: (configWithPreset.manualPureFunctions as readonly string[] | undefined) ?? EMPTY_ARRAY, moduleSideEffects: getHasModuleSideEffects( diff --git a/src/utils/options/options.ts b/src/utils/options/options.ts index b7f454ef9a6..996f623ee09 100644 --- a/src/utils/options/options.ts +++ b/src/utils/options/options.ts @@ -47,7 +47,6 @@ export const treeshakePresets: { recommended: { annotations: true, correctVarValueBeforeDeclaration: false, - deterministicDynamicImports: false, manualPureFunctions: EMPTY_ARRAY, moduleSideEffects: () => true, propertyReadSideEffects: true, @@ -57,7 +56,6 @@ export const treeshakePresets: { safest: { annotations: true, correctVarValueBeforeDeclaration: true, - deterministicDynamicImports: true, manualPureFunctions: EMPTY_ARRAY, moduleSideEffects: () => true, propertyReadSideEffects: true, @@ -67,7 +65,6 @@ export const treeshakePresets: { smallest: { annotations: true, correctVarValueBeforeDeclaration: false, - deterministicDynamicImports: false, manualPureFunctions: EMPTY_ARRAY, moduleSideEffects: () => false, propertyReadSideEffects: false, diff --git a/test/form/samples/no-treeshake/_config.js b/test/form/samples/no-treeshake/_config.js index 76d525b3517..e49231de636 100644 --- a/test/form/samples/no-treeshake/_config.js +++ b/test/form/samples/no-treeshake/_config.js @@ -5,7 +5,8 @@ module.exports = { treeshake: false, output: { globals: { external: 'external' }, - name: /* not shaken, but */ 'stirred' + name: /* not shaken, but */ 'stirred', + inlineDynamicImports: true } } }; diff --git a/test/form/samples/no-treeshake/_expected.js b/test/form/samples/no-treeshake/_expected.js index d0a6b53417a..9ec3c7d3449 100644 --- a/test/form/samples/no-treeshake/_expected.js +++ b/test/form/samples/no-treeshake/_expected.js @@ -54,4 +54,16 @@ try { const x = 1; } catch {} +const { fred: fred$1 } = await Promise.resolve().then(function () { return dynamicImported$1; }); + +const fred = 1; + +var dynamicImported = () => fred; + +var dynamicImported$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + default: dynamicImported, + fred: fred +}); + export { create, getPrototypeOf, quux, quux as strange }; diff --git a/test/form/samples/no-treeshake/dynamic-imported.js b/test/form/samples/no-treeshake/dynamic-imported.js new file mode 100644 index 00000000000..42eb4136650 --- /dev/null +++ b/test/form/samples/no-treeshake/dynamic-imported.js @@ -0,0 +1,3 @@ +export const fred = 1; + +export default () => fred; diff --git a/test/form/samples/no-treeshake/main.js b/test/form/samples/no-treeshake/main.js index 2dde2e93fa0..c60d8840915 100644 --- a/test/form/samples/no-treeshake/main.js +++ b/test/form/samples/no-treeshake/main.js @@ -51,3 +51,5 @@ test({ try { const x = 1; } catch {} + +const { fred } = await import('./dynamic-imported.js'); From 8a17025096babd694d965d2a7972ec6660aac743 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 21 Apr 2023 09:36:36 +0200 Subject: [PATCH 15/25] feat: support side-effect only dynamic import --- src/ast/nodes/ImportExpression.ts | 43 ++++++++++++-- .../_config.js | 25 +++++--- .../_expected.js | 57 ++++++++++++++++++- .../main.js | 15 ++++- 4 files changed, 125 insertions(+), 15 deletions(-) diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index 8fca5c0352f..6b8086de816 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -3,6 +3,7 @@ import ExternalModule from '../../ExternalModule'; import type Module from '../../Module'; import type { GetInterop, NormalizedOutputOptions } from '../../rollup/types'; import type { PluginDriver } from '../../utils/PluginDriver'; +import { EMPTY_ARRAY } from '../../utils/blank'; import type { GenerateCodeSnippets } from '../../utils/generateCodeSnippets'; import { INTEROP_NAMESPACE_DEFAULT_ONLY_VARIABLE, @@ -60,12 +61,23 @@ export default class ImportExpression extends NodeBase { * - `(await import('bar')).foo` * - `import('bar').then(({ foo }) => {})` * - * Returns undefined if it's not deterministic. + * Returns empty array if it's side-effect only import. + * Returns undefined if it's not fully deterministic. */ - getDeterministicImportedNames(): string[] | undefined { + getDeterministicImportedNames(): Readonly { + // side-effect only + if (this.parent.type === 'ExpressionStatement') { + return EMPTY_ARRAY; + } + if (this.parent?.type === 'AwaitExpression') { const awaitExpression = this.parent as AwaitExpression; + // side-effect only + if (awaitExpression.parent.type === 'ExpressionStatement') { + return EMPTY_ARRAY; + } + // Case 1: const { foo } = await import('bar') if (awaitExpression.parent?.type === 'VariableDeclarator') { const variableDeclarator = awaitExpression.parent as VariableDeclarator; @@ -87,14 +99,29 @@ export default class ImportExpression extends NodeBase { 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' + memberExpression.parent?.type !== 'CallExpression' || + memberExpression.property.type !== 'Identifier' ) return; + const memberName = (memberExpression.property as Identifier).name; const callExpression = memberExpression.parent as CallExpression; + // side-effect only, when only chaining .catch or .finally + if ( + callExpression.parent.type === 'ExpressionStatement' && + ['catch', 'finally'].includes(memberName) + ) { + return EMPTY_ARRAY; + } else if (memberName !== 'then') { + return; + } + + // side-effect only + if (callExpression.arguments.length === 0) { + return EMPTY_ARRAY; + } + if ( callExpression.arguments.length !== 1 || !['ArrowFunctionExpression', 'FunctionExpression'].includes( @@ -104,6 +131,12 @@ export default class ImportExpression extends NodeBase { return; const callback = callExpression.arguments[0] as ArrowFunctionExpression | FunctionExpression; + + // side-effect only + if (callback.params.length === 0) { + return EMPTY_ARRAY; + } + if (callback.params.length !== 1 || callback.params[0].type !== 'ObjectPattern') return; return getDeterministicObjectDestructure(callback.params[0] as ObjectPattern); diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js index 1717f1b4f25..f95acf12f6f 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js @@ -7,20 +7,29 @@ module.exports = { plugins: [ { resolveId(id) { - if (/bail-(\d+).js$/.test(id)) { + if (/(bail|effect)-(\d+).js$/.test(id)) { return id; } return null; }, load(id) { - const match = /bail-(\d+).js$/.exec(id); + const match = /(bail|effect)-(\d+).js$/.exec(id); if (match) { - return { - code: [ - `export default 'should be included ${match[1]}'`, - `export const named${match[1]} = 'bail${match[1]}';` - ].join('\n') - }; + if (match[1] === 'bail') + return { + code: [ + `export default 'should be included ${match[2]}'`, + `export const named${match[2]} = 'bail${match[2]}';` + ].join('\n') + }; + else if (match[1] === 'effect') { + return { + code: [ + 'export function fn() { /* this should be tree-shaken */ }', + `console.log('side-effect ${match[2]} should be included');` + ].join('\n') + }; + } } return null; } diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js index ae725071d15..c1ab5423a1c 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js @@ -7,6 +7,14 @@ async function entry() { Promise.resolve().then(function () { return sub2; }).then(({ baz2 }) => baz2); Promise.resolve().then(function () { return sub2; }).then(function({ reexported }) { }); + // side-effect only + Promise.resolve().then(function () { return effect1; }); + await Promise.resolve().then(function () { return effect2; }); + Promise.resolve().then(function () { return effect3; }).then(function() { }); + Promise.resolve().then(function () { return effect4; }).then(); + Promise.resolve().then(function () { return effect5; }).catch(() => {}); + Promise.resolve().then(function () { return effect6; }).finally(() => {}); + // bail out await Promise.resolve().then(function () { return bail1$1; }); Promise.resolve().then(function () { return bail1$1; }); // this make it bail out @@ -15,11 +23,16 @@ async function entry() { (await Promise.resolve().then(function () { return bail3$1; }))[foo]; - await Promise.resolve().then(function () { return bail4$1; }).name4; + await Promise.resolve().then(function () { return bail4$1; }).name4; // access on promise, not on export Promise.resolve().then(function () { return bail5$1; }).then(foo); await Promise.resolve().then(function () { return bail6$1; }).then(function({ named6, ...args }) { }); + + [ + Promise.resolve().then(function () { return bail7$1; }), + Promise.resolve().then(function () { return bail8$1; }), + ]; } function foo1() { @@ -65,6 +78,30 @@ var sub2 = /*#__PURE__*/Object.freeze({ reexported: bar3 }); +var effect1 = /*#__PURE__*/Object.freeze({ + __proto__: null +}); + +var effect2 = /*#__PURE__*/Object.freeze({ + __proto__: null +}); + +var effect3 = /*#__PURE__*/Object.freeze({ + __proto__: null +}); + +var effect4 = /*#__PURE__*/Object.freeze({ + __proto__: null +}); + +var effect5 = /*#__PURE__*/Object.freeze({ + __proto__: null +}); + +var effect6 = /*#__PURE__*/Object.freeze({ + __proto__: null +}); + var bail1 = 'should be included 1'; const named1 = 'bail1'; @@ -119,4 +156,22 @@ var bail6$1 = /*#__PURE__*/Object.freeze({ named6: named6 }); +var bail7 = 'should be included 7'; +const named7 = 'bail7'; + +var bail7$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + default: bail7, + named7: named7 +}); + +var bail8 = 'should be included 8'; +const named8 = 'bail8'; + +var bail8$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + default: bail8, + named8: named8 +}); + export { entry }; diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/main.js b/test/form/samples/treeshake-deterministic-dynamic-import/main.js index 63b9d557915..1bb1c8893d7 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/main.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/main.js @@ -9,6 +9,14 @@ export async function entry() { import('./sub2.js').then(({ baz2 }) => baz2) import('./sub2.js').then(function({ reexported }) { reexported }) + // side-effect only + import('./effect-1.js') + await import('./effect-2.js') + import('./effect-3.js').then(function() { }) + import('./effect-4.js').then() + import('./effect-5.js').catch(() => {}) + import('./effect-6.js').finally(() => {}) + // bail out const { named1 } = await import('./bail-1.js'); const promise = import('./bail-1.js') // this make it bail out @@ -17,9 +25,14 @@ export async function entry() { (await import('./bail-3.js'))[foo] - await import('./bail-4.js').name4 + await import('./bail-4.js').name4 // access on promise, not on export import('./bail-5.js').then(foo) await import('./bail-6.js').then(function({ named6, ...args }) { }) + + const promises = [ + import('./bail-7.js'), + import('./bail-8.js'), + ] } From 160d7ad0d96387dbe208fb5e95bb9caf4a0abd3f Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 21 Apr 2023 09:38:00 +0200 Subject: [PATCH 16/25] chore: clean up --- src/Module.ts | 5 +---- src/ast/nodes/ImportExpression.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Module.ts b/src/Module.ts index 7c4d16d0df3..20a824681ec 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -716,7 +716,7 @@ export default class Module { this.includeAllExports(false); } - includeExportsByNames(names: string[], includeNamespaceMembers: boolean): void { + includeExportsByNames(names: readonly string[], includeNamespaceMembers: boolean): void { for (const name of names) { const variable = this.getVariableForExportName(name)[0]; if (variable) { @@ -724,9 +724,6 @@ export default class Module { if (!variable.included) { this.includeVariable(variable); } - // if (variable instanceof ExternalVariable) { - // variable.module.reexported = true; - // } } } diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index 6b8086de816..e0a29b0a872 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -64,7 +64,7 @@ export default class ImportExpression extends NodeBase { * Returns empty array if it's side-effect only import. * Returns undefined if it's not fully deterministic. */ - getDeterministicImportedNames(): Readonly { + getDeterministicImportedNames(): readonly string[] | undefined { // side-effect only if (this.parent.type === 'ExpressionStatement') { return EMPTY_ARRAY; From 6ec1de6ee68b0c7d1189786eca0887bb73979210 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 21 Apr 2023 14:56:58 +0200 Subject: [PATCH 17/25] fix: side-effects --- src/Module.ts | 8 ++++++++ test/form/samples/dynamic-import-inlining/_expected.js | 9 +++------ .../samples/perf-adds-plugin-context-to-plugins/foo.js | 2 ++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Module.ts b/src/Module.ts index 20a824681ec..8297b941fb1 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -717,6 +717,11 @@ export default class Module { } includeExportsByNames(names: readonly string[], includeNamespaceMembers: boolean): void { + if (!this.isExecuted) { + markModuleAndImpureDependenciesAsExecuted(this); + this.graph.needsTreeshakingPass = true; + } + for (const name of names) { const variable = this.getVariableForExportName(name)[0]; if (variable) { @@ -724,6 +729,9 @@ export default class Module { if (!variable.included) { this.includeVariable(variable); } + if (variable instanceof ExternalVariable) { + variable.module.reexported = true; + } } } diff --git a/test/form/samples/dynamic-import-inlining/_expected.js b/test/form/samples/dynamic-import-inlining/_expected.js index ae434046e6c..5a35de939db 100644 --- a/test/form/samples/dynamic-import-inlining/_expected.js +++ b/test/form/samples/dynamic-import-inlining/_expected.js @@ -1,11 +1,8 @@ const bar = 2; -Promise.resolve().then(function () { return foo$1; }); +Promise.resolve().then(function () { return foo; }); -const foo = 1; - -var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo +var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); export { bar }; diff --git a/test/function/samples/perf-adds-plugin-context-to-plugins/foo.js b/test/function/samples/perf-adds-plugin-context-to-plugins/foo.js index d02ba545bd3..aaadd073c11 100644 --- a/test/function/samples/perf-adds-plugin-context-to-plugins/foo.js +++ b/test/function/samples/perf-adds-plugin-context-to-plugins/foo.js @@ -1 +1,3 @@ export default 'foo'; + +console.log('side-effect'); From 42649c741782a0140a8094385dcfc9281468339a Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 21 Apr 2023 15:03:35 +0200 Subject: [PATCH 18/25] chore: update snapshot --- .../_expected/amd/entry.js | 9 +++------ .../_expected/cjs/entry.js | 9 +++------ .../_expected/es/entry.js | 9 +++------ .../_expected/system/entry.js | 9 +++------ .../_expected/amd/entry.js | 9 +++------ .../_expected/cjs/entry.js | 9 +++------ .../_expected/es/entry.js | 9 +++------ .../_expected/system/entry.js | 9 +++------ .../resolve-dynamic-import/_expected/amd/main.js | 4 +--- .../resolve-dynamic-import/_expected/cjs/main.js | 4 +--- .../resolve-dynamic-import/_expected/es/main.js | 4 +--- .../resolve-dynamic-import/_expected/system/main.js | 4 +--- .../dynamic-import-inlining-array/_expected.js | 9 +++------ .../deprecated/dynamic-import-inlining/_expected.js | 9 +++------ .../dynamic-import-inlining-array/_expected.js | 9 +++------ .../_expected.js | 12 ++++++++++++ 16 files changed, 49 insertions(+), 78 deletions(-) diff --git a/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/amd/entry.js b/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/amd/entry.js index a7385ad2c1b..fef3de875f4 100644 --- a/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/amd/entry.js +++ b/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/amd/entry.js @@ -1,13 +1,10 @@ define(['exports'], (function (exports) { 'use strict'; const bar = 2; - Promise.resolve().then(function () { return foo$1; }); + Promise.resolve().then(function () { return foo; }); - const foo = 1; - - var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo + var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); exports.bar = bar; diff --git a/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/cjs/entry.js b/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/cjs/entry.js index e20ce0c5bb8..218beb76d7d 100644 --- a/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/cjs/entry.js +++ b/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/cjs/entry.js @@ -1,13 +1,10 @@ 'use strict'; const bar = 2; -Promise.resolve().then(function () { return foo$1; }); +Promise.resolve().then(function () { return foo; }); -const foo = 1; - -var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo +var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); exports.bar = bar; diff --git a/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/es/entry.js b/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/es/entry.js index ae434046e6c..5a35de939db 100644 --- a/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/es/entry.js +++ b/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/es/entry.js @@ -1,11 +1,8 @@ const bar = 2; -Promise.resolve().then(function () { return foo$1; }); +Promise.resolve().then(function () { return foo; }); -const foo = 1; - -var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo +var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); export { bar }; diff --git a/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/system/entry.js b/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/system/entry.js index f3161f5e77f..2f4d23eacdf 100644 --- a/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/system/entry.js +++ b/test/chunking-form/samples/deprecated/dynamic-import-inlining-object/_expected/system/entry.js @@ -4,13 +4,10 @@ System.register([], (function (exports) { execute: (function () { const bar = exports('bar', 2); - Promise.resolve().then(function () { return foo$1; }); + Promise.resolve().then(function () { return foo; }); - const foo = 1; - - var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo + var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); }) diff --git a/test/chunking-form/samples/dynamic-import-inlining-object/_expected/amd/entry.js b/test/chunking-form/samples/dynamic-import-inlining-object/_expected/amd/entry.js index a7385ad2c1b..fef3de875f4 100644 --- a/test/chunking-form/samples/dynamic-import-inlining-object/_expected/amd/entry.js +++ b/test/chunking-form/samples/dynamic-import-inlining-object/_expected/amd/entry.js @@ -1,13 +1,10 @@ define(['exports'], (function (exports) { 'use strict'; const bar = 2; - Promise.resolve().then(function () { return foo$1; }); + Promise.resolve().then(function () { return foo; }); - const foo = 1; - - var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo + var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); exports.bar = bar; diff --git a/test/chunking-form/samples/dynamic-import-inlining-object/_expected/cjs/entry.js b/test/chunking-form/samples/dynamic-import-inlining-object/_expected/cjs/entry.js index e20ce0c5bb8..218beb76d7d 100644 --- a/test/chunking-form/samples/dynamic-import-inlining-object/_expected/cjs/entry.js +++ b/test/chunking-form/samples/dynamic-import-inlining-object/_expected/cjs/entry.js @@ -1,13 +1,10 @@ 'use strict'; const bar = 2; -Promise.resolve().then(function () { return foo$1; }); +Promise.resolve().then(function () { return foo; }); -const foo = 1; - -var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo +var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); exports.bar = bar; diff --git a/test/chunking-form/samples/dynamic-import-inlining-object/_expected/es/entry.js b/test/chunking-form/samples/dynamic-import-inlining-object/_expected/es/entry.js index ae434046e6c..5a35de939db 100644 --- a/test/chunking-form/samples/dynamic-import-inlining-object/_expected/es/entry.js +++ b/test/chunking-form/samples/dynamic-import-inlining-object/_expected/es/entry.js @@ -1,11 +1,8 @@ const bar = 2; -Promise.resolve().then(function () { return foo$1; }); +Promise.resolve().then(function () { return foo; }); -const foo = 1; - -var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo +var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); export { bar }; diff --git a/test/chunking-form/samples/dynamic-import-inlining-object/_expected/system/entry.js b/test/chunking-form/samples/dynamic-import-inlining-object/_expected/system/entry.js index f3161f5e77f..2f4d23eacdf 100644 --- a/test/chunking-form/samples/dynamic-import-inlining-object/_expected/system/entry.js +++ b/test/chunking-form/samples/dynamic-import-inlining-object/_expected/system/entry.js @@ -4,13 +4,10 @@ System.register([], (function (exports) { execute: (function () { const bar = exports('bar', 2); - Promise.resolve().then(function () { return foo$1; }); + Promise.resolve().then(function () { return foo; }); - const foo = 1; - - var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo + var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); }) diff --git a/test/chunking-form/samples/resolve-dynamic-import/_expected/amd/main.js b/test/chunking-form/samples/resolve-dynamic-import/_expected/amd/main.js index 9b96903bba5..07244980fcc 100644 --- a/test/chunking-form/samples/resolve-dynamic-import/_expected/amd/main.js +++ b/test/chunking-form/samples/resolve-dynamic-import/_expected/amd/main.js @@ -24,12 +24,10 @@ define(['require', './direct-relative-external', 'to-indirect-relative-external' new Promise(function (resolve, reject) { require(['direct-absolute-external'], function (m) { resolve(/*#__PURE__*/_interopNamespaceDefault(m)); }, reject); }); new Promise(function (resolve, reject) { require(['to-indirect-absolute-external'], function (m) { resolve(/*#__PURE__*/_interopNamespaceDefault(m)); }, reject); }); - const value = 'existing'; console.log('existing'); var existing = /*#__PURE__*/Object.freeze({ - __proto__: null, - value: value + __proto__: null }); //main diff --git a/test/chunking-form/samples/resolve-dynamic-import/_expected/cjs/main.js b/test/chunking-form/samples/resolve-dynamic-import/_expected/cjs/main.js index 75bb253218c..118daa57a84 100644 --- a/test/chunking-form/samples/resolve-dynamic-import/_expected/cjs/main.js +++ b/test/chunking-form/samples/resolve-dynamic-import/_expected/cjs/main.js @@ -12,12 +12,10 @@ import('to-indirect-relative-external'); import('direct-absolute-external'); import('to-indirect-absolute-external'); -const value = 'existing'; console.log('existing'); var existing = /*#__PURE__*/Object.freeze({ - __proto__: null, - value: value + __proto__: null }); //main diff --git a/test/chunking-form/samples/resolve-dynamic-import/_expected/es/main.js b/test/chunking-form/samples/resolve-dynamic-import/_expected/es/main.js index fb6bacbadd2..821ceee41c6 100644 --- a/test/chunking-form/samples/resolve-dynamic-import/_expected/es/main.js +++ b/test/chunking-form/samples/resolve-dynamic-import/_expected/es/main.js @@ -10,12 +10,10 @@ import('to-indirect-relative-external'); import('direct-absolute-external'); import('to-indirect-absolute-external'); -const value = 'existing'; console.log('existing'); var existing = /*#__PURE__*/Object.freeze({ - __proto__: null, - value: value + __proto__: null }); //main diff --git a/test/chunking-form/samples/resolve-dynamic-import/_expected/system/main.js b/test/chunking-form/samples/resolve-dynamic-import/_expected/system/main.js index db84850fb05..425eeb50564 100644 --- a/test/chunking-form/samples/resolve-dynamic-import/_expected/system/main.js +++ b/test/chunking-form/samples/resolve-dynamic-import/_expected/system/main.js @@ -11,12 +11,10 @@ System.register(['./direct-relative-external', 'to-indirect-relative-external', module.import('direct-absolute-external'); module.import('to-indirect-absolute-external'); - const value = 'existing'; console.log('existing'); var existing = /*#__PURE__*/Object.freeze({ - __proto__: null, - value: value + __proto__: null }); //main diff --git a/test/form/samples/deprecated/dynamic-import-inlining-array/_expected.js b/test/form/samples/deprecated/dynamic-import-inlining-array/_expected.js index ae434046e6c..5a35de939db 100644 --- a/test/form/samples/deprecated/dynamic-import-inlining-array/_expected.js +++ b/test/form/samples/deprecated/dynamic-import-inlining-array/_expected.js @@ -1,11 +1,8 @@ const bar = 2; -Promise.resolve().then(function () { return foo$1; }); +Promise.resolve().then(function () { return foo; }); -const foo = 1; - -var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo +var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); export { bar }; diff --git a/test/form/samples/deprecated/dynamic-import-inlining/_expected.js b/test/form/samples/deprecated/dynamic-import-inlining/_expected.js index ae434046e6c..5a35de939db 100644 --- a/test/form/samples/deprecated/dynamic-import-inlining/_expected.js +++ b/test/form/samples/deprecated/dynamic-import-inlining/_expected.js @@ -1,11 +1,8 @@ const bar = 2; -Promise.resolve().then(function () { return foo$1; }); +Promise.resolve().then(function () { return foo; }); -const foo = 1; - -var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo +var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); export { bar }; diff --git a/test/form/samples/dynamic-import-inlining-array/_expected.js b/test/form/samples/dynamic-import-inlining-array/_expected.js index ae434046e6c..5a35de939db 100644 --- a/test/form/samples/dynamic-import-inlining-array/_expected.js +++ b/test/form/samples/dynamic-import-inlining-array/_expected.js @@ -1,11 +1,8 @@ const bar = 2; -Promise.resolve().then(function () { return foo$1; }); +Promise.resolve().then(function () { return foo; }); -const foo = 1; - -var foo$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - foo: foo +var foo = /*#__PURE__*/Object.freeze({ + __proto__: null }); export { bar }; diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js index c1ab5423a1c..8e47824c28a 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js @@ -78,26 +78,38 @@ var sub2 = /*#__PURE__*/Object.freeze({ reexported: bar3 }); +console.log('side-effect 1 should be included'); + var effect1 = /*#__PURE__*/Object.freeze({ __proto__: null }); +console.log('side-effect 2 should be included'); + var effect2 = /*#__PURE__*/Object.freeze({ __proto__: null }); +console.log('side-effect 3 should be included'); + var effect3 = /*#__PURE__*/Object.freeze({ __proto__: null }); +console.log('side-effect 4 should be included'); + var effect4 = /*#__PURE__*/Object.freeze({ __proto__: null }); +console.log('side-effect 5 should be included'); + var effect5 = /*#__PURE__*/Object.freeze({ __proto__: null }); +console.log('side-effect 6 should be included'); + var effect6 = /*#__PURE__*/Object.freeze({ __proto__: null }); From 5a730d9c72dff3be84ae1a5c5599812a1612dc53 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 21 Apr 2023 15:19:44 +0200 Subject: [PATCH 19/25] chore: cleanup --- src/Module.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Module.ts b/src/Module.ts index 8297b941fb1..03ba90b8950 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -729,9 +729,6 @@ export default class Module { if (!variable.included) { this.includeVariable(variable); } - if (variable instanceof ExternalVariable) { - variable.module.reexported = true; - } } } From b537ad5a2187c790f82efae8d6f9bbd431fa0451 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 22 Apr 2023 09:46:39 +0200 Subject: [PATCH 20/25] chore: apply suggestions --- src/ast/nodes/ImportExpression.ts | 102 +++++++++--------- .../_expected.js | 22 ++++ .../main.js | 4 + 3 files changed, 80 insertions(+), 48 deletions(-) diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index e0a29b0a872..c37976a788f 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -13,16 +13,17 @@ 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 FunctionExpression from './FunctionExpression'; -import type Identifier from './Identifier'; -import type MemberExpression from './MemberExpression'; +import ArrowFunctionExpression from './ArrowFunctionExpression'; +import AwaitExpression from './AwaitExpression'; +import CallExpression from './CallExpression'; +import ExpressionStatement from './ExpressionStatement'; +import FunctionExpression from './FunctionExpression'; +import Identifier from './Identifier'; +import MemberExpression from './MemberExpression'; import type * as NodeType from './NodeType'; import type ObjectExpression from './ObjectExpression'; -import type ObjectPattern from './ObjectPattern'; -import type VariableDeclarator from './VariableDeclarator'; +import ObjectPattern from './ObjectPattern'; +import VariableDeclarator from './VariableDeclarator'; import { type ExpressionNode, type GenericEsTreeNode, @@ -65,81 +66,86 @@ export default class ImportExpression extends NodeBase { * Returns undefined if it's not fully deterministic. */ getDeterministicImportedNames(): readonly string[] | undefined { - // side-effect only - if (this.parent.type === 'ExpressionStatement') { + const parent1 = this.parent; + + // Side-effect only: import('bar') + if (parent1 instanceof ExpressionStatement) { return EMPTY_ARRAY; } - if (this.parent?.type === 'AwaitExpression') { - const awaitExpression = this.parent as AwaitExpression; + if (parent1 instanceof AwaitExpression) { + const parent2 = parent1.parent; - // side-effect only - if (awaitExpression.parent.type === 'ExpressionStatement') { + // Side-effect only: await import('bar') + if (parent2 instanceof ExpressionStatement) { return EMPTY_ARRAY; } // Case 1: const { foo } = await import('bar') - if (awaitExpression.parent?.type === 'VariableDeclarator') { - const variableDeclarator = awaitExpression.parent as VariableDeclarator; - if (variableDeclarator.id?.type !== 'ObjectPattern') return; - - return getDeterministicObjectDestructure(variableDeclarator.id as ObjectPattern); + if (parent2 instanceof VariableDeclarator) { + const declaration = parent2.id; + return declaration instanceof ObjectPattern + ? getDeterministicObjectDestructure(declaration) + : undefined; } - // Case 2: (await import('bar')).foo - else { - if (awaitExpression.parent?.type !== 'MemberExpression') return; - const memberExpression = awaitExpression.parent as MemberExpression; - if (memberExpression.computed || memberExpression.property.type !== 'Identifier') return; - - return [(memberExpression.property as Identifier).name]; + // Case 2: (await import('bar')).foo + if (parent2 instanceof MemberExpression) { + const id = parent2.property; + if (!parent2.computed && id instanceof Identifier) { + return [id.name]; + } } + + return; } + // Case 3: import('bar').then(({ foo }) => {}) - else if (this.parent?.type === 'MemberExpression') { - const memberExpression = this.parent as MemberExpression; - if ( - memberExpression.parent?.type !== 'CallExpression' || - memberExpression.property.type !== 'Identifier' - ) + if (parent1 instanceof MemberExpression) { + const callExpression = parent1.parent; + const property = parent1.property; + + if (!(callExpression instanceof CallExpression) || !(property instanceof Identifier)) { return; + } - const memberName = (memberExpression.property as Identifier).name; - const callExpression = memberExpression.parent as CallExpression; + const memberName = property.name; // side-effect only, when only chaining .catch or .finally if ( - callExpression.parent.type === 'ExpressionStatement' && + callExpression.parent instanceof ExpressionStatement && ['catch', 'finally'].includes(memberName) ) { return EMPTY_ARRAY; - } else if (memberName !== 'then') { - return; } - // side-effect only + if (memberName !== 'then') return; + + // Side-effect only: import('bar').then() if (callExpression.arguments.length === 0) { return EMPTY_ARRAY; } + const argument = callExpression.arguments[0]; + if ( callExpression.arguments.length !== 1 || - !['ArrowFunctionExpression', 'FunctionExpression'].includes( - callExpression.arguments[0].type - ) - ) + !(argument instanceof ArrowFunctionExpression || argument instanceof FunctionExpression) + ) { return; + } - const callback = callExpression.arguments[0] as ArrowFunctionExpression | FunctionExpression; - - // side-effect only - if (callback.params.length === 0) { + // Side-effect only: import('bar').then(() => {}) + if (argument.params.length === 0) { return EMPTY_ARRAY; } - if (callback.params.length !== 1 || callback.params[0].type !== 'ObjectPattern') return; + const declaration = argument.params[0]; + if (argument.params.length === 1 && declaration instanceof ObjectPattern) { + return getDeterministicObjectDestructure(declaration); + } - return getDeterministicObjectDestructure(callback.params[0] as ObjectPattern); + return; } } diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js index 8e47824c28a..922a671bc30 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js @@ -33,6 +33,10 @@ async function entry() { Promise.resolve().then(function () { return bail7$1; }), Promise.resolve().then(function () { return bail8$1; }), ]; + + await Promise.resolve().then(function () { return bail9$1; }); + + Promise.resolve().then(function () { return bail10$1; }).then(({ [foo]: bar }) => {}); } function foo1() { @@ -186,4 +190,22 @@ var bail8$1 = /*#__PURE__*/Object.freeze({ named8: named8 }); +var bail9 = 'should be included 9'; +const named9 = 'bail9'; + +var bail9$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + default: bail9, + named9: named9 +}); + +var bail10 = 'should be included 10'; +const named10 = 'bail10'; + +var bail10$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + default: bail10, + named10: named10 +}); + export { entry }; diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/main.js b/test/form/samples/treeshake-deterministic-dynamic-import/main.js index 1bb1c8893d7..6b269302161 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/main.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/main.js @@ -35,4 +35,8 @@ export async function entry() { import('./bail-7.js'), import('./bail-8.js'), ] + + const { [foo]: bar } = await import('./bail-9.js') + + import('./bail-10.js').then(({ [foo]: bar }) => {}) } From 277496f343df0f69b56e7fb7bc4b50a3f37fdfce Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 23 Apr 2023 12:04:55 +0200 Subject: [PATCH 21/25] fix: auto includeNamespaceMembers --- src/Module.ts | 10 +++++-- .../_config.js | 1 + .../_expected.js | 27 +++++++++++++++++++ .../main.js | 3 +++ .../sub4.js | 4 +++ 5 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 test/form/samples/treeshake-deterministic-dynamic-import/sub4.js diff --git a/src/Module.ts b/src/Module.ts index 03ba90b8950..8d539ad2c90 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -716,12 +716,14 @@ export default class Module { this.includeAllExports(false); } - includeExportsByNames(names: readonly string[], includeNamespaceMembers: boolean): void { + includeExportsByNames(names: readonly string[]): void { if (!this.isExecuted) { markModuleAndImpureDependenciesAsExecuted(this); this.graph.needsTreeshakingPass = true; } + let includeNamespaceMembers = false; + for (const name of names) { const variable = this.getVariableForExportName(name)[0]; if (variable) { @@ -730,6 +732,10 @@ export default class Module { this.includeVariable(variable); } } + + if (!this.exports.has(name) && !this.reexportDescriptions.has(name)) { + includeNamespaceMembers = true; + } } if (includeNamespaceMembers) { @@ -1248,7 +1254,7 @@ export default class Module { : undefined; if (importedNames) { - resolution.includeExportsByNames(importedNames, true); + resolution.includeExportsByNames(importedNames); } else { resolution.includeAllExports(true); } diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js index f95acf12f6f..3f69634adb1 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js @@ -4,6 +4,7 @@ module.exports = { output: { inlineDynamicImports: true }, + external: ['external'], plugins: [ { resolveId(id) { diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js index 922a671bc30..f7e3cb0757a 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js @@ -1,3 +1,20 @@ +import * as external from 'external'; + +function _mergeNamespaces(n, m) { + m.forEach(function (e) { + e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) { + if (k !== 'default' && !(k in n)) { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { return e[k]; } + }); + } + }); + }); + return Object.freeze(n); +} + async function entry() { // simple const { foo1: foo } = await Promise.resolve().then(function () { return sub1; }); @@ -7,6 +24,9 @@ async function entry() { Promise.resolve().then(function () { return sub2; }).then(({ baz2 }) => baz2); Promise.resolve().then(function () { return sub2; }).then(function({ reexported }) { }); + // external with unknown namespace + await Promise.resolve().then(function () { return sub4; }); + // side-effect only Promise.resolve().then(function () { return effect1; }); await Promise.resolve().then(function () { return effect2; }); @@ -82,6 +102,13 @@ var sub2 = /*#__PURE__*/Object.freeze({ reexported: bar3 }); +const foo4 = 3; + +var sub4 = /*#__PURE__*/_mergeNamespaces({ + __proto__: null, + foo4: foo4 +}, [external]); + console.log('side-effect 1 should be included'); var effect1 = /*#__PURE__*/Object.freeze({ diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/main.js b/test/form/samples/treeshake-deterministic-dynamic-import/main.js index 6b269302161..c1eff663e5c 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/main.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/main.js @@ -9,6 +9,9 @@ export async function entry() { import('./sub2.js').then(({ baz2 }) => baz2) import('./sub2.js').then(function({ reexported }) { reexported }) + // external with unknown namespace + const { foo4, x } = await import('./sub4'); + // side-effect only import('./effect-1.js') await import('./effect-2.js') diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/sub4.js b/test/form/samples/treeshake-deterministic-dynamic-import/sub4.js new file mode 100644 index 00000000000..746302805dd --- /dev/null +++ b/test/form/samples/treeshake-deterministic-dynamic-import/sub4.js @@ -0,0 +1,4 @@ +export const foo4 = 3; +export const bar4 = 4; + +export * from 'external'; From fdbc6847965d24c3b1d671aeed088bfec93f286b Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 23 Apr 2023 12:28:35 +0200 Subject: [PATCH 22/25] fix: coverage missing variable --- .../samples/treeshake-deterministic-dynamic-import/_expected.js | 1 + test/form/samples/treeshake-deterministic-dynamic-import/main.js | 1 + 2 files changed, 2 insertions(+) diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js index f7e3cb0757a..847cd9aeb0c 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js @@ -18,6 +18,7 @@ function _mergeNamespaces(n, m) { async function entry() { // simple const { foo1: foo } = await Promise.resolve().then(function () { return sub1; }); + await Promise.resolve().then(function () { return sub1; }); (await Promise.resolve().then(function () { return sub2; })).bar2(); await Promise.resolve().then(function () { return sub2; }); await Promise.resolve().then(function () { return sub2; }); diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/main.js b/test/form/samples/treeshake-deterministic-dynamic-import/main.js index c1eff663e5c..48bd026a602 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/main.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/main.js @@ -1,6 +1,7 @@ export async function entry() { // simple const { foo1: foo } = await import('./sub1.js'); + const { doesNotExists } = await import('./sub1.js'); // multiple ;(await import('./sub2.js')).bar2() From bab03874671b3e7c6d05fb88c78e6d8c8beaa94d Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 23 Apr 2023 12:49:28 +0200 Subject: [PATCH 23/25] chore: assert for generated code --- .../_config.js | 23 +++++++++++-- .../_expected.js | 32 +++++++++---------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js index 3f69634adb1..89c505e2cc9 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js @@ -1,3 +1,5 @@ +const assert = require('node:assert'); + module.exports = { description: 'treeshakes dynamic imports when the target is deterministic', options: { @@ -19,21 +21,36 @@ module.exports = { if (match[1] === 'bail') return { code: [ - `export default 'should be included ${match[2]}'`, + `export default '@included-bail-${match[2]}'`, `export const named${match[2]} = 'bail${match[2]}';` ].join('\n') }; else if (match[1] === 'effect') { return { code: [ - 'export function fn() { /* this should be tree-shaken */ }', - `console.log('side-effect ${match[2]} should be included');` + 'export function fn() { /* @tree-shaken */ }', + `console.log('@included-effect-${match[2]}');` ].join('\n') }; } } return null; } + }, + { + generateBundle(_, bundle) { + const code = bundle['_actual.js'].code; + + assert.ok(!code.includes('@tree-shaken')); + + for (let index = 1; index <= 10; index++) { + assert.ok(code.includes(`@included-bail-${index}`)); + } + + for (let index = 1; index <= 6; index++) { + assert.ok(code.includes(`@included-effect-${index}`)); + } + } } ] } diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js index 847cd9aeb0c..8a69e51d073 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_expected.js @@ -110,43 +110,43 @@ var sub4 = /*#__PURE__*/_mergeNamespaces({ foo4: foo4 }, [external]); -console.log('side-effect 1 should be included'); +console.log('@included-effect-1'); var effect1 = /*#__PURE__*/Object.freeze({ __proto__: null }); -console.log('side-effect 2 should be included'); +console.log('@included-effect-2'); var effect2 = /*#__PURE__*/Object.freeze({ __proto__: null }); -console.log('side-effect 3 should be included'); +console.log('@included-effect-3'); var effect3 = /*#__PURE__*/Object.freeze({ __proto__: null }); -console.log('side-effect 4 should be included'); +console.log('@included-effect-4'); var effect4 = /*#__PURE__*/Object.freeze({ __proto__: null }); -console.log('side-effect 5 should be included'); +console.log('@included-effect-5'); var effect5 = /*#__PURE__*/Object.freeze({ __proto__: null }); -console.log('side-effect 6 should be included'); +console.log('@included-effect-6'); var effect6 = /*#__PURE__*/Object.freeze({ __proto__: null }); -var bail1 = 'should be included 1'; +var bail1 = '@included-bail-1'; const named1 = 'bail1'; var bail1$1 = /*#__PURE__*/Object.freeze({ @@ -155,7 +155,7 @@ var bail1$1 = /*#__PURE__*/Object.freeze({ named1: named1 }); -var bail2 = 'should be included 2'; +var bail2 = '@included-bail-2'; const named2 = 'bail2'; var bail2$1 = /*#__PURE__*/Object.freeze({ @@ -164,7 +164,7 @@ var bail2$1 = /*#__PURE__*/Object.freeze({ named2: named2 }); -var bail3 = 'should be included 3'; +var bail3 = '@included-bail-3'; const named3 = 'bail3'; var bail3$1 = /*#__PURE__*/Object.freeze({ @@ -173,7 +173,7 @@ var bail3$1 = /*#__PURE__*/Object.freeze({ named3: named3 }); -var bail4 = 'should be included 4'; +var bail4 = '@included-bail-4'; const named4 = 'bail4'; var bail4$1 = /*#__PURE__*/Object.freeze({ @@ -182,7 +182,7 @@ var bail4$1 = /*#__PURE__*/Object.freeze({ named4: named4 }); -var bail5 = 'should be included 5'; +var bail5 = '@included-bail-5'; const named5 = 'bail5'; var bail5$1 = /*#__PURE__*/Object.freeze({ @@ -191,7 +191,7 @@ var bail5$1 = /*#__PURE__*/Object.freeze({ named5: named5 }); -var bail6 = 'should be included 6'; +var bail6 = '@included-bail-6'; const named6 = 'bail6'; var bail6$1 = /*#__PURE__*/Object.freeze({ @@ -200,7 +200,7 @@ var bail6$1 = /*#__PURE__*/Object.freeze({ named6: named6 }); -var bail7 = 'should be included 7'; +var bail7 = '@included-bail-7'; const named7 = 'bail7'; var bail7$1 = /*#__PURE__*/Object.freeze({ @@ -209,7 +209,7 @@ var bail7$1 = /*#__PURE__*/Object.freeze({ named7: named7 }); -var bail8 = 'should be included 8'; +var bail8 = '@included-bail-8'; const named8 = 'bail8'; var bail8$1 = /*#__PURE__*/Object.freeze({ @@ -218,7 +218,7 @@ var bail8$1 = /*#__PURE__*/Object.freeze({ named8: named8 }); -var bail9 = 'should be included 9'; +var bail9 = '@included-bail-9'; const named9 = 'bail9'; var bail9$1 = /*#__PURE__*/Object.freeze({ @@ -227,7 +227,7 @@ var bail9$1 = /*#__PURE__*/Object.freeze({ named9: named9 }); -var bail10 = 'should be included 10'; +var bail10 = '@included-bail-10'; const named10 = 'bail10'; var bail10$1 = /*#__PURE__*/Object.freeze({ From fd1d76152b9bd62beb0c0e98518f786da606fa09 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 23 Apr 2023 13:04:51 +0200 Subject: [PATCH 24/25] chore: update --- .../_config.js | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js index 89c505e2cc9..4db137fa175 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js @@ -36,22 +36,18 @@ module.exports = { } return null; } - }, - { - generateBundle(_, bundle) { - const code = bundle['_actual.js'].code; - - assert.ok(!code.includes('@tree-shaken')); - - for (let index = 1; index <= 10; index++) { - assert.ok(code.includes(`@included-bail-${index}`)); - } - - for (let index = 1; index <= 6; index++) { - assert.ok(code.includes(`@included-effect-${index}`)); - } - } } ] + }, + test(code) { + assert.ok(!code.includes('@tree-shaken')); + + for (let index = 1; index <= 10; index++) { + assert.ok(code.includes(`@included-bail-${index}`)); + } + + for (let index = 1; index <= 6; index++) { + assert.ok(code.includes(`@included-effect-${index}`)); + } } }; From 44620dbdd173f819bd8618d68b9db722a466a562 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 23 Apr 2023 14:34:52 +0200 Subject: [PATCH 25/25] chore: update test --- .../_config.js | 13 ------ .../_config.js | 46 +++++++++++++++++++ .../main.js | 26 +++++++++++ 3 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 test/function/samples/tree-shake-deterministic-dynamic-import/_config.js create mode 100644 test/function/samples/tree-shake-deterministic-dynamic-import/main.js diff --git a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js index 4db137fa175..e611c1d4186 100644 --- a/test/form/samples/treeshake-deterministic-dynamic-import/_config.js +++ b/test/form/samples/treeshake-deterministic-dynamic-import/_config.js @@ -1,5 +1,3 @@ -const assert = require('node:assert'); - module.exports = { description: 'treeshakes dynamic imports when the target is deterministic', options: { @@ -38,16 +36,5 @@ module.exports = { } } ] - }, - test(code) { - assert.ok(!code.includes('@tree-shaken')); - - for (let index = 1; index <= 10; index++) { - assert.ok(code.includes(`@included-bail-${index}`)); - } - - for (let index = 1; index <= 6; index++) { - assert.ok(code.includes(`@included-effect-${index}`)); - } } }; diff --git a/test/function/samples/tree-shake-deterministic-dynamic-import/_config.js b/test/function/samples/tree-shake-deterministic-dynamic-import/_config.js new file mode 100644 index 00000000000..1a8ad6ecf3f --- /dev/null +++ b/test/function/samples/tree-shake-deterministic-dynamic-import/_config.js @@ -0,0 +1,46 @@ +module.exports = { + description: 'treeshakes dynamic imports when the target is deterministic', + options: { + output: { + inlineDynamicImports: true + }, + plugins: [ + { + resolveId(id) { + if (/(bail|effect)-(\d+).js$/.test(id)) { + return id; + } + return null; + }, + load(id) { + const match = /(bail|effect)-(\d+).js$/.exec(id); + if (match) { + if (match[1] === 'bail') + return { + code: [ + `export default '@included-bail-${match[2]}'`, + `export const named${match[2]} = 'bail${match[2]}';` + ].join('\n') + }; + else if (match[1] === 'effect') { + return { + code: [ + 'export function fn() { /* @tree-shaken */ }', + `console.log('@included-effect-${match[2]}');` + ].join('\n') + }; + } + } + return null; + } + } + ] + }, + async exports({ allExports }) { + await allExports(); + }, + context: { + named1: 'named1', + named4: 'named4' + } +}; diff --git a/test/function/samples/tree-shake-deterministic-dynamic-import/main.js b/test/function/samples/tree-shake-deterministic-dynamic-import/main.js new file mode 100644 index 00000000000..32edef50e36 --- /dev/null +++ b/test/function/samples/tree-shake-deterministic-dynamic-import/main.js @@ -0,0 +1,26 @@ +export const allExports = () => Promise.all([ + (async () => { + const value = (await import('./bail-1.js'))[named1] + assert.strictEqual(value, 'bail1'); + })(), + (async () => { + const fn = (exports) => { + assert.deepEqual(exports, { + named2: 'bail2', + default: '@included-bail-2' + }); + } + await import('./bail-2.js').then(fn) + })(), + (async () => { + await import('./bail-3.js').then(({ default: _, ...args }) => { + assert.deepEqual(args, { + named3: 'bail3' + }); + }) + })(), + (async () => { + const { [named4]: value } = await import('./bail-4.js') + assert.strictEqual(value, 'bail4'); + })(), +]);