vite: CSS files cannot be treeshaken with side effects

Describe the bug

image

vue-cli(webpack) VS vite

image

Reproduction

https://github.com/BuptStEve/vite-css-treeshake

  • npm start to run vue-cli

  • npm run vite to run vite

  • npm run build to run vue-cli

  • npm run vite:build to run vite

System Info

System:
    OS: macOS 11.4
    CPU: (8) x64 Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz
    Memory: 2.44 GB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 14.17.1 - ~/.nvm/versions/node/v14.17.1/bin/node
    Yarn: 1.22.4 - /usr/local/bin/yarn
    npm: 6.14.13 - ~/.nvm/versions/node/v14.17.1/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  Browsers:
    Chrome: 92.0.4515.107
    Safari: 14.1.1
  npmPackages:
    vite: ^2.4.3 => 2.4.3

Used Package Manager

yarn

Logs

No response

Validations

About this issue

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

Most upvoted comments

Yeah, this feature is useful for UI library to auto import component’s own style by default. And when you build app, it also helps treeshake unused ones(with style).

@mayank99 I don’t think there’s a workaround today, but I agree there should be a way around this, like the sideEffects field discussed above. I’ll put this on the team board to confirm if this is the design we want, or if there’s an alternate API design.

I have a Vuetify 3 + Vite project and was hoping to get rid of the unnecessary 20K lines of css from my final bundle.

@bluwy I think your first understanding about sideEffects field is correct. According to the webpack docs here, it shows "sideEffects": [ "**/*.css" ], means **/*.css has side-effectful. (Not side-effect-free)

When this was changed like this, webpack did not output any css.

  "sideEffects": [
-    "*.css",
    "src/components/a/index.js",
    "src/components/b/index.js"
  ],

So in conclusion I think when there is sideEffects field, Vite should respect the field for css files (and others). For example, if sideEffects field exists and does not include something like *.css, vite should stop treating css files as side-effect-ful.

It seems rollup treats each export statements as side-effect free if the barrel file is marked as side effect free. https://stackblitz.com/edit/rollup-repro-jgfcu7?file=package.json,rollup.config.js

Also it seems the repro now works: https://stackblitz.com/edit/github-sqz9ee?file=package.json With Vite 2.x, the CSS file is .a{color:#00f}.a,.b{color:#ff0}. With Vite 3.x, the CSS file doesn’t exist at all. With Vite 4.x, the CSS file is .a{color:#00f}. Interesting that the behavior changes 🤔

I took another look at the repro and understand the issue better now. It isn’t related to CSS at all. Webpack has a unique optimization where if you mark a barrel file as side-effect free, then any re-exports will redirect to the re-exported module directly. Specifically optimization.sideEffects and optimization.providedExports are at play here, which is also enabled in dev. For example:

// src/barrel/index.js
export { a } from './a.js'
export { b } from './b.js'
// src/index.js
import { a } from './barrel'
// webpack rewrites this to
// import { a } from './barrel/a.js'
// because the barrel file is marked side effect free

console.log(a)

Vite intentionally doesn’t have these optimizations in dev because they have perf penalties. If there’s interest for it, I think https://github.com/vitejs/vite/issues/8237 is the right issue to follow. As even if we respect sideEffects in user project today, it will only work in build which can cause unintended inconsistencies.

For this issue, I’ll focus on CSS treeshaking only and will close this once it’s resolved. To get the repro resolved, https://github.com/vitejs/vite/issues/8237 should be followed instead.

The repo you linked seems to be about “CSS modules”

Ah, I overlooked it and assumed it’s the normal global CSS imports 🤦. Webpack does recommend adding *.css to sideEffects field.

I’m currently looking into this as it’ll benefit UI frameworks with scoped CSS. Especially for component libraries or barrel files, as today, all the scoped CSS will get bundled even if the related component is not bundled.

However marking CSS are side-effect-free is technically not the solution. For example, if you have import "./global.css" in your entrypoint JS, “side-effect-free” means that the import can be removed completely (and its related CSS asset) because you’re not really using anything from the module anyway, which is incorrect.

I’m currently trying to figure an API to say “this CSS import is related to this default/named export. if the export is treeshaken, also treeshake the CSS”. I’m not sure if it’s possible to do it automatically. Also, this issue affects CSS modules today.

Vite treats all CSS files as side-effectful by default, that means even if we didn’t used B, we still imported B, and B contains an import to a CSS file, that CSS file “effects” the state of the application (which makes sense because you add global styles), so that CSS is included in the final bundle, or else your application state could be inconsistent.

@bluwy Is there a way to work around this behavior?

Components often use scoped selectors to ensure there are no global side-effects, so it would be nice to tell vite to treat these css imports as side-effect free. I understand that this is a feature request but there must be something we (component library authors) can do or document today to reduce bundle size?

I’m confused, if the package.json marked CSS files as side-effectful, shouldn’t Webpack include the CSS that would result in what Vite produces? Vite currently treats all CSS as side-effectful, and side-effect modules aren’t treeshaken out because they aren’t pure. So from my point of view, Vite is doing the right thing here. Can you shed some light on the issue here?