Skip to content

Commit 1aa434e

Browse files
aleclarsonsapphi-red
andauthoredDec 24, 2024··
fix(ssrTransform): preserve line offset when transforming imports (#19004)
Co-authored-by: 翠 / green <green@sapphi.red>
1 parent 902567a commit 1aa434e

File tree

2 files changed

+222
-171
lines changed

2 files changed

+222
-171
lines changed
 

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

+179-149
Original file line numberDiff line numberDiff line change
@@ -14,43 +14,39 @@ const ssrTransformSimpleCode = async (code: string, url?: string) =>
1414
test('default import', async () => {
1515
expect(
1616
await ssrTransformSimpleCode(`import foo from 'vue';console.log(foo.bar)`),
17-
).toMatchInlineSnapshot(`
18-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["default"]});
19-
console.log(__vite_ssr_import_0__.default.bar)"
20-
`)
17+
).toMatchInlineSnapshot(
18+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["default"]});console.log(__vite_ssr_import_0__.default.bar)"`,
19+
)
2120
})
2221

2322
test('named import', async () => {
2423
expect(
2524
await ssrTransformSimpleCode(
2625
`import { ref } from 'vue';function foo() { return ref(0) }`,
2726
),
28-
).toMatchInlineSnapshot(`
29-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["ref"]});
30-
function foo() { return (0,__vite_ssr_import_0__.ref)(0) }"
31-
`)
27+
).toMatchInlineSnapshot(
28+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["ref"]});function foo() { return (0,__vite_ssr_import_0__.ref)(0) }"`,
29+
)
3230
})
3331

3432
test('named import: arbitrary module namespace specifier', async () => {
3533
expect(
3634
await ssrTransformSimpleCode(
3735
`import { "some thing" as ref } from 'vue';function foo() { return ref(0) }`,
3836
),
39-
).toMatchInlineSnapshot(`
40-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["some thing"]});
41-
function foo() { return (0,__vite_ssr_import_0__["some thing"])(0) }"
42-
`)
37+
).toMatchInlineSnapshot(
38+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["some thing"]});function foo() { return (0,__vite_ssr_import_0__["some thing"])(0) }"`,
39+
)
4340
})
4441

4542
test('namespace import', async () => {
4643
expect(
4744
await ssrTransformSimpleCode(
4845
`import * as vue from 'vue';function foo() { return vue.ref(0) }`,
4946
),
50-
).toMatchInlineSnapshot(`
51-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");
52-
function foo() { return __vite_ssr_import_0__.ref(0) }"
53-
`)
47+
).toMatchInlineSnapshot(
48+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");function foo() { return __vite_ssr_import_0__.ref(0) }"`,
49+
)
5450
})
5551

5652
test('export function declaration', async () => {
@@ -93,7 +89,6 @@ test('export named from', async () => {
9389
await ssrTransformSimpleCode(`export { ref, computed as c } from 'vue'`),
9490
).toMatchInlineSnapshot(`
9591
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["ref","computed"]});
96-
9792
Object.defineProperty(__vite_ssr_exports__, "ref", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.ref }});
9893
Object.defineProperty(__vite_ssr_exports__, "c", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.computed }});"
9994
`)
@@ -106,7 +101,6 @@ test('named exports of imported binding', async () => {
106101
),
107102
).toMatchInlineSnapshot(`
108103
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["createApp"]});
109-
110104
Object.defineProperty(__vite_ssr_exports__, "createApp", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.createApp }});"
111105
`)
112106
})
@@ -117,11 +111,9 @@ test('export * from', async () => {
117111
`export * from 'vue'\n` + `export * from 'react'`,
118112
),
119113
).toMatchInlineSnapshot(`
120-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");
121-
__vite_ssr_exportAll__(__vite_ssr_import_0__);
114+
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");__vite_ssr_exportAll__(__vite_ssr_import_0__);
122115
;
123-
const __vite_ssr_import_1__ = await __vite_ssr_import__("react");
124-
__vite_ssr_exportAll__(__vite_ssr_import_1__);
116+
const __vite_ssr_import_1__ = await __vite_ssr_import__("react");__vite_ssr_exportAll__(__vite_ssr_import_1__);
125117
"
126118
`)
127119
})
@@ -130,7 +122,6 @@ test('export * as from', async () => {
130122
expect(await ssrTransformSimpleCode(`export * as foo from 'vue'`))
131123
.toMatchInlineSnapshot(`
132124
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");
133-
134125
Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ }});"
135126
`)
136127
})
@@ -140,7 +131,6 @@ test('export * as from arbitrary module namespace identifier', async () => {
140131
await ssrTransformSimpleCode(`export * as "arbitrary string" from 'vue'`),
141132
).toMatchInlineSnapshot(`
142133
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");
143-
144134
Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ }});"
145135
`)
146136
})
@@ -163,7 +153,6 @@ test('export as from arbitrary module namespace identifier', async () => {
163153
),
164154
).toMatchInlineSnapshot(`
165155
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["arbitrary string2"]});
166-
167156
Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__["arbitrary string2"] }});"
168157
`)
169158
})
@@ -180,9 +169,7 @@ test('export then import minified', async () => {
180169
`export * from 'vue';import {createApp} from 'vue';`,
181170
),
182171
).toMatchInlineSnapshot(`
183-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["createApp"]});
184-
const __vite_ssr_import_1__ = await __vite_ssr_import__("vue");
185-
__vite_ssr_exportAll__(__vite_ssr_import_1__);
172+
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["createApp"]});const __vite_ssr_import_1__ = await __vite_ssr_import__("vue");__vite_ssr_exportAll__(__vite_ssr_import_1__);
186173
"
187174
`)
188175
})
@@ -192,9 +179,94 @@ test('hoist import to top', async () => {
192179
await ssrTransformSimpleCode(
193180
`path.resolve('server.js');import path from 'node:path';`,
194181
),
182+
).toMatchInlineSnapshot(
183+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["default"]});__vite_ssr_import_0__.default.resolve('server.js');"`,
184+
)
185+
})
186+
187+
test('whitespace between imports does not trigger hoisting', async () => {
188+
expect(
189+
await ssrTransformSimpleCode(
190+
`import { dirname } from 'node:path';\n\n\nimport fs from 'node:fs';`,
191+
),
192+
).toMatchInlineSnapshot(`
193+
"const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["dirname"]});
194+
195+
196+
const __vite_ssr_import_1__ = await __vite_ssr_import__("node:fs", {"importedNames":["default"]});"
197+
`)
198+
})
199+
200+
test('preserve line offset when rewriting imports', async () => {
201+
// The line number of each non-import statement must not change.
202+
const inputLines = [
203+
`debugger;`,
204+
``,
205+
`import {`,
206+
` dirname,`,
207+
` join,`,
208+
`} from 'node:path';`,
209+
``,
210+
`debugger;`,
211+
``,
212+
`import fs from 'node:fs';`,
213+
``,
214+
`debugger;`,
215+
``,
216+
`import {`,
217+
` red,`,
218+
` green,`,
219+
`} from 'kleur/colors';`,
220+
``,
221+
`debugger;`,
222+
]
223+
224+
const output = await ssrTransformSimpleCode(inputLines.join('\n'))
225+
expect(output).toBeDefined()
226+
227+
const outputLines = output!.split('\n')
228+
expect(
229+
outputLines
230+
.map((line, i) => `${String(i + 1).padStart(2)} | ${line}`.trimEnd())
231+
.join('\n'),
232+
).toMatchInlineSnapshot(`
233+
" 1 | const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["dirname","join"]});const __vite_ssr_import_1__ = await __vite_ssr_import__("node:fs", {"importedNames":["default"]});const __vite_ssr_import_2__ = await __vite_ssr_import__("kleur/colors", {"importedNames":["red","green"]});debugger;
234+
2 |
235+
3 |
236+
4 |
237+
5 |
238+
6 |
239+
7 |
240+
8 | debugger;
241+
9 |
242+
10 |
243+
11 |
244+
12 | debugger;
245+
13 |
246+
14 |
247+
15 |
248+
16 |
249+
17 |
250+
18 |
251+
19 | debugger;"
252+
`)
253+
254+
// Ensure the debugger statements are still on the same lines.
255+
expect(outputLines[0].endsWith(inputLines[0])).toBe(true)
256+
expect(outputLines[7]).toBe(inputLines[7])
257+
expect(outputLines[11]).toBe(inputLines[11])
258+
expect(outputLines[18]).toBe(inputLines[18])
259+
})
260+
261+
// not implemented
262+
test.skip('comments between imports do not trigger hoisting', async () => {
263+
expect(
264+
await ssrTransformSimpleCode(
265+
`import { dirname } from 'node:path';// comment\nimport fs from 'node:fs';`,
266+
),
195267
).toMatchInlineSnapshot(`
196-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["default"]});
197-
__vite_ssr_import_0__.default.resolve('server.js');"
268+
"const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["dirname"]});// comment
269+
const __vite_ssr_import_1__ = await __vite_ssr_import__("node:fs", {"importedNames":["default"]});"
198270
`)
199271
})
200272

@@ -220,21 +292,19 @@ test('do not rewrite method definition', async () => {
220292
const result = await ssrTransformSimple(
221293
`import { fn } from 'vue';class A { fn() { fn() } }`,
222294
)
223-
expect(result?.code).toMatchInlineSnapshot(`
224-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});
225-
class A { fn() { (0,__vite_ssr_import_0__.fn)() } }"
226-
`)
295+
expect(result?.code).toMatchInlineSnapshot(
296+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});class A { fn() { (0,__vite_ssr_import_0__.fn)() } }"`,
297+
)
227298
expect(result?.deps).toEqual(['vue'])
228299
})
229300

230301
test('do not rewrite when variable is in scope', async () => {
231302
const result = await ssrTransformSimple(
232303
`import { fn } from 'vue';function A(){ const fn = () => {}; return { fn }; }`,
233304
)
234-
expect(result?.code).toMatchInlineSnapshot(`
235-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});
236-
function A(){ const fn = () => {}; return { fn }; }"
237-
`)
305+
expect(result?.code).toMatchInlineSnapshot(
306+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A(){ const fn = () => {}; return { fn }; }"`,
307+
)
238308
expect(result?.deps).toEqual(['vue'])
239309
})
240310

@@ -243,10 +313,9 @@ test('do not rewrite when variable is in scope with object destructuring', async
243313
const result = await ssrTransformSimple(
244314
`import { fn } from 'vue';function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }`,
245315
)
246-
expect(result?.code).toMatchInlineSnapshot(`
247-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});
248-
function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }"
249-
`)
316+
expect(result?.code).toMatchInlineSnapshot(
317+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }"`,
318+
)
250319
expect(result?.deps).toEqual(['vue'])
251320
})
252321

@@ -255,10 +324,9 @@ test('do not rewrite when variable is in scope with array destructuring', async
255324
const result = await ssrTransformSimple(
256325
`import { fn } from 'vue';function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }`,
257326
)
258-
expect(result?.code).toMatchInlineSnapshot(`
259-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});
260-
function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }"
261-
`)
327+
expect(result?.code).toMatchInlineSnapshot(
328+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }"`,
329+
)
262330
expect(result?.deps).toEqual(['vue'])
263331
})
264332

@@ -267,10 +335,9 @@ test('rewrite variable in string interpolation in function nested arguments', as
267335
const result = await ssrTransformSimple(
268336
`import { fn } from 'vue';function A({foo = \`test\${fn}\`} = {}){ return {}; }`,
269337
)
270-
expect(result?.code).toMatchInlineSnapshot(`
271-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});
272-
function A({foo = \`test\${__vite_ssr_import_0__.fn}\`} = {}){ return {}; }"
273-
`)
338+
expect(result?.code).toMatchInlineSnapshot(
339+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A({foo = \`test\${__vite_ssr_import_0__.fn}\`} = {}){ return {}; }"`,
340+
)
274341
expect(result?.deps).toEqual(['vue'])
275342
})
276343

@@ -279,21 +346,19 @@ test('rewrite variables in default value of destructuring params', async () => {
279346
const result = await ssrTransformSimple(
280347
`import { fn } from 'vue';function A({foo = fn}){ return {}; }`,
281348
)
282-
expect(result?.code).toMatchInlineSnapshot(`
283-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});
284-
function A({foo = __vite_ssr_import_0__.fn}){ return {}; }"
285-
`)
349+
expect(result?.code).toMatchInlineSnapshot(
350+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A({foo = __vite_ssr_import_0__.fn}){ return {}; }"`,
351+
)
286352
expect(result?.deps).toEqual(['vue'])
287353
})
288354

289355
test('do not rewrite when function declaration is in scope', async () => {
290356
const result = await ssrTransformSimple(
291357
`import { fn } from 'vue';function A(){ function fn() {}; return { fn }; }`,
292358
)
293-
expect(result?.code).toMatchInlineSnapshot(`
294-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});
295-
function A(){ function fn() {}; return { fn }; }"
296-
`)
359+
expect(result?.code).toMatchInlineSnapshot(
360+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A(){ function fn() {}; return { fn }; }"`,
361+
)
297362
expect(result?.deps).toEqual(['vue'])
298363
})
299364

@@ -302,62 +367,56 @@ test('do not rewrite when function expression is in scope', async () => {
302367
const result = await ssrTransformSimple(
303368
`import {fn} from './vue';var a = function() { return function fn() { console.log(fn) } }`,
304369
)
305-
expect(result?.code).toMatchInlineSnapshot(`
306-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["fn"]});
307-
var a = function() { return function fn() { console.log(fn) } }"
308-
`)
370+
expect(result?.code).toMatchInlineSnapshot(
371+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["fn"]});var a = function() { return function fn() { console.log(fn) } }"`,
372+
)
309373
})
310374

311375
// #16452
312376
test('do not rewrite when function expression is in global scope', async () => {
313377
const result = await ssrTransformSimple(
314378
`import {fn} from './vue';foo(function fn(a = fn) { console.log(fn) })`,
315379
)
316-
expect(result?.code).toMatchInlineSnapshot(`
317-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["fn"]});
318-
foo(function fn(a = fn) { console.log(fn) })"
319-
`)
380+
expect(result?.code).toMatchInlineSnapshot(
381+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["fn"]});foo(function fn(a = fn) { console.log(fn) })"`,
382+
)
320383
})
321384

322385
test('do not rewrite when class declaration is in scope', async () => {
323386
const result = await ssrTransformSimple(
324387
`import { cls } from 'vue';function A(){ class cls {} return { cls }; }`,
325388
)
326-
expect(result?.code).toMatchInlineSnapshot(`
327-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["cls"]});
328-
function A(){ class cls {} return { cls }; }"
329-
`)
389+
expect(result?.code).toMatchInlineSnapshot(
390+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["cls"]});function A(){ class cls {} return { cls }; }"`,
391+
)
330392
expect(result?.deps).toEqual(['vue'])
331393
})
332394

333395
test('do not rewrite when class expression is in scope', async () => {
334396
const result = await ssrTransformSimple(
335397
`import { cls } from './vue';var a = function() { return class cls { constructor() { console.log(cls) } } }`,
336398
)
337-
expect(result?.code).toMatchInlineSnapshot(`
338-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["cls"]});
339-
var a = function() { return class cls { constructor() { console.log(cls) } } }"
340-
`)
399+
expect(result?.code).toMatchInlineSnapshot(
400+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["cls"]});var a = function() { return class cls { constructor() { console.log(cls) } } }"`,
401+
)
341402
})
342403

343404
test('do not rewrite when class expression is in global scope', async () => {
344405
const result = await ssrTransformSimple(
345406
`import { cls } from './vue';foo(class cls { constructor() { console.log(cls) } })`,
346407
)
347-
expect(result?.code).toMatchInlineSnapshot(`
348-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["cls"]});
349-
foo(class cls { constructor() { console.log(cls) } })"
350-
`)
408+
expect(result?.code).toMatchInlineSnapshot(
409+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["cls"]});foo(class cls { constructor() { console.log(cls) } })"`,
410+
)
351411
})
352412

353413
test('do not rewrite catch clause', async () => {
354414
const result = await ssrTransformSimple(
355415
`import {error} from './dependency';try {} catch(error) {}`,
356416
)
357-
expect(result?.code).toMatchInlineSnapshot(`
358-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["error"]});
359-
try {} catch(error) {}"
360-
`)
417+
expect(result?.code).toMatchInlineSnapshot(
418+
`"const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["error"]});try {} catch(error) {}"`,
419+
)
361420
expect(result?.deps).toEqual(['./dependency'])
362421
})
363422

@@ -368,8 +427,7 @@ test('should declare variable for imported super class', async () => {
368427
`import { Foo } from './dependency';` + `class A extends Foo {}`,
369428
),
370429
).toMatchInlineSnapshot(`
371-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["Foo"]});
372-
const Foo = __vite_ssr_import_0__.Foo;
430+
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["Foo"]});const Foo = __vite_ssr_import_0__.Foo;
373431
class A extends Foo {}"
374432
`)
375433

@@ -382,8 +440,7 @@ test('should declare variable for imported super class', async () => {
382440
`export class B extends Foo {}`,
383441
),
384442
).toMatchInlineSnapshot(`
385-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["Foo"]});
386-
const Foo = __vite_ssr_import_0__.Foo;
443+
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["Foo"]});const Foo = __vite_ssr_import_0__.Foo;
387444
class A extends Foo {};
388445
class B extends Foo {}
389446
Object.defineProperty(__vite_ssr_exports__, "B", { enumerable: true, configurable: true, get(){ return B }});
@@ -448,9 +505,7 @@ test('sourcemap is correct for hoisted imports', async () => {
448505
const result = (await ssrTransform(code, null, 'input.js', code))!
449506

450507
expect(result.code).toMatchInlineSnapshot(`
451-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["foo"]});
452-
const __vite_ssr_import_1__ = await __vite_ssr_import__("vue2", {"importedNames":["bar"]});
453-
508+
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["foo"]});const __vite_ssr_import_1__ = await __vite_ssr_import__("vue2", {"importedNames":["bar"]});
454509
455510
456511
console.log((0,__vite_ssr_import_0__.foo), (0,__vite_ssr_import_1__.bar));
@@ -465,7 +520,7 @@ test('sourcemap is correct for hoisted imports', async () => {
465520
column: 0,
466521
name: null,
467522
})
468-
expect(originalPositionFor(traceMap, { line: 2, column: 0 })).toStrictEqual({
523+
expect(originalPositionFor(traceMap, { line: 1, column: 90 })).toStrictEqual({
469524
source: 'input.js',
470525
line: 6,
471526
column: 0,
@@ -532,8 +587,7 @@ test('overwrite bindings', async () => {
532587
`function g() { const f = () => { const inject = true }; console.log(inject) }\n`,
533588
),
534589
).toMatchInlineSnapshot(`
535-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["inject"]});
536-
const a = { inject: __vite_ssr_import_0__.inject };
590+
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["inject"]});const a = { inject: __vite_ssr_import_0__.inject };
537591
const b = { test: __vite_ssr_import_0__.inject };
538592
function c() { const { test: inject } = { test: true }; console.log(inject) }
539593
const d = __vite_ssr_import_0__.inject;
@@ -561,9 +615,8 @@ function c({ _ = bar() + foo() }) {}
561615
`,
562616
),
563617
).toMatchInlineSnapshot(`
564-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo","bar"]});
565-
566-
618+
"
619+
const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo","bar"]});
567620
const a = ({ _ = (0,__vite_ssr_import_0__.foo)() }) => {};
568621
function b({ _ = (0,__vite_ssr_import_0__.bar)() }) {}
569622
function c({ _ = (0,__vite_ssr_import_0__.bar)() + (0,__vite_ssr_import_0__.foo)() }) {}
@@ -583,9 +636,8 @@ const a = () => {
583636
`,
584637
),
585638
).toMatchInlineSnapshot(`
586-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["n"]});
587-
588-
639+
"
640+
const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["n"]});
589641
const a = () => {
590642
const { type: n = 'bar' } = {};
591643
console.log(n)
@@ -606,9 +658,8 @@ const foo = {}
606658
`,
607659
),
608660
).toMatchInlineSnapshot(`
609-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["n","m"]});
610-
611-
661+
"
662+
const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["n","m"]});
612663
const foo = {};
613664
614665
{
@@ -649,9 +700,8 @@ objRest()
649700
`,
650701
),
651702
).toMatchInlineSnapshot(`
652-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["remove","add","get","set","rest","objRest"]});
653-
654-
703+
"
704+
const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["remove","add","get","set","rest","objRest"]});
655705
656706
function a() {
657707
const {
@@ -699,9 +749,8 @@ const obj = {
699749
`,
700750
),
701751
).toMatchInlineSnapshot(`
702-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]});
703-
704-
752+
"
753+
const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]});
705754
706755
const bar = 'bar';
707756
@@ -731,9 +780,8 @@ class A {
731780
`,
732781
),
733782
).toMatchInlineSnapshot(`
734-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["remove","add"]});
735-
736-
783+
"
784+
const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["remove","add"]});
737785
738786
const add = __vite_ssr_import_0__.add;
739787
const remove = __vite_ssr_import_0__.remove;
@@ -763,9 +811,8 @@ class A {
763811
`,
764812
),
765813
).toMatchInlineSnapshot(`
766-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]});
767-
768-
814+
"
815+
const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]});
769816
770817
const bar = 'bar';
771818
@@ -809,9 +856,8 @@ bbb()
809856
`,
810857
),
811858
).toMatchInlineSnapshot(`
812-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["aaa","bbb","ccc","ddd"]});
813-
814-
859+
"
860+
const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["aaa","bbb","ccc","ddd"]});
815861
816862
function foobar() {
817863
ddd();
@@ -856,8 +902,6 @@ test('jsx', async () => {
856902
.toMatchInlineSnapshot(`
857903
"const __vite_ssr_import_0__ = await __vite_ssr_import__("react", {"importedNames":["default"]});
858904
const __vite_ssr_import_1__ = await __vite_ssr_import__("foo", {"importedNames":["Foo","Slot"]});
859-
860-
861905
function Bar({ Slot: Slot2 = /* @__PURE__ */ __vite_ssr_import_0__.default.createElement((0,__vite_ssr_import_1__.Foo), null) }) {
862906
return /* @__PURE__ */ __vite_ssr_import_0__.default.createElement(__vite_ssr_import_0__.default.Fragment, null, /* @__PURE__ */ __vite_ssr_import_0__.default.createElement(Slot2, null));
863907
}
@@ -930,8 +974,7 @@ import foo from "foo"`,
930974
),
931975
).toMatchInlineSnapshot(`
932976
"#!/usr/bin/env node
933-
const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]});
934-
console.log((0,__vite_ssr_import_0__.default));
977+
const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]});console.log((0,__vite_ssr_import_0__.default));
935978
"
936979
`)
937980
})
@@ -946,7 +989,6 @@ foo()`,
946989
).toMatchInlineSnapshot(`
947990
"#!/usr/bin/env node
948991
const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]});
949-
950992
(0,__vite_ssr_import_0__.foo)()"
951993
`)
952994
})
@@ -982,7 +1024,6 @@ export class Test {
9821024

9831025
expect(await ssrTransformSimpleCode(code)).toMatchInlineSnapshot(`
9841026
"const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar"]});
985-
9861027
if (false) {
9871028
const foo = 'foo';
9881029
console.log(foo)
@@ -1023,9 +1064,8 @@ function test() {
10231064
return [foo, bar]
10241065
}`),
10251066
).toMatchInlineSnapshot(`
1026-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar"]});
1027-
1028-
1067+
"
1068+
const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar"]});
10291069
function test() {
10301070
if (true) {
10311071
var foo = () => { var why = 'would' }, bar = 'someone'
@@ -1050,9 +1090,8 @@ function test() {
10501090
return bar;
10511091
}`),
10521092
).toMatchInlineSnapshot(`
1053-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar","baz"]});
1054-
1055-
1093+
"
1094+
const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar","baz"]});
10561095
function test() {
10571096
[__vite_ssr_import_0__.foo];
10581097
{
@@ -1082,9 +1121,8 @@ for (const test in tests) {
10821121
console.log(test)
10831122
}`),
10841123
).toMatchInlineSnapshot(`
1085-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./test.js", {"importedNames":["test"]});
1086-
1087-
1124+
"
1125+
const __vite_ssr_import_0__ = await __vite_ssr_import__("./test.js", {"importedNames":["test"]});
10881126
10891127
for (const test of tests) {
10901128
console.log(test)
@@ -1114,9 +1152,8 @@ const Baz = class extends Foo {}
11141152
`,
11151153
)
11161154
expect(result?.code).toMatchInlineSnapshot(`
1117-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["default","Bar"]});
1118-
1119-
1155+
"
1156+
const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["default","Bar"]});
11201157
11211158
console.log((0,__vite_ssr_import_0__.default), (0,__vite_ssr_import_0__.Bar));
11221159
const obj = {
@@ -1135,9 +1172,8 @@ test('import assertion attribute', async () => {
11351172
import('./bar.json', { with: { type: 'json' } });
11361173
`),
11371174
).toMatchInlineSnapshot(`
1138-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo.json");
1139-
1140-
1175+
"
1176+
const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo.json");
11411177
__vite_ssr_dynamic_import__('./bar.json', { with: { type: 'json' } });
11421178
"
11431179
`)
@@ -1157,14 +1193,11 @@ console.log(foo + 2)
11571193
`),
11581194
).toMatchInlineSnapshot(`
11591195
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]});
1160-
11611196
console.log(__vite_ssr_import_0__.foo + 1);
1162-
const __vite_ssr_import_1__ = await __vite_ssr_import__("./a");
1163-
__vite_ssr_exportAll__(__vite_ssr_import_1__);
1197+
const __vite_ssr_import_1__ = await __vite_ssr_import__("./a");__vite_ssr_exportAll__(__vite_ssr_import_1__);
11641198
;
11651199
1166-
const __vite_ssr_import_2__ = await __vite_ssr_import__("./b");
1167-
__vite_ssr_exportAll__(__vite_ssr_import_2__);
1200+
const __vite_ssr_import_2__ = await __vite_ssr_import__("./b");__vite_ssr_exportAll__(__vite_ssr_import_2__);
11681201
;
11691202
console.log(__vite_ssr_import_0__.foo + 2)
11701203
"
@@ -1180,12 +1213,10 @@ export * as bar from './bar'
11801213
console.log(bar)
11811214
`),
11821215
).toMatchInlineSnapshot(`
1183-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]});
1184-
1185-
1216+
"
1217+
const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]});
11861218
__vite_ssr_exports__.default = (0,__vite_ssr_import_0__.foo)();
11871219
const __vite_ssr_import_1__ = await __vite_ssr_import__("./bar");
1188-
11891220
Object.defineProperty(__vite_ssr_exports__, "bar", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__ }});;
11901221
console.log(bar)
11911222
"
@@ -1256,9 +1287,8 @@ switch (1) {
12561287
}
12571288
`),
12581289
).toMatchInlineSnapshot(`
1259-
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./f", {"importedNames":["f"]});
1260-
1261-
1290+
"
1291+
const __vite_ssr_import_0__ = await __vite_ssr_import__("./f", {"importedNames":["f"]});
12621292
12631293
let x = 0;
12641294

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

+43-22
Original file line numberDiff line numberDiff line change
@@ -126,32 +126,53 @@ async function ssrTransformScript(
126126
) {
127127
const source = importNode.source.value as string
128128
deps.add(source)
129-
const importId = `__vite_ssr_import_${uid++}__`
130129

131130
// Reduce metadata to undefined if it's all default values
132-
if (
133-
metadata &&
134-
(metadata.importedNames == null || metadata.importedNames.length === 0)
135-
) {
136-
metadata = undefined
137-
}
138-
const metadataStr = metadata ? `, ${JSON.stringify(metadata)}` : ''
139-
140-
s.update(
141-
importNode.start,
142-
importNode.end,
143-
`const ${importId} = await ${ssrImportKey}(${JSON.stringify(
144-
source,
145-
)}${metadataStr});\n`,
146-
)
131+
const metadataArg =
132+
(metadata?.importedNames?.length ?? 0) > 0
133+
? `, ${JSON.stringify(metadata)}`
134+
: ''
147135

148-
if (importNode.start === index) {
149-
// no need to hoist, but update hoistIndex to keep the order
150-
hoistIndex = importNode.end
151-
} else {
152-
// There will be an error if the module is called before it is imported,
153-
// so the module import statement is hoisted to the top
136+
const importId = `__vite_ssr_import_${uid++}__`
137+
const transformedImport = `const ${importId} = await ${ssrImportKey}(${JSON.stringify(
138+
source,
139+
)}${metadataArg});`
140+
141+
s.update(importNode.start, importNode.end, transformedImport)
142+
143+
// If there's only whitespace characters between the last import and the
144+
// current one, that means there's no statements between them and
145+
// hoisting is not needed.
146+
// FIXME: account for comments between imports
147+
const nonWhitespaceRegex = /\S/g
148+
nonWhitespaceRegex.lastIndex = index
149+
nonWhitespaceRegex.exec(code)
150+
if (importNode.start > nonWhitespaceRegex.lastIndex) {
151+
// Imports are moved to the top of the file (AKA “hoisting”) to ensure any
152+
// non-import statements before them are executed after the import. This
153+
// aligns SSR imports with native ESM import behavior.
154154
s.move(importNode.start, importNode.end, index)
155+
} else {
156+
// Only update hoistIndex when *not* hoisting the current import. This
157+
// ensures that once any import in this module has been hoisted, all
158+
// remaining imports will also be hoisted. This is inherently true because
159+
// we work from the top of the file downward.
160+
hoistIndex = importNode.end
161+
}
162+
163+
// Track how many lines the original import statement spans, so we can
164+
// preserve the line offset.
165+
let linesSpanned = 1
166+
for (let i = importNode.start; i < importNode.end; i++) {
167+
if (code[i] === '\n') {
168+
linesSpanned++
169+
}
170+
}
171+
if (linesSpanned > 1) {
172+
// This leaves behind any extra newlines that were removed during
173+
// transformation, in the position of the original import statement
174+
// (before any hoisting).
175+
s.prependRight(importNode.end, '\n'.repeat(linesSpanned - 1))
155176
}
156177

157178
return importId

0 commit comments

Comments
 (0)
Please sign in to comment.