この記事は,Sass 言語で記述して生成させた CSS に付加される(場合がある)BOM に注意しよう,というもの。
はじめに
BOM(Byte Order Mark;バイト順マーク)はファイルの先頭に付加される(ことがある)バイト列で,そのファイルが何の文字コード(エンコーディング)で書かれているかを示すのに役立つ(ことがある)もの。
U+FEFF という文字をファイルの先頭に置くと,それがどんなバイト列になっているかによってエンコーディングが(まあまあ)判定できるわけだ。
もう少し細かい話は,最後の節「おまけ:U+FEFF と BOM」に書いておいた。
Sass 言語で記述されたスタイルシートを CSS に変換する手段はいくつもあるが,手段に関わりなく,ある条件を満たす場合に変換結果の先頭に BOM が付加されるようだ。
どんな場合に BOM が付くのか
仕様をきちんと調べたわけではないので,以下は手元の実験結果に基づいたもの。Ruby の sassc という gem を用いて実験を行った。
まず,生成される CSS が ASCII 文字しか含まない場合は BOM は付かないようだ。
この場合,エンコーディングを示す必要が無いから付かないのだろう。
また,生成される CSS のスタイル(expanded
, nested
, compact
, compressed
の四種類がある)にも依存し,BOM が付くのは compressed
1 の場合だけのようだ。
その他の形式の場合,BOM ではなく,
@charset "UTF-8";
によってエンコーディングが示される(ASCII 文字だけの場合はこれも付かない)。
落とし穴
この仕様はときとして人を陥穽に落とし入れる。
つか,ぶっちゃけ,まんまと罠にはまったからこそ,この現象を発見したんだけどね。
どういうことかというと,compressed
スタイルで出力された CSS をそのまま CSS ファイルとして書き出すんなら問題ないのだが2,得られた CSS テキストをほかの何かと結合して利用すると問題が起きる。
たとえば複数の Sass ファイルをそれぞれ CSS 化して繋げる場合。
あるいは,Sass を CSS 化して,それを HTML の <style>
/</style>
の中に埋め込む場合。
U+FEFF は,ファイルの先頭にあってこそ BOM として働くが,そうでなければ BOM ではない(「おまけ」の節参照)。
たとえば
body {
color: #333;
}
という記述の body
の直前に U+FEFF が入っていたとしよう(しかし,ファイルの先頭ではないとする)。
確かめてはいないが,ブラウザーは body
の前に U+FEFF のついた 5 文字の要素セレクターとして解釈するはずだ。
しかし,そんなセレクターにマッチする要素は存在しないので,この色指定は効かないことになる。
CSS 全体が無効になるのではなく,上記の記述だけが無かったことになる。だから,そもそも問題に気づきにくい。
そして,気づいたとしても,なぜここだけが効かないのか,生成した CSS をいくら眺めてもさっぱり分からない,ということになる。U+FEFF は目に見えないからね。
@charset にも同じ問題が
もちろん,compressed
以外の形式で付加される
@charset "UTF-8";
にも同様の問題がある。
つまり,これが付加された CSS テキストを単純にほかの何かと結合して利用するとダメ。
とはいえ,@charset
は CSS の途中に書いてはいけないし,HTML の style
要素の内容に含めてもいけない。
したがって,もしそういうことをやっちゃったら invalid として怒られる。
しかも目で見て分かりやすいから,問題の所在に気づきやすい。
それに対して,BOM のほうは問題が顕在化しにくいだけに厄介,ということだ。
おまけ:U+FEFF と BOM
U+FEFF は本来はバイト順を示すものではなく,アラビア語などで使う zero width no-break space という「幅の無い分割されないスペース」であった。これはアラビア文字とアラビア文字の間に挿入して使う。
したがって,本来の使い方ではテキストの先頭に来ることはない。
このことと,「上位バイトと下位バイトを逆にした U+FFFE という文字は Unicode に存在しない」という事実を利用して,UTF-16 というバイト順がどっちだかよく分かんない文字コード3のバイト順を示すのに使っちゃえ!という奇策が BOM の始まりであった。
だから,ファイル先頭以外では U+FEFF を BOM と解釈してはいけないし,無視することもできないんである。
そのうち,UTF-16 だけでなく,UTF-8 だって U+FEFF をファイル先頭に置いておけば UTF-8 ということが分かっていいんじゃね?ということで,「BOM 付き UTF-8」というものまで生まれた4。
こうなると,もはや「バイト順」ではない。Unicode 系エンコーディングの判別をするためのマークになっちゃってる。それでも「BOM」と呼ばれている。やれやれ。
sassc 以外の手段でも同様(追記 2021-11-20)
sass-embedded という gem を使った場合でも,Node.js の sass パッケージを使った場合でも同様に,非 ASCII 文字を含むと compressed スタイルで BOM が付くようだ。
参考:Ruby で Sass るには - Qiita