Skip to content

fix(ssr): fix execution order of re-export #19841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ test('export then import minified', async () => {
`export * from 'vue';import {createApp} from 'vue';`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["createApp"]});const __vite_ssr_import_1__ = await __vite_ssr_import__("vue");__vite_ssr_exportAll__(__vite_ssr_import_1__);
"
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");__vite_ssr_exportAll__(__vite_ssr_import_0__);
const __vite_ssr_import_1__ = await __vite_ssr_import__("vue", {"importedNames":["createApp"]});"
`)
})

Expand Down Expand Up @@ -1264,14 +1264,14 @@ export * from './b'
console.log(foo + 2)
`),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]});
console.log(__vite_ssr_import_0__.foo + 1);
const __vite_ssr_import_1__ = await __vite_ssr_import__("./a");__vite_ssr_exportAll__(__vite_ssr_import_1__);
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./a");__vite_ssr_exportAll__(__vite_ssr_import_0__);
;const __vite_ssr_import_1__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]});const __vite_ssr_import_2__ = await __vite_ssr_import__("./b");__vite_ssr_exportAll__(__vite_ssr_import_2__);
;
console.log(__vite_ssr_import_1__.foo + 1);

const __vite_ssr_import_2__ = await __vite_ssr_import__("./b");__vite_ssr_exportAll__(__vite_ssr_import_2__);
;
console.log(__vite_ssr_import_0__.foo + 2)


console.log(__vite_ssr_import_1__.foo + 2)
"
`)
})
Expand All @@ -1288,9 +1288,9 @@ console.log(bar)
"Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, get(){ return __vite_ssr_export_default__ }});
Object.defineProperty(__vite_ssr_exports__, "bar", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__ }});

const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]});
const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]});const __vite_ssr_import_1__ = await __vite_ssr_import__("./bar");;
const __vite_ssr_export_default__ = (0,__vite_ssr_import_0__.foo)();
const __vite_ssr_import_1__ = await __vite_ssr_import__("./bar");;

console.log(bar)
"
`)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { IonTypes } from './IonTypes.js';
import * as dom from './dom/index.js';
export { dom };
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const IonTypes = {
BLOB: 'Blob',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
reproduction from https://github.com/vitest-dev/vitest/issues/4143#issuecomment-1724526796
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { IonTypes } from '../Ion.js';
export const Blob = IonTypes.BLOB;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Blob } from './Blob.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
console.log('dep1');
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
console.log('dep2');
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './dep1.js';
import './dep2.js'
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,36 @@ describe('module runner initialization', async () => {
)
})

it(`cyclic with mixed import and re-export`, async ({ runner }) => {
const mod = await runner.import('/fixtures/cyclic2/test7/Ion.js')
expect(mod).toMatchInlineSnapshot(`
{
"IonTypes": {
"BLOB": "Blob",
},
"dom": {
"Blob": "Blob",
},
}
`)
})

it(`execution order with mixed import and re-export`, async ({
runner,
onTestFinished,
}) => {
const spy = vi.spyOn(console, 'log')
onTestFinished(() => spy.mockRestore())

await runner.import('/fixtures/execution-order-re-export/index.js')
expect(spy.mock.calls.map((v) => v[0])).toMatchInlineSnapshot(`
[
"dep1",
"dep2",
]
`)
})

it(`live binding (export default function f)`, async ({ runner }) => {
const mod = await runner.import('/fixtures/live-binding/test1/index.js')
expect(mod.default).toMatchInlineSnapshot(`
Expand Down
56 changes: 42 additions & 14 deletions packages/vite/src/node/ssr/ssrTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,27 +186,63 @@ async function ssrTransformScript(
)
}

const imports: RollupAstNode<ImportDeclaration>[] = []
const imports: (
| RollupAstNode<ImportDeclaration>
| RollupAstNode<ExportNamedDeclaration>
| RollupAstNode<ExportAllDeclaration>
)[] = []
const exports: (
| RollupAstNode<ExportNamedDeclaration>
| RollupAstNode<ExportDefaultDeclaration>
| RollupAstNode<ExportAllDeclaration>
)[] = []
const reExportImportIdMap = new Map<
RollupAstNode<ExportNamedDeclaration> | RollupAstNode<ExportAllDeclaration>,
string
>()

for (const node of ast.body as Node[]) {
if (node.type === 'ImportDeclaration') {
imports.push(node)
} else if (node.type === 'ExportDefaultDeclaration') {
exports.push(node)
} else if (
node.type === 'ExportNamedDeclaration' ||
node.type === 'ExportDefaultDeclaration' ||
node.type === 'ExportAllDeclaration'
) {
imports.push(node)
exports.push(node)
}
}

// 1. check all import statements and record id -> importName map
// 1. check all import statements, hoist imports, and record id -> importName map
for (const node of imports) {
// hoist re-export's import at the same time as normal imports to preserve execution order
if (node.type === 'ExportNamedDeclaration') {
if (node.source) {
// export { foo, bar } from './foo'
const importId = defineImport(
hoistIndex,
node as RollupAstNode<ExportNamedDeclaration & { source: Literal }>,
{
importedNames: node.specifiers.map(
(s) => getIdentifierNameOrLiteralValue(s.local) as string,
),
},
)
reExportImportIdMap.set(node, importId)
}
continue
}
if (node.type === 'ExportAllDeclaration') {
if (node.source) {
// export * from './foo'
const importId = defineImport(hoistIndex, node)
reExportImportIdMap.set(node, importId)
}
continue
}

// import foo from 'foo' --> foo -> __import_foo__.default
// import { baz } from 'foo' --> baz -> __import_foo__.baz
// import * as ok from 'foo' --> ok -> __import_foo__
Expand Down Expand Up @@ -263,18 +299,9 @@ async function ssrTransformScript(
}
s.remove(node.start, (node.declaration as Node).start)
} else {
s.remove(node.start, node.end)
if (node.source) {
// export { foo, bar } from './foo'
const importId = defineImport(
node.start,
node as RollupAstNode<ExportNamedDeclaration & { source: Literal }>,
{
importedNames: node.specifiers.map(
(s) => getIdentifierNameOrLiteralValue(s.local) as string,
),
},
)
const importId = reExportImportIdMap.get(node)!
for (const spec of node.specifiers) {
const exportedAs = getIdentifierNameOrLiteralValue(
spec.exported,
Expand All @@ -290,6 +317,7 @@ async function ssrTransformScript(
}
}
} else {
s.remove(node.start, node.end)
// export { foo, bar }
for (const spec of node.specifiers) {
// spec.local can be Literal only when it has "from 'something'"
Expand Down Expand Up @@ -333,7 +361,7 @@ async function ssrTransformScript(

// export * from './foo'
if (node.type === 'ExportAllDeclaration') {
const importId = defineImport(node.start, node)
const importId = reExportImportIdMap.get(node)!
if (node.exported) {
const exportedAs = getIdentifierNameOrLiteralValue(
node.exported,
Expand Down