Vueで試すCSS modules
開発チームの下田です。
新年あけましておめでとうございます!
入れ替わりが激しいフロントエンドの技術をキャッチアップするモダンフロントエンド勉強会を社内で月2回開催しています。今回はVueの単一ファイルコンポーネントからCSS Modulesを使う方法を取り上げました。
今回のテーマはCSS Modulesです。VueのCSSモジュールではありません。紛らわしいですね・・この先VueのCSSモジュールの話は一切出てきませんので、CSS Modulesと言ったらCSS Modulesの話です。
CSS Modulesはreactとセットで解説している場合が多いですが、HTMLのテンプレートエンジンには依存しない、独立した仕様です。Vueで使ってもいいですし、他のフレームワークで使用しても問題ありません。
目的と環境
今回はわかりやすさを重視して、headerが青、mainが赤地に白の非常にダサいページを作っていきます。
解説で使用しているコードです。
https://github.com/onfi/try-css-modules-on-vue
下記のいずれかの方法で実行できます。
nodeをインストールしてある環境
npm run dev
dockerをインストールしてある環境 かつ uid=1000の環境
docker compose up
ポート番号5173にbindしています。
何を解決する技術なのか、CSSのグローバル汚染問題
CSSのグローバル汚染問題は、CSSがついうっかり思ってもない要素に当たってしまう問題です。
CSSのそもそもの目的は、HTMLなどのソースドキュメントとCSSで表す修飾的な表現を分離して記述すること、分離することで再利用可能にすることです。
classセレクタでの指定は柔軟性が高く、汎用的に使用できます。classに限らず、簡潔で明瞭な命名をするべきです。するべきなのですが、簡潔で明瞭に命名してしまうと重複してしまいます。こういう問題が起きないよう、プログラミングでは変数の名前が使用できる範囲がごく一部に限定されるように「スコープ」という概念があります。しかしCSSでは一部に限定する機能がなく、読み込んだら全てに適用される「グローバルスコープ」となってしまうため、CSSを読み込んだら最後、思いもよらぬ所に反映されてしまうことがあります。
説明するまでもない話かとは思いますが、ちょっと実験してみましょう。
CSSでimportする
assets/app.module.cssには.headerが青地になるCSS、assets/test.module.cssには.mainが赤地に白文字になるCSSが書いてあります。これを普通のCSSの@import構文で読み込んでみます。
App.vueのstyleのapp.module.cssのコメントアウトを外します。
<style>
@import './assets/app.module.css';
/* @import './assets/test.module.css'; */
</style>
そうすると、.headerが青地に修飾されます。
mainも修飾する
続いてtest.module.cssもコメントアウトを外し、.mainも修飾します。
<style>
@import './assets/app.module.css';
@import './assets/test.module.css';
</style>
.mainが赤地に白文字になりましたが、.headerが黒くなってしまいました。
assets/test.module.cssから.mainのみCSSを読み込みたかったものの、.headerも読み込まれてしまい、意図せずCSSが適用されてしまいました。
今回は問題が単純なので、test.module.cssを読み込んだ後にapp.module.cssを読み込めば解決するものの、CSSの構成が複雑だとお手上げになってしまいます。
CSS modules以外の解決方法
BEM等の重複しづらい命名ルールしたり、idセレクタを使用したり、Vueの単一ファイルコンポーネントでスコープつき CSSを使用する方法があります。
いずれも問題は解決できますが、再利用しづらくなる問題を抱えています。
CSS modules
という前提のもと、CSS modulesを使っていきましょう。
CSS modulesはCSSと言いつつJSから利用します。まず通常のstyleタグはバッサリ削除してください。
CSS modulesを使うには、次の手順で行います。
- 〇〇.module.css という名前のCSSを用意する
- ESのimport構文で〇〇.module.cssをimportする
- クラス名にimportしたCSSのクラス名を指定する
1. 〇〇.module.css という名前のCSSを用意する
jsからcssをロードできるライブラリは、デフォルトでファイル名が.module.css
で終わるとき、CSS modulesとして読み込む設定になっている場合が多いです。それに習って.module.css
で終わるように命名しましょう。
今回はもともとapp.module.css
とtest.module.css
と命名しています。
2. ESのimport構文で〇〇.module.cssをimportする
app.module.css
をESのimport構文でimportします。CSS modulesに対応したLoaderが含まれていれば、CSS modulesとしてimportできます。
コメントアウトしてあるのでコメントアウトを外してください。
<script setup>
import AppCss from './assets/app.module.css'
// import TestCss from './assets/test.module.css'
</script>
この時点で出力結果を確認すると、CSSが含まれた状態になっています。ただし、トップレベルのクラス名は規則に沿って書き換えられているため、どこにもCSSは適用されていません。
3. クラス名にimportしたCSSのクラス名を指定する
CSS modulesとしてimportすると、{元々のクラス名: 書き換えられたクラス名}
という形のjsのオブジェクトになっています。Vueでいうv-bind:class
でクラス名を設定すると、反映できます。
<header v-bind:class="AppCss.header">
画面を表示してみると、app.module.cssの.headerが適用されています。
<header :class="AppCss.header">
Vueの省略記法で書くと、実際にはjsで書いているのに、普通のHTMLとCSSで書いているような見た目でおしゃれです。
test.module.css
も同様に指定します。
<script setup>
import AppCss from './assets/app.module.css'
import TestCss from './assets/test.module.css'
</script>
<template>
<header :class="AppCss.header">
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
</header>
<main :class="TestCss.main">
<h1>CSS modulesを使ってみよう!</h1>
</main>
</template>
狙い通りにCSSが適用されました。
まとめ
CSS modulesはCSS自体はCSSと同じ記法、Vueやreactでbindするときも違和感が少ない記法で、必要なCSSを当てる技術です。
もちろん裏側にあるCSSのLoaderやESのimport構文などは覚えなければなりませんが、もはや常識なので説明するまでもないかなと思います。
標準化も進んでいるようで、ChromeやEdgeではライブラリなしで使用できるようです。
万が一デファクトスタンダードの地位から外れてしまっても、書いているCSSは標準仕様のため、取り返しがつきやすいです。
メリットもデメリットも穏やかな技術なので、とりあえず導入してしまってよいのではないでしょうか?
ラクーンホールディングスは勉強会を活発に行っている会社で、一緒に勉強してくれるエンジニア・デザイナーを大募集中です!興味を持っていただいた方は是非、カジュアル面談を実施しているのでお話ししましょう!
Youtubeチャンネルも盛り上げて行きたいので、こちらもご覧ください!