Tokyo Otaku ModeがStylusを使う理由

Здравствуйте! Tokyo Otaku Modeでフロント周りを担当している今吉です。
社内でロシア人という設定を付けられていますが、ロシア語は挨拶くらいしかできません。

Webサイトを作るのに避けて通れないのがCSSだと思います。エンジニアも多かれ少なかれCSSを書く事があるかと思います。
しかし、CSSを書くのはとても面倒くさいです。出来る限り楽をしたいと常々思っています。そこで、CSSプリプロセッサを導入してCSSを作成している方も多いでしょう。preprocessorCSSプリプロセッサとは、乱暴に言ってしまえばCSSをクールにラクに書く事ができる言語です。(設計が素晴らしく、COOLなCSSというものも、もちろんあるとは思いますが、これは一旦置いておきます。) 恐らく最も知られているものは、LESSとSass/Scssでしょう。

しかし、Tokyo Otaku Modeでは、StylusというCSSプリプロセッサを使用しています。Stylusは今はまだ有名でないかもしれません。
LESSもSass/ScssもStylusプロパティをネストして記述できたり、値に変数が使えたり、何度も同じ記述をしなくて済むようなMixinが使えたりと、CSSプリプロセッサは基本的にどれも機能としては似たり寄ったりです。
まず、LESS、Sass/Scssとの違いを説明した上で、Tokyo Otaku Modeで、第三のCSSプリプロセッサとも言えるStylusを使っている理由を書いていきたいと思います。


LESS、Sass/Scss、Stylusの違い

###LESS
less

LESSはTwitter Bootstrapで使用されていることでも有名なCSSプリプロセッサです。

JavaScriptを読み込むだけでコンパイルされたCSSを確認することができるので
コマンドのインストールや、コマンドを実行させる事無くクライアントサイトだけで確認をすることができるので導入はとても簡単です。
ただし、JavaScriptを実行させてCSSとして読み込ませるのはコストがかかるので実際の本番環境ではコマンド、または配布されているアプリでコンパイルしたCSSを使うのが良いでしょう。

SassのCompassやStylusのnibの様なライブラリはありませんが、
Twitter BootstrapLESShatなどのプロジェクトででよく使うようなスタイルを作成できるMixinのライブラリが配布されているので、これらを利用すればほぼ同等のことができると思います。

LESSはExtendが実装されていないので、その点がSass / Scss、Stylusとの大きな差となっています。

###Sass / Scss
sass

SassはRubyで書かれたCSSプリプロセッサです。
SassではRubyの様にCSSを記述していましたが、Sass 3.0からCSSの記述の仕方に近いSCSS(Sassy CSS)記法でも記述できるようになったので、Ruby的な記述に慣れていない人でも使いやすくなりました。Rubyの様に記述する時は拡張子を.sass、CSSと同じ様に記述する時は拡張子を.scssとします。

コンパイルにはRubyが必要なので、まずRubyをインストールする必要があり、コマンドからScssをインストールしなければなりません。さらにCompassというフレームワークを使用する場合はコンパイルの設定ファイルを作成する必要もあります。
導入は少し手間ですが、日本国内での使用例が多く、使っていてどこかでつまづいたとしても検索すれば大抵の事は解決できるかと思います。

Compassを使えばベンダープレフィックスやCSS Spriteを簡単に作成する事ができます。非常に多機能なので興味があれば公式のドキュメントなどを見てみてください。

(恐らくScssで使われている方が多いと思いますので以下の説明はScssで記述しています)

###Stylus
stylus

StylusはNode製のCSSプリプロセッサです。
Node.jsのExpressやJadeと同じ人が開発しています。以前はSass(SCSS対応前のバージョン)をNode.jsに移植したSass.jsを開発していましたが、現在はSass.jsの開発を止めStylusの開発を行っているようです。

Stylusは、LESSの様なシンプルな書き方とSassの機能を取り込んだような、いわばSassとLESSのいいとこ取りをしたようなCSSプリプロセッサです。SassのCompassのようにnibを利用することでベンダープレフィックスなどの記述が楽になります。

Node製なので導入するにはNode.jsをインストールする必要があります。
そして、日本での使用している事例がまだ少く、なにかハマってしまった際に解決方法が見つけにくいのが欠点かもしれません。
しかしながら、記述方法などLESSやSass / Scssを触ったことがあれば特に難しくは無いかと思います。


###基本的な構文

####LESS、 Scss

1
2
3
4
5
6
7
8
9
10
11
12
13
/* LESS Scss */
section {
position: relative;
ul {
margin: 1em;
li {
margin-top: .75em;
&:first-child {
margin-top: 0;
}
}
}
}

プロパティのネストはほぼCSSのままです。
両者とも文末の;を書き忘れるとコンパイルエラーになってしまうので注意が必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
/* コンパイルされたCSS */
section {
position: relative;
}
section ul {
margin: 1em;
}
section ul li {
margin-top: .75em;
}
section ul li:first-child {
margin-top: 0;
}

####Stylus

1
2
3
4
5
6
7
8
9
/* Stylus */
section
position: relative
ul
margin: 1em
li
margin-top: .75em
&:first-child
margin-top: 0

StylusではCSSの{}を省略しインデントで記述します。また文末の;だけでなく、プロパティと値の間の:も省略可能です。;:はあってもなくても問題なくコンパイルする事ができます。(プロジェクトで記述方法のレギュレーションを作った方が良いでしょう)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* コンパイルされたCSS */
section {
position: relative;
}
section ul {
margin: 1em;
}
section ul li {
margin-top: 0.75em;
}
section ul li:first-child {
margin-top: 0;
}
nav {
position: relative;
}

インデントで記述していくので、インデントがズレている時はプロパティがおかしくなってしまったり、スタイルが無い空のセレクタを作ってしまうとエラーになってしまったりする点には注意が必要です。

Sassも{}を省略しインデントで記述できますが、:を省略したり、文末に;をつけるとエラーに成ってしまいます。StylusとSassとの違いはこのサイトに詳しくまとめられているので気になったら見てみてください。

また、最新のバージョンではScssの様に {} 付きで記述することも出来るようになっていました。1つのファイルの中に {} 有りのものと無しのものが混在しても問題なくコンパイルできます。{} 有りのものはインデントがズレていても正しくCSSにコンパイルされます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* Stylus */
section
position: relative
ul
margin: 1em
li
margin-top: .75em
&:first-child
margin-top: 0
nav {
position: relative
a {
color: #27b3d1
&:hover {
color: #78b3c4
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* コンパイルされたCSS */
section {
position: relative;
}
section ul {
margin: 1em;
}
section ul li {
margin-top: 0.75em;
}
section ul li:first-child {
margin-top: 0;
}
nav {
position: relative;
}
nav a {
color: #27b3d1;
}
nav a:hover {
color: #78b3c4;
}

変数

LESS

先頭に@をつけて定義します

1
2
3
4
5
6
7
8
/* LESS */
@fontColor: #3b3e42;
@mainWidth: 1024px;
body {
color: @fontColor;
width: @mainWidth;
}
Scss

先頭に$をつけて定義します

1
2
3
4
5
6
7
8
/* Scss */
$fontColor: #3b3e42;
$mainWidth: 1024px;
body {
color: $fontColor;
width: $mainWidth;
}
Stylus

変数の定義時に何か付ける必要はありません。(@は別の機能で使われているので使用できません)
LESS、Scssと違い 変数名=値 という形で書きます。

1
2
3
4
5
6
7
/* Stylus */
fontColor = #3b3e42
$mainWidth = 1024px
body
color: fontColor
width: $mainWidth

Mixin

LESS

.Mixin名で定義します。
同名のクラス名が存在しても問題も問題なくコンパイルできますが、同名のクラスが引数を取らないMixinとして実行されて同じスタイル内に入ってしまうので注意が必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* LESS */
.messageLabel( @bgColor:#da0000 ) {
color: #FFF;
background-color: @bgColor;
padding: 3px 5px;
}
// Mixin名と被っているClass
.messageLabel {
color: #F00;
}
.label {
&.error {
.messageLabel();
}
&.success {
.messageLabel(#25af4a);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* コンパイルされたCSS */
.messageLabel {
color: #F00;
}
.label.error {
color: #FFF;
background-color: #da0000;
padding: 3px 5px;
// Mixin名と被っていたClass名が引数を取らないMixinとして実行されて一緒になってしまう
color: #F00;
}
.label.success {
color: #FFF;
background-color: #25af4a;
padding: 3px 5px;
}
Scss

@mixin Mixin名 で定義、Mixinを使う時は@include Mixin名のようにします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Scss */
@mixin messageLabel( $bgColor:#da0000 ) {
color: #FFF;
background-color: $bgColor;
padding: 3px 5px;
}
.label {
&.error {
@include messageLabel();
}
&.success {
@include messageLabel(#25af4a);
}
}
1
2
3
4
5
6
7
8
9
10
11
/* コンパイルされたCSS */
.label.error {
color: #FFF;
background-color: #da0000;
padding: 3px 5px;
}
.label.success {
color: #FFF;
background-color: #25af4a;
padding: 3px 5px;
}

ぱっと見でMixinを使っているのが解りますが、都度@includeを書かなければならないので少し面倒です。(@includeを書き忘れるとコンパイルエラーになってしまいます)

Stylus

Mixinを定義するのに特に先頭に何かを付ける必要がありません。

1
2
3
4
5
6
7
8
9
10
11
/* STylus */
messageLabel( bgColor=#da0000 )
color: #FFF
background-color: bgColor
padding: 3px 5px
.label
&.error
messageLabel()
&.success
messageLabel(#25af4a)
1
2
3
4
5
6
7
8
9
10
11
/* コンパイルされたCSS */
.label.error {
color: #fff;
background-color: #da0000;
padding: 3px 5px;
}
.label.success {
color: #fff;
background-color: #25af4a;
padding: 3px 5px;
}

Scssの様に都度@includeを書く必要もなく、LESSの様にMixin名がCSSのクラス名と紛らわしくなることもなく、非常にシンプルなように感じられます。


継承 (extend)

LESS

LESSには extend という機能がありません[^1]が、
引数なしの Mixinを作成することで継承に近いことができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* LESS */
// 引数なしのMixin
.block {
display: block;
margin: 1em .5em;
padding: 5px;
}
p {
.block;
border-top: 1px dashed #EEE;
}
ul, ol, dl {
.block;
list-style: none;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* コンパイルされたCSS */
.block {
display: block;
margin: 1em .5em;
padding: 5px;
}
p {
display: block;
margin: 1em .5em;
padding: 5px;
border-top: 1px dashed #EEE;
}
ul, ol, dl {
display: block;
margin: 1em .5em;
padding: 5px;
list-style: none;
}

この方法だとどうしてもスタイルが重複して出力されてしまいますので、Scss、Stylusのextend機能には少し劣るかなと思います。

Scss

@extend 継承元のクラス名 で記述します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Scss */
.block {
display: block;
margin: 1em .5em;
padding: 5px;
}
p {
@extend .block;
border-top: 1px dashed #EEE;
}
ul, ol, dl {
@extend .block;
list-style: none;
}
1
2
3
4
5
6
7
8
9
10
11
12
/* コンパイルされたCSS */
.block, p, ul, ol, dl {
display: block;
margin: 1em .5em;
padding: 5px;
}
p {
border-top: 1px dashed #EEE;
}
ul, ol, dl {
list-style: none;
}

extendを使うとextendしたものは1つのスタイル指定になるので、同じスタイルの記述を何回もしたり、同じスタイルの指定でCSSファイルが肥大化するのを防ぐことができます。

Stylus

Scssと同様で @extend 継承元のクラス名 で記述します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* Stylus */
.block
display: block
margin: 1em .5em
padding: 5px
// stylusはネストされた要素の継承も可能
.block2
display: block
margin: 0
padding: 5px
section
margin-top: 1em
&:first-child
margin-top: 0
p
@extend .block
border-top: 1px dashed #EEE
ul, ol, dl
@extend .block
list-style: none
.section
@extend .block2 section
color: #ddd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* コンパイルされたCSS */
.block,
p, ul, ol, dl {
display: block;
margin: 1em 0.5em;
padding: 5px;
}
.block2 {
display: block;
margin: 0;
padding: 5px;
}
.block2 section,
.section {
margin-top: 1em;
}
.block2 section:first-child,
.section:first-child {
margin-top: 0;
}
p {
border-top: 1px dashed #eee;
}
ul, ol, dl {
list-style: none;
}
.section {
color:

Scssではできませんでしたが、Stylusではネストされたセレクタでも継承をさせることができます。


ファイルのインポート

ファイルのincludeは、LESS、Scss、Stylus共に同じです。
.cssのCSSファイルの場合はimport文が出力されます。
LESS、Scss、Stylusそれぞれのファイルをインポートすると、内容が展開され1つのCSSファイルになります。

1
2
3
4
5
6
/* file.{fileType} */
body {
color: #333;
background: #FFF;
}
1
2
3
/* LESS Scss Stylus */
@import "reset.css";
@import "file.{fileType}";
1
2
3
4
5
6
/* コンパイルされたCSS */
@import "reset.css";
body {
color: #333;
background: #FFF;
}

ライブラリのCSSなども、それぞれの拡張子に変更してしまえばinportで1つのCSSにまとめてしまうことができます。最新版のStylusでは{}有りのファイルでも問題なく展開してインポートすることができました。
LESS、Scssは通常のCSSのインポート文はどこに書かれていても、常に出力されたCSSファイルの先頭に出力されますが、Stylusはインポート文を書いた所にCSSのインポート文が出力されてしまうので注意が必要です。


計算

LESS、Scss、Stylus共に値の数字を計算させることが出来ます。

1
2
3
4
5
6
/* LESS Scss Stylus */
#main {
width: 1024- (30 * 2);
margin: 0 (30px / 2);
padding: 0 (30px - 15);
}
1
2
3
4
5
6
/* コンパイルされたCSS */
#main {
width: 964px;
margin: 0 15px;
padding: 0 15px;
}

Stylusでは@プロパティ名でセレクタ内の値を使うこともできます。
ただ、なぜか割り算だけ正しく計算されませんでした。(バグだと思われますのでその内治っているかもしれません。)

1
2
3
4
5
6
7
8
/* Stylus */
#main
width: 1024- (30 * 2);
margin-top: (1024 - @width)
margin-left: @margin-top * 2
// 何故か割り算は出来ない
margin-right: @margin-top / 2
padding: 0 (30px - 15)

1
2
3
4
5
6
7
8
9
/* コンパイルされたCSS */
#main {
width: 964px;
margin-top: 60px;
margin-left: 120px;
// 何故か割り算は出来ない
margin-right: 120px/2;
padding: 0 15px;
}

なぜStylusなのか

ざっくりLESS, Sass/Scss, Stylusの書き方と出力方法を書いてみました。
Stylusは、変数の定義もJavaScriptに慣れている人には馴染みのある=だったり、RubyやCoffeeScriptなどの様なタイプ数が少なくて済む記述方法ができたりと、エンジニアが書きやすいスタイルシートだと言えると思います。
Tokyo Otaku Modeはスタートアップの中で、役割を厳格に決めてコミットするのではなく、エンジニアもフレキシブにデザインもしたりしてきたことや、サイトが全体がNode.jsでできており、テンプレートエンジンにJadeを採用している事もあり親和性の高いStylusが選択されています。

絶対的にStylusが優れている訳ではなく、LESS, Sass/Scss, Stylusそれぞれにメリット / デメリットがあるので、そのプロジェクトにおいて何を取捨選択するかによって、使用するCSSプリプロセッサを選ぶのが良いと思います。(
もちろんCSSプリプロセッサを使わないという選択肢もあります。)


最終的に出来上がるのはCSSという事を忘れずに

Stylusに限らず最終的にCSSメタ言語を使って出来上がるのはCSSです。
そのことを頭の片隅にでも置いておかなければ簡単にセレクタの入れ子ができるので、つい長いセレクタで指定してしまったり、同じようなスタイルの記述が量産されたりと、最終的にできあがるCSSファイルが巨大なものになってしまいがちです。入れ子の数を気にしたり、同じようなスタイルはクラスにするなど、出来上がるCSSを気にしてHTMLから設計する必要があります。

LESSにextendの機能が無いのはシンプルにすることそして、あくまでCSSになるという事を前提にしており、CSSを綺麗に書くのと同じように同じスタイルになるものは最初からまとめて書けば良いという設計思想のようにも感じられます。

しかしながら、不特定多数の人がHTML、CSSの変更を入れていくようなプロジェクトではパフォーマンスを重視しすぎるあまり、複雑なMixinやextendでファイルの構造を把握するだけで大変なモノになってしまったり、特定のページだけで使用されているスタイルに、ありがちなクラス名だけで指定してしまい、他のページで他の人が同じクラス名を使っていた際に意図せず他のページを崩してしまうなどということになれば、逆に開発のパフォーマンスが落ちてしまいます。

スタートアップの様に多くの人が関わり常にコードが動き続けていてるプロジェクトでは、設定や共通パーツのスタイルと各ページ用のスタイルを分けておいたり、例えばページごとにユニークなclass名をbodyタグに与えておき、各ページだけのスタイルはそのclassの中でのみ適応されるようにするなど、前もって誰が触っても問題が起きにくいファイル構成やCSSのレギュレーションを設計しておく事や、今触っている部分にまた変更が加わる可能性の当然あるので都度全てを最適化しようとしないことなども重要になってくるかと思います。

最後に

結局ツールはツールであり、チームで使う以上、便利なツールを導入すれば自動的に楽になるとは限りませんので、”ツールの導入”だけでの慢心は「ダメ!絶対!」です。

こんな風に偉そうに書いていますが、Tokyo Otaku ModeのCSSは、必ずしもキレイとは言えません。Tokyo Otaku Modeでは、革命的な設計でコード的にも美しいサイトをコーディングしてくれるデザイナーを募集しています。こちらからご応募ください!それでは До свидания!


追記

[^1]: 別途LESSのextend機能が開発されていた事は感知していましたが、既にLESSの本ブランチに取り込まれておりextend機能が使えるようになっていました。
Add Sass like extend

####Lessでのextendの記述方法
&:extend(継承元のクラス名); で記述します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* LESS */
.block {
display: block;
margin: 1em .5em;
padding: 5px;
}
p {
&:extend(.block);
border-top: 1px dashed #EEE;
}
ul, ol, dl {
&:extend(.block);
list-style: none;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* コンパイルされたCSS */
.block,
p,
ul, ol, dl {
display: block;
margin: 1em .5em;
padding: 5px;
}
p {
border-top: 1px dashed #EEE;
}
ul, ol, dl {
list-style: none;
}

LESSでもextendが出来るようになったので、ますますプロジェクトや自分のスタイルに合わせて選択すれば良いようになってきているように思いました。