CDN から CSS ファイルが読み込めなかった時のフォールバック対策方法

CDN から CSS ファイルが読み込めなかった時に、自前で用意しているミラーの CSS ファイルを読み込ませるようなフォールバック (切り戻し) 対策の方法はないか調べた。

JavaScript の場合は、グローバルに対象の変数が存在するかどうかで JS ファイルを読み直す方法がよく知られている。以下のようなやり方だ。

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="./jquery.min.js"><\/script>');</script>

1行目で CDN から正しく読み込めていたら window.jQuery が存在するはずなので、その場合は2行目は何もしない。window.jQuery が存在しない = CDN から読み込めていないということになるので、document.write を使用して自サーバの jQuery を読み込ませようとしている。

これの CSS 版はないかな、というのが今回の話。

上のサイトで紹介されていた、以下のコードが一番スッキリしてやりやすそう。

<link rel="stylesheet" onload="window.hljscss = 1;" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/docco.min.css">
<script>window.hljscss || document.write('<link rel="stylesheet" href="/highlight.docco.min.css">');</script>

link 要素の onload 属性内で、その CSS ファイルを示すグローバル変数に何か値を入れておく。そして次の行で JS の場合と同じようにグローバル変数の存在チェックを行い、なければ document.writelink 要素を生成する、というやり方だ。

onload 属性は body 要素だけでなく、imgiframelink 要素などにも適用できるようだ。

ただ、この書き方だと、2行目のスクリプトが1行目の CSS の読み込みを待ってから実行されるのかどうか、微妙な気がする。

と思って調べてみると、以下の記事では window.onload イベント内で CSS の検証をしていた。やはり window.onloadaddEventListener でイベントを追加する方が良いかもしれない。

CSSのロードは onload 以前に行われるもので、DOMContentLoaded はCSSのロードを待ちませんので、onload に仕掛ける必要があります。一方で、CSSどうしに(こっちをロードしておかないと別のがロードにも失敗する、という意味合いでの)依存関係は発生し得ないので、後から差し替えても問題なく表示できます。

CSSRule を確認してハンドリングする

CSS が読み込めているかの検証方法は、実際にその CSS ファイルで指定されそうなスタイルがページに適用されているかをチェックしたりしている他、CSSStyleSheet が返す CSSRule の数を数える、という方法もあるみたい。

<script type="text/javascript">
  $.each(document.styleSheets, function(i, sheet) {
    if(sheet.href === 'http://code.jquery.com/mobile/1.0b3/jquery.mobile-1.0b3.min.css') {
      var rules = sheet.rules ? sheet.rules : sheet.cssRules;
      if(rules.length == 0) {
        $('<link rel="stylesheet" type="text/css" href="path/to/local/jquery.mobile-1.0b3.min.css">').appendTo('head');
      }
    }
  });
</script>

jQuery を使っているが、Vanilla JS にも書き換えられるレベル。これまで知らなかったのだが、document.styleSheets で、読み込んだ順に link 要素や style 要素を抱えて持っているみたい。

ただ、どうも @import しか書いていない CSS ファイルの場合は CSSRule に計上されない?らしい (未検証)。読み込む CSS ファイルの内容によっては完璧ではないかもしれないが、かなり精緻なやり方かなと思う。

フォールバックを考慮した JavaScript ライブラリを使う

YepNope や Fallback.js といったライブラリで、こうしたフォールバックを考慮した記述ができるようだ。

以下のサイトにサンプルコードがあるので、それぞれ雰囲気を掴んでみてもらいたい。

ただ、これってこのライブラリの JavaScript の読み込みを待ってから CSS の読み込みをして…って流れになるから、表示までが少し遅くなりそう?そしてこのライブラリのフォールバックも…とかなると大変かも。

こんなところ。CDN を使う時は少し考慮してみよう…。