Skip to content

Commit 8cd846c

Browse files
authored
fix(ssr): crash on circular import (#14441)
1 parent d82e8b1 commit 8cd846c

7 files changed

Lines changed: 52 additions & 17 deletions

File tree

‎packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ test('export * from', async () => {
104104
).toMatchInlineSnapshot(`
105105
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");
106106
__vite_ssr_exportAll__(__vite_ssr_import_0__);
107+
107108
const __vite_ssr_import_1__ = await __vite_ssr_import__("react");
108109
__vite_ssr_exportAll__(__vite_ssr_import_1__);
109-
110110
"
111111
`)
112112
})
@@ -964,14 +964,14 @@ console.log(foo + 2)
964964
`),
965965
).toMatchInlineSnapshot(`
966966
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]});
967-
const __vite_ssr_import_1__ = await __vite_ssr_import__("./a");
968-
__vite_ssr_exportAll__(__vite_ssr_import_1__);
969-
const __vite_ssr_import_2__ = await __vite_ssr_import__("./b");
970-
__vite_ssr_exportAll__(__vite_ssr_import_2__);
971967
972968
console.log(__vite_ssr_import_0__.foo + 1)
969+
const __vite_ssr_import_1__ = await __vite_ssr_import__("./a");
970+
__vite_ssr_exportAll__(__vite_ssr_import_1__);
973971
974972
973+
const __vite_ssr_import_2__ = await __vite_ssr_import__("./b");
974+
__vite_ssr_exportAll__(__vite_ssr_import_2__);
975975
976976
console.log(__vite_ssr_import_0__.foo + 2)
977977
"

‎packages/vite/src/node/ssr/ssrTransform.ts‎

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,11 @@ async function ssrTransformScript(
9494
// hoist at the start of the file, after the hashbang
9595
consthoistIndex=code.match(hashbangRE)?.[0].length??0
9696

97-
functiondefineImport(source: string,metadata?: DefineImportMetadata){
97+
functiondefineImport(
98+
index: number,
99+
source: string,
100+
metadata?: DefineImportMetadata,
101+
){
98102
deps.add(source)
99103
constimportId=`__vite_ssr_import_${uid++}__`
100104

@@ -110,7 +114,7 @@ async function ssrTransformScript(
110114
// There will be an error if the module is called before it is imported,
111115
// so the module import statement is hoisted to the top
112116
s.appendLeft(
113-
hoistIndex,
117+
index,
114118
`const ${importId} = await ${ssrImportKey}(${JSON.stringify(
115119
source,
116120
)}${metadataStr});\n`,
@@ -132,7 +136,7 @@ async function ssrTransformScript(
132136
// import { baz } from 'foo' --> baz -> __import_foo__.baz
133137
// import * as ok from 'foo' --> ok -> __import_foo__
134138
if(node.type==='ImportDeclaration'){
135-
constimportId=defineImport(node.source.valueasstring,{
139+
constimportId=defineImport(hoistIndex,node.source.valueasstring,{
136140
importedNames: node.specifiers
137141
.map((s)=>{
138142
if(s.type==='ImportSpecifier')returns.imported.name
@@ -182,13 +186,16 @@ async function ssrTransformScript(
182186
s.remove(node.start,node.end)
183187
if(node.source){
184188
// export { foo, bar } from './foo'
185-
constimportId=defineImport(node.source.valueasstring,{
186-
importedNames: node.specifiers.map((s)=>s.local.name),
187-
})
188-
// hoist re-exports near the defined import so they are immediately exported
189+
constimportId=defineImport(
190+
node.start,
191+
node.source.valueasstring,
192+
{
193+
importedNames: node.specifiers.map((s)=>s.local.name),
194+
},
195+
)
189196
for(constspecofnode.specifiers){
190197
defineExport(
191-
hoistIndex,
198+
node.start,
192199
spec.exported.name,
193200
`${importId}.${spec.local.name}`,
194201
)
@@ -234,12 +241,11 @@ async function ssrTransformScript(
234241
// export * from './foo'
235242
if(node.type==='ExportAllDeclaration'){
236243
s.remove(node.start,node.end)
237-
constimportId=defineImport(node.source.valueasstring)
238-
// hoist re-exports near the defined import so they are immediately exported
244+
constimportId=defineImport(node.start,node.source.valueasstring)
239245
if(node.exported){
240-
defineExport(hoistIndex,node.exported.name,`${importId}`)
246+
defineExport(node.start,node.exported.name,`${importId}`)
241247
}else{
242-
s.appendLeft(hoistIndex,`${ssrExportAllKey}(${importId});\n`)
248+
s.appendLeft(node.start,`${ssrExportAllKey}(${importId});\n`)
243249
}
244250
}
245251
}

‎playground/ssr/__tests__/ssr.spec.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ test(`circular dependencies modules doesn't throw`, async () => {
1212
)
1313
})
1414

15+
test(`circular import doesn't throw`,async()=>{
16+
awaitpage.goto(`${url}/circular-import`)
17+
18+
expect(awaitpage.textContent('.circ-import')).toMatchInlineSnapshot(
19+
'"A is: __A__"',
20+
)
21+
})
22+
1523
test(`deadlock doesn't happen`,async()=>{
1624
awaitpage.goto(`${url}/forked-deadlock`)
1725

‎playground/ssr/src/app.js‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { escapeHtml } from './utils'
33
constpathRenderers={
44
'/': renderRoot,
55
'/circular-dep': renderCircularDep,
6+
'/circular-import': renderCircularImport,
67
'/forked-deadlock': renderForkedDeadlock,
78
}
89

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

38+
asyncfunctionrenderCircularImport(rootDir){
39+
const{ logA }=awaitimport('./circular-import/index.js')
40+
return`<div class="circ-import">${escapeHtml(logA())}</div>`
41+
}
42+
3743
asyncfunctionrenderForkedDeadlock(rootDir){
3844
const{ commonModuleExport }=awaitimport('./forked-deadlock/common-module')
3945
commonModuleExport()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import{getB}from'./b'
2+
3+
exportconstA='__A__'
4+
5+
exportconstB=getB()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
exportfunctiongetB(){
2+
return'__B__'
3+
}
4+
5+
export{A}from'./a'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import{A}from'./b'
2+
3+
exportfunctionlogA(){
4+
return`A is: ${A}`
5+
}

0 commit comments

Comments
 (0)