はじめに
前回の記事では、BEMの概念やポリシー、ルールについて書きました。
前回の記事:
実践BEM 〜BEM導入の目的と現役で使い続ける運用ルール〜
これらは単一のHTMLやCSS上で実現することも可能ですが、効率的な運用のためには開発環境を構築することが必須と言えるでしょう。
この記事では、BEMに則ったコードを効率的に運用していくための開発環境やコーディングについて、私が現場で使っているものに近い環境をご紹介します。
開発環境のサンプルコード
今回ご紹介する開発環境のソースコードはGitHubで公開しています。
https://github.com/oooshinnn/2020-bem-development
使い方
上記のURLにアクセスし少し下へスクロールすると、使い方の説明が書いてありますので、そちらをご参考ください。
概要(流れ)
開発環境の処理の流れはざっくり以下のようになっています。
- HTMLのプリプロセッサとしてPugを、CSSのプリプロセッサとしてSassを使う
- gulp、webpack、npm scriptsなどを使ってPugとSassをコンパイルする
- PostCSSでコンパイル後のCSSを良い感じに加工する
それでは、具体的な運用ルールの説明です。
運用ルール
1ブロック=1ファイル
1つのブロックに関する記述は、1つのファイルの中にまとめて記載します。1つのブロックに関する記述が複数にまたがったり、1つのファイルの中に複数のブロックに関する記述が含まれないようにします。
ディレクトリ構成は以下のようになります。
src/
┗ sass/
┗ style.scss ← コンパイルされてstyle.cssになる大元
┗ blocks/ ← ここにブロックのファイルを置いていく
┗ _b-card.scss
┗ _b-table.scss
┗ ...
┗ pug/
┗ index.pug ← コンパイルされてstyle.cssになる大元
┗ blocks/ ← ここにブロックのファイルを置いていく
┗ _b-card.pug
┗ _b-table.pug
┗ ...
1ブロック=1ファイルのルールを守ることで、スタイルを探しやすくなったり、gitで管理している場合ファイルの変更履歴がブロックの変更履歴と結びつくため管理がしやすくなります。
NGな例
.b-linkButton {
// 通常のスタイル
}
.b-decorationFrame {
// 通常のスタイル
.b-linkButton {
// 装飾枠に入った時だけの特別なスタイル
}
}
こうしてしまうと、キーセレクター(そのスタイルを適用したいメインのセレクター)であるb-linkButton
の指定が複数ファイルに分散してしまい、どちらか一方の更新漏れなどが発生しやすくなります。
OKな例
.b-linkButton {
// 通常のスタイル
.b-decorationFrame & {
// 装飾枠に入った時だけの特別なスタイル
}
}
.b-decorationFrame {
// 通常のスタイル
// ※ここに b-linkButton のスタイルを書いてはいけない
}
1ブロックの記述を1ファイルにまとめるためにはこのように記述すると良いでしょう。
gulp-sass-globを使う
1ブロック=1ファイルのデメリットとして、すべてのスタイルを読み込む大元のscssファイルから、以下のように読み込むブロックをひとつひとつ指定しなければならない点があります。
@import ./blocks/_card;
@import ./blocks/_table;
@import ./blocks/_text;
...
これでは、ブロックの増減があるたびにstyle.scssのファイルも同時に更新しなければならず手間になります。
そこで、blockディレクトリの中のファイルを一括で読み込むことができるよう、以下のような記述を利用します。
@import 'blocks/*.scss';
この書き方は標準のsassの機能では使えないため、gulp-sass-glob
というライブラリをプロジェクトにインストールする必要があります。
メディアクエリーの使い方、使い所
mixinを作る
ブラウザの横幅に応じたCSSを書く場合、メディアクエリーの指定は@media ...
といったものを直接書くのではなく、以下のようなmixinを使用します。
$breakpoints: (
's': '560px',
'm': '960px',
'l': '1280px'
) !default;
@mixin mq($breakpoint: m) {
@media only screen and ( min-width: #{ map-get( $breakpoints, $breakpoint ) } ) {
@content;
}
}
ブロックのスタイル指定ではmixinを以下のように使います。
.b-someBlock {
color: red;
@include mq(s) {
color: blue;
}
@include mq() { // デフォルトのmが当たる
color: green;
}
}
メディアクエリーの使い所
基本的な方針として、メディアクエリーを極力使わなくて済むような設計を考えます。
たとえば、とある要素の横幅をブラウザの横幅に応じて変化させたい場合、ブレイクポイントごとの横幅を考える前にまず%
やvw
といった相対値では実現できないかということから検討します。
その上で、ブラウザの横幅に応じた相対指定ではどうしても実現できないようなもの、たとえば列の数の変化などに対してのみ、メディアクエリーを使用してスタイルを適用します。具体的には以下のような形です。
.b-card {
display: flex;
@include mq() {
display: block;
}
}
.b-cart__item {
margin-right: 0;
margin-left: 0;
@include mq(s) {
margin-right: 10px;
margin-left: 10px;
}
}
メディアクエリーは、使用したいプロパティのすぐ近くで使います。(ただし、メディアクエリーを極力使わないという方針を前提としない場合はコードを見づらくしてしまうので注意が必要です。)
メディアクエリーを複数の場所で使用すると、コンパイル後のCSS容量が増加することも考えられます。
これに関しては、1ブロック=1ファイルのルールを前提とした時点ですでに避けられない問題となるため、後述するPostCSSによる対策を実施します。
PostCSSにいろいろ任せる
PostCSSは、CSSにいろいろ便利な処理を加えてくれるフレームワークです。この開発環境では、SassからコンパイルされたCSSに対して、人がやらなくても良いさまざまな処理を自動で行ってもらう目的で使用します。
人がやらなくても良いさまざまな処理とは、具体的には
- ベンダープレフィックスの自動付与
- バラバラに記述されたメディアクエリーを1箇所にまとめる
- CSSを圧縮(minify)する
これらの処理は、package.jsonのこの箇所にすべて書かれていて、Sassのコンパイルが実行されるたびに自動で実行されるため、通常の開発の中で意識することはありません。
ベンダープレフィックスの自動付与をしてくれるautoprefixer
ベンダープレフィックスを人が記述する時代は終わりました。
ベンダープレフィックスはすべてautorefixerに任せ、人が扱うファイルの中にベンダープレフィックスの記述は書かないようにします。
autoprefixerによって、ベンダープレフィックスの付与が必要なプロパティーに対し、コンパイル時に必要なコードを追加してくれます。
.b-card {
display: flex
}
.b-card {
display: -webkit-box;
display: -ms-flexbox;
display: flex
}
対応するブラウザーは、package.jsonのこの箇所で指定します。
browserslist
の指定は、「2015年以降」「シェアが1%以上」といったようにかなり柔軟な指定ができるようになっています。詳細はbrowserslist
で検索してみてください。
バラバラに記述されたメディアクエリーを1箇所にまとめるcss-mqpacker
先ほど「メディアクエリーの使い所」のセクションで書いた通り、1ブロック=1ファイルとするこの設計では、出力されるCSSのメディアクエリーの記述が分散し、CSSの容量が増加します。
これを解消するために使用するプラグインが、css-mqpackerです。
コンパイル直後のCSSが以下のようなものだった場合、
@media only screen and (min-width:560px) {
.b-card {
display: flex;
}
}
@media only screen and (min-width:560px) {
.b-card__item {
margin-bottom: 0;
margin-right: 20px;
}
}
@media only screen and (min-width:560px) {
.b-card__item:last-child {
margin-right: 0;
}
}
css-mqpackerの処理を通すことで、以下のような形に変換してくれます。
@media only screen and (min-width:560px) {
.b-card {
display: flex;
}
.b-card__item {
margin-bottom: 0;
margin-right: 20px;
}
.b-card__item:last-child {
margin-right: 0;
}
}
ただし、css-mqpackerの使用には注意が必要で、メディアクエリをまとめる順番が、CSSファイルで登場した順番になってしまいます。たとえば以下のように。
@media only screen and (min-width:960px) {
.b-someBlock {
color: green;
}
}
@media only screen and (min-width:560px) {
.b-someBlock {
color: red;
}
}
@media only screen and (min-width:1280px) {
.b-someBlock {
color: blue;
}
}
これでは意図した通りに動作しません。
これを防ぐために、メディアクエリー用のmixinの中で、指定したい順番にmixinの適用を並べ、中にコメントだけ書いておきます。
@include mq(s) {/* base-size */}
@include mq(m) {/* Wider than s-size */}
@include mq(l) {/* Wider than m-size */}
このコメントは、後続の処理で削除します。
CSSを圧縮(minify)する
Webサイトで圧縮(minify)されたCSSを読み込むようにすることで、データ通信量を削減できます。
また真偽の程は不明ですが、Googleのサイト読み込み速度計測ツールであるPageSpeed Insightsのスコアが改善することで、SEO効果が高まるという噂もあります。
PageSpeed Insightsのスコア改善自体にSEO効果があるとは言い切れませんが、不要な情報を削除しユーザに余計な通信をさせないWebサイトは、良質なWebサイトと判断されGoogleからの評価を受けるという考えは自然なものだと思いますので、CSS圧縮は基本的に行うべきでしょう。
CSS圧縮の方法はいろいろありますが、この開発環境ではcss nanoというライブラリを使用し、PostCSSによるすべての処理の最後にCSSを圧縮し、style.min.css
という名前で出力するようにしています。
ちなみに、CSS圧縮の否定派の意見として「デベロッパーツールでの検証でCSSの記述場所が特定できない」といったものを聞いたことがあります。これに対する個人的な考えは
- 開発用の情報と本番環境で利用する情報は分けるべき(開発用の情報を本番環境に上げるべきではない)
- 必要に応じてSassのmapファイルを生成すれば、sass上の記述箇所が特定できる
といったものなので、やはりCSSは圧縮すべきだと思います。
終わりに
以上で、BEMを適切に運用していくための開発環境についての説明を終わります。
コードに紐づいた具体的な話が多くなりましたが、そのコードやライブラリを使用する背景まで含めて書いたつもりです。
そのため、具体的なコードや使用するライブラリは、自分でもプロジェクトごとに異なることを使用することがあります。今回のものは、ほんの一例と考えていただき、ご覧くださった方が使いやすいようにアレンジしていただければと思います。
最後までお読みいただきありがとうございました。