Qiitaを開発しているIncrementsで、フロントエンドをやっていってる @morishitter です。入社して4ヶ月が経ち、会社にも少しずつ慣れてきました。 CSS Advent Calendar 2016 の1日目の記事として、Qiita及びQiita:TeamでのCSSの構成について書きます。
脱Sprockets
Qiita / Qiita:Teamでは、サーバーサイドのアプリケーションフレームワークとしてRuby on Railsを採用しています。そのため、これまでは Sprockets というRailsにbundleされているassetビルドツールを使ってSassをCSSにコンパイルしていました。しかし今では、全てのCSS, JSのビルドをNode.jsで行っています。
CSSでの移行手順はざっと以下の通りです。
まず、Sprocketsで使われているRuby製のSassから node-sass に変更しました。node-sassは LibSass というC++で実装されたSassエンジンのNode.jsラッパーで、Ruby Sassで .sass-cache
を使ったときよりも約10倍高速です。
次に、Qiita / Qiita:Teamが依存しているbootstrap-sass等の外部のCSSライブラリを全てnpmからインストールします。インストールしたライブラリはSassの @import
を使って読み込みます。また、QiitaはRuby製の Compass というSassのフレームワークを使用していたので、その部分のコードを全て書き換えます。Compassの @mixin
でベンダープリフィックスを付けていたところは、後述するAutoprefixerに任せました。
そして、Sprocketsの //= require
での依存解決も @import
に変更し、Sassを1エントリーファイルでコンパイルできるようにディレクトリ構成を見直しました。
最後に、画像ファイルを public/images
以下に移動させ、background-image
などでRailsの image_path()
のようなヘルパー関数を使っていたところを、url()
で書き換えます。
しかし、Sprocketsはassetsファイルのビルドだけでなく、以下のことも行います。
- assetsファイルに変更があったら再コンパイルする
- assetsのファイル名にダイジェスト値を付ける
- assetsファイルのビルドが終わるまで、Railsがレスポンスを返すのを待つ
1, 2のスクリプトをNode.jsで書くことで、ビルド完了までをNode.jsで完結することができました。また、3はRubyでRack Middlewareとして実装されています。
この部分の変更とデプロイ周りを @yuku_t が、JSを @mizchi が担当し、脱Sprocketsをすることができました。
PostCSSの導入
PostCSSという、CSSのソースコードを入力として受け取り、任意の処理を加えてから出力するツールがあります。Qiita / Qiita:Teamの開発でも、PostCSSを使ったプリプロセスや最適化、コードのリントを行っています。gulp等のタスクランナーを使わず、ビルドタスクを全てnpm-scriptで実行しているので、 postcss-cli を使っています。
ここではPostCSSについて詳しい説明はしませんが、 一人PostCSS Advent Calendar で書いていくので、良かったらこちらを読んでみて下さい。
プリプロセスのためのPostCSSプラグイン
Qiita / Qiita:Teamでは、新規に追加するコードはSass(node-sass)ではなく、PostCSSのプラグインをいくつか使ってプリプロセスをしています。プリプロセスのために使っているプラグインは以下の5つです。
- postcss-import
- postcss-custom-properties
- postcss-custom-media
- postcss-apply
- postcss-nesting
postcss-import は @import
を使ったファイルのインライン展開をするプラグインです。postcss-importを使って、これまでのSassでコンパイルしたコードとPostCSSでプリプロセスしたコードをconcatしています。
postcss-custom-properties と postcss-custom-media はそれぞれ、 :root
のセレクタでのみ定義したカスタムプロパティーを展開するプラグインと、@custom-media
という@ルールでメディアクエリーのパラメーターに名前を付けるプラグインです。
/* カスタムプロパティーの定義 */
:root {
--thumb-xs: 20px;
}
/* カスタムプロパティーの使用 */
.thumb--xs {
width: var(--thumb-xs);
height: var(--thumb-xs);
}
/* カスタムメディアクエリーの定義 */
@custom-media --mobile (max-width: 415px);
/* カスタムメディアクエリーの使用 */
@media (--mobile) {
...
}
postcss-apply と postcss-nesting は、CSSワーキンググループのメンバーでありGoogleのソフトウェアエンジニアであるTab Atkins Jr.という人が書いている仕様を元にしたプラグインです。それぞれ、Custom Sets of Propertiesという複数のプロパティー宣言に名前を付けるものと、既存のプリプロセッサーにあるようなセレクタのネスト表記をするためのものです。
/* Custom Sets of Properties */
:root {
--truncate: {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
};
}
.foo {
@apply --truncate;
}
/* Nesting */
.thumb {
display: flex;
overflow: hidden;
border-radius: 0.2em;
& img {
width: 100%;
height: 100%;
border-radius: 0.2em;
}
...
}
僕は、プリプロセッサーとしてPostCSSを使うなら(少しでも)標準化される可能性のある仕様をベースにしたプラグインを使うようにしています。SassやStylus等、既存のプリプロセッサーにロックインされるのを防ぐためですが、PostCSSのプラグインは不安定なものも多く、良いプラグインを嗅ぎ分ける嗅覚と、何かトラブルが起こったときにはコントリビュートする覚悟が必要になります。
最適化のためのPostCSSプラグイン
次にプリプロセス後のCSSコードに対して後処理(ポストプロセス)、最適化を行うためのプラグインについてです。Qiita / Qiita:Teamでは以下の3つを使っています。
- Autoprefixer
- postcss-flexbugs-fixes
- postcss-sorting
- csswring
Autoprefixer は、Can I Use というサイトのデータを元にベンダープリフィックスを自動で付与するためのプラグインです。ベンダープリフィックス付与ツールとしてだと、前述したCompassよりも約55倍高速です。
postcss-flexbugs-fixesは、報告されている Flexboxのバグ の中から、CSSの変形のみで対応できるバグ修正をおこなうプラグインです。postcss-sorting はプロパティー宣言の順序を並び替えるプラグインで、最適化のフェーズでpostcss-sortingを使っているのは、gzip時の圧縮率を高める可能性を上げるためです。csswring はCSSのminifyをしています。
stylelintを使ったSCSS, CSSコードのリント
stylelint というCSSのリンターがあります。PostCSSをパーサーに使っているので、SassのSCSS記法やLESS、Custom Properties等を使ったCSSの新しいシンタックスに対してもリントすることができます。Qiita / Qiita:Teamでは今までscss-lintを使っていましたが、stylelintに乗り換えました。
stylelintは指定できるルールの数が約170個と多く、また全てのルールはPostCSSのプラグインとして実装されているため、独自のルールを書くことも可能です。JSのESLintのように、非常にカスタマイズ性の高いCSSリンターです。
stylelint-config-*
のプリフィックスで始まるパッケージはstylelintの設定ファイル集で、Qiita / Qiita:Teamで使用しているルールを stylelint-config-qiita という名前で公開しています。
またフォーマッターとして、stylelintのルールを元に自動整形する stylefmt を使用しています。
QiitaのCSS設計
前述したPostCSSを使ったコードにおける、Qiita / Qiita:Teamでの設計手法についてです。概ね僕が自分のブログで書いた通りのルール分割となっているので、以下の記事を参照して下さい。
ComponentsレイヤーとPatternsレイヤーではスタイルガイドを用意しており、スタイルガイドジェネレーターとして Aigis を使っています。
また、Qiita / Qiita:TeamではJSのライブラリとしてReact.jsを一部採用していますが、大部分のHTMLがReact.jsではなくRailsでレンダリングされているので、Radiumやcss-modulesのような所謂CSS in JS系のツールは使っていません。
いかがでしたでしょうか。実際のプロダクトでのCSS構成を公開することでどこかで誰かの役に立ち、ソフトウェア開発がよくなって、世界の進化が加速すればと思います :)