Describe the bug
With css.devSourcemap: true, the sources of dev CSS sourcemaps are produced with different base conventions depending on the code path. Per the sourcemap spec, a sources entry is a URL reference resolved against the map's location — and for an inline map in a <style> tag, that location falls back to the page hosting the tag. Several of the shapes Vite emits today do not survive that resolution.
Concrete example. An app at /Users/me/app with two pages and a nested css file:
index.html <script type="module" src="/main.js"></script>
admin/index.html <script type="module" src="/main.js"></script>
main.js import './nested/dir/from-js.css'
nested/dir/from-js.css @import './dep.css'; .from-js { color: red; }
nested/dir/dep.css .dep { color: green; }
Shape 1 — style tag, postcss. The css injected by main.js carries this inline map:
{ "sources": ["/Users/me/app/nested/dir/dep.css",
"/Users/me/app/nested/dir/from-js.css"] }
Absolute filesystem paths. The browser resolves them to http://localhost:5173/Users/me/app/nested/dir/dep.css. This happens to return the right file today only because the dev server serves in-root files at their fs path. It leaks the machine's directory layout into the browser, and for an out-of-root file (a monorepo package) the same shape produces a URL the server refuses.
Shape 2 — style tag, lightningcss. Same import, css.transformer: 'lightningcss':
{ "sources": ["nested/dir/from-js.css", "nested/dir/dep.css"] }
Relative to the project root — but the resolver's base is the page, not the root. The same map is injected into both pages:
from / → /nested/dir/dep.css ✓ works, only because page base == root
from /admin/index.html → /admin/nested/dir/dep.css ✗ answered by the HTML fallback (200, wrong content)
One map, two meanings. Any page that is not at the root resolves every source to a wrong URL.
Shape 3 — URL-served css, lightningcss. curl -H "Accept: text/css" /nested/dir/from-js.css returns the css with:
{ "sources": ["nested/dir/from-js.css", "nested/dir/dep.css"] }
Here the map's base is the css file's own URL, so the names double the path:
base /nested/dir/from-js.css + "nested/dir/dep.css" → /nested/dir/nested/dir/dep.css ✗
This one is a plain bug: the rewrite in transformRequest.ts that re-bases served maps only handles path.isAbsolute sources, so lightningcss's root-relative names slip through unrewritten. The postcss equivalent of this request is correct — "sources": ["dep.css", "from-js.css"], relative to the file — because postcss maps are absolutized first and the rewrite catches them.
None of this is visible in everyday use, because dev maps embed sourcesContent and devtools then never fetch sources by name. The names only misbehave when something actually resolves them — workspace mapping, external tooling, or any consumer without the embedded content.
Expected behavior
sources should be correct relative to where the map is located:
- Maps that have their own URL (URL-served css, chunk maps, build output) should use names relative to the map — as postcss URL-served responses and build output already do. Shape 3 should become
["from-js.css", "dep.css"].
- Style-tag maps have no URL of their own and their page base varies, so the only page-independent reference is the path-absolute form:
{ "sources": ["/nested/dir/dep.css", "/nested/dir/from-js.css"] }
This resolves identically from /, /admin/index.html, or any other page. Out-of-root files cannot be expressed as a URL reference at all (resolution clamps ../ at the root); they stay labels, carried by sourcesContent — no /@fs/ in sources.
Reproduction
https://github.com/vitejs/vite/tree/test/css-sourcemap-sources-repro
Steps to reproduce
pnpm test-serve sources-resolution
System Info
System:
OS: macOS 26.5.1
CPU: (14) arm64 Apple M4 Pro
Memory: 658.31 MB / 48.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 24.19.0 - /Users/shuyuan/.vite-plus/js_runtime/node/24.19.0/bin/node
npm: 11.17.0 - /Users/shuyuan/.vite-plus/js_runtime/node/24.19.0/bin/npm
pnpm: 10.34.5 - /Users/shuyuan/.local/state/fnm_multishells/6116_1785830242280/bin/pnpm
Browsers:
Chrome: 151.0.7922.77
Safari: 26.5
npmPackages:
@vitejs/release-scripts: ^1.8.0 => 1.8.0
rolldown: ~1.2.1 => 1.2.1
rollup: ^4.59.0 => 4.59.0
vite: workspace:* => 8.2.1
Used Package Manager
pnpm
Logs
No response
Validations
Describe the bug
With
css.devSourcemap: true, thesourcesof dev CSS sourcemaps are produced with different base conventions depending on the code path. Per the sourcemap spec, asourcesentry is a URL reference resolved against the map's location — and for an inline map in a<style>tag, that location falls back to the page hosting the tag. Several of the shapes Vite emits today do not survive that resolution.Concrete example. An app at
/Users/me/appwith two pages and a nested css file:Shape 1 — style tag, postcss. The css injected by
main.jscarries this inline map:{ "sources": ["/Users/me/app/nested/dir/dep.css", "/Users/me/app/nested/dir/from-js.css"] }Absolute filesystem paths. The browser resolves them to
http://localhost:5173/Users/me/app/nested/dir/dep.css. This happens to return the right file today only because the dev server serves in-root files at their fs path. It leaks the machine's directory layout into the browser, and for an out-of-root file (a monorepo package) the same shape produces a URL the server refuses.Shape 2 — style tag, lightningcss. Same import,
css.transformer: 'lightningcss':{ "sources": ["nested/dir/from-js.css", "nested/dir/dep.css"] }Relative to the project root — but the resolver's base is the page, not the root. The same map is injected into both pages:
One map, two meanings. Any page that is not at the root resolves every source to a wrong URL.
Shape 3 — URL-served css, lightningcss.
curl -H "Accept: text/css" /nested/dir/from-js.cssreturns the css with:{ "sources": ["nested/dir/from-js.css", "nested/dir/dep.css"] }Here the map's base is the css file's own URL, so the names double the path:
This one is a plain bug: the rewrite in
transformRequest.tsthat re-bases served maps only handlespath.isAbsolutesources, so lightningcss's root-relative names slip through unrewritten. The postcss equivalent of this request is correct —"sources": ["dep.css", "from-js.css"], relative to the file — because postcss maps are absolutized first and the rewrite catches them.None of this is visible in everyday use, because dev maps embed
sourcesContentand devtools then never fetch sources by name. The names only misbehave when something actually resolves them — workspace mapping, external tooling, or any consumer without the embedded content.Expected behavior
sourcesshould be correct relative to where the map is located:["from-js.css", "dep.css"].{ "sources": ["/nested/dir/dep.css", "/nested/dir/from-js.css"] }This resolves identically from
/,/admin/index.html, or any other page. Out-of-root files cannot be expressed as a URL reference at all (resolution clamps../at the root); they stay labels, carried bysourcesContent— no/@fs/insources.Reproduction
https://github.com/vitejs/vite/tree/test/css-sourcemap-sources-repro
Steps to reproduce
pnpm test-serve sources-resolutionSystem Info
System: OS: macOS 26.5.1 CPU: (14) arm64 Apple M4 Pro Memory: 658.31 MB / 48.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 24.19.0 - /Users/shuyuan/.vite-plus/js_runtime/node/24.19.0/bin/node npm: 11.17.0 - /Users/shuyuan/.vite-plus/js_runtime/node/24.19.0/bin/npm pnpm: 10.34.5 - /Users/shuyuan/.local/state/fnm_multishells/6116_1785830242280/bin/pnpm Browsers: Chrome: 151.0.7922.77 Safari: 26.5 npmPackages: @vitejs/release-scripts: ^1.8.0 => 1.8.0 rolldown: ~1.2.1 => 1.2.1 rollup: ^4.59.0 => 4.59.0 vite: workspace:* => 8.2.1Used Package Manager
pnpm
Logs
No response
Validations