Open BuptStEve opened 3 years ago
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?
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?
But I just import the component A
, so the codes of B
(including the CSS) should be treeshaken out.
Ah okay. Found the webpack documentation for it, it's weird that the project's package.json
's sideEffects
determine which module is side-effect-free (or pure), the inverse of its implication. But that may be me not understanding the docs well yet.
Vite currently doesn't support sideEffects
in the project's package.json
AFAIK, so this makes it a feature request.
But I just import the component
A
, so the codes ofB
(including the CSS) should be treeshaken out.
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.
If you remove the sideEffects
in package.json
, you can see that's webpack's default behaviour too.
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).
@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.
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?
@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've needed something like this as well - I'm currently working on a UI component library that is optimized for server-side rendering, and the challenge I'm facing is that the app doesn't need the CSS for every component in the library, but only the components that it is actually using.
I can think of several approaches - one would be a build configuration parameter telling it to treat files with the "module.css" extension differently than other imports.
The other would be a doc-comment such as @localdep
or @pure
that would be associated with the CSS module import statement, letting Vite know that the CSS import is a local dependency of the exported symbols in that module - meaning that if all of those exported symbols are tree-shaken out, then the CSS can be removed as well. (I say "exported symbols" because tree-shaking, as I understand it, does not eliminate modules, but rather exports. So the removal of the CSS module would be contingent on the module contents, not the module itself.)
I have a Vuetify 3 + Vite project and was hoping to get rid of the unnecessary 20K lines of css from my final bundle.
My team has a component library using different technologies for building components, like Lit and Quasar/Vue. This would be useful to help strip out unused Quasar styles.
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.
This is a tricky problem. The ideal way would be something like: "If nothing else from this module is imported, then tree-shake its CSS imports too". But I understand that's not how JS works, especially when using barrel files.
I'm not sure that a new API would be useful for component library authors, because it wouldn't work with other bundlers (esbuild, webpack, etc). It would be nice if there was some consensus across different bundlers, to ease the burden from library authors.
(To get around this problem today, I've resorted to removing all side-effect imports, and provide explicit CSS exports for applications to consume, which is obviously not ideal.)
Yeah it seems like the respective issue on Webpack side is https://github.com/webpack-contrib/css-loader/issues/506
The ideal way would be something like: "If nothing else from this module is imported, then tree-shake its CSS imports too".
This is a little risky I think. If you have a library that export my-lib/styles.js
that only imports CSS, and export nothing (or export something but you don't necessarily have to import), then we can't treeshake the CSS away. It's technically still being used.
Also, I think we should look at CSS treeshaking as an optimization standpoint. In dev where no treeshaking is happening, you'd load all the styles from a barrel file, but in prod, suddenly some may be missing which causes inconsistencies.
The main usecase I'm looking into so far are for scoped CSS and CSS modules only, as in dev, the styles shouldn't bleed so extraneously-loaded styles are fine. In prod, we can then optimize and remove them.
So from that, I think we need some sort of opt-in API and likely Vite plugins could automate it. And if it comes to that, it'll be tricky to make it bundler-agnostic.
Yeah it seems like the respective issue on Webpack side is webpack-contrib/css-loader#506
I think that issue is about a more granular tree shaking at selector level. The OP is writing about the same problem with us but the discussion is mainly focused on a more granular one.
The ideal way would be something like: "If nothing else from this module is imported, then tree-shake its CSS imports too".
I guess Webpack handles like this. I found this example (https://github.com/pp3nd3x/webpack-treeshaking-css-modules) and it looks like doing like that. (I found it at https://github.com/webpack-contrib/css-loader/issues/769#issuecomment-427634591)
Yeah the linked issue seems to diverge to a different treeshaking topic, but that's the best I've found 😅
The repo you linked seems to be about "CSS modules", which I also want to cover. But my main concern of the approach proposed by Mayank was for general global CSS imports, which I don't think is safe to treeshake that way.
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 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.
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 🤔
Oh interesting. I thought the behaviour would be the same so I didn't bother to check that. So I guess on the build side it is already fixed (sideEffects
works in user files from the repro), however it doesn't work in dev yet. So now it has the inconsistency. That's an interesting reason to work towards https://github.com/vitejs/vite/issues/8237 perhaps.
Describe the bug
vue-cli(webpack) VS vite
Reproduction
https://github.com/BuptStEve/vite-css-treeshake
npm start
to run vue-clinpm run vite
to run vitenpm run build
to run vue-clinpm run vite:build
to run viteSystem Info
Used Package Manager
yarn
Logs
No response
Validations