Rails や Sinatra などのフレームワークで CSS を編集する人はたいがい Sass 言語で書いていると思う。
フレームワークが全てをやってくれるのであまり意識することは無いが,多くの場合に sassc という gem のお世話になっているだろう1。
この記事は,sassc を直接扱うことについて書く。
なお,Sass 言語の構文には,当初から存在した「Sass 構文」と,のちに追加された「SCSS 構文」の二つがあるが,本記事では Sass 構文で例を示す2。SCSS 構文でも本質的に変わらない。
使ってみる
require
Gemfile に
gem "sassc"
と書いて,スクリプトで
require "bundler"
Bundler.require
すれば使える。
また,sassc がインストールされている環境ではスクリプトで
require "sassc"
すれば使える。
この記事では後者の方法で示す。
変換する
変換元の Sass ソースを与えて SassC::Engine オブジェクトを作り,render
メソッドを呼ぶだけ。
以下のように書く。
require "sassc"
source = <<SASS
body
margin: 0
SASS
p SassC::Engine.new(source, syntax: :sass, style: :compact).render
# => "body { margin: 0; }\n"
オプション
SassC::Engine.new
に与える重要なオプションとして,以下のものがある。
:syntax
どっちの構文かをシンボルで与える。
Sass 構文なら syntax: :sass
とし,SCSS 構文なら syntax: :scss
とする。
:style
出力の形式を :nested
,:compact
,:compressed
,:expanded
から選ぶ。
最も小さくなるのが :compressed
。
CSS の最もふつうの(?)形式が :expanded
。
非 ASCII 文字の扱い
仕様を確認したわけではないが,非 ASCII 文字を含むかどうかで変換結果の形式に少し違いがあるようだ。
実のところ,記事を書こうと思った動機がコレ。
特筆すべきは compressed スタイルの場合なので,これを expanded と比較する。
まず最初に ASCII 文字だけの例を見よう。
Sass が
a
font-family: "hoge"
だとする。
expanded と compressed の出力は以下のとおり。
a {
font-family: "hoge";
}
a{font-family:"hoge"}
とくにどうということもない。
次に,Sass に非 ASCII 文字が入った以下のケース。
a
font-family: "ほげ"
これを expanded で出力するとこうなる:
@charset "UTF-8";
a {
font-family: "ほげ";
}
ふむ,@charset "UTF-8";
が付いた。なるほどこれは理にかなっている。
では compressed は?
a{font-family:"ほげ"}
おや? @charset
が無い。いいのこれで?
謎解き
実は,さきほどの実験で,非 ASCII 文字を含む場合に,compressed
スタイルで出力した CSS は,a
で始まっているように見えるが,そうではない。
先頭に BOM(Byte Order Mark:バイト順マーク)が入っているのだ。
それを確かめるため,
a
font-family: "ほげ"
を compressed
で CSS に変換した結果の最初の 5 文字のコードポイントを表示させてみよう:
require "sassc"
source = <<SASS
a
font-family: "ほげ"
SASS
css_text = SassC::Engine.new(source, syntax: :sass, style: :compressed).render
puts css_text.codepoints.take(5).map{ |c| "U+%04X" % c }.join(" ")
# => U+FEFF U+0061 U+007B U+0066 U+006F
やっぱり最初の文字は U+FEFF,つまり BOM であった。
スタイルを四つ全部試したところ,BOM が付くのは compressed
だけであった。
また,ASCII 文字だけの場合は BOM が付かなかった。
よくは調べてないが,上記の動作は sassc というライブラリーの特性とかではなく,Sass 言語の仕様であるらしい。したがって Dart Sass など,他の Sass 処理系でやっても同じ結果になると思う(確かめてない)。
さて,U+FEFF は不可視3なので,入っていることに気づきにくい。これがときに大きな問題を引き起こすのだが,それについては以下の別記事で述べた。