Skip to content

Commit 8e382a6

Browse files
smcenllysapphi-red
andauthored
fix(ssr): preserve source maps for hoisted imports (fix #16355) (#16356)
Co-authored-by: sapphi-red <[email protected]>
1 parent 3774881 commit 8e382a6

File tree

2 files changed

+68
-22
lines changed

2 files changed

+68
-22
lines changed

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs'
22
import { fileURLToPath } from 'node:url'
33
import { assert, expect, test } from 'vitest'
44
import type { SourceMap } from 'rollup'
5+
import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping'
56
import { transformWithEsbuild } from '../../plugins/esbuild'
67
import { ssrTransform } from '../ssrTransform'
78

@@ -445,6 +446,36 @@ test('sourcemap source', async () => {
445446
expect(map?.sourcesContent).toStrictEqual(['export const a = 1 /* */'])
446447
})
447448

449+
test('sourcemap is correct for hoisted imports', async () => {
450+
const code = `\n\n\nconsole.log(foo, bar);\nimport { foo } from 'vue';\nimport { bar } from 'vue2';`
451+
const result = (await ssrTransform(code, null, 'input.js', code))!
452+
453+
expect(result.code).toMatchInlineSnapshot(`
454+
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["foo"]});
455+
const __vite_ssr_import_1__ = await __vite_ssr_import__("vue2", {"importedNames":["bar"]});
456+
457+
458+
459+
console.log(__vite_ssr_import_0__.foo, __vite_ssr_import_1__.bar);
460+
461+
"
462+
`)
463+
464+
const traceMap = new TraceMap(result.map as any)
465+
expect(originalPositionFor(traceMap, { line: 1, column: 0 })).toStrictEqual({
466+
source: 'input.js',
467+
line: 5,
468+
column: 0,
469+
name: null,
470+
})
471+
expect(originalPositionFor(traceMap, { line: 2, column: 0 })).toStrictEqual({
472+
source: 'input.js',
473+
line: 6,
474+
column: 0,
475+
name: null,
476+
})
477+
})
478+
448479
test('sourcemap with multiple sources', async () => {
449480
const code = readFixture('bundle.js')
450481
const map = readFixture('bundle.js.map')

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

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'node:path'
22
import MagicString from 'magic-string'
3-
import type { SourceMap } from 'rollup'
3+
import type { RollupAstNode, SourceMap } from 'rollup'
44
import type {
55
ExportAllDeclaration,
66
ExportDefaultDeclaration,
@@ -99,13 +99,21 @@ async function ssrTransformScript(
9999
const declaredConst = new Set<string>()
100100

101101
// hoist at the start of the file, after the hashbang
102-
const hoistIndex = hashbangRE.exec(code)?.[0].length ?? 0
102+
let hoistIndex = hashbangRE.exec(code)?.[0].length ?? 0
103103

104104
function defineImport(
105105
index: number,
106-
source: string,
106+
importNode: (
107+
| ImportDeclaration
108+
| (ExportNamedDeclaration & { source: Literal })
109+
| ExportAllDeclaration
110+
) & {
111+
start: number
112+
end: number
113+
},
107114
metadata?: DefineImportMetadata,
108115
) {
116+
const source = importNode.source.value as string
109117
deps.add(source)
110118
const importId = `__vite_ssr_import_${uid++}__`
111119

@@ -118,14 +126,23 @@ async function ssrTransformScript(
118126
}
119127
const metadataStr = metadata ? `, ${JSON.stringify(metadata)}` : ''
120128

121-
// There will be an error if the module is called before it is imported,
122-
// so the module import statement is hoisted to the top
123-
s.appendLeft(
124-
index,
129+
s.update(
130+
importNode.start,
131+
importNode.end,
125132
`const ${importId} = await ${ssrImportKey}(${JSON.stringify(
126133
source,
127134
)}${metadataStr});\n`,
128135
)
136+
137+
if (importNode.start === index) {
138+
// no need to hoist, but update hoistIndex to keep the order
139+
hoistIndex = importNode.end
140+
} else {
141+
// There will be an error if the module is called before it is imported,
142+
// so the module import statement is hoisted to the top
143+
s.move(importNode.start, importNode.end, index)
144+
}
145+
129146
return importId
130147
}
131148

@@ -137,12 +154,12 @@ async function ssrTransformScript(
137154
)
138155
}
139156

140-
const imports: (ImportDeclaration & { start: number; end: number })[] = []
141-
const exports: ((
142-
| ExportNamedDeclaration
143-
| ExportDefaultDeclaration
144-
| ExportAllDeclaration
145-
) & { start: number; end: number })[] = []
157+
const imports: RollupAstNode<ImportDeclaration>[] = []
158+
const exports: (
159+
| RollupAstNode<ExportNamedDeclaration>
160+
| RollupAstNode<ExportDefaultDeclaration>
161+
| RollupAstNode<ExportAllDeclaration>
162+
)[] = []
146163

147164
for (const node of ast.body as Node[]) {
148165
if (node.type === 'ImportDeclaration') {
@@ -161,7 +178,7 @@ async function ssrTransformScript(
161178
// import foo from 'foo' --> foo -> __import_foo__.default
162179
// import { baz } from 'foo' --> baz -> __import_foo__.baz
163180
// import * as ok from 'foo' --> ok -> __import_foo__
164-
const importId = defineImport(hoistIndex, node.source.value as string, {
181+
const importId = defineImport(hoistIndex, node, {
165182
importedNames: node.specifiers
166183
.map((s) => {
167184
if (s.type === 'ImportSpecifier')
@@ -170,7 +187,6 @@ async function ssrTransformScript(
170187
})
171188
.filter(isDefined),
172189
})
173-
s.remove(node.start, node.end)
174190
for (const spec of node.specifiers) {
175191
if (spec.type === 'ImportSpecifier') {
176192
if (spec.imported.type === 'Identifier') {
@@ -220,7 +236,7 @@ async function ssrTransformScript(
220236
// export { foo, bar } from './foo'
221237
const importId = defineImport(
222238
node.start,
223-
node.source.value as string,
239+
node as RollupAstNode<ExportNamedDeclaration & { source: Literal }>,
224240
{
225241
importedNames: node.specifiers.map(
226242
(s) => getIdentifierNameOrLiteralValue(s.local) as string,
@@ -234,13 +250,13 @@ async function ssrTransformScript(
234250

235251
if (spec.local.type === 'Identifier') {
236252
defineExport(
237-
node.start,
253+
node.end,
238254
exportedAs,
239255
`${importId}.${spec.local.name}`,
240256
)
241257
} else {
242258
defineExport(
243-
node.start,
259+
node.end,
244260
exportedAs,
245261
`${importId}[${JSON.stringify(spec.local.value as string)}]`,
246262
)
@@ -291,15 +307,14 @@ async function ssrTransformScript(
291307

292308
// export * from './foo'
293309
if (node.type === 'ExportAllDeclaration') {
294-
s.remove(node.start, node.end)
295-
const importId = defineImport(node.start, node.source.value as string)
310+
const importId = defineImport(node.start, node)
296311
if (node.exported) {
297312
const exportedAs = getIdentifierNameOrLiteralValue(
298313
node.exported,
299314
) as string
300-
defineExport(node.start, exportedAs, `${importId}`)
315+
defineExport(node.end, exportedAs, `${importId}`)
301316
} else {
302-
s.appendLeft(node.start, `${ssrExportAllKey}(${importId});\n`)
317+
s.appendLeft(node.end, `${ssrExportAllKey}(${importId});\n`)
303318
}
304319
}
305320
}

0 commit comments

Comments
 (0)