vitejs / vite

Next generation frontend tooling. It's fast!
http://vitejs.dev
MIT License
66.88k stars 6k forks source link

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

Open pretender91 opened 3 years ago

pretender91 commented 3 years ago

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} }

export default Button

```css
/* 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

Home page

    <Button className={styles.greenTextButton}>should be green</Button>
</div>

}

export default HomePage

```cs
/* 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.
jasikpark commented 1 year ago

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..

Matt-Biddick commented 1 year ago

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..

trgoofi commented 11 months ago

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

image

replace app with zpp in manualChunks

image
budlin2 commented 9 months ago

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

nitzcard commented 5 months ago

is there any update on this?

adamxi commented 5 months ago

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?

mayank99 commented 5 months ago

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?

IanVS commented 5 months ago

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.

NickCashFlow commented 5 months ago

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? @adamxi I also encountered a similar problem in project. In our project we use Vue3, and to make global styles more basic, we imported them into main.js before importing App.vue, so on the final page in the chunk tree, global styles are imported first, and more specific ones in tags