fix(css): preserve source-import order in CSS cascade (#4890)#22541
Open
karlvr wants to merge 3 commits into
Open
fix(css): preserve source-import order in CSS cascade (#4890)#22541karlvr wants to merge 3 commits into
karlvr wants to merge 3 commits into
Conversation
Walk the chunk's modules via `getModuleInfo(id).importedIds` in DFS source-import order when building a chunk's concatenated CSS, instead of iterating `Object.keys(chunk.modules)`. The chunk-record order is determined by Rollup's chunking and does not reliably match the order in which the source code imports the CSS modules — so two CSS modules in the same chunk could end up concatenated in the wrong cascade order. Falls back to chunk-record order for modules not reached from the facade (side-effect-only auto-injected modules, manualChunks output without a facade). Refs vitejs#4890.
`getCssFilesForChunk` previously walked `chunk.imports` post-order and appended each chunk's `viteMetadata.importedCss` in iteration order. Both orderings are derived from Rollup's chunking and do not match the order in which the source code imports the CSS — so when two pure-CSS chunks were absorbed into a shared importer, the resulting `<link>` tags could load in the wrong cascade order. With two HTML entries that share their imports (the classic shape from vitejs#4890), the absorption ordering reversed and the app's override stylesheet ended up loading *before* the vendor stylesheet it was meant to override. Instead, walk the module graph from the entry chunk's facade module in DFS source-import order. Each CSS module's emitted file (looked up in `cssModuleFileMapCache`, populated as chunks are rendered) is registered at the position in the walk where its importing module references it. Post-order DFS preserves the existing "dependencies' CSS first" cascade behavior. The old chunk-import post-order walk is kept as a fallback for CSS files that aren't reached via the module graph (e.g., CSS-only chunks with no facade-reachable module, or implicit Rollup edges), and for callers that don't pass module-graph info. Adds unit tests in `html.spec.ts` that demonstrate the bug (without module info, the fallback still produces the inverted order) and the fix (with module info, source-import order wins). Fixes vitejs#4890.
Adds `playground/css-import-order/` covering the OP shape from vitejs#4890: two HTML entries that both import a vendor stylesheet (via a shared helper module) and then an override stylesheet. The vendor.css is forced into its own chunk via `manualChunks` so it lands in the absorbed-pure-CSS-chunk path — historically the place where the override link ended up before the vendor link. The build-mode tests assert that `vendor.css` precedes `override.css` in both `dist/index.html` and `dist/entry2/index.html`, and the runtime test asserts the cascade outcome (`.box` is the override's blue, not the vendor's red).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR fixes #4890: CSS
<link>tags being emitted in the wrong order in production builds, which reverses the CSS cascade so the wrong rules win.Two things could scramble the CSS order:
Object.keys(chunk.modules)order (roughly Rollup's internal order), rather than in source-import order.getCssFilesForChunkcollected<link>tags by walking the chunk import graph. When two CSS modules with a definite source-import order ended up in different emitted CSS files (e.g. one pulled into its own chunk and absorbed as a pure-CSS chunk into a shared chunk's importedCss), the chunk-level walk could emit their link tags in the wrong relative order.The fix in this PR is:
css.ts: InrenderChunkwe walk the chunk's modules via the module graph (importedIds) in DFS source-import order. (post-order, so a module's dependencies' CSS is concatenated before its own) and concatenate CSS in that order. We also build a newcssModuleFileMapCachemapping each CSS module id to its emitted CSS filename, which thehtml.tschange below consumes.html.ts: We rewritegetCssFilesForChunkto DFS the module graph from the entry's facade module (same post-order), looking up each module's emitted file in the map built above. A chunk-import post-order walk remains as a fallback for CSS not reachable via the module graph.There's also a new playground to demonstrate the fix and test for regressions, and unit tests in
html.spec.tswere updated and extended.