Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
pretender91 opened this issue Jun 23, 2021 · 71 comments
Labels
feat: css p4-important Violate documented behavior or significantly improves performance (priority)

Comments

@pretender91
Copy link

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

@iztsv

This comment was marked as spam.

@youzizi1

This comment was marked as spam.

@FelixIncerta

This comment was marked as spam.

@zlyyyy

This comment was marked as spam.

@zlyyyy
Copy link

zlyyyy commented Jul 20, 2021

I also have the same problem with React

How did you solve it

@kamilic

This comment was marked as spam.

4 similar comments
@cisen

This comment was marked as spam.

@zhangzheng-zz

This comment was marked as spam.

@stanfil

This comment was marked as spam.

@buddywang

This comment was marked as spam.

@pretender91
Copy link
Author

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
Copy link
Contributor

IanVS commented Dec 9, 2021

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
Copy link
Contributor

IanVS commented Dec 9, 2021

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
Copy link

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

@davidlin88
Copy link

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
Copy link

any idea when will #6301 be merged?

@IanVS
Copy link
Contributor

IanVS commented Jan 13, 2022

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
Copy link

i understand. thanks for all this work!

@mrcljx
Copy link

mrcljx commented Jan 13, 2022

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
Copy link

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
Copy link

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

@jasikpark
Copy link

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
Copy link

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
Copy link

trgoofi commented Oct 3, 2023

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

image

replace app with zpp in manualChunks

image

@budlin2
Copy link

budlin2 commented Nov 5, 2023

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

@nitzcard
Copy link

nitzcard commented Mar 7, 2024

is there any update on this?

@adamxi
Copy link

adamxi commented Mar 21, 2024

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
Copy link
Contributor

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
Copy link
Contributor

IanVS commented Mar 21, 2024

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
Copy link

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 <style> in single file components.

@Matt-Biddick
Copy link

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

UPDATE: I just want to retract my addition to this issue. Mine was an embarrassingly newbie mistake of leaving a comment open >.<

@MarkusJLechner
Copy link

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.

@ortizdaniel
Copy link

I've got a really hacky solution in case it helps anyone, but it is very possible that it only works for my particular project: it consists of moving the styles from the <head> to the bottom of the <body>:

<script>
  document.addEventListener('DOMContentLoaded', function () {
    const head = document.head;
    const body = document.body;


    const styleTags = head.querySelectorAll('style[type="text/css"]');
    styleTags.forEach(function (styleTag) {
      head.removeChild(styleTag);
      body.appendChild(styleTag);
    });

    const linkTags = head.querySelectorAll('link[rel="stylesheet"]');
    linkTags.forEach(function (linkTag) {
      head.removeChild(linkTag);
      body.appendChild(linkTag);
    });
  });
</script>

However, keep in mind that in my project:

  • In development the styles are added with multiple <style type="text/css"> tags.
  • In production, after building, they are added with a single <link rel="stylesheet"> tag.
  • The other conflicting styles do not meet this criteria, so beware that it might not work for you.
  • It is working (for the time being).

Also let me know if this is a terrible idea and why, please 🙏🏼

@jasikpark
Copy link

jasikpark commented May 29, 2024

Still not a solution, but https://admin.defined.net uses https://developer.mozilla.org/en-US/docs/Web/CSS/@layer (finally part of the "Baseline") + https://github.com/DefinedNet/postcss-assign-layer to ensure our cascade is enforced programmatically.

/* establish a layer order up-front, from lowest to highest priority */
@layer defaults, theme, libraries, patterns, components, utilities, overrides;

/** Defaults */
@import url('defaults.css');

/** Theme */
@import url('theme/colors.css');
@import url('theme/typography.css');

/** Patterns */
@import url('../../components/atoms/forms/border-focusable.css');
@import url('../../components/patterns/menus/menu.css');

/** Components are assigned by a PostCSS plugin for all `*.module.css` files */

/** Utilities */
@import url('utilities/layout.css');
@import url('utilities/color.css');
@import url('utilities/borders.css');
@import url('utilities/typography.css');

The PostCSS plugin rewrites all of our *.module.css files to have the components layer wrapped around them, so now our base styles, component styles, and utility styles can all be loaded in whatever way we wish without causing issues.

This still requires caution when it comes to specificity within each cascade layer, but it's much easier to avoid that.

This enabled us to setup fully async react-router-dom routes, code-splitting our app finally!

@budlin2
Copy link

budlin2 commented Jun 18, 2024

Hey want to get people's thoughts on the new React 19 docs, specifically this section, as it relates to this issue:

https://react.dev/blog/2024/04/25/react-19?ck_subscriber_id=2099611990#support-for-stylesheets

It looks like you can inline style sheets directly in the components, themselves, which unless I am mistaken, should solve pretty much everyone's issues here? Please correct me if I am wrong. Thanks.

EDIT: Should have asked: "Does this solve all of the React users' issues?"

@tivac
Copy link

tivac commented Jun 18, 2024

I use svelte, so that doesn't help me.

@budlin2
Copy link

budlin2 commented Jun 18, 2024

That's ok. Nobody's perfect

@rekhaagarwal09
Copy link

rekhaagarwal09 commented Aug 1, 2024

Hey, is there any update on same? This issue is happening in latest vite version also with css module . ( when css code split is set to true)

@novym
Copy link

novym commented Aug 15, 2024

@jasikpark's workaround works in my build.

Vite 5
React 18.3.1

vite.config.ts

import postcssAssignLayer from 'postcss-assign-layer';
//...
      postcss: {
        plugins: [
          postcssAssignLayer([
            {
              include: '**/*.module.scss',
              layerName: 'components'
            }
          ])
        ]
      }
//...

Index.scss

@layer base, components;

@layer base {
  @import './scss/normalize';
  @import './scss/variables';
  @import './scss/fonts';
  @import './scss/mixins';
  @import './scss/globals';
  @import './scss/typography';
}

Router.tsx

import { Suspense, lazy } from 'react';
import { createBrowserRouter, RouterProvider, LoaderFunctionArgs, Outlet } from 'react-router-dom';

const Home = lazy(() => import('../components/Home'));
const About = lazy(() => import('../components/About'));

export const Router = () => {
  const routeConfig = createBrowserRouter([
    {
       path: '/',
       element: (
          <Suspense fallback={<div>Loading...</div>}>
            <Home />
          </Suspense>
        )
      },
      {
        path: '/about',
        element: (
          <Suspense fallback={<div>Loading...</div>}>
            <About />
          </Suspense>
        )
      },
    ])
    
    return (
      <RouterProvider
        fallbackElement={<div>Loading...</div>}
        router={routeConfig}
      />
  );
}

@rekhaagarwal09
Copy link

Hey @novym , will the above solution solve the order issue in same layer like component ? In my case I have 2 components module css which is not being imported in correct order.

@grady-oneill-at-burgtec
Copy link

This was impacting our code as well, a react based app which uses import 'filepath.css'; to import per-component CSS styles for components. We had a single import for our main global styles css file in our App.jsx entry point, and the order of imports of CSS files was changing between preview and build.

Seems like a bug to me, CSS stylesheets should be appended to in the order that you reference/import them, right? If you import 'main_styles.css' right back at the start in the entry point, or reference it in your tag of a page, that should stay placed before anything else imported later on, in my opinion, and that seems to be the behaviour of HMR preview.

At first my work around for this was basically to import the CSS for the global styles as 'inline', and add the styles to the page with a function that ensures the styles get placed before any other styles:

import styles from "./../assets/css/styles.css?inline";

// Inject CSS styles into head before any other style elements.
function injectCSS(css) {
  const styleElement = document.createElement('style');
  styleElement.textContent = css;
  const head = document.head;
  const firstStyleOrLinkElement = head.querySelector('style, link');
  if (firstStyleOrLinkElement) {
    head.insertBefore(styleElement, firstStyleOrLinkElement);
  } else {
    head.appendChild(styleElement);
  }
}

injectCSS(styles);

Fixed the order of the only style sheet that was in the wrong placement. Very hacky solution though so I wasn't a fan of it.

My new solution is to import our global style sheet before the per-component style sheet in every React component.
So now each of our components looks like this:

import './../assets/css/styles.css';
import './../assets/css/components/ThisComponent.css';

And that seems to do the trick to ensure that the order of the styles is as each component expects, but has the downside of needing to import our CSS styles on every single component. Bit of a pain, but it seems to work at least.

@privatenumber
Copy link
Contributor

privatenumber commented Sep 21, 2024

Looking into the issue... Can anyone provide a minimal reproduction of the bug on StackBlitz using the latest Vite?
The one provided back in 2021 works fine when upgrading to the latest Vite: https://stackblitz.com/edit/vite-kuaaat

If anyone is experiencing this issue, please try checking if using https://github.com/privatenumber/vite-css-modules fixes the problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat: css p4-important Violate documented behavior or significantly improves performance (priority)
Projects
None yet
Development

Successfully merging a pull request may close this issue.