Skip to content

Commit

Permalink
fix(ssr): crash on circular import (#14441)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunqing authored Mar 13, 2024
1 parent d82e8b1 commit 8cd846c
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 17 deletions.
10 changes: 5 additions & 5 deletions packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ test('export * from', async () => {
).toMatchInlineSnapshot(`
"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__("react");
__vite_ssr_exportAll__(__vite_ssr_import_1__);
"
`)
})
Expand Down Expand Up @@ -964,14 +964,14 @@ console.log(foo + 2)
`),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]});
const __vite_ssr_import_1__ = await __vite_ssr_import__("./a");
__vite_ssr_exportAll__(__vite_ssr_import_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 + 1)
const __vite_ssr_import_1__ = await __vite_ssr_import__("./a");
__vite_ssr_exportAll__(__vite_ssr_import_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)
"
Expand Down
30 changes: 18 additions & 12 deletions packages/vite/src/node/ssr/ssrTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ async function ssrTransformScript(
// hoist at the start of the file, after the hashbang
const hoistIndex = code.match(hashbangRE)?.[0].length ?? 0

function defineImport(source: string, metadata?: DefineImportMetadata) {
function defineImport(
index: number,
source: string,
metadata?: DefineImportMetadata,
) {
deps.add(source)
const importId = `__vite_ssr_import_${uid++}__`

Expand All @@ -110,7 +114,7 @@ async function ssrTransformScript(
// There will be an error if the module is called before it is imported,
// so the module import statement is hoisted to the top
s.appendLeft(
hoistIndex,
index,
`const ${importId} = await ${ssrImportKey}(${JSON.stringify(
source,
)}${metadataStr});\n`,
Expand All @@ -132,7 +136,7 @@ async function ssrTransformScript(
// import { baz } from 'foo' --> baz -> __import_foo__.baz
// import * as ok from 'foo' --> ok -> __import_foo__
if (node.type === 'ImportDeclaration') {
const importId = defineImport(node.source.value as string, {
const importId = defineImport(hoistIndex, node.source.value as string, {
importedNames: node.specifiers
.map((s) => {
if (s.type === 'ImportSpecifier') return s.imported.name
Expand Down Expand Up @@ -182,13 +186,16 @@ async function ssrTransformScript(
s.remove(node.start, node.end)
if (node.source) {
// export { foo, bar } from './foo'
const importId = defineImport(node.source.value as string, {
importedNames: node.specifiers.map((s) => s.local.name),
})
// hoist re-exports near the defined import so they are immediately exported
const importId = defineImport(
node.start,
node.source.value as string,
{
importedNames: node.specifiers.map((s) => s.local.name),
},
)
for (const spec of node.specifiers) {
defineExport(
hoistIndex,
node.start,
spec.exported.name,
`${importId}.${spec.local.name}`,
)
Expand Down Expand Up @@ -234,12 +241,11 @@ async function ssrTransformScript(
// export * from './foo'
if (node.type === 'ExportAllDeclaration') {
s.remove(node.start, node.end)
const importId = defineImport(node.source.value as string)
// hoist re-exports near the defined import so they are immediately exported
const importId = defineImport(node.start, node.source.value as string)
if (node.exported) {
defineExport(hoistIndex, node.exported.name, `${importId}`)
defineExport(node.start, node.exported.name, `${importId}`)
} else {
s.appendLeft(hoistIndex, `${ssrExportAllKey}(${importId});\n`)
s.appendLeft(node.start, `${ssrExportAllKey}(${importId});\n`)
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions playground/ssr/__tests__/ssr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ test(`circular dependencies modules doesn't throw`, async () => {
)
})

test(`circular import doesn't throw`, async () => {
await page.goto(`${url}/circular-import`)

expect(await page.textContent('.circ-import')).toMatchInlineSnapshot(
'"A is: __A__"',
)
})

test(`deadlock doesn't happen`, async () => {
await page.goto(`${url}/forked-deadlock`)

Expand Down
6 changes: 6 additions & 0 deletions playground/ssr/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { escapeHtml } from './utils'
const pathRenderers = {
'/': renderRoot,
'/circular-dep': renderCircularDep,
'/circular-import': renderCircularImport,
'/forked-deadlock': renderForkedDeadlock,
}

Expand Down Expand Up @@ -34,6 +35,11 @@ async function renderCircularDep(rootDir) {
return `<div class="circ-dep-init">${escapeHtml(getValueAB())}</div>`
}

async function renderCircularImport(rootDir) {
const { logA } = await import('./circular-import/index.js')
return `<div class="circ-import">${escapeHtml(logA())}</div>`
}

async function renderForkedDeadlock(rootDir) {
const { commonModuleExport } = await import('./forked-deadlock/common-module')
commonModuleExport()
Expand Down
5 changes: 5 additions & 0 deletions playground/ssr/src/circular-import/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { getB } from './b'

export const A = '__A__'

export const B = getB()
5 changes: 5 additions & 0 deletions playground/ssr/src/circular-import/b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function getB() {
return '__B__'
}

export { A } from './a'
5 changes: 5 additions & 0 deletions playground/ssr/src/circular-import/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { A } from './b'

export function logA() {
return `A is: ${A}`
}

0 comments on commit 8cd846c

Please sign in to comment.