vitejs / vite

Next generation frontend tooling. It's fast!
http://vitejs.dev
MIT License
66.19k stars 5.92k 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.
iztsv commented 3 years ago

the same issue for vue vite

youzizi1 commented 3 years ago

i also meet the same problem

FelixIncerta commented 3 years ago

I also have the same problem with React

zlyyyy commented 2 years ago

I also have the same problem with React

+1

zlyyyy commented 2 years ago

I also have the same problem with React

How did you solve it

kamilic commented 2 years ago

Same problem.

cisen commented 2 years ago

Same problem.

zhangzheng-zz commented 2 years ago

Same problem.

stanfil commented 2 years ago

Same problem.

buddywang commented 2 years ago

Same problem.

pretender91 commented 2 years ago

Any updates?(

Maybe @sodatea will provide some thoughts where to find problem in project and i will try to make pull request to vite?

IanVS commented 2 years ago

I'm seeing this issue as well, now that I've started to use storybook's new dynamic loading feature, my styles are broken in production stories. I've found that the css files are directly after the component in the network tab. So, in one example, I have a RadioButton component that imports Label (which has its own styles that I want to override), and then it imports some css module with the overrides. However, when building now, I see this:

image

Which puts the RadioButton styles higher in the cascade, and they are overwritten by the Label styles, which is backwards from what I want.

IanVS commented 2 years ago

Here is a minimal stackblitz reproduction: https://stackblitz.com/edit/vite-gu874q?file=main.jsx

vite: 2.7.1 @vitejs/plugin-react: 1.1.1

laurentvd commented 2 years ago

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

davidlin88 commented 2 years ago

I finally found the culprit, and FOR a while I thought vite-plugin-pages and unplugin-vue-components were causing the problem, my bad. Glad to use them again.

My temporary solution is to load influenced page synchronously in router configuration.

mgiraldo commented 2 years ago

any idea when will #6301 be merged?

IanVS commented 2 years ago

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!

mgiraldo commented 2 years ago

i understand. thanks for all this work!

mrcljx commented 2 years ago

I wonder whether Vite could maybe look for a __vite__injectStyle global hook to allow overriding the head.appendChild behavior.

With Webpack's style-loader one can specify a function/hook that will be called to place the style tag into the DOM (by default it will just append it to the head like Vite does).

Webpack Configuration ``` // webpack.config.js { test: /\.css$/, use: [ { loader: "style-loader", options: { injectType: "singletonStyleTag", // this hook will be insert: require.resolve("./tools/insertStyleTag.ts"), }, }, { loader: "css-loader" }, ], }, ``` ``` // insertStyleTag.ts function insertStyleTag(element) { element.dataset.styleLoader = ""; // Custom logic, could be more complicated. Here I added a marker // to ensure `import ...` styles are added before `styled-component` // styles. document.head.insertBefore( element, document.head.querySelector("[data-styled]") ); } module.exports = insertStyleTag; ```
laurentvd commented 2 years 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.

mgiraldo commented 2 years ago

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

poyoho commented 2 years ago

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.

Winston-Guess commented 2 years ago

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?

zhuweiyou commented 2 years ago

same issue

kadiryazici commented 2 years ago

Same

ghost commented 2 years ago

@poyoho Any progress?

geng130127 commented 2 years ago

Same problem.

IanVS commented 2 years ago

I tried out the latest vite 2.9.14 version, and the situation seems to be improved (not as many style errors as before), but not quite yet resolved. I rebased https://github.com/vitejs/vite/pull/6301 onto the latest 2.9 branch as well, but its test is still failing.

hmz22 commented 2 years ago

I have this problem i add antd css in main file and other css in component but in dev ant last style and component css before antd. Any have solution until fix it?

mayank99 commented 2 years ago

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.

hmz22 commented 2 years ago

I import css end of all import in component before and now most first import all css and then import other component and solved this issue. Component A

import "compnentA css"
import ComponentB from "."

ComponentB

import "compnentB css"
import other component

But with CRA haven't this problem

awacode21 commented 1 year ago

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?

flipace commented 1 year ago

which forces us to do parent > child selectors

@awacode21 not really a solution, but yet another workaround when you can't use (or it would be tricky) a parent selector -> .something:not(#\#) (or actually any other bogus value for :not(...)) to make a selector more specific easily.

(still struggling with this issue here since forever (already had it in webpack too))

ghost commented 1 year ago

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

dante01yoon commented 1 year ago

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

IanVS commented 1 year ago

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.

intrnl commented 1 year ago

just to be certain, is the goal with #9278 is to make CSS insertion (in dev) follow what's already happening in production builds?

IanVS commented 1 year ago

just to be certain, is the goal with https://github.com/vitejs/vite/pull/9278 is to make CSS insertion (in dev) follow what's already happening in production builds?

I don't think so. If anything, it is to make production builds behave similarly to dev.

matthewp commented 1 year ago

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.

IanVS commented 1 year ago

I suppose it's also not possible to just bundle all CSS into a single bundle at build, because the order of the dynamic imports can't be known until runtime, right?

matthewp commented 1 year ago

@IanVS For the entire site or a single page? For an entire site that's typically unwanted because the CSS file is going to be quite large and it will cause unwanted styles on some pages. If you mean on a single page you can do that, but it just means that shared CSS gets loaded once for each page, and people often don't like that.

jasikpark commented 1 year ago

I'm curious if it's possible to deploy a true unbundled build that just loads the CSS the same as in dev..

jasikpark commented 1 year ago

akin to the "granular chunking" in project aurora: https://developer.chrome.com/blog/introducing-aurora/

IanVS commented 1 year ago

@IanVS For the entire site or a single page?

In my case, I have a single-page-app (so I guess, both?). Right now, I'm not able to use dynamic imports / lazy loading because of the style problems. Even if I had to load the entire site's CSS up front, that would be a bit of an improvement if I could start to code-split the JS. But, I doubt that it's possible to bundle all the CSS in the correct order in that situation.

This issue is specifically about css modules. I suppose other styling approaches do not suffer this same problem, and perhaps I need to investigate other approaches if I want to use dynamic imports. Has anyone else here had any luck working around this problem by changing your approach to styling?

mayank99 commented 1 year ago

i mentioned above that i worked around it using cascade layers. it was a long time ago so i don't remember the exact details (i think it was with css modules).

when you define your layers upfront, the order of imports doesn't matter anymore so it will behave predictably even if vite messes it up.

IanVS commented 1 year ago

I'm very excited about cascade layers. I even created https://github.com/DefinedNet/postcss-assign-layer to make it possible to automatically assign component css to a particular layer based on a glob. Unfortunately browser support is not yet good enough for me, and the postcss polyfill requires that all styles are known up-front: https://github.com/csstools/postcss-plugins/issues/376#issuecomment-1126511448.

alexdeia commented 1 year ago

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

ronaldosc commented 1 year ago

This reference could help with this issue?

Vite css.modules

Liubasara commented 1 year ago

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

DaiQiangReal commented 1 year ago

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