@importトリックの回顧

“昔々あるところに……。” CSS昔話Advent Calendarの4日目。

CSSの@importルールはCSS内で違うCSSファイルをインポートすることができる機能だが、ブラウザによって宣言の解釈が異なっている時代があった。

  • WinIE4は@importルールでurl()を使わないと解釈できない
  • MacIE4.5は@importルールでファイルパス指定をシングルクォーテーションで記述しないと解釈できない
/* WinIE4はurl()でないと解釈できない */
@import "/css/style.css" /* 無効 */
@import url("/css/style.css") /* 有効 */

/* MacIE4.5ではシングルクォーテーションでないと解釈できない */
@import url("/css/style.css") /* 無効 */
@import url('/css/style.css') /* 有効 */

MacIE5ではurl("/css/style.css")は正常にインポートできる。

CSS2仕様では@importルールでファイルパスを指定する記述は 2つある

@import "style.css";
@import url("style.css");

上記のように@importのあとにスペースを置き、クォーテーションでファイルパスを指定するか、url()ファンクションを使ってクォーテーションでファイルパスを指定するかである。クォーテーションは CSS2における文字列の定義にしたがって、シングルでもダブルでも良い。

@importルールでは文字列が指定されるとurl()で記述されたかのように振る舞うとされている。よって、上記2つの記述は意味上等価となる。

IE4がダブルクォーテーションだけのファイルパス指定を解釈できないのも、MacIE4.5がurl("file")を解釈できないのもCSS2仕様に反した挙動だが、ブラウザの実装として未対応なのか実装がミスっていてバグなのかはわからない。

ブラウザの解釈に差異があるところ、CSSトリックあり

この解釈の違いを利用してブラウザに読み込ませるCSSファイルを制御していた、というのが今回のエントリの主なトピックだ。

主要ブラウザがIE6の時代がきても、ニュースサイトやコーポレートサイトではシェアの低くなった前述のブラウザにも対応させる必要があった。その頃に僕が関わっていたサイトでは、@importの解釈の違いを利用してブラウザごとに違うCSSファイルを読み込ませる手法が取り入れられていた。

用意するCSSファイルは3つ。

  • すべてのブラウザに読み込まれるimport.css
  • IE4では読み込まれず、MacIE4.5とIE5以上で読み込subまれるsub.css
  • MacIE4.5では読み込まれず、IE5以上で読み込まれるmain.css

import.cssは、HTML側でlink要素で指定する。

<link rel="stylesheet" href="/css/import.css" media="screen">

sub.cssはimport.css内の@importルールで読み込む。

/* import.css */
@import "/css/sub.css"; /* url()でないのでIE4で読み込まれない */

main.cssはsub.css内で同じく@importルールで読み込む。

/* sub.css */
@import url("/css/main.css"); /* シングルクォーテーションでないのでMac IE4.5で読み込まれない */

これを適用すると各ブラウザで読み込まれるCSSファイルは

  • IE4:import.css
  • MacIE4.5:import.css + sub.css
  • IE5:import.css + sub.css + main.css

となり、link要素と2段階の@importによって擬似的にブラウザ判定のようなことをしてリソースを制御するというわけだ。

このテクニックは個人的には「CSSハック」ではなく「CSSトリック」だと思う。@importルールの記述はvalidなのでCSS2仕様に準拠したブラウザでは正常に動作し、準拠しているがゆえに未来のブラウザでも解釈が変わることはない。影響範囲がレガシーブラウザ以外に及ぶことはないので安心である。ということでこのテクニックを「@importトリック」と呼ぶことにしたい。今更感あるけど。

なぜ@importトリックが必要だったか

当時のレガシーブラウザ向け記述をファイル単位で分離するためもあったかと思うが、レガシーブラウザで スタイルを当てないことが最大の目的だったと思う。僕が関わっていたサイトでは主要ブラウザをメインターゲットにCSSが書かれていたが、IE4ではそれらのスタイルを読み込ませると表示が大きく崩れてしまうとかフリーズしてしまうとかでまともに閲覧できない状況だった。IE4だけに適用させるハックは当時は発見されておらず(おそらく今でも)、セレクタやプロパティーレベルのハックで対応することは不可能だった。そこでIE4ではCSSを当てない状態で閲覧可能にする選択肢が提案された。

当時の主要ブラウザであったIE6向けのスタイル宣言では、背景画像指定などで多数の画像ファイルを読み込んでいた。スプライト画像の利用がほとんどなかった頃なので、IE4を使っているプラットフォームでは当然それらのリソースファイルのロードも負荷がかかる。そのうえ表示が崩れるわ文字が見切れるわ固まるわで何も良いことがない。であればいっそ、CSSファイルを読み込ませずに軽量なUAのデフォルトスタイルでサイトを見てもらおう、というわけだ。

この思想は @hilokiさんがブログに書いていたUniversal IE6 CSSの提案と同じものだ。僕はUniversal IE6 CSSについては知らなかったのだけど、ブログエントリを読んでとても懐かしく感じた。@importトリックを使ってIE4向けにスタイルを当てないという選択はやはり理にかなっていたのだなと嬉しくなった。

@importトリックのミソ

CSSの@importルールは他の@ルールよりも前に、かつ@charsetルールの後に記述しなければならないと仕様で定められている。@importルールを記述するとそのインポートファイルのスタイル宣言が先に読み込まれるので、ブラウザに解釈されるスタイル宣言はインポートする順番と逆の順になる。

  1. main.cssのスタイル宣言
  2. sub.cssのスタイル宣言
  3. import.cssのスタイル宣言

main.cssよりもsub.css、sub.cssよりもimport.cssに書いたスタイル宣言がカスケーディングによって上書きされる。IE6向けのスタイル宣言がIE4向けのスタイル宣言で上書きされては本末転倒なので、 セレクタの詳細度によって制御する。これが@importハックのミソだ。

僕が関わっていたサイトではimport.cssにはごく少量のスタイル宣言は書かれていて、そのセレクタはclassやIDを使わないタイプセレクタによる記述だった。sub.cssではもう少し装飾を加えていたと思う。main.cssではclassセレクタやIDセレクタで書かれていたので、IE6ではimport.cssやsub.cssのスタイル宣言の影響を受けない仕組みだった。main.cssの読み込みまで到達できるブラウザのスタイリングを邪魔しないように詳細度の差をうまく使ったこのトリックは非常に賢いと感じる。

チームに入って最初にこの手法を見た時はその仕組みも詳細度のこともほとんど理解していなかったのだけど、今で言うreset.cssに近い内容がimport.cssにもmain.cssにも書かれているのをコーディングリーダーに質問した時、丁寧に教えてくれたことを覚えている。より多くの人に情報を届けるべく、当時できる最大限のブラウザ対応を使命のように思っている人だった。僕より先に退職したが、今もどこかでウェブ制作に関わっているといいなと思う。

@importトリックやUniversal IE6 CSSなどを使ってレガシーブラウザではあえてスタイリングしないアプローチは、今後も残っていって欲しいと思っている。新しいUniversal ** CSS現れた時、僕は「より多くの人に情報を届ける」という、ウェブサイトの命題とも言えるそれを思い出すだろう。その言葉は、ウェブの進化の波に飲まれてレガシーとモダンの狭間という果てのない砂漠でカラカラになった僕に、あたたかな雨となって降り注ぐのだ。


ポエム締め楽しい。5日目は @kojika17さんです。