読者です 読者をやめる 読者になる 読者になる

僕とCSSとCSSの生きる道

この記事が2014年最後のものになる。年の瀬だし、今年1年を振り返って軽くポエむ。

CSSとの出会い

CSSとの出会いはちょうど1年前くらいだと思う。約3年前からプログラミングを初めて、その頃からなんとなくCSSも書いていたけど、作っているWebアプリの見た目を表現したいものにできればいいぐらいにしか思っていなかった。だから、CSSという技術そのものと向き合い始めたのが1年前ということだ。そして2014年は、ひたすらCSSに投資した年だった。

去年の夏に2人で3000行くらいのCSSを書くことがあった。その頃の僕はCSSをどう書いていけばいいのかなんて考えたこともなく、好きなように書いて、破綻した。この頃になんとなくCSSはヤバい言語だという意識が生まれた。そして、今までめちゃくちゃ適当に書いていたHTMLとCSSについて勉強しなおすことにした。HTMLのセマンティックさなんて知らなかったし、floatやpositionプロパティの振る舞いも知らなかった。

CSSとの歩み

良いCSSを書くためには、既存の手法を知る必要がある。OOCSSやSMACSS、BEMという単語を調べたり、hilokiさんのスライドを見て「なるほど〜」って言ってた。そして、SassやLess、Stylusといったプリプロセッサーを触ってみたり、CSSLintCSSCSSUnCSSのような便利グッズの存在を知っていった。

Sass等を使い始めて、世の中のCSSプリプロセッサーはどれもクソだな〜って思ってYACPを作った。この頃から自分なりにCSSの理想みたいなものを考えるようになった。

今までWebサービスのアプリケーションコードしか書いてこなかったのに、急にぼくがかんがえたCSSの便利グッズを量産するようになった。ほとんどのツールはNode.jsで書いていて、Nodeにも少し詳しくなれた。雑に作ったのでメンテナンスできていないものが多くなってしまった。

CSSへの気持ち

僕は仕様をそこまで追えていないし経験も浅いので、これからCSSがどうなっていくか、CSSの生きる道についてはあれこれ言うことはできない。

CSSは今年の10月10日に誕生20周年を迎え、そのときのインタビュー記事で、CSSの提唱者でありOperaのCTOでもある、Håkon Wium Lie氏は最後にこのように述べた。

I believe that the CSS code we write today will be readable by computers 500 years from now.

「私たちが書いてきたCSSは今から500年後も読むことができるはずだ」と。僕もそうあってほしいと思う。

CSSとの思い出

最後に今年1年で書いたCSSに関する記事のリンクを貼っておく。それではみなさん、良いお年を。

CSSのカスケーディングを禁止する - morishitter blog

世の中のCSSプリプロセッサーがどれもクソだから自作したった - morishitter blog

スタイルガイドジェネレータを自作した - morishitter blog

CSSセレクタの名前を付けるときに考えていること - morishitter blog

CSSポストプロセッサーの必要性 - morishitter blog

CSSのマルチクラス設計の問題点 - morishitter blog

デザイナーのいないチームでのCSSの書き方 - morishitter blog

AltCSSビルドツール「Alchemia」 - morishitter blog

機能が行動を誘発する - morishitter blog

CSSオジサン #0に行ってきた - morishitter blog

CSSプリプロセッサーの`extend`の悪いところ - morishitter blog

YACP: よりオブジェクト指向なCSS設計のためのプリプロセッサー - morishitter blog

CSSのプリプロセスとポストプロセス、そしてReworkとPostCSS - morishitter blog

CSSのプリプロセスとポストプロセス、そしてReworkとPostCSS

この投稿はFrontrend Advent Calendar 2014の7日目の記事です。

CSSプリプロセッサーとポストプロセッサー、そしてそれらをビルドするツールであるReworkとPostCSSについて。

CSSプリプロセッサー、ポストプロセッサー

まずは用語の定義を確認する。CSSプリプロセッサー(またはメタ言語)とは、CSSとは異なる独自の構文で記述された文字列を入力とし、ブラウザが解釈可能なCSSコードを出力するもの。SassやLess、Stylus等がその実装に当たる。

f:id:morishitter:20141202002547p:plain

次にCSSポストプロセッサーとは、CSSを入力とし、より効果的なCSSに変換し、最適化するもの。例えば、コードを圧縮したり、自動でベンダープリフィックスを付与したり、プロパティ宣言の順番を読みやすいように並び変えたりするもので、CSSWringやAutoprefixer、CSSCombがその実装。いわゆるオプティマイザと呼ばれるものが行うことが、CSSにおけるポストプロセスだと思ってる。

f:id:morishitter:20141206205857p:plain

定義はだいたいこれで間違っていないはず。

Rework

ReworkCSSプリプロセッサーをビルドするためのツールTJ Holowaychuk製。開発者はReworkのプラグインを書くことで、独自のプリプロセスを定義することができる。MythはReworkで作られたプリプロセッサーだ。

あ!そうそう、僕もReworkを利用してYACP (Yet Another CSS Preprocessor)というものを作っている。YACPについては、CSS Architecture Advent Calendarの2日目として書いた記事があるので、よかったらこちらも読んでください。

Reworkは非常に小さいライブラリで、プラグインを呼び出してパースをする機能しか持たず、生成したASTをごにょごにょするAPIもない。そういえば、ReworkのRuby実装なんてものもあった。

PostCSS

PostCSSAndrey Sitnik氏が作っている、CSSポストプロセッサーをビルドするツール。Andrey氏はAutoprefixerの作者でもあり、AutoprefixerはPostCSSから作られている。

AutoprefixerはもともとReworkを使って作られていた。しかし、TJにAutoprefixerはCoffeeScriptだからダメだとかAPIがダサいとか言われたり、Reworkにブラウザハックをパースできないバグがあったりしたので、新しくPostCSSを作り、乗り換えた。

PostCSSとReworkは機能的によく似ている。PostCSSはReworkをよりリッチにしたもので、インデント幅やプロパティ宣言中のスペース等も考慮した完全なSource Mapに対応しており、ASTを操作するためのAPIも用意されている。

PostCSSのプラグインも徐々に充実してきている。そして、postcss-custom-propertiespostcss-nestedのようなプラグインが作られた。これらが提供するシンタックスは、ブラウザが解釈することができない独自構文だ。これらのプラグインを使ってPostCSSでビルドされたツールは、上述の定義に従うとポストプロセッサーとは呼べない。PostCSSはプリプロセッサーをビルドするツールでもあるのだ。

現に、プリプロセッサーであるMythをPostCSSで書きなおすという提案もあった。そして、cssnextというプリプロセッサー(CSS4 to CSS3のトランスパイラ)が作られた。これはMythと同機能のものだ。Reworkにできて、PostCSSにできないことはない。PostCSSはReworkの上位互換だと言える。

Reworkの開発はこの半年間停滞しているが、PostCSSは日々issueが立ち、議論され、バージョンが更新されている。ReworkのメンバーであるNicolas GallagherMaxime Thirouinも今はPostCSSにコミットしている。今後、この手のツールはPostCSSで作るのが主流となると予想できる。

PostCSSを褒めすぎたので一応欠点も述べておく。

まず、完全なSauce Mapに対応するためにPostCSSが提供しているAPIを使用しなければいけないが、これがなかなか複雑だということ。作者のAndrey氏自身扱いきれていない。MythがPostCSSで書きなおすのを拒んだのもこれが理由だ。僕もpostcss-namespaceを書いてみてこれを実感した。

あとは、個人的な問題だが「PostCSS」という名前。PostCSSでビルドするものはポストプロセッサーだけではなくなったので混乱する。

最後に

この記事では、CSSプリプロセッサーとポストプロセッサーについて定義を確認し、それぞれのビルドツールとして生まれたReworkとPostCSSについて述べた。少し(かなり?)マニアックな内容だったかもしれない。

ReworkやPostCSSのような、いわゆるプラグインアーキテクチャなメタCSSビルドツールの登場により、開発者は自分にとって必要な機能だけを持ったメタCSS言語を生成でき、必要があれば独自の機能を追加することができる。これらの影響を受けて、現在主流として使われているSassやStylusはどう変わっていく(変わっていけばいい)のか。これについても書こうと思ったけど、長くなってきたのでまた今度。

次回はyoheiMuneさんです。よろしくお願いします。

YACP: よりオブジェクト指向なCSS設計のためのプリプロセッサー

この投稿はCSS Architecture Advent Calendar 2014の2日目の記事です。

よりオブジェクト指向CSSの記述を助ける、YACPというCSSプリプロセッサーを作っています。具体的な、セレクタ命名規則やディレクトリ構成の話ではないです。

Object Oriented CSS

数あるCSSの設計手法のベースとなる、OOCSS (Object Oriented CSS、オブジェクト指向CSS)というものがある。OOCSSはその名の通り、CSSのクラス設計(ルールセットの定義)にオブジェクト指向プログラミングの考え方を少し取り入れたようなものだ。

OOCSSの原則として、「構造と見た目の分離」、「コンテナとコンテンツの分離」というものがある。OOCSSが提唱していることは要するに、HTMLの構造に依存しないセレクタを書き、レイアウトと見た目に関するルールセットは別に定義しましょう、ということだ(と思っている)。そしてSMACSSMCSS等の設計手法は、OOCSSを元にルールセットの命名規則、分け方を提案したものだ。

CSS Preprocessor

話は変わってCSSプリプロセッサーについて。モダンな開発をしている人なら、SassやLess、Stylusといったプリプロセッサーを使ってCSSを書いていると思う。これらのCSSプリプロセッサーはCSSの貧弱な構文を補うためのもので、例えば変数の定義や四則演算、制御構文等の機能を持つ。

僕は、既存のCSSプリプロセッサーの機能の多くは、CSSシンタックスシュガーを定義したものに過ぎず、本質的に設計を良くするためのものではないと思っている。しかし、他のルールセットを継承する機能(Sassでいうところの@extend)は良いものだ。これにより、 意味を持つ最小限のかたまりをルールセットとして定義することができる。

CSSのプロパティの定義は複数の宣言で1つの意味を持つ場合がある。例えば、コンテンツをページの最下部に固定させるための宣言はこのように2つのプロパティを要する。

.selector {
  /* 他のプロパティ */

  position: fixed;
  bottom: 0;
}

ここでSassの@extendを用いると、この部分を別のルールセットとして定義し、元のルールセットから継承できる。

.fixed-bottom {
  position: fixed;
  bottom: 0;
}

.selector {
  // 他のプロパティ

  @extend .fixed-bottom;
}

しかし、このSassの@extendにはいくつか問題点がある。例えば以下のようなもので、

.foo {
  color: red;
}

.bar {
  font-size: 12px;
  @extend: .foo;
}

.foo {
  color: blue;
}

コンパイル結果:

.foo, .bar {
  color: red;
}

.bar {
  font-size: 12px;
}

.foo, .bar {
  color: blue;
}

.foo.barで継承したけど、どこかで.fooがカスケードしたときにそれが.barにも適用されるので意図した結果にならないかもしれない。あと、プレースホルダセレクタ (%ではじまるやつ) 以外で継承元のルールセットを定義するのは良くない。

Yet Another CSS Preprocessor

上記のような既存のCSSプリプロセッサーの問題点を解決しつつ、本質的にCSSの設計を良くするプリプロセッサーとして、YACP (Yet Another CSS Preprocessor)というものを作った。YACPが持つ、既存のプリプロセッサーにはない特徴的な機能は以下の2つだ。

まず、カスケーディングを制御する構文について。僕はCSS (Cascading Style Sheets)のカスケーディングという機能が設計が破綻しやすい原因として一役買っていると思っている。

YACPでは、セレクタを丸括弧「()」で囲むとそのルールセットはカスケーディングできなくなる。

(.bind-rule) {
  padding: 15px;
  border: 1px solid #999;
}

/* Error */
.bind-rule {
  padding: 0;
}
/* Error */
(.bind-rule) {
  border: none;
}

これにより、ルールセットのカプセル化(のようなこと)が可能となり、意図しないカスケーディングを阻止することができる。

次に、安全なルールセットの継承について。YACPにもルールセット内で他のルールセットを継承する機能がある。しかし、YACPでは継承元となるルールセットはプレースホルダセレクタ%から始まるセレクタ)で定義しなければならない。YACPはプレースホルダセレクタで定義したルールセットのみ継承可能で、この継承元となるルールセットはカスケーディングできず、コンパイル時にCSSファイルに出力されない。

%btn {
  border-radius: 5px;
  color: #fff;
  padding: 6px 12px;
}

.btn-success {
  extend: %btn;
  background-color: #4dac26;
}
/* Error */
%btn {
  padding: 8px 16px;
}

CSSではpublicな(上書き可能な)ルールセットしか作れなかったが、YACPではprivateな(上書き不可能でYACPコード内でのみ参照可能な)ルールセットを定義することができる。

また、継承元となるルールセットが複数存在し、それらが共通のプロパティを持っている場合もエラーになる。

%foo {
  font-size: 16px;
  padding: 5px 10px;
}
%bar {
  color: #fff;
  font-size: 14px;
}
/* Error */
.baz {
  extend: %foo;
  extend: %bar;
}

他にもコンパイル時にオプションとしてstrictモードを指定することもできる。

strictモードではclassセレクタと擬似要素セレクタを単体での使用のみを許容する。IDセレクタや要素セレクタセレクタのネスト、!importantも禁止している。strictモードを使用するとルールセットの詳細度が均一になるので、スタイルの適用範囲を理解しやすく、拡張性の高いスタイルシートを記述できる。

最後に

CSSは記述があまりにも柔軟なので、コピペでどんどん書けるしバッドプラクティスのようなものがあって、良いCSSerはそれをノウハウとして持っていて最悪なコードを書くことはない。既存のプリプロセッサーはシンタックスが間違っていない限り何でもコンパイルしてしまうので、バッドプラクティスをプリプロセッサーが弾いてくれると良いと思って作った。

以上、よりオブジェクト指向CSS設計を可能にするプリプロセッサー、YACPの紹介でした。GitHubのStarしてもらえると泣いて喜びます!

あわせて読みたい

僕が過去に書いた記事ですが。YACPを作り始めたときに考えていたことや実装について書いていたりします。


次回はcancerさんです。よろしくお願いします。

CSSプリプロセッサーの`extend`の悪いところ

「共通するプロパティが上書きされる」が今回の例。

「ルールセット間の暗黙の依存関係」っていうのは、Twitter Bootstrapを例にすると、.btn-primary.btn-default.btnと同じ要素に書かないといけないっていうアレ。HTMLの記述まで載った分厚いスタイルガイドが必要なのはこのせいだと思ってる。

危険な継承の例 (Sass):

.foo {
  padding: 10px;
}

.bar {
  @extend .foo;
  font-size: 12px;
}

.foo {
  padding: 0;
}

コンパイル結果:

.foo, .bar {
  padding: 10px;
}

.bar {
  font-size: 12px;
}

.foo, .bar {
   padding: 0; 
}

.foo.barで継承したけど、どこかで.fooが上書きされたときにそれが.barにも適用されるので意図した結果にならないかもしれない、ってこと。

あと、プレースホルダセレクタ (%ではじまるやつ) 以外で継承元のルールセットを定義するのは良くない。

CSSオジサン #0に行ってきた

昨日大阪で開催された、CSSオジサンっていうCSSの勉強会に行ってきたのでその雑感。CSSオジサンってだけに若者は少なかった。女性の人が思ったよりいた印象ある。

発表は、最初がCSS設計の教科書の著者である@hilokiさん。@hilokiさんと言えばCSS設計。「メンテなブルであり続けるためのCSS設計」というタイトルの発表だった。CSSを片手間に書いている人たち、@hilokiさんのスライドは一読すべきだと思う。

次が@cssradarさんで、「CSS Investigation: CSSコードレビューの仕方教えます」という発表。コードレビューをする側の心構えや、おなじみの便利ツールの紹介、コードの不吉な匂いの見つけ方とかの話だった。

最後が@t32kさん。「CSSオジサン、この先生きのこるためには」というタイトルで、@t32kさんのキャリアパスとそれについてどう考えているのかの話。スライドは公開されてないけど、スライドのリンク集は公開されていた。


僕はCSSについてはいろいろと情報収集していて、自分なりにもいろいろと考えている方だと思っているので、発表の内容は知識としては知っていることが多かった。しかし勉強会に参加してとても良かったと思っている。と言うのも、終わったあとの懇親会に参加してスピーカーの人や参加者の人たちと話ができたからだ。

その中でも特に、@hilokiさんと@t32kさんと話せたのがよかった。@t32kさんとは、StyleStatsがどう広まっていったかとか、英語大事って話、あと僕も1週間ほどフィリピンに行ったことがあるので現地の話とかできた。

@hilokiさんとは、CSSエンジニアの生存戦略の話や、CSSプリプロセッサーやWebComponentsの話、YACPの宣伝もできてよかった。参加者の方から「@hilokiさんはすごくいい人だから付いていくといい」ってアドバイスをいただいたりもした。あと、2人から某緑の会社の中の話が聞けたのもよかった。まだまだ話し足りなかったし、@cssradarさんともお話したかった。

帰りは建物から出て途方に暮れてたら参加者の人が最寄り駅まで一緒に行ってくれた。CSS界隈いい人多いなと思ったし、CSSオジサン #1が開催されたらまた参加したいと思いました。また、僕もこういった勉強会でスピーカーとして参加できるようなエンジニアになりたいと思いました。

機能が行動を誘発する

自作しているCSSプリプロセッサー、YACPcss-whitespaceというライブラリを導入した。css-whitespaceがどのようなものかというと、例えば

form
  button
    border-radius: 5px
    padding: 5px 10px

@media print
  body
    padding: 0

  button
    border-radius: 0
    width: 100%

上記のコードをcss-whitespaceに通すと、

form button {
  border-radius: 5px;
  padding: 5px 10px;
}

@media print {
  body {
    padding: 0;
  }
  button {
    border-radius: 0;
    width: 100%;
  }
}

このようなCSSを出力する。

css-whitespaceを用いるとCSSで以下のことが可能となる。

  • プロパティ定義の末尾の;の省略
  • インデントによるブロックの表現
  • ネストによるセレクタの表現

YACPではコンパイル時に-w (--whitespace)オプションを渡すことで、これらの記法を用いたコードをコンパイルできるようにした。

$ yacp -i input.css -o output.css -w

今までYACPにcss-whitespaceを導入していなかった理由は2つある。

1つは、YACPのコンセプトが「本質的にCSSの設計を良くするためのプリプロセッサー」なので、css-whitespaceによるただCSSシンタックスシュガーを提供するだけの機能は不必要だと考えていたからだ。

そして2つ目の理由は、個人的にセレクタのネスト表記はときにバッドパーツになりうると思っているからだ。セレクタのネスト表記はSassやStylus等のメジャーなCSSプリプロセッサーにもある機能で、セレクタの親子関係を親のルールセット内にネストして記述できるもの。親要素を何回も書く必要がなくなり、とても便利でSassを書くときにいつも使っている機能だ。

しかしこのセレクタのネスト表記の機能があると、初めてSassを使ってみようとなったときに、ついついネストしたコードを書く傾向がある気がしている。CSSをわかっている人なら、セレクタのネストはルールの詳細度を高めていることだと分かっているので、必要なときにしか使わないと思う。だが、そうでない人は下記のような無駄にネストして詳細度を高めてしまったコードを書く人が多い。

.container {
  width: 960px;
  .sidebar {
    .menu {
      // 
    }
  }
}

僕自身このようなコードを書いてしまったことがあるし、人が書いているのも見てきた。僕がCSSを始めたときにはCSSプリプロセッサーというものは存在していたし、Sassについて調べると「ネストが使えてすごく便利」って推し機能のように書かれている気もしなくもない。それに最近だと若者が初めてRailsでWebアプリ作るぞってなったときに、Sassを強要されるのもある。

このようなことはCSSプリプロセッサーのネスト記法に限ったことではないと思う。Rubyを始めたら.mapとか使ってよりRubyっぽい書き方がしたくなるものだ。人は、機能があるとそれを使ってみたくなる、機能は行動を誘発するものだと思う。

そしてこれはプログラミング言語、ソフトウェアに限ったことでもない。世の中のありとあらゆる道具にあてはまると思う。モノを作るときはシンプルに、または、YACPでネスト記法をオプションで有効にできるようにしたように、ユーザーに機能を選択させるのもいい。モノ作りをする人は使う人のことを考えて道具をデザイン・設計すべきだし、僕ももっとそうなりたいと思う。

AltCSSビルドツール「Alchemia」

Alchemiaという、AltCSS(CSSメタ言語 or プリプロセッサー or ポストプロセッサー)をビルドするツールを作った。

CSS pre- vs. post-processing

似ているツールとして、ReworkPostCSSがある。Reworkはプリプロセッサーをビルドするツールで、PostCSSはポストプロセッサーをビルドするツール

CSSプリプロセッサーとポストプロセッサーの定義の境界はとても曖昧で、どちらもほぼ同じようなものなので、個人的にはAltCSSとかCSSメタ言語とか呼んでしまえばいいかなと思っている。だから、AlchemiaはAltCSSビルドツールと呼ぶことにした。

Alchemia

AlchemiaはRubyで作った。実装はReworkを参考にしていて、CSSパーサーにはcrassというものを使っている。Ruby製のCSSパーサーは他にはcss_parserというものがあって、こっちの方がGitHubのStar数が多いが、crassの方がパースが細かくて(?)PostCSSのパース結果に似ていたのでcrassを採用した。

インストールはgemから。

$ gem isntall alchemia

使い方は、

require "alchemia"

css = ".selector { padding: 10px; }"

# CSS -> AST
alchemia = Alchemia.new(css)

# Alchemiaプラグインの読み込み
alchemia.use(awesome_plugin)

# AST -> CSS
alchemia.to_s

こんな感じ。ReworkのAPIとまんま同じで、メソッドチェイン風に.use()できる。

Alchemia Plugins

開発者はAlchemiaのプラグインを書くことで、独自のシンタックスや機能を追加できる。 他の人が書いたプラグインと組み合わせることで、自分専用のAltCSSを作ることができる。

プラグインを作るときはAlchemia::Plugins::Baseを継承する。

require 'alchemia'

module YourAlchemiaPlugin < Alchemia::Plugin::Base
  def process(ast)
    # 追加したい構文とか
  end
end

YourAlchemiaPlugin.processというメソッドの中に追加したい構文や機能を記述する。


今後はcrassのASTを簡単に操作できるAPIを作っていく予定。テストも十分に書けていないし、バグもあると思う。プラグインも自分でいくつか作っておかないと誰も作ってくれなさそう。Alchemiaプラグインジェネレーターみたいな、Yeoman Generatorのようなインターフェースも作りたい。

初めてRubyでライブラリ書いて、自分で考えてクラス作って〜ってするのが難しかった。オブジェクト指向の勉強をしてもっと強くならないといけない。

オブジェクト指向入門 第2版 原則・コンセプト (IT Architect’Archive クラシックモダン・コンピューティング)

オブジェクト指向入門 第2版 原則・コンセプト (IT Architect’Archive クラシックモダン・コンピューティング)

オブジェクト指向入門 第2版 方法論・実践 (IT Architects’Archive CLASSIC MODER)

オブジェクト指向入門 第2版 方法論・実践 (IT Architects’Archive CLASSIC MODER)

あわせて読みたい