Skip to content

Commit 60ce7f9

Browse files
authored
fix(ssr): capture scope declaration correctly (vitejs#6281)
1 parent eb08ec5 commit 60ce7f9

File tree

2 files changed

+84
-36
lines changed

2 files changed

+84
-36
lines changed

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

+61
Original file line numberDiff line numberDiff line change
@@ -529,3 +529,64 @@ class A {
529529
"
530530
`)
531531
})
532+
533+
test('delcare scope', async () => {
534+
expect(
535+
(
536+
await ssrTransform(
537+
`
538+
import { aaa, bbb, ccc, ddd } from 'vue'
539+
540+
function foobar() {
541+
ddd()
542+
543+
const aaa = () => {
544+
bbb(ccc)
545+
ddd()
546+
}
547+
const bbb = () => {
548+
console.log('hi')
549+
}
550+
const ccc = 1
551+
function ddd() {}
552+
553+
aaa()
554+
bbb()
555+
ccc()
556+
}
557+
558+
aaa()
559+
bbb()
560+
`,
561+
null,
562+
null
563+
)
564+
).code
565+
).toMatchInlineSnapshot(`
566+
"
567+
const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\");
568+
569+
570+
function foobar() {
571+
ddd()
572+
573+
const aaa = () => {
574+
bbb(ccc)
575+
ddd()
576+
}
577+
const bbb = () => {
578+
console.log('hi')
579+
}
580+
const ccc = 1
581+
function ddd() {}
582+
583+
aaa()
584+
bbb()
585+
ccc()
586+
}
587+
588+
__vite_ssr_import_0__.aaa()
589+
__vite_ssr_import_0__.bbb()
590+
"
591+
`)
592+
})

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

+23-36
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ export async function ssrTransform(
180180
// 3. convert references to import bindings & import.meta references
181181
walk(ast, {
182182
onIdentifier(id, parent, parentStack) {
183-
const grandparent = parentStack[parentStack.length - 2]
183+
const grandparent = parentStack[1]
184184
const binding = idToImportMap.get(id.name)
185185
if (!binding) {
186186
return
@@ -203,7 +203,7 @@ export async function ssrTransform(
203203
if (!declaredConst.has(id.name)) {
204204
declaredConst.add(id.name)
205205
// locate the top-most node containing the class declaration
206-
const topNode = parentStack[1]
206+
const topNode = parentStack[parentStack.length - 2]
207207
s.prependRight(topNode.start, `const ${id.name} = ${binding};\n`)
208208
}
209209
} else {
@@ -266,33 +266,32 @@ function walk(
266266
{ onIdentifier, onImportMeta, onDynamicImport }: Visitors
267267
) {
268268
const parentStack: Node[] = []
269-
const scope: Record<string, number> = Object.create(null)
270269
const scopeMap = new WeakMap<_Node, Set<string>>()
270+
const identifiers: [id: any, stack: Node[]][] = []
271271

272272
const setScope = (node: FunctionNode, name: string) => {
273273
let scopeIds = scopeMap.get(node)
274274
if (scopeIds && scopeIds.has(name)) {
275275
return
276276
}
277-
if (name in scope) {
278-
scope[name]++
279-
} else {
280-
scope[name] = 1
281-
}
282277
if (!scopeIds) {
283278
scopeIds = new Set()
284279
scopeMap.set(node, scopeIds)
285280
}
286281
scopeIds.add(name)
287282
}
288283

284+
function isInScope(name: string, parents: Node[]) {
285+
return parents.some((node) => node && scopeMap.get(node)?.has(name))
286+
}
287+
289288
;(eswalk as any)(root, {
290289
enter(node: Node, parent: Node | null) {
291290
if (node.type === 'ImportDeclaration') {
292291
return this.skip()
293292
}
294293

295-
parent && parentStack.push(parent)
294+
parent && parentStack.unshift(parent)
296295

297296
if (node.type === 'MetaProperty' && node.meta.name === 'import') {
298297
onImportMeta(node)
@@ -301,8 +300,12 @@ function walk(
301300
}
302301

303302
if (node.type === 'Identifier') {
304-
if (!scope[node.name] && isRefIdentifier(node, parent!, parentStack)) {
305-
onIdentifier(node, parent!, parentStack)
303+
if (
304+
!isInScope(node.name, parentStack) &&
305+
isRefIdentifier(node, parent!, parentStack)
306+
) {
307+
// record the identifier, for DFS -> BFS
308+
identifiers.push([node, parentStack.slice(0)])
306309
}
307310
} else if (isFunction(node)) {
308311
// If it is a function declaration, it could be shadowing an import
@@ -372,18 +375,15 @@ function walk(
372375
},
373376

374377
leave(node: Node, parent: Node | null) {
375-
parent && parentStack.pop()
376-
const scopeIds = scopeMap.get(node)
377-
if (scopeIds) {
378-
scopeIds.forEach((id: string) => {
379-
scope[id]--
380-
if (scope[id] === 0) {
381-
delete scope[id]
382-
}
383-
})
384-
}
378+
parent && parentStack.shift()
385379
}
386380
})
381+
382+
// emit the identifier events in BFS so the hoisted declarations
383+
// can be captured correctly
384+
identifiers.forEach(([node, stack]) => {
385+
if (!isInScope(node.name, stack)) onIdentifier(node, stack[0], stack)
386+
})
387387
}
388388

389389
function isRefIdentifier(id: Identifier, parent: _Node, parentStack: _Node[]) {
@@ -459,12 +459,7 @@ function isFunction(node: _Node): node is FunctionNode {
459459
}
460460

461461
function findParentFunction(parentStack: _Node[]): FunctionNode | undefined {
462-
for (let i = parentStack.length - 1; i >= 0; i--) {
463-
const node = parentStack[i]
464-
if (isFunction(node)) {
465-
return node
466-
}
467-
}
462+
return parentStack.find((i) => isFunction(i)) as FunctionNode
468463
}
469464

470465
function isInDestructuringAssignment(
@@ -475,15 +470,7 @@ function isInDestructuringAssignment(
475470
parent &&
476471
(parent.type === 'Property' || parent.type === 'ArrayPattern')
477472
) {
478-
let i = parentStack.length
479-
while (i--) {
480-
const p = parentStack[i]
481-
if (p.type === 'AssignmentExpression') {
482-
return true
483-
} else if (p.type !== 'Property' && !p.type.endsWith('Pattern')) {
484-
break
485-
}
486-
}
473+
return parentStack.some((i) => i.type === 'AssignmentExpression')
487474
}
488475
return false
489476
}

0 commit comments

Comments
 (0)