QiitaのCSS構成2016


Qiitaを開発しているIncrementsで、フロントエンドをやっていってる @morishitter です。入社して4ヶ月が経ち、会社にも少しずつ慣れてきました。 [CSS Advent Calendar 2016](http://qiita.com/advent-calendar/2016/css) の1日目の記事として、Qiita及びQiita:TeamでのCSSの構成について書きます。

## 脱Sprockets

Qiita / Qiita:Teamでは、サーバーサイドのアプリケーションフレームワークとしてRuby on Railsを採用しています。そのため、これまでは [Sprockets](https://github.com/rails/sprockets) というRailsにbundleされているassetビルドツールを使ってSassをCSSにコンパイルしていました。しかし今では、全てのCSS, JSのビルドをNode.jsで行っています。

CSSでの移行手順はざっと以下の通りです。

まず、Sprocketsで使われているRuby製のSassから [node-sass](https://github.com/sass/node-sass) に変更しました。node-sassは [LibSass](https://github.com/sass/libsass) というC++で実装されたSassエンジンのNode.jsラッパーで、Ruby Sassで `.sass-cache` を使ったときよりも約10倍高速です。

次に、Qiita / Qiita:Teamが依存しているbootstrap-sass等の外部のCSSライブラリを全てnpmからインストールします。インストールしたライブラリはSassの `@import` を使って読み込みます。また、QiitaはRuby製の [Compass](http://compass-style.org/) というSassのフレームワークを使用していたので、その部分のコードを全て書き換えます。Compassの `@mixin` でベンダープリフィックスを付けていたところは、後述するAutoprefixerに任せました。

そして、Sprocketsの `//= require` での依存解決も `@import` に変更し、Sassを1エントリーファイルでコンパイルできるようにディレクトリ構成を見直しました。

最後に、画像ファイルを `public/images` 以下に移動させ、`background-image` などでRailsの `image_path()` のようなヘルパー関数を使っていたところを、`url()` で書き換えます。

しかし、Sprocketsはassetsファイルのビルドだけでなく、以下のことも行います。

1. assetsファイルに変更があったら再コンパイルする
2. assetsのファイル名にダイジェスト値を付ける
3. 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](https://github.com/postcss/postcss-cli) を使っています。

ここではPostCSSについて詳しい説明はしませんが、 [一人PostCSS Advent Calendar](http://qiita.com/advent-calendar/2016/postcss) で書いていくので、良かったらこちらを読んでみて下さい。

### プリプロセスのためのPostCSSプラグイン

Qiita / Qiita:Teamでは、新規に追加するコードはSass(node-sass)ではなく、PostCSSのプラグインをいくつか使ってプリプロセスをしています。プリプロセスのために使っているプラグインは以下の5つです。

- postcss-import
- postcss-custom-properties
- postcss-custom-media
- postcss-apply
- postcss-nesting

[postcss-import](https://github.com/postcss/postcss-import) は `@import` を使ったファイルのインライン展開をするプラグインです。postcss-importを使って、これまでのSassでコンパイルしたコードとPostCSSでプリプロセスしたコードをconcatしています。

[postcss-custom-properties](https://github.com/postcss/postcss-custom-properties) と [postcss-custom-media](https://github.com/postcss/postcss-custom-media) はそれぞれ、 `:root` のセレクタでのみ定義したカスタムプロパティーを展開するプラグインと、`@custom-media` という@ルールでメディアクエリーのパラメーターに名前を付けるプラグインです。

```css
/* カスタムプロパティーの定義 */
:root {
  --thumb-xs: 20px;
}

/* カスタムプロパティーの使用 */
.thumb--xs {
  width: var(--thumb-xs);
  height: var(--thumb-xs);
}

/* カスタムメディアクエリーの定義 */
@custom-media --mobile (max-width: 415px);

/* カスタムメディアクエリーの使用 */
@media (--mobile) {
  ...
}

```

[postcss-apply](https://github.com/pascalduez/postcss-apply) と [postcss-nesting](https://github.com/jonathantneal/postcss-nesting) は、CSSワーキンググループのメンバーでありGoogleのソフトウェアエンジニアであるTab Atkins Jr.という人が書いている仕様を元にしたプラグインです。それぞれ、Custom Sets of Propertiesという複数のプロパティー宣言に名前を付けるものと、既存のプリプロセッサーにあるようなセレクタのネスト表記をするためのものです。

```css
/* 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](https://github.com/postcss/autoprefixer) は、[Can I Use](http://caniuse.com) というサイトのデータを元にベンダープリフィックスを自動で付与するためのプラグインです。ベンダープリフィックス付与ツールとしてだと、前述したCompassよりも約55倍高速です。

[postcss-flexbugs-fixes](https://github.com/luisrudge/postcss-flexbugs-fixes)は、報告されている [Flexboxのバグ](https://github.com/philipwalton/flexbugs) の中から、CSSの変形のみで対応できるバグ修正をおこなうプラグインです。[postcss-sorting](https://github.com/hudochenkov/postcss-sorting) はプロパティー宣言の順序を並び替えるプラグインで、最適化のフェーズでpostcss-sortingを使っているのは、gzip時の圧縮率を高める可能性を上げるためです。[csswring](https://github.com/hail2u/node-csswring) はCSSのminifyをしています。

### stylelintを使ったSCSS, CSSコードのリント

[stylelint](https://github.com/stylelint/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](https://github.com/increments/stylelint-config-qiita) という名前で公開しています。

またフォーマッターとして、stylelintのルールを元に自動整形する [stylefmt](https://github.com/morishitter/stylefmt) を使用しています。

## QiitaのCSS設計

前述したPostCSSを使ったコードにおける、Qiita / Qiita:Teamでの設計手法についてです。概ね僕が自分のブログで書いた通りのルール分割となっているので、以下の記事を参照して下さい。

- [morishitterのCSSの書き方（2016年夏）](http://morishitter.hatenablog.com/entry/2016/07/29/204642)
- [Extensible CSS Idealistic Structure Theory](https://medium.com/@morishitter/extensible-css-idealistic-structure-theory-36bd892d3a50#.bqjudoejv) (英語)

ComponentsレイヤーとPatternsレイヤーではスタイルガイドを用意しており、スタイルガイドジェネレーターとして [Aigis](https://github.com/pxgrid/aigis/) を使っています。

また、Qiita / Qiita:TeamではJSのライブラリとしてReact.jsを一部採用していますが、大部分のHTMLがReact.jsではなくRailsでレンダリングされているので、Radiumやcss-modulesのような所謂CSS in JS系のツールは使っていません。

---

いかがでしたでしょうか。実際のプロダクトでのCSS構成を公開することでどこかで誰かの役に立ち、ソフトウェア開発がよくなって、世界の進化が加速すればと思います :)