Stylus を使ってみました

Hugo のテーマを作成したときに、少しだけ CSS を記述しました。 SCSS にしようか、 LESS にしようか、とも思いましたが、 Stylus を使うことにしました。

環境

  • Stylus 0.54.5

install

Get styling with Stylus

Installing Stylus is very easy once you have Node.js. So get the binaries for your platform and make sure that they also include npm, Node’s package manager.

Now, type in your terminal:

$ npm install stylus -g

Get styling with Stylus

npm でインストールしました。

$ npm install stylus --save-dev

グローバルにはインストールしたくなかったので、 --save-dev をつけてローカルにインストールしました。 実行するときには npx を使おうと思っています。

Try Stylus Online!

Try Stylus Online! を順番に見ていきたいと思います。 このページで編集しながらコンパイルもできてしまうようで便利です。 Node.js で使えるくらいだからブラウザー(JavaScript)でも動かせてしまうのは当然と言えば当然かな。

Nesting

/* Stylus */
body {
  font: 14px/1.5 Helvetica, arial, sans-serif;
  #logo {
    border-radius: 5px;
  }
}
/* CSS */
body {
  font: 14px/1.5 Helvetica, arial, sans-serif;
}
body #logo {
  border-radius: 5px;
}

body #logo がネストされています。

Nesting は便利で使いやすそうです。

Flexible syntax

/* Stylus */
body
  font 14px/1.5 Helvetica, arial, sans-serif
  button
  button.button
  input[type='button']
  input[type='submit']
    border-radius 5px
/* CSS */
body {
  font: 14px/1.5 Helvetica, arial, sans-serif;
}
body button,
body button.button,
body input[type='button'],
body input[type='submit'] {
  border-radius: 5px;
}

;:{} が省略できるようです。 SASS っぽいようです。

Flexible syntax も便利で使いやすそうです。

Parent reference

/* Stylus */
ul
  li a
    display: block
    color: blue
    padding: 5px
    html.ie &
      padding: 6px
    &:hover
      color: red
/* CSS */
ul li a {
  display: block;
  color: #00f;
  padding: 5px;
}
html.ie ul li a {
  padding: 6px;
}
ul li a:hover {
  color: #f00;
}

& で親の要素を参照できるようです。

Parent reference も便利で使いやすそうです。

Mixins

/* Stylus */
border-radius(val)
  -webkit-border-radius: val
  -moz-border-radius: val
  border-radius: val
  
button {
  border-radius(5px);
}
/* CSS */
button {
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-radius: 5px;
}

再利用可能な関数のようなものが定義しておけるようです。

border-radius という名前は、 CSS のプロパティの名前なのか、 Mixin の名前なのか、混乱しそうに感じます。 border-radius() のように、呼び出すときに () をつけると Mixin として判断してくれるのかな。

CSS に慣れていないせいか、どこまでを Mixin として定義すればよいのかが難しかったです。 中途半端に共通化した定義をすると、全体的に見通しが悪く、メンテナンスするのが大変そうに感じてしまいました。

Mixins はすぐに使いこなせそうです。

Transparent mixins

/* Stylus */
border-radius()
  -webkit-border-radius: arguments
  -moz-border-radius: arguments
  border-radius: arguments

button {
  border-radius: 5px 10px;
}
/* CSS */
button {
  -webkit-border-radius: 5px 10px;
  -moz-border-radius: 5px 10px;
  border-radius: 5px 10px;
}

透明な Mixins かな?

Mixin は呼び出すときに () は必要ではないようでした。 border-radius: 5px 10px; のように、 CSS のプロパティと同じように記述できるようでした。 :; を省略して、 border-radius 5px 10px のようにも記述できるようです。 そうなると、この記述は、 CSS のプロパティの border-radius を直接的に設定する記述なのか、 Mixin の border-radius を設定する記述なのか、やっぱり混乱しそうです。 命名規則の問題かな。 CSS の命名規則をあまり見たことないですけれども。

arguments ですべての引数を受け取れるようです。

making it easy to provide cross-browser support

ブラウザー間のサポートを簡単に提供できる、と記載されていましたので、そういう目的のために Mixin を使えばいいのかな。 -webkit--moz- の指定をするために。 逆に、そういうこと以外に積極的に Mixin を使わなくてもいいのかな。

Transparent mixins もすぐに使いこなせそうです。

Variables

/* Stylus */
#prompt
  position: absolute
  top: 150px
  left: 50%
  width: w = 200px
  margin-left: -(w / 2)
/* CSS */
#prompt {
  position: absolute;
  top: 150px;
  left: 50%;
  width: 200px;
  margin-left: -100px;
}

変数が使えるようです。 width: w = 200px のように、 CSS のプロパティを設定しながら、同時に、変数も定義することができるようです。 ここでは w という変数が定義されているようです。

may optionally be prefixed by the “$” character:

任意に "$" の文字をつけることができるようです。 なので、 $w の変数の名前にすると、変数であることがわかりやすくなりそうです。

Variables は便利で使いやすそうです。

Block property access

/* Stylus */
#prompt
  position: absolute
  top: 150px
  left: 50%
  width: 200px
  margin-left: -(@width / 2)
/* CSS */
#prompt {
  position: absolute;
  top: 150px;
  left: 50%;
  width: 200px;
  margin-left: -100px;
}

現在のブロック内のプロパティにアクセスできるようです。 @ をつけるだけのようです。

@width と記述すると、ブロック内の width プロパティにアクセスできるようです。

Block property access も便利で使いやすそうです。

Robust feature-rich language

/* Stylus */
-pos(type, args)
  i = 0
  position: unquote(type)
  {args[i]}: args[i + 1] is a 'unit' ? args[i += 1] : 0
  {args[i += 1]}: args[i + 1] is a 'unit' ? args[i += 1] : 0

absolute()
  -pos('absolute', arguments)

fixed()
  -pos('fixed', arguments)

#prompt
  absolute: top 150px left 5px
  width: 200px
  margin-left: -(@width / 2)

#logo
  fixed: top left
/* CSS */
#prompt {
  position: absolute;
  top: 150px;
  left: 5px;
  width: 200px;
  margin-left: -100px;
}
#logo {
  position: fixed;
  top: 0;
  left: 0;
}

これは少し難しかったです。

#prompt からは absolute() 経由で -pos() が呼び出されています。 #logo からは fixed() 経由で -pos() が呼び出されています。

Stylus の ‘string’ で受け取った文字列を CSS のプロパティに設定する場合は、 unquote(type) のように、 unquote(str | ident) という Built-in Function を使うようです。 '" を取り除いてくれるようです。

CSS のプロパティの名前として変数を展開する場合は、 {args[i]} のように、 Interpolation を使うようです。

配列にアクセスする場合は、 [] のように、 Subscript [] オペレーターを使うようです。

変数の型をチェックする場合は、 args[i + 1] is a 'unit' のように、 Instance Check: is a オペレーターを使うようです。 ここで言う型は、 Built-in Functions の typeof(node) で確認できるようで、いくつか確認した結果を記載しておきます。

node typeof(node)
test ‘ident’
width ‘ident’
‘absolute’ ‘string’
‘fixed’ ‘string’
150 ‘unit’
150% ‘unit’
150em ‘unit’
150pt ‘unit’
150px ‘unit’
150rem ‘unit’
-(@width / 2) ‘unit’
#fff ‘rgba’
white ‘rgba’

Stylus でも args[i + 1] is a 'unit' ? args[i += 1] : 0 のように、 Conditional Assignment: ?= := オペレーターが使えるようです。

三項演算子。プログラミング言語では、 Conditional Operator や ternary Operator と呼ぶのかな。

Robust feature-rich language は、この例だけ見てしまうと、見通しがあまりよくなく、メンテナンスしづらい印象を受けてしまいました。 すぐには使いこなせなそうです。

Iteration

/* Stylus */
table
  for row in 1 2 3 4 5
    tr:nth-child({row})
      height: 10px * row
/* CSS */
table tr:nth-child(1) {
  height: 10px;
}
table tr:nth-child(2) {
  height: 20px;
}
table tr:nth-child(3) {
  height: 30px;
}
table tr:nth-child(4) {
  height: 40px;
}
table tr:nth-child(5) {
  height: 50px;
}

繰り返しができるようです。

Range .. … オペレーターを使って、 for row in 1..5 のようにも記述できるようです。

Iteration は便利そうです。 けれども、自分にとっては、あまり使うことはなさそうです。

Interpolation

/* Stylus */
vendors = webkit moz o ms official

border-radius()
  for vendor in vendors
    if vendor == official
      border-radius: arguments
    else
      -{vendor}-border-radius: arguments
#content
  border-radius: 5px
/* CSS */
#content {
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  -o-border-radius: 5px;
  -ms-border-radius: 5px;
  border-radius: 5px;
}

Interpolation は “補間” という意味があるようです。

この例は、 -{vendor}- のことを指しているのかな。 Stylus はブラウザー間のサポートのために複雑な機能を持っているように感じてきました。

-webkit- とかって、 2018 年の現在も記述する必要があるのかな。 もうないんじゃないかという印象も受けているのですけれども。 あるいは、こういう記述をするほど複雑なものって必要なのかなとか思っています。

自分にとっては、あまり使うことはなさそうです。

Operators

/* Stylus */
body
  foo: 5px + 10
  foo: 2 ** 8
  foo: 5px * 2
  foo: !!''
  foo: foo and bar and baz
  foo: foo or bar or baz
  foo: 1..5
  foo: 1...5
  foo: 'foo' is a 'string'
  foo: (1 2 3) == (1 2 3)
  foo: (1 2 3) == (1 2)
  foo: ((one 1) (two 2)) == ((one 1) (two 2)) 
  foo: ((one 1) (two 2)) == ((one 1) (two)) 
  foo: ((one 1) (two 2))[0]
  foo: 3 in (1 2 3 4)
/* CSS */
body {
  foo: 15px;
  foo: 256;
  foo: 10px;
  foo: false;
  foo: baz;
  foo: foo;
  foo: 1 2 3 4 5;
  foo: 1 2 3 4;
  foo: true;
  foo: true;
  foo: false;
  foo: true;
  foo: false;
  foo: one 1;
  foo: true;
}

オペレーターは Operators を参考にします。

Operators は便利そうです。 自分でも加減算は使うこともありそうかな。

Type coercion

/* Stylus */
body
  foo: foo + bar
  foo: 'foo ' + bar
  foo: 'foo ' + 'bar'
  foo: 'foo ' + 5px
  foo: 2s - 500ms
  foo: 5000ms == 5s
  foo: 50deg
/* CSS */
body {
  foo: foobar;
  foo: 'foo bar';
  foo: 'foo bar';
  foo: 'foo 5px';
  foo: 1.5s;
  foo: true;
  foo: 50deg;
}

型を強制することについて記載されているようです。 これを見ると、 2s500ms とかは ‘unit’ 型のようで、文字列としては扱われず、 - オペレーターで減算することができるようです。

型の扱いは注意しておいた方が良さそうです。

The sprintf operator

/* Stylus */
body
  foo: '%s / %s' % (5px 10px)
  foo: 'MS:WeirdStuff(opacity=%s)' % 1
  foo: unquote('MS:WeirdStuff(opacity=1)')
/* CSS */
body {
  foo: 5px / 10px;
  foo: MS:WeirdStuff(opacity=1);
  foo: MS:WeirdStuff(opacity=1);
}

sprintf のようなことができるようです。 sprintf は C 言語の構文だったかな。 % を使った書式に従った文字列を出力することができるようです。

あまり使うところがイメージできないです。

Color operations

/* Stylus */
body
  foo: white - 50%
  foo: black + 50%
  foo: #eee - #f00
  foo: #eee - rgba(black,.5)
  foo: #cc0000 + 30deg
/* CSS */
body {
  foo: #808080;
  foo: #808080;
  foo: #0ee;
  foo: rgba(238,238,238,0.5);
  foo: #c60;
}

Color に対して加減算できるようです。 加減算する値は、 ‘unit’ 型の 50% とかでもいいみたいですし、 ‘rgba’ 型の rgba(black,.5) とかでもいいみたいです。

deg って、初めて知ったのですが、 CSS3 のゆがみを表す単位のようでした。 それも Color に対して加減算することができるようです。

-50% とか + 50% とかは使うこともありそうかな。

Functions

/* Stylus */
sum(nums...)
  n = 0
  n += num for num in nums

body
  foo: sum(1, 2, 3)
/* CSS */
body {
  foo: 6;
}

何か値を返す関数を定義することができるようです。

mixin と同じ方法で定義することができると記載されていました。 Function は値を返すところが Mixin と違う、とも記載されていました。

これを見ると、最後に設定した変数の値が返されるのかな。 というより、最後に評価した式の値が返されるのかな。

あまり使うところがイメージできないです。

Keyword arguments

/* Stylus */
fade-out(color, amount = 50%)
  color - rgba(0,0,0,amount)

body
  foo: fade-out(#eee)
  foo: fade-out(#eee, 20%)
  foo: fade-out(#eee, amount: 50%)
  foo: fade-out(color: #eee, amount: 50%)
  foo: fade-out(amount: 50%, #eee)
  foo: fade-out(amount: 50%, color: #eee)
/* CSS */
body {
  foo: rgba(238,238,238,0.5);
  foo: rgba(238,238,238,0.8);
  foo: rgba(238,238,238,0.5);
  foo: rgba(238,238,238,0.5);
  foo: rgba(238,238,238,0.5);
  foo: rgba(238,238,238,0.5);
}

キーワード引数があるようです。

fade-out(amount: 50%, #eee) のように、引数の順番を無視できることに意味があるようです。

Mixin と合わせて使うのかな。

Built-in functions

/* Stylus */
body
  foo: red(#fc0)
  foo: green(#fc0)
  foo: blue(#fc0)
  foo: alpha(#fff)
  foo: alpha(rgba(#fff, .5))
/* CSS */
body {
  foo: 255;
  foo: 204;
  foo: 0;
  foo: 1;
  foo: 0.5;
}

組み込み関数は Built-in Functions を参考にします。

red(#fc0) とかは、指定された色の赤の成分を返す組み込み関数のようです。 これはどういうときに必要なのだろう?

Built-in functions はほかにもたくさんあったので、使うこともありそうです。

Color BIFs

/* Stylus */
body
  foo: light(#eee)
  foo: dark(#eee)
  foo: dark(#333)
  foo: darken(#eee, 50%)
  foo: lighten(black, 50%)
  foo: #eee - 50%
  foo: black + 50%
/* CSS */
body {
  foo: true;
  foo: false;
  foo: true;
  foo: #777;
  foo: #808080;
  foo: #777;
  foo: #808080;
}

Color に関する Built-in Functions のようです。 darken(#eee, 50%)lighten(black, 50%) は SASS, SCSS で使ったことがありました。

Color operations のところにも記載されていましたが、こちらにも #eee - 50% のような Color に対する加減算の記載がありました。 これは Built-in Functions でもあるようです。

Color BIFs は便利で使いやすそうです。

End

That’s it for now! To view the rest of the powerful features Stylus provides click “View Documentation” in the corner.

come-back later
  to-see: more unquote(':)')

come-back later {
  to-see: more :);
}

Try Stylus! が一通り終わりました。

Executable

コンパイルについて引用しておきます。 Stylus のファイルの拡張子は .styl のようです。

STDIO

stylus reads from stdin and outputs to stdout, so for example:

$ stylus --compress < some.styl > some.css

STDIO Compilation Example

標準入出力のリダイレクトを使ってコンパイルできるようです。 < で Stylus のファイルを読み込んで、 > で CSS のファイルを出力しているようです。

npx で実行するなら、 Stylus をインストールしたディレクトリーに移動して、次のようになるかな。

$ npx stylus --compress < some.styl > some.css

Files

stylus also accepts files and directories. For example, a directory named css will compile and output .css files in the same directory.

$ stylus css

The following will output to ./public/stylesheets:

$ stylus css --out public/stylesheets

Compiling Files Example

オプションなしの引数に Styles のファイル(ディレクトリー)を指定して、 --out オプションの引数に CSS のファイル(ディレクトリー)を指定するようです。

npx で実行するなら、 Stylus をインストールしたディレクトリーに移動して、次のようになるかな。

$ npx stylus css --out public/stylesheets

終わり

Try Stylus! を見ながらこの記事を書いてみましたが、書きながら調べたりもしまして、ただ眺めるだけの Try Stylus! よりは少しだけ理解が深まったと思います。

自分で作った Hugo のテーマでは、シンプルにしたかったので、ここに記載した機能のほとんど使いませんでした。

参考