-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(ssrTransform): preserve line offset when transforming imports #19004
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes sense.
Would you add a test case for "Broken import hoisting"? It'd be also nice if there's a skipped test case for the edge case related to the comment.
This comment was marked as outdated.
This comment was marked as outdated.
commit: |
This comment was marked as outdated.
This comment was marked as outdated.
The failure of laravel and marko is a known issue on ecosystem-ci side. |
@sapphi-red Good call to write that test, as there were bugs 🙈 |
test('preserve line offset when rewriting imports', async () => { | ||
// The line number of each non-import statement must not change. | ||
const inputLines = [ | ||
`debugger;`, | ||
``, | ||
`import {`, | ||
` dirname,`, | ||
` join,`, | ||
`} from 'node:path';`, | ||
``, | ||
`debugger;`, | ||
``, | ||
`import fs from 'node:fs';`, | ||
``, | ||
`debugger;`, | ||
``, | ||
`import {`, | ||
` red,`, | ||
` green,`, | ||
`} from 'kleur/colors';`, | ||
``, | ||
`debugger;`, | ||
] | ||
|
||
const output = await ssrTransformSimpleCode(inputLines.join('\n')) | ||
expect(output).toBeDefined() | ||
|
||
const outputLines = output!.split('\n') | ||
expect( | ||
outputLines | ||
.map((line, i) => `${String(i + 1).padStart(2)} | ${line}`.trimEnd()) | ||
.join('\n'), | ||
).toMatchInlineSnapshot(` | ||
" 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; | ||
2 | | ||
3 | | ||
4 | | ||
5 | | ||
6 | | ||
7 | | ||
8 | debugger; | ||
9 | | ||
10 | | ||
11 | | ||
12 | debugger; | ||
13 | | ||
14 | | ||
15 | | ||
16 | | ||
17 | | ||
18 | | ||
19 | debugger;" | ||
`) | ||
|
||
// Ensure the debugger statements are still on the same lines. | ||
expect(outputLines[0].endsWith(inputLines[0])).toBe(true) | ||
expect(outputLines[7]).toBe(inputLines[7]) | ||
expect(outputLines[11]).toBe(inputLines[11]) | ||
expect(outputLines[18]).toBe(inputLines[18]) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New test is here!
test.fails('comments between imports do not trigger hoisting', async () => { | ||
expect( | ||
await ssrTransformSimpleCode( | ||
`import { dirname } from 'node:path';// comment\nimport fs from 'node:fs';`, | ||
), | ||
).toMatchInlineSnapshot(` | ||
"const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["default"]}); | ||
__vite_ssr_import_0__.default.resolve('server.js');" | ||
"const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["dirname"]});// comment | ||
const __vite_ssr_import_1__ = await __vite_ssr_import__("node:fs", {"importedNames":["default"]});" | ||
`) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The failing “comment between imports” hoisting test.
test('do not hoist import if only whitespace is between them', async () => { | ||
expect( | ||
await ssrTransformSimpleCode( | ||
`import { dirname } from 'node:path';\n\n\nimport fs from 'node:fs';`, | ||
), | ||
).toMatchInlineSnapshot(` | ||
"const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["dirname"]}); | ||
const __vite_ssr_import_1__ = await __vite_ssr_import__("node:fs", {"importedNames":["default"]});" | ||
`) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally, here's a basic “preserve whitespace between imports” hoisting test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the tests!
/ecosystem-ci run |
📝 Ran ecosystem CI on
✅ analogjs, astro, ladle, previewjs, quasar, qwik, rakkas, storybook, sveltekit, unocss, vite-environment-examples, vite-plugin-pwa, vite-plugin-react, vite-plugin-react-swc, vite-plugin-vue, vite-setup-catalogue, vitepress, vuepress |
Co-authored-by: 翠 / green <[email protected]>
Co-authored-by: 翠 / green <[email protected]>
@hi-ogawa Would you take a look if the coverage change in the Vitest suite in the ecosystem-ci makes sense? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
I checked Vitest failures in vitest-dev/vitest#7124 and changes are mostly irrelevant for users and actually some bugs are on Vitest side, so it should be fine to proceed with this PR.
I'll follow up with Vitest side fix later to pass ecosystem CI.
| datasource | package | from | to | | ---------- | ------- | ----- | ----- | | npm | vite | 6.0.5 | 6.0.6 | ## [v6.0.6](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small606-2024-12-26-small) - fix: replace runner-side path normalization with `fetchModule`-side resolve ([#18361](vitejs/vite#18361)) ([9f10261](vitejs/vite@9f10261)), closes [#18361](vitejs/vite#18361) - fix(css): resolve style tags in HTML files correctly for lightningcss ([#19001](vitejs/vite#19001)) ([afff05c](vitejs/vite@afff05c)), closes [#19001](vitejs/vite#19001) - fix(css): show correct error when unknown placeholder is used for CSS modules pattern in lightningcs ([9290d85](vitejs/vite@9290d85)), closes [#19070](vitejs/vite#19070) - fix(resolve): handle package.json with UTF-8 BOM ([#19000](vitejs/vite#19000)) ([902567a](vitejs/vite@902567a)), closes [#19000](vitejs/vite#19000) - fix(ssrTransform): preserve line offset when transforming imports ([#19004](vitejs/vite#19004)) ([1aa434e](vitejs/vite@1aa434e)), closes [#19004](vitejs/vite#19004) - chore: fix typo in comment ([#19067](vitejs/vite#19067)) ([eb06ec3](vitejs/vite@eb06ec3)), closes [#19067](vitejs/vite#19067) - chore: update comment about `build.target` ([#19047](vitejs/vite#19047)) ([0e9e81f](vitejs/vite@0e9e81f)), closes [#19047](vitejs/vite#19047) - revert: unpin esbuild version ([#19043](vitejs/vite#19043)) ([8bfe247](vitejs/vite@8bfe247)), closes [#19043](vitejs/vite#19043) - test(ssr): test virtual module with query ([#19044](vitejs/vite#19044)) ([a1f4b46](vitejs/vite@a1f4b46)), closes [#19044](vitejs/vite#19044)
Description
The ssrTransformScript function doesn‘t preserve line numbers, and there are two reasons for this:
import
statement being transformedBroken import hoisting
Imports were being hoisted when it wasn't necessary. This was due to a
\n
character between imports and the fact thatimportNode.end
does not include the trailing line break.I've changed this logic to only hoist imports when a non-whitespace character is found between the current import and the previous one. Note that this won't account for JavaScript comments between import statements, but that was already the case. I've added a
TODO
comment about this edge case.Preserving line breaks
Often times, an import statement will span multiple lines, particularly when using named exports.
The SSR transform was not respecting this. I've changed the logic to include extra
\n
characters at the end of a transformed import to ensure the line offset is preserved.Why should line offset be preserved?
The main reason to preserve line offset is it avoids the need for sourcemaps. Vitest prefers not to add
sourceMappingURL
comments to transformed modules from anode_modules
directory, resulting in a poor debugging experience (see vitest-dev/vitest#5605). But it wouldn't be necessary to apply a sourcemap if the line offset was just preserved.While one could argue that you shouldn't be stepping into an installed package, people do it nonetheless, and it's not a big ask for the experience to not be completely broken by Vite's SSR transform.