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')