Describe the bug
vite-css-cross-link-repro.zip
CSS-only entry points with the same filename in different directories are incorrectly cross-linked in the manifest
Environment
- Vite: 8.0.2
- Rolldown: 1.0.0-rc.11
- OS: macOS (Darwin 24.6.0)
- CSS preprocessor: Sass (via
scss)
Description
When two CSS-only entry points (SCSS files) in different directories share the same base filename, Vite's CSS post-processing incorrectly adds one as a CSS dependency of the other in the build manifest. This causes both stylesheets to be loaded at runtime when only one was requested, leading to unintended style overrides.
Reproduction
A minimal reproduction project is attached: vite-css-cross-link-repro.zip. To reproduce:
npm install
npm run build
cat dist/.vite/manifest.json
The project contains two independent SCSS entry points that share the same base filename in different directories:
resources/assets/scss/
├── store/skins/
│ └── store_skin_85535.scss ← entry point A (color: #f75a38)
└── store3/skins/
└── store_skin_85535.scss ← entry point B (color: #ffffff)
The vite.config.js lists both as build inputs with build.manifest: true. No other plugins are needed to trigger the bug.
Expected manifest output
Each entry should be independent — no cross-references:
{
"resources/assets/scss/store/skins/store_skin_85535.scss": {
"file": "assets/store_skin_85535-AAAA.css",
"src": "resources/assets/scss/store/skins/store_skin_85535.scss"
},
"resources/assets/scss/store3/skins/store_skin_85535.scss": {
"file": "assets/store_skin_85535-BBBB.css",
"src": "resources/assets/scss/store3/skins/store_skin_85535.scss",
"isEntry": true
}
}
Actual manifest output
The store3 entry incorrectly lists the store entry's CSS file as a dependency (output from the minimal reproduction):
{
"resources/assets/scss/store/skins/store_skin_85535.scss": {
"file": "assets/store_skin_85535-wmVeozrv.css",
"src": "resources/assets/scss/store/skins/store_skin_85535.scss"
},
"resources/assets/scss/store3/skins/store_skin_85535.scss": {
"file": "assets/store_skin_85535-CixdYeUC.css",
"name": "store_skin_85535",
"names": [
"store_skin_85535.css"
],
"src": "resources/assets/scss/store3/skins/store_skin_85535.scss",
"isEntry": true,
"css": [
"assets/store_skin_85535-wmVeozrv.css"
]
}
}
The "css" array causes the framework's @vite() directive to emit <link> tags for both files when only the store3 version was requested.
Impact
- The two files compile to CSS with identical selectors but different property values (they are different versions/generations of the same theme).
- Because both are loaded, the unwanted stylesheet's rules override the intended ones based on source order.
- In our case,
color: #fff (correct, from store3) is overridden by color: #f75a38 (wrong, from store), making button text invisible against its background.
- This only manifests in production builds — the Vite dev server serves each entry independently and does not exhibit the cross-linking.
Scale
This is not limited to a single file. In our build, every pair of SCSS entry points that share the same base filename across the store/ and store3/ directories is affected (24 pairs total). The cross-link is one-directional: store3 entries pull in store entries, but not vice versa.
Root cause
The bug is in Vite's CSS post-processing plugin, not in Rolldown's bundling logic. Rolldown correctly assigns distinct facadeModuleId values to each chunk but gives both the same chunk.name (store_skin_85535) since they share a basename — this is standard bundler behaviour.
Vite's cssEntriesMap uses chunk.name as a Map key when registering CSS entry chunks:
// vite/src/node/plugins/css.ts — during renderChunk
cssEntriesMap.get(this.environment).set(chunk.name, referenceId);
Because both entries share chunk.name = "store_skin_85535", the second write overwrites the first. Later, when resolving the "real" CSS asset for each pure-CSS entry chunk:
// vite/src/node/plugins/css.ts — during generateBundle
const cssReferenceId = cssEntriesMap.get(this.environment).get(emptyJsPlaceholder.name);
const realCssEntryName = this.getFileName(cssReferenceId);
const realCssEntry = bundle[realCssEntryName];
importedCss.delete(realCssEntryName); // fails — wrong filename
if (importedCss.size) realCssEntry.viteMetadata.importedCss = importedCss; // spurious transfer
The first placeholder processed looks up the overwritten reference, gets the wrong CSS asset, fails to self-delete from importedCss, and transfers its CSS file as a dependency of the other entry's asset. This surfaces in the manifest as the spurious "css" array.
A fix would need to use a unique key (e.g., facadeModuleId or the full relative input path) instead of chunk.name.
Reproduction
see included
vite-css-cross-link-repro.zip
Steps to reproduce
see included
vite-css-cross-link-repro.zip
System Info
System:
OS: macOS 26.3.1
CPU: (10) arm64 Apple M1 Max
Memory: 9.23 GB / 64.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 25.8.0 - /opt/homebrew/bin/node
Yarn: 1.22.22 - /opt/homebrew/bin/yarn
npm: 11.11.0 - /opt/homebrew/bin/npm
Deno: 2.7.3 - /opt/homebrew/bin/deno
Browsers:
Chrome: 146.0.7680.155
Firefox Developer Edition: 149.0
Safari: 26.3.1
npmPackages:
vite: ^8.0.2 => 8.0.2
Used Package Manager
npm
Logs
No response
Validations
Describe the bug
vite-css-cross-link-repro.zip
CSS-only entry points with the same filename in different directories are incorrectly cross-linked in the manifest
Environment
scss)Description
When two CSS-only entry points (SCSS files) in different directories share the same base filename, Vite's CSS post-processing incorrectly adds one as a CSS dependency of the other in the build manifest. This causes both stylesheets to be loaded at runtime when only one was requested, leading to unintended style overrides.
Reproduction
A minimal reproduction project is attached: vite-css-cross-link-repro.zip. To reproduce:
The project contains two independent SCSS entry points that share the same base filename in different directories:
The
vite.config.jslists both as build inputs withbuild.manifest: true. No other plugins are needed to trigger the bug.Expected manifest output
Each entry should be independent — no cross-references:
{ "resources/assets/scss/store/skins/store_skin_85535.scss": { "file": "assets/store_skin_85535-AAAA.css", "src": "resources/assets/scss/store/skins/store_skin_85535.scss" }, "resources/assets/scss/store3/skins/store_skin_85535.scss": { "file": "assets/store_skin_85535-BBBB.css", "src": "resources/assets/scss/store3/skins/store_skin_85535.scss", "isEntry": true } }Actual manifest output
The
store3entry incorrectly lists thestoreentry's CSS file as a dependency (output from the minimal reproduction):{ "resources/assets/scss/store/skins/store_skin_85535.scss": { "file": "assets/store_skin_85535-wmVeozrv.css", "src": "resources/assets/scss/store/skins/store_skin_85535.scss" }, "resources/assets/scss/store3/skins/store_skin_85535.scss": { "file": "assets/store_skin_85535-CixdYeUC.css", "name": "store_skin_85535", "names": [ "store_skin_85535.css" ], "src": "resources/assets/scss/store3/skins/store_skin_85535.scss", "isEntry": true, "css": [ "assets/store_skin_85535-wmVeozrv.css" ] } }The
"css"array causes the framework's@vite()directive to emit<link>tags for both files when only thestore3version was requested.Impact
color: #fff(correct, fromstore3) is overridden bycolor: #f75a38(wrong, fromstore), making button text invisible against its background.Scale
This is not limited to a single file. In our build, every pair of SCSS entry points that share the same base filename across the
store/andstore3/directories is affected (24 pairs total). The cross-link is one-directional:store3entries pull instoreentries, but not vice versa.Root cause
The bug is in Vite's CSS post-processing plugin, not in Rolldown's bundling logic. Rolldown correctly assigns distinct
facadeModuleIdvalues to each chunk but gives both the samechunk.name(store_skin_85535) since they share a basename — this is standard bundler behaviour.Vite's
cssEntriesMapuseschunk.nameas aMapkey when registering CSS entry chunks:Because both entries share
chunk.name = "store_skin_85535", the second write overwrites the first. Later, when resolving the "real" CSS asset for each pure-CSS entry chunk:The first placeholder processed looks up the overwritten reference, gets the wrong CSS asset, fails to self-delete from
importedCss, and transfers its CSS file as a dependency of the other entry's asset. This surfaces in the manifest as the spurious"css"array.A fix would need to use a unique key (e.g.,
facadeModuleIdor the full relative input path) instead ofchunk.name.Reproduction
see included
vite-css-cross-link-repro.zip
Steps to reproduce
see included
vite-css-cross-link-repro.zip
System Info
System: OS: macOS 26.3.1 CPU: (10) arm64 Apple M1 Max Memory: 9.23 GB / 64.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 25.8.0 - /opt/homebrew/bin/node Yarn: 1.22.22 - /opt/homebrew/bin/yarn npm: 11.11.0 - /opt/homebrew/bin/npm Deno: 2.7.3 - /opt/homebrew/bin/deno Browsers: Chrome: 146.0.7680.155 Firefox Developer Edition: 149.0 Safari: 26.3.1 npmPackages: vite: ^8.0.2 => 8.0.2Used Package Manager
npm
Logs
No response
Validations