3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Sass の吐き出す BOM に注意!

Last updated at Posted at 2020-05-31

この記事は,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 が付くのは compressed1 の場合だけのようだ。
その他の形式の場合,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

  1. compressed という形式は,人間には見づらいがファイルサイズが最も小さくなるので,配信用に向いている。

  2. CSS ファイルの先頭に BOM をつけてエンコーディングを表現するのは,CSS 標準で決められた正当なやり方の一つ。

  3. UTF-16 は 16 bit コードなので,この 16 bit を 8 bit 二つに分割して,それを並べるときに,上位 8 bit と下位 8 bit のどっちを先に置くのかについて,二つのやり方が考えられる。

  4. バイト列としては EF BB BF となる。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?