この記事は One ASP.NET Advent Calendar 2012 の19日目の記事です。
IEにはいろいろなバグがあってWebサイトを作る際には割増料金が欲しいところなわけですが、その中でもあまり知られていない(?)制限の一つとして、CSSファイルあたりのセレクタの数の制限があります。
これを解決するために Bless という node.js で実装された CSS を分割するツールがあります。これをASP.NET MVC4で使用します。
ASP.NET MVC4 には静的コンテンツを出力前に加工する Bundle という仕組みがあり、 CSS と JS については minify と結合を行う際にはこれを使うようになっています。また独自の Transformer を定義することにより sass を css に変換するといったことも可能になっています。(この仕組みを利用したライブラリとして Bundle Transformer があります。良くできてる感じがしますが使いませんでした。)
Bundleで実装できればフレームワークとの相性が良く、一度加工したら内部的にキャッシュしてくれたり、ブラウザにキャッシュを破棄させるためのダミーのパラメータをURLに付与するなどの定型処理を任せることができてとても楽です。ただし、加工の処理を.netで実装できる必要があります。今回行いたい bless は node.js で実装されており、.netのnode処理系というものは存在しないため、Bundleは採用しませんでした。
静的コンテンツを加工するもう一つの方法は、ビルド時に実行するということです。この場合、開発環境で動くexeさえあればなんとかなります。
手順を追って紹介していきます。
ASP.NET MVC4 で node を使うのはそれはそれで非常に面倒なのですが、Bundlerを使えば簡単です。nodeが同梱されていて動く状態になっていますので、まずこれをインストールします。Bundlerにはいろいろな機能があって便利そうですが今回は使用しません。
README.txt がプロジェクト直下に配置されるので、これを削除しておきます。(Webアプリをデプロイした後に見えてしまう)
npm install bless -g すると node_modules ディレクトリ配下にblessディレクトリが作成されますので、これを先ほどBundlerをインストールしたプロジェクト内のbundler/node_modulesにコピーします。
コピーしたらプロジェクトに含めておきます。
bless を呼び出すバッチ(GenerateCss.bat)をプロジェクト直下に作成します。このとき、ファイルの文字コードはSJIS、改行コードはCRLFになるようにしてください。 pushd、popdしているのは、node関係や場合によってはrubyでsassを変換する際に、パスに空白があるとだめだったりcygwinとの相互運用がうまくいかなかったりすることがあるので、その対策です。
1 @echo off
2
3 set ProjectDir=%~1
4 pushd "%ProjectDir%"
5
6 rem 必要に応じてbundlerを実行してcssの変換やminifyを行う
7
8 rem bundler.cmd はパス内に空白がある場合の考慮が足りないので直接node.exeを実行する。
9 rem bundler\node.exe bundler\bundler.js {BUNDLE_DIRECTORY}
10
11 bundler\node.exe bundler\node_modules\bless\bin\blessc {ORIGINAL_CSS_FILE_NAME} {BLESSED_CSS_FILE_NAME}
12
13 popd
これだけのスクリプトでどれだけの落とし穴があるというのでしょうか。Windowsは早く滅びればいいと思います。そして21世紀にもなってbatが生き長らえているというのはとても悲しいことです。
ビルド時にbless(とbundler)を実行するように、ビルドイベントの追加を行います。ダブルクオートとcallを忘れないようにしましょう。
1 call "$(ProjectDir)GenerateCss.bat" "$(ProjectDir)"
注意点は生成用バッチと同じです。
1 @echo off
2
3 set ProjectDir=%~1
4 pushd "%ProjectDir%"
5
6 del {BLESSED_CSS_FILES}
7
8 popd
プロジェクトを一旦アンロードし、プロジェクトの編集を行います。Projectエレメントの直下に次のxml断片を記述します。
1 <Target Name="AfterClean">
2 <Exec Command="call "$(ProjectDir)CleanCss.bat" "$(ProjectDir)"" />
3 </Target>
ところでヴィヅアルスタヂオという人はこう……なんでクリーンのフックについてはUIがないんでしょうか……??なんとなくわかる気もしますがわかりたくない……
ここでプロジェクトのビルド・クリーンを試してみてください。長いCSSがビルド時にblessされてIEでも問題ないように分割されたり、それらがクリーン時に消えてくれることがわかるはずです。
できていたらblesscにより生成されたIE対応CSSを忘れずにプロジェクトファイルに含めておきましょう。
ここで問題なのは bless によって生成されるファイルがいくつになるかわからないということです。そうそう数が増減することはないので普段は困りませんが、CSSのセレクタが増えて分割ご のファイル数が増えたときには忘れずにプロジェクトファイルに追加するようにしてください。
祝福されたファイルをScriptBundleに食わせて出力します。こうすることで、キャッシュ飛ばし用のパラメータ(正式な名前があれば教えてください)が自動的に付与されます。blesscの出力は hoge.css hoge-blessed1.css hoge-blessed2.css … というようになっていて、hoge.cssが他を @import する形になります。このとき、hoge.cssの中にはキャッシュ飛ばし用のパラメータがつきますが、親ファイル自身はアプリケーション側でこれを付与してあげる必要があります。そのためにScriptBundleを利用します。
BundleConfig.cs に次のように記述します。
1 bundles.Add(new StyleBundle("~/assets/css/hoge").Include("~/assets/css/hoge.css"));
2
3 // これがないとキャッシュ飛ばしパラメータがつかない
4 BundleTable.EnableOptimizations = true;
その上で、 Layout.cshtml にて
@Scripts.Render(“~/assets/css/hoge”)
のように参照します。これで完成です。
ASP.NET MVCのBundle側に寄せるのか、NuGetのBundlerに寄せるのか、どっちかにしたらいいんだと思いますが……どっちも面倒そうですねえ……。方向性としてはMVC標準の方が中間ファイルがないしバッチファイルも書かなくていいし管理しやすいと思います。誰かそういうNuGetパッケージを作ってください。