fix(css): preserve source import order in bundle#22374
Draft
maruthang wants to merge 1 commit into
Draft
Conversation
When cssCodeSplit is false, the previous chunk-level walk visited chunk.imports first and appended the chunk's own CSS last, inverting source-import order whenever a force-split dependency's CSS belonged to a module imported after the importing module's own CSS. Replace the chunk-level collect() walk with a per-module walk keyed by source module id during renderChunk. In generateBundle we walk modules via getModuleInfo(id).importedIds (source order) and recurse into other chunks at the exact source position of the cross-chunk import. Multi- entry iteration runs in two passes so an importing entry recurses into its dependency entries at the correct source position. Scope is limited to the cssCodeSplit:false branch. fixes vitejs#22301
Member
|
converting to draft as the tests are failing |
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.
Description
Fixes the CSS bundle ordering when
cssCodeSplit: false. The previous chunk-level walk visitedchunk.imports(cross-chunk CSS) first and appended the chunk's own CSS last. Because a chunk's "own CSS" can come from modules imported before other modules whose CSS lives in another chunk, this inverted the source-import order whenever a force-split dependency was involved.Concrete repro from the issue: an entry that does
emitted
counter-style.cssbeforestyle.cssin the bundled stylesheet — the opposite of source order — so later overrides incounter.js's CSS lost to earlier base styles.Root cause
In
packages/vite/src/node/plugins/css.ts, the oldcollect(chunkName)recursion walkedchunk.importsfirst and only then appendedchunkCSSMap.get(chunkName). That ordering is fine when a chunk has no own CSS interleaved with imports, but breaks the moment a module's CSS comes between other imports in the source.Fix
Replaced the chunk-level walk with a per-module walk, scoped strictly to the
cssCodeSplit: falsebranch:renderChunk, record CSS keyed by source module id (per chunk) in addition to the existing per-chunk map.generateBundle, walk modules viagetModuleInfo(id).importedIds, which preserves source-import order. When the walk encounters an import that lives in a different chunk, recurse into that chunk at exactly that source position, so cross-chunk CSS is interleaved correctly.cssCodeSplit: trueand the rest of the pipeline are untouched.fixes #22301
Alternatives explored
This is a non-trivial change. Earlier reporters and patches tried more localized fixes — e.g. swapping the order of "own CSS" vs "imported CSS" inside
collect(), or sorting chunks by entry order — and each one broke a different ordering case (CSS overrides from a dependency vs. CSS overrides from the entry, multi-entry shared chunks, async imports). The only ordering that's actually well-defined is the source import order of the modules, which Rollup already exposes viagetModuleInfo().importedIds. Walking that graph directly avoids having to reconstruct module order from chunk metadata.Testing
Added a regression scenario to
playground/css-no-codesplit:order-static.html/order-static.jsimport a base stylesheet and then a force-split dependency (order-static-dep.js) whose CSS overrides the base.vite.config.jsadds the new entry and force-splits the dep into its own chunk to reproduce the original bug shape.playground/css-no-codesplit/__tests__/css-no-codesplit.spec.tsverifies the source-imported override (green) wins over the earlier-imported base (red) in the built bundle.Verified failing-before / passing-after by reverting only the
css.tschange.Validation run locally:
pnpm run build— passpnpm run test-unit— 779 passed, 4 skipped (all 44 css unit tests pass)pnpm run typecheck— passpnpm run lint— pass (only a pre-existing parse error inplayground/preserve-symlinks/module-a/linked.js, unrelated)playground/css-codesplit(cssCodeSplit: true) andplayground/cssbuilds still succeed — fix is scoped tocssCodeSplit: falseplayground/css-no-codesplitbuild output verified manually; full e2e on Windows is blocked by an unrelated symlink fixture setup limitation.What type of PR is this?