vite: Vite injects css assets in wrong order with dynamic import and css modules.

Describe the bug

Vite injects css assets in wrong order with dynamic import and css modules.

Reproduction

Repo to reproduce

For example:

  1. You have component Button with default styles (text color: red)
/* button.tsx */
import React from "react"
import classnames from 'classnames'

import styles from './button.module.css'

type ButtonProps = {
	children: string
	className?: string
	onClick?: () => void
}

function Button({ children, className, onClick }: ButtonProps) {
	return <button className={classnames(styles.button, className)} onClick={onClick}>{children}</button>
}

export default Button
/* button.module.css */
.button {
	color: red
}
  1. You have Page component. This page use Button component and overrides it styles by passing custom class name as a prop (text color: green)
/* home.tsx /*
import React from "react"

import Button from "../../components/button/button"

import styles from './home.module.css'


function HomePage() {
	return <div>
		<h1>Home page</h1>
		<Button className={styles.greenTextButton}>should be green</Button>
	</div>
}

export default HomePage
/* home.module.css */
.greenTextButton {
	color: green;
}
  1. You import Page component with dynamic import.
import React, { lazy, Suspense, useState } from 'react'


const HomePage = lazy(() => import('./pages/home/home'))
const AboutPage = lazy(() => import('./pages/about/about'))


function App() {
  const [page, setPage] = useState('home')
  return (
    <div>
      <a href="#" onClick={() => setPage('home')} style={{ marginRight: '5px' }}>home page</a>
      <a href="#" onClick={() => setPage('about')}>about page</a>

      <Suspense fallback={<div>loading</div>}>
        {page === 'home' && (
          <HomePage />
        )}
        {page === 'about' && (
          <AboutPage />
        )}
      </Suspense>
    </div>
  )
}

export default App
  1. You exepect page styles will override button styles, but they are not (as vite injects styles in wrong order)
Screenshot 2021-06-23 at 19 12 44

P.S. You can reproduce this but with cssCodeSplit: true or false.

System Info

Output of npx envinfo --system --npmPackages vite,@vitejs/plugin-vue --binaries --browsers:

  System:
    OS: macOS 11.4
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 94.85 MB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 14.16.1 - ~/.volta/tools/image/node/14.16.1/bin/node
    npm: 6.14.12 - ~/.volta/tools/image/node/14.16.1/bin/npm
  Browsers:
    Chrome: 91.0.4472.114
    Firefox: 89.0
    Safari: 14.1.1
  npmPackages:
    vite: ^2.3.8 => 2.3.8 

Used package manager:

Logs

 vite:config bundled config file loaded in 42ms +0ms
  vite:config using resolved config: {
  vite:config   plugins: [
  vite:config     'alias',
  vite:config     'react-refresh',
  vite:config     'vite:dynamic-import-polyfill',
  vite:config     'vite:resolve',
  vite:config     'vite:html',
  vite:config     'vite:css',
  vite:config     'vite:esbuild',
  vite:config     'vite:json',
  vite:config     'vite:wasm',
  vite:config     'vite:worker',
  vite:config     'vite:asset',
  vite:config     'vite:define',
  vite:config     'vite:css-post',
  vite:config     'vite:build-html',
  vite:config     'commonjs',
  vite:config     'vite:data-uri',
  vite:config     'rollup-plugin-dynamic-import-variables',
  vite:config     'vite:import-analysis',
  vite:config     'vite:esbuild-transpile',
  vite:config     'vite:terser',
  vite:config     'vite:reporter'
  vite:config   ],
  vite:config   build: {
  vite:config     target: [ 'es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ],
  vite:config     polyfillDynamicImport: false,
  vite:config     outDir: 'dist',
  vite:config     assetsDir: 'assets',
  vite:config     assetsInlineLimit: 4096,
  vite:config     cssCodeSplit: true,
  vite:config     sourcemap: false,
  vite:config     rollupOptions: {},
  vite:config     commonjsOptions: { include: [Array], extensions: [Array] },
  vite:config     minify: 'terser',
  vite:config     terserOptions: {},
  vite:config     cleanCssOptions: {},
  vite:config     write: true,
  vite:config     emptyOutDir: null,
  vite:config     manifest: false,
  vite:config     lib: false,
  vite:config     ssr: false,
  vite:config     ssrManifest: false,
  vite:config     brotliSize: true,
  vite:config     chunkSizeWarningLimit: 500,
  vite:config     watch: null
  vite:config   },
  vite:config   configFile: '/Users/anatoliidomaratskyi/Work/Mimy/css-code-split-order/vite.config.ts',
  vite:config   configFileDependencies: [ 'vite.config.ts' ],
  vite:config   inlineConfig: {
  vite:config     root: undefined,
  vite:config     base: undefined,
  vite:config     mode: undefined,
  vite:config     configFile: undefined,
  vite:config     logLevel: undefined,
  vite:config     clearScreen: undefined,
  vite:config     build: {}
  vite:config   },
  vite:config   root: '/Users/anatoliidomaratskyi/Work/Mimy/css-code-split-order',
  vite:config   base: '/',
  vite:config   resolve: { dedupe: undefined, alias: [ [Object] ] },
  vite:config   publicDir: '/Users/anatoliidomaratskyi/Work/Mimy/css-code-split-order/public',
  vite:config   cacheDir: '/Users/anatoliidomaratskyi/Work/Mimy/css-code-split-order/node_modules/.vite',
  vite:config   command: 'build',
  vite:config   mode: 'production',
  vite:config   isProduction: true,
  vite:config   server: {
  vite:config     fsServe: {
  vite:config       root: '/Users/anatoliidomaratskyi/Work/Mimy/css-code-split-order',
  vite:config       strict: false
  vite:config     }
  vite:config   },
  vite:config   env: { BASE_URL: '/', MODE: 'production', DEV: false, PROD: true },
  vite:config   assetsInclude: [Function: assetsInclude],
  vite:config   logger: {
  vite:config     hasWarned: false,
  vite:config     info: [Function: info],
  vite:config     warn: [Function: warn],
  vite:config     warnOnce: [Function: warnOnce],
  vite:config     error: [Function: error],
  vite:config     clearScreen: [Function: clearScreen]
  vite:config   },
  vite:config   createResolver: [Function: createResolver],
  vite:config   optimizeDeps: { esbuildOptions: { keepNames: undefined } }
  vite:config } +6ms
vite v2.3.8 building for production...
✓ 33 modules transformed.
dist/assets/favicon.17e50649.svg   1.49kb
dist/index.html                    0.45kb
dist/assets/home.5d3a6e0a.js       0.26kb / brotli: 0.15kb
dist/assets/button.14aa6fb9.js     0.80kb / brotli: 0.41kb
dist/assets/home.deac2baa.css      0.04kb / brotli: 0.04kb
dist/assets/about.563410d2.js      0.60kb / brotli: 0.28kb
dist/assets/button.a44dba50.css    0.03kb / brotli: 0.03kb
dist/assets/index.3938cd91.js      1.55kb / brotli: 0.60kb
dist/assets/about.d57e38e3.css     0.06kb / brotli: 0.05kb
dist/assets/vendor.cc984a25.js     127.61kb / brotli: 36.05kb


Before submitting the issue, please make sure you do the following

  • [ +] Read the Contributing Guidelines.
  • [ +] Read the docs.
  • [ +] Check that there isn’t already an issue that reports the same bug to avoid creating a duplicate.
  • [ +] Provide a description in this issue that describes the bug.
  • [+] Make sure this is a Vite issue and not a framework-specific issue. For example, if it’s a Vue SFC related bug, it should likely be reported to https://github.com/vuejs/vue-next instead.
  • [ +] Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 85
  • Comments: 61 (19 by maintainers)

Commits related to this issue

Most upvoted comments

This seems to still be an issue. Really killing me. Any updates?

This is a pretty big issue. The production build styling order also differs in order compared with the dev build making working with Vite a nightmare for me. Is there no workaround I can use in the meanwhile, it seems we are a long way from having this fixed?

Any progress on this issue please? I’m having the same problem when packaging my application using the Federation module.

is there any update on this?

In my case css injection seem to be sorted by name.

image

replace app with zpp in manualChunks

image

@poyoho Can we please get an update on the progress of this and the open PR https://github.com/vitejs/vite/pull/9278?

This is a really big issue which is now open for over one and a half year and more. Is there any progress? anything on the horizon? We are using BEM. But with this problem parent component is not able to overwrite child css with a class which forces us to do parent > child selectors to get higher specifity which is against what we want to do with BEM. So we are a bit in trouble.

Any workaround on this, or idea when this will get solved?

While this is frustrating, I was able to work around it using CSS layers.

<style>
  @import url('../path/to/css') layer(layer-1);
  @import url('../path/to/css-2') layer(layer-2);
  /* unlayered styles take preference over any layers */
</style>

If you’re worried about browser support, there is a PostCSS plugin to help with that.

The only way to preserve CSS ordering when bundling is to bundle all CSS into 1 CSS file per-page. Once you chunk common dependencies you are creating scenarios where ordering will be different on some pages. In Astro we are reconsidering whether to use the :where selector due to this. We might also come up with some other solutions. Just chiming in that the underlying problem is probably unsolvable.

has this issue perfectly resolved? Or still working on fixing?

Speaking of cascade layers, if you’re using css modules (which is likely if you’re hitting this issue), you could try out https://github.com/DefinedNet/postcss-assign-layer to assign all of your components to a separate layer from your global / utility layers. This doesn’t work with the cascade layers polyfill, but I think browser support is good enough you shouldn’t need the polyfill anymore.

Another option for avoiding this issue is to not code-split your app. There are tradeoffs there, but it’s something to consider.

Applying styles in the wrong order is a deal breaker for using vite. I don’t understand - why is this issue still open and more importantly, how are people actually developing with vite when you cannot trust the css order?

For those who (like me) had to ship and need a temporary workaround: Depending on your project, you may be able to use build.cssCodeSplit to extract a single css file which has the classes in the correct order.

the same issue for vue vite

I’ve also encountered this issue as we upgrade a legacy project. The load order of CSS is important. Why is there no option to specify the order within the manifest.json? CSS order is, unfortunately, a real concern. Seeing how many people are interacting with this issue, I wonder why there hasn’t been a solution proposed.

This reference could help with this issue?

Vite css.modules

hay!

It’s almost finished now, but it’s stuck at one point. Now the loading order should be as expected, but there is still a problem with the execution order

Why pre-load blue.module.css but execute first __vite__updateStyle is post-load red.module.css 🥲

However change the import order I do, it always load order by red -> green -> blue in dev mod.

sadly this didnt work out for me @laurentvd 😔 i might need to refactor my components 🤔

Great work @IanVS! Really hope this helps resolving the issue. I would help, but have no idea on where to start unfortunately.

I also have the same problem with React

How did you solve it

I don’t understand - why is this issue still open

Because it isn’t fixed yet. Have you considered that this may be a difficult problem?

more importantly, how are people actually developing with vite when you cannot trust the css order?

There are solutions to manage cascade order that do not depend on CSS order. Have you considered reading the thread that you’re commenting on?

I’m having related a related issue but perhaps also different? Every time I run vite build it slices all of the css code above the media queries in one of my components off. I checked the compiled css and half of that module is no where to be found. Any update on this? I tried disabling code splitting but it didn’t seem to work, or perhaps my config file was wrong…

For those who (like me) had to ship and need a temporary workaround: Depending on your project, you may be able to use build.cssCodeSplit to extract a single css file which has the classes in the correct order.

I see the wisdom in Remix of having “per-page” css loaders in this case - JS imports and CSS cascade generally follow an inverse relationship, right? So the most correct CSS cascade will need about one CSS chunk per page…

I’m having the same problem when packaging multiple page apps.

I have the same issue. Vite --host works fine, but code after Vite build mixes order css files. My workaround is !important property but it is crap

As far as I know, it is not fixed and I don’t think anyone is actively working on it. @poyoho made some heroic efforts on it, but was never able to handle all the edge cases. I wonder if code splitting is just fundamentally incompatible with css modules.

@poyoho Any progress?

As far as I know it’s not ready, @mgiraldo. This is a sticky problem to solve. If you have any ideas or can help out, please do!