Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What is going on with this article?
@scivola

Sass の吐き出す BOM に注意!

この記事は,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」というものまで生まれた。

こうなると,もはや「バイト順」ではない。Unicode 系エンコーディングの判別をするためのマークになっちゃってる。それでも「BOM」と呼ばれている。やれやれ。


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

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

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

1
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
scivola
主に Ruby を使ってます。Rust に興味を持っています。校閲承ります。私信は Slack の ruby-jp からどうぞ。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
1
Help us understand the problem. What is going on with this article?