diff --git a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts index e9ec999fafa9ab..d95d025663b638 100644 --- a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts +++ b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts @@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { assert, expect, test } from 'vitest' import type { SourceMap } from 'rollup' +import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping' import { transformWithEsbuild } from '../../plugins/esbuild' import { ssrTransform } from '../ssrTransform' @@ -442,6 +443,36 @@ test('sourcemap source', async () => { expect(map?.sourcesContent).toStrictEqual(['export const a = 1 /* */']) }) +test('sourcemap is correct for hoisted imports', async () => { + const code = `\n\n\nconsole.log(foo, bar);\nimport { foo } from 'vue';\nimport { bar } from 'vue2';` + const result = (await ssrTransform(code, null, 'input.js', code))! + + expect(result.code).toMatchInlineSnapshot(` + "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["foo"]}); + const __vite_ssr_import_1__ = await __vite_ssr_import__("vue2", {"importedNames":["bar"]}); + + + + console.log(__vite_ssr_import_0__.foo, __vite_ssr_import_1__.bar); + + " + `) + + const traceMap = new TraceMap(result.map as any) + expect(originalPositionFor(traceMap, { line: 1, column: 0 })).toStrictEqual({ + source: 'input.js', + line: 5, + column: 0, + name: null, + }) + expect(originalPositionFor(traceMap, { line: 2, column: 0 })).toStrictEqual({ + source: 'input.js', + line: 6, + column: 0, + name: null, + }) +}) + test('sourcemap with multiple sources', async () => { const code = readFixture('bundle.js') const map = readFixture('bundle.js.map') diff --git a/packages/vite/src/node/ssr/ssrTransform.ts b/packages/vite/src/node/ssr/ssrTransform.ts index 360f1d76f66d49..99eb0578699aa4 100644 --- a/packages/vite/src/node/ssr/ssrTransform.ts +++ b/packages/vite/src/node/ssr/ssrTransform.ts @@ -1,6 +1,6 @@ import path from 'node:path' import MagicString from 'magic-string' -import type { SourceMap } from 'rollup' +import type { RollupAstNode, SourceMap } from 'rollup' import type { ExportAllDeclaration, ExportDefaultDeclaration, @@ -98,13 +98,21 @@ async function ssrTransformScript( const declaredConst = new Set() // hoist at the start of the file, after the hashbang - const hoistIndex = hashbangRE.exec(code)?.[0].length ?? 0 + let hoistIndex = hashbangRE.exec(code)?.[0].length ?? 0 function defineImport( index: number, - source: string, + importNode: ( + | ImportDeclaration + | (ExportNamedDeclaration & { source: Literal }) + | ExportAllDeclaration + ) & { + start: number + end: number + }, metadata?: DefineImportMetadata, ) { + const source = importNode.source.value as string deps.add(source) const importId = `__vite_ssr_import_${uid++}__` @@ -117,14 +125,23 @@ async function ssrTransformScript( } const metadataStr = metadata ? `, ${JSON.stringify(metadata)}` : '' - // 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( - index, + s.update( + importNode.start, + importNode.end, `const ${importId} = await ${ssrImportKey}(${JSON.stringify( source, )}${metadataStr});\n`, ) + + if (importNode.start === index) { + // no need to hoist, but update hoistIndex to keep the order + hoistIndex = importNode.end + } else { + // 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.move(importNode.start, importNode.end, index) + } + return importId } @@ -136,12 +153,12 @@ async function ssrTransformScript( ) } - const imports: (ImportDeclaration & { start: number; end: number })[] = [] - const exports: (( - | ExportNamedDeclaration - | ExportDefaultDeclaration - | ExportAllDeclaration - ) & { start: number; end: number })[] = [] + const imports: RollupAstNode[] = [] + const exports: ( + | RollupAstNode + | RollupAstNode + | RollupAstNode + )[] = [] for (const node of ast.body as Node[]) { if (node.type === 'ImportDeclaration') { @@ -160,7 +177,7 @@ async function ssrTransformScript( // import foo from 'foo' --> foo -> __import_foo__.default // import { baz } from 'foo' --> baz -> __import_foo__.baz // import * as ok from 'foo' --> ok -> __import_foo__ - const importId = defineImport(hoistIndex, node.source.value as string, { + const importId = defineImport(hoistIndex, node, { importedNames: node.specifiers .map((s) => { if (s.type === 'ImportSpecifier') @@ -169,7 +186,6 @@ async function ssrTransformScript( }) .filter(isDefined), }) - s.remove(node.start, node.end) for (const spec of node.specifiers) { if (spec.type === 'ImportSpecifier') { if (spec.imported.type === 'Identifier') { @@ -219,7 +235,7 @@ async function ssrTransformScript( // export { foo, bar } from './foo' const importId = defineImport( node.start, - node.source.value as string, + node as RollupAstNode, { importedNames: node.specifiers.map( (s) => getIdentifierNameOrLiteralValue(s.local) as string, @@ -233,13 +249,13 @@ async function ssrTransformScript( if (spec.local.type === 'Identifier') { defineExport( - node.start, + node.end, exportedAs, `${importId}.${spec.local.name}`, ) } else { defineExport( - node.start, + node.end, exportedAs, `${importId}[${JSON.stringify(spec.local.value as string)}]`, ) @@ -290,15 +306,14 @@ async function ssrTransformScript( // export * from './foo' if (node.type === 'ExportAllDeclaration') { - s.remove(node.start, node.end) - const importId = defineImport(node.start, node.source.value as string) + const importId = defineImport(node.start, node) if (node.exported) { const exportedAs = getIdentifierNameOrLiteralValue( node.exported, ) as string - defineExport(node.start, exportedAs, `${importId}`) + defineExport(node.end, exportedAs, `${importId}`) } else { - s.appendLeft(node.start, `${ssrExportAllKey}(${importId});\n`) + s.appendLeft(node.end, `${ssrExportAllKey}(${importId});\n`) } } }