Skip to content

fix(css): preserve css order when merging pure css chunks#21588

Open
DukeDeSouth wants to merge 1 commit intovitejs:mainfrom
DukeDeSouth:fix/css-pure-chunk-order
Open

fix(css): preserve css order when merging pure css chunks#21588
DukeDeSouth wants to merge 1 commit intovitejs:mainfrom
DukeDeSouth:fix/css-pure-chunk-order

Conversation

@DukeDeSouth
Copy link

@DukeDeSouth DukeDeSouth commented Feb 7, 2026

Human View

Description

Fixes #3924
Fixes #6375

When Vite creates pure CSS chunks (via manualChunks, code splitting, or when a chunk contains only CSS imports), it removes them from chunk.imports in the generateBundle hook and merges their CSS into the importing chunk's viteMetadata.importedCss.

The bug: CSS files from removed pure CSS chunks were appended to importedCss via Set.add(). Since the chunk's own CSS was already in the Set, the dependency's CSS ended up after the chunk's own CSS in the cascade. This inverted the expected order — a page's CSS could no longer override a shared component's CSS with same-specificity selectors.

Root Cause

In packages/vite/src/node/plugins/css.ts, inside the generateBundle hook:

// BEFORE (broken): dependency CSS appended AFTER chunk's own CSS
chunk.imports = chunk.imports.filter((file) => {
  if (pureCssChunkNames.includes(file)) {
    const { importedCss } = (bundle[file] as OutputChunk).viteMetadata!
    importedCss.forEach((file) => chunk.viteMetadata!.importedCss.add(file)) // ← appends to end
    return false
  }
  return true
})

Since importedCss is a Set and the chunk's own CSS entries are already present, .add() places the pure CSS chunk's styles at the end — after the chunk's own styles. This breaks the cascade: dependency styles should come first so the importing module can override them.

Fix

The fix saves the chunk's own CSS before the merge, collects CSS from pure CSS chunks in import order, then rebuilds the Set with correct ordering:

// AFTER (fixed): dependency CSS placed BEFORE chunk's own CSS
const ownImportedCss = [...chunk.viteMetadata!.importedCss]
const pureCssFromImports: string[] = []

chunk.imports = chunk.imports.filter((file) => {
  if (pureCssChunkNames.includes(file)) {
    const { importedCss } = (bundle[file] as OutputChunk).viteMetadata!
    importedCss.forEach((file) => pureCssFromImports.push(file))
    return false
  }
  return true
})

if (chunkImportsPureCssChunk) {
  chunk.viteMetadata!.importedCss = new Set([
    ...pureCssFromImports, // dependency CSS first
    ...ownImportedCss,     // chunk's own CSS second
  ])
}

Reproduction

  1. Create a shared component (base.css) with .el { color: red }
  2. Create a page (page.css) with .el { color: green } — same specificity, intended override
  3. Use manualChunks to split base.css into its own chunk
  4. Dynamic-import the page → the element should be green but was red (dependency CSS incorrectly placed after page CSS)

Test Plan

  • Added e2e test in playground/css-dynamic-import that creates a pure CSS chunk via manualChunks and verifies the cascade order is correct
  • The new test fails without the fix (color is red) and passes with the fix (color is green)
  • Full test suite passes with no regressions

This is a long-standing issue (opened 2021, 600+ reactions) affecting anyone using CSS Modules, dynamic imports, or manualChunks where cascade order matters.


AI View (DCCE Protocol v1.0)

Metadata

  • Generator: Claude (Anthropic) via Cursor IDE
  • Methodology: AI-assisted development with human oversight and review

AI Contribution Summary

  • Root cause analysis through code tracing
  • Solution design and implementation

Verification Steps Performed

  1. Reproduced the reported issue
  2. Analyzed source code to identify root cause
  3. Implemented and tested the fix

Human Review Guidance

  • Verify the root cause analysis matches your understanding of the codebase
  • Core changes are in: chunk.imports, packages/vite/src/node/plugins/css.ts, base.css

Made with M7 Cursor

When a pure CSS chunk (created via `manualChunks` or code splitting) is
removed from `chunk.imports`, its CSS files were appended to the
importing chunk's `importedCss` Set via `.add()`. Since Sets maintain
insertion order and the chunk's own CSS was already in the Set, the pure
CSS chunk's styles ended up AFTER the importing chunk's styles in the
cascade.

This broke the expected CSS order: dependency styles should appear
before the importing module's styles so the importing module can
override them with same-specificity selectors.

The fix saves the chunk's own CSS before merging, collects CSS from
removed pure CSS chunks in their original import order, then rebuilds
`importedCss` with the correct ordering: pure CSS chunk styles first,
then the chunk's own styles.

Fixes vitejs#3924
Fixes vitejs#6375

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

incorrect CSS order after build when manualChunk is used Vite injects css assets in wrong order with dynamic import and css modules.

1 participant