Skip to content

CSS bundle order is incorrect #22301

@jods4

Description

@jods4

Describe the bug

My main entry point starts with

import "virtual:uno.css"
import "./Components/Css/index.css";
// Then JS imports, some of which have associated CSS because of <style> tag in Vue SFC.

But when bundling for production, the index.css chunk, containing unocss output and the index.css imported above, comes last.
This can be observed with cssCodeSplit: true because that index.css is last included in html, or with cssCodeSplit: false because its content is last in the merged css bundle.

I have debugged the issue when cssCodeSplit: false and will explain below what happens.

Reproduction

https://stackblitz.com/edit/vitejs-vite-1tvfcfrw?file=src%2Fmain.js

Steps to reproduce

Idea in repro above is that main.js imports style.css first, then indirectly counter.js imports counter-style.css next.
Counter-style overrides the .counter color to red, which is what happens in dev mode, live in vite.new.
I don't know how to test a production bundle on vite.new, but I expect the order of scripts to be reversed (and the counter to be black).
I explain why below.

System Info

System:
    OS: Windows 11 10.0.26100
    CPU: (20) x64 13th Gen Intel(R) Core(TM) i7-1370P
    Memory: 11.85 GB / 31.64 GB
  Binaries:
    Node: 25.9.0 - C:\Users\***\scoop\apps\nodejs\current\node.EXE
    npm: 11.12.1 - C:\Users\***\scoop\apps\nodejs\current\npm.CMD
  Browsers:
    Edge: Chromium (143.0.3650.75)
    Internet Explorer: 11.0.26100.7309

Not sure why that command doesn't list Vite version, anyway:
Vite: 8.0.9

Used Package Manager

npm

Logs

I have debugged the issue, when cssCodeSplit: false.
The culprit is the collect function here, which attempts to preserve order:
https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/css.ts#L1010-L1022

It tries to include in order: the static imports, then the dynamic imports, then the module own styles.

The key issue is that when a JS module does import "style.css", this isn't listed in chunk.imports.
Instead, the CSS ends up attached to current chunk in chunkCSSMap, which is added last.

In my own project, moving the line
extractedCss += chunkCSSMap.get(chunk.preliminaryFileName) ?? ''
first solves the issue, but I don't think it's the right fix, for two reasons:

  1. It doesn't solve the ordering issue amongst static import. What if import "virtual:uno.css" wasn't the first import in my file and we had JS with an indirect CSS import first?
  2. It's probably incorrect for chunks representing Vue components with <style> tags, those ones should come after static imports.

I believe the right fix is for Vite to create one empty chunk for each import "xyz.css", that is listed in chunk.imports in correct order, and attach the chunkCSSMap to this chunk instead.
Everything should then be in right order.

Validations

Metadata

Metadata

Assignees

No one assigned

    Labels

    feat: cssp3-minor-bugAn edge case that only affects very specific usage (priority)

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions