diff --git a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts index f788ef14274ff0..03613d0b791b9e 100644 --- a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts +++ b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts @@ -529,3 +529,64 @@ class A { " `) }) + +test('delcare scope', async () => { + expect( + ( + await ssrTransform( + ` +import { aaa, bbb, ccc, ddd } from 'vue' + +function foobar() { + ddd() + + const aaa = () => { + bbb(ccc) + ddd() + } + const bbb = () => { + console.log('hi') + } + const ccc = 1 + function ddd() {} + + aaa() + bbb() + ccc() +} + +aaa() +bbb() +`, + null, + null + ) + ).code + ).toMatchInlineSnapshot(` + " + const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\"); + + + function foobar() { + ddd() + + const aaa = () => { + bbb(ccc) + ddd() + } + const bbb = () => { + console.log('hi') + } + const ccc = 1 + function ddd() {} + + aaa() + bbb() + ccc() + } + + __vite_ssr_import_0__.aaa() + __vite_ssr_import_0__.bbb() + " + `) +}) diff --git a/packages/vite/src/node/ssr/ssrTransform.ts b/packages/vite/src/node/ssr/ssrTransform.ts index ebbd92f8d09df8..c33e9ddf8f9b7c 100644 --- a/packages/vite/src/node/ssr/ssrTransform.ts +++ b/packages/vite/src/node/ssr/ssrTransform.ts @@ -180,7 +180,7 @@ export async function ssrTransform( // 3. convert references to import bindings & import.meta references walk(ast, { onIdentifier(id, parent, parentStack) { - const grandparent = parentStack[parentStack.length - 2] + const grandparent = parentStack[1] const binding = idToImportMap.get(id.name) if (!binding) { return @@ -203,7 +203,7 @@ export async function ssrTransform( if (!declaredConst.has(id.name)) { declaredConst.add(id.name) // locate the top-most node containing the class declaration - const topNode = parentStack[1] + const topNode = parentStack[parentStack.length - 2] s.prependRight(topNode.start, `const ${id.name} = ${binding};\n`) } } else { @@ -266,19 +266,14 @@ function walk( { onIdentifier, onImportMeta, onDynamicImport }: Visitors ) { const parentStack: Node[] = [] - const scope: Record = Object.create(null) const scopeMap = new WeakMap<_Node, Set>() + const identifiers: [id: any, stack: Node[]][] = [] const setScope = (node: FunctionNode, name: string) => { let scopeIds = scopeMap.get(node) if (scopeIds && scopeIds.has(name)) { return } - if (name in scope) { - scope[name]++ - } else { - scope[name] = 1 - } if (!scopeIds) { scopeIds = new Set() scopeMap.set(node, scopeIds) @@ -286,13 +281,17 @@ function walk( scopeIds.add(name) } + function isInScope(name: string, parents: Node[]) { + return parents.some((node) => node && scopeMap.get(node)?.has(name)) + } + ;(eswalk as any)(root, { enter(node: Node, parent: Node | null) { if (node.type === 'ImportDeclaration') { return this.skip() } - parent && parentStack.push(parent) + parent && parentStack.unshift(parent) if (node.type === 'MetaProperty' && node.meta.name === 'import') { onImportMeta(node) @@ -301,8 +300,12 @@ function walk( } if (node.type === 'Identifier') { - if (!scope[node.name] && isRefIdentifier(node, parent!, parentStack)) { - onIdentifier(node, parent!, parentStack) + if ( + !isInScope(node.name, parentStack) && + isRefIdentifier(node, parent!, parentStack) + ) { + // record the identifier, for DFS -> BFS + identifiers.push([node, parentStack.slice(0)]) } } else if (isFunction(node)) { // If it is a function declaration, it could be shadowing an import @@ -372,18 +375,15 @@ function walk( }, leave(node: Node, parent: Node | null) { - parent && parentStack.pop() - const scopeIds = scopeMap.get(node) - if (scopeIds) { - scopeIds.forEach((id: string) => { - scope[id]-- - if (scope[id] === 0) { - delete scope[id] - } - }) - } + parent && parentStack.shift() } }) + + // emit the identifier events in BFS so the hoisted declarations + // can be captured correctly + identifiers.forEach(([node, stack]) => { + if (!isInScope(node.name, stack)) onIdentifier(node, stack[0], stack) + }) } function isRefIdentifier(id: Identifier, parent: _Node, parentStack: _Node[]) { @@ -459,12 +459,7 @@ function isFunction(node: _Node): node is FunctionNode { } function findParentFunction(parentStack: _Node[]): FunctionNode | undefined { - for (let i = parentStack.length - 1; i >= 0; i--) { - const node = parentStack[i] - if (isFunction(node)) { - return node - } - } + return parentStack.find((i) => isFunction(i)) as FunctionNode } function isInDestructuringAssignment( @@ -475,15 +470,7 @@ function isInDestructuringAssignment( parent && (parent.type === 'Property' || parent.type === 'ArrayPattern') ) { - let i = parentStack.length - while (i--) { - const p = parentStack[i] - if (p.type === 'AssignmentExpression') { - return true - } else if (p.type !== 'Property' && !p.type.endsWith('Pattern')) { - break - } - } + return parentStack.some((i) => i.type === 'AssignmentExpression') } return false }