🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

ViteのCSS Modulesは、JSのようにTree shakingできない

なんとなしにビルド後のCSSファイルを眺めてて、なんで使ってないUIパーツのCSS定義が含まれてるの・・?って思ったのがきっかけ。

こういうこと

たとえば、こういうディレクトリ構成でコンポーネントを書いてたとする。

- components
  - action-buttons
    - index.jsx
    - styles.module.css
- page.jsx

コンポーネントの実装はこのように。

// index.jsx
import styles from "./styles.module.css";

export const ButtonA = () => <button class={styles.buttonA}>A</button>

export const ButtonB = () => <button class={styles.buttonB}>B</button>

export const ButtonC = () => <button class={styles.buttonC}>C</button>

+

/* styles.module.css */
.buttonA { /* ... */ }

.buttonB { /* ... */ }

.buttonC { /* ... */ }

で、使う側はこう。

// page.jsx
import { ButtonA } from "@/components/action-buttons";

export const Page = () => (
  <>
    <ButtonA />
  </>
);

想定していた挙動と実際の挙動

  • JS: `ButtonA`のみバンドルされる
  • CSS: `.buttonA`の定義のみバンドルされる

という挙動を想定していたけど、実際は、

  • JS: `ButtonA`のみバンドルされる
  • CSS: `.buttonA`だけでなく、`.buttonB`も`.buttonC`もバンドルされる

という少し残念な感じになってしまう。

仕様らしい

(そこまで自信と確証はないけど)自分の理解では、どうやら現状のViteの仕様らしい。

CSS files cannot be treeshaken with side effects · Issue #4389 · vitejs/vite · GitHub

CSSファイルはすべて副作用あり(グローバルに作用する可能性あり)と判断されるので、そうなっちゃうと。

そして現状では、JSのコンポーネント定義ごとにCSS Modulesのファイルも分けてやるのが一番実直かつ簡単な方法そう。

- components
  - action-buttons
    - index.jsx
    - button-a.jsx
    - button-a.module.css
    - button-b.jsx
    - button-b.module.css
    - button-c.jsx
    - button-c.module.css
- page.jsx

う〜む、トレードオフ・・。

PurgeCSSとかそういう方向性も考えたけど、CSS Modulesと相性よくないことは目に見えてる。

CSS modules | PurgeCSS

ViteじゃなくてWebpackなら、頑張ればできるという感じ。

GitHub - markmur/webpack-css-tree-shaking-example: Working example of tree shaking CSS modules

ローダーまみれ。

個人的にはこうなってくるとCSS Modulesやめたくなってくるし、CSSを書きたい気持ちがあるときはおとなしくSvelte一択っていう話になっちゃう。