vite icon indicating copy to clipboard operation
vite copied to clipboard

CSS referenced assets are included even when the file is tree shaken

Open Csszabi98 opened this issue 3 years ago • 3 comments

Describe the bug

When a vite build includes a css file conditionally bound to environment variables the referenced assets by the CSS file always end up in the assets output folder even when the CSS file itself which references them does not.

index.css

@font-face {
    font-family: 'Roboto Regular';
    src: url('assets/roboto-regular.woff2') format('woff2');
    font-weight: normal;
    font-display: swap;
}

.env

VITE_INCLUDE_CSS="false"

index.js

if(import.meta.env.VITE_INCLUDE_CSS === 'true') {
  import('./index.css')
}

Expected behavior: index.css and roboto-regular.woff2 are not included in the build output folder. Actual behavior: roboto-regular.woff2 is included in the build output folder.

Reproduction

https://github.com/Csszabi98/css-tree-shake-asset-issue

System Info

System:
    OS: macOS 12.4
    CPU: (10) x64 Apple M1 Pro
    Memory: 29.74 MB / 32.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 14.19.1 - ~/Library/Caches/fnm_multishells/957_1657095588762/bin/node
    npm: 6.14.16 - ~/Library/Caches/fnm_multishells/957_1657095588762/bin/npm
  Browsers:
    Brave Browser: 101.1.38.109
    Chrome: 103.0.5060.114
    Firefox: 101.0.1
    Safari: 15.5
  npmPackages:
    @vitejs/plugin-vue: ^2.3.3 => 2.3.3 
    vite: ^2.9.9 => 2.9.14

Used Package Manager

npm

Logs

No response

Validations

Csszabi98 avatar Jul 13 '22 12:07 Csszabi98

This seems to be because CSS in Vite are side-effectful by default, so Rollup would proceed to render the chunk even if it's unused. We can't change that though since it would break many other tooling, though Vite supports the ?inline query to disable side effects, and you have to inject it manually.

I also found another bug, which after using ?inline, the assets are still emitted, so that seems like a bug.

bluwy avatar Jul 14 '22 16:07 bluwy

Update: I took another look at the issue today. The index.css (or main.css in the repro) is not included in the bundled CSS anymore, but the font assets are still emitted. It's because even though the import is unused, we would still transform it and it'll unconditionally emit the assets when transforming.

I'm not sure if there's a way for Rollup to skip transforming imports in dead code, I assume it doesn't treeshake after finished transforming/parsing a module. (Only during rendering?). So there isn't an easy fix on our side at the meantime, other that manually analyzing all assets emitted against the rendered chunks and delete them.

About the ?inline issue, I found that it's the same cause as above.

bluwy avatar Nov 12 '23 15:11 bluwy

I'm running into same result but the import is coming from JS file and not CSS. @bluwy do you think this is a different issue and has different root cause?

https://stackblitz.com/edit/vite-unused-assets-tree-shake?file=main.js

Here the asset.png ends up in build even when main.js doesn't import the JS file that imports asset.png. Final build has no references to asset.png on JS or HTML side.

However when the process.env.NODE_ENV check is modified a bit, the dead code elimination seems to work better and asset.png is no longer included in build:

async function enableMocking() {
+  if (process.env.NODE_ENV === 'development') {
-  if (process.env.NODE_ENV !== 'development') {
-    return;
-  }

  const devTools = await import('./development-only');
  console.log(devTools);
+  }
}

The initial pattern is suggested by MSW documentation. I have MSW serving .png files in development mode, and these are now ending up in production build as well.

AriPerkkio avatar Mar 01 '24 11:03 AriPerkkio