Cutting Edge

CSS のプログラミング: バンドルと縮小

Dino Esposito

Web 開発では古くから、要求が多すぎるとページのパフォーマンスに悪影響があると繰り返し言われてきました。Web ページで行われる HTTP 要求の数を減らす方法をご存知であれば、ぜひ適用してください。Web ページにリッチなビジュアル コンテンツを盛り込むと、CSS、スクリプト、画像など、関連リソースのダウンロード コストが著しく増加します。もちろん、ほとんどの場合、これらのリソースはブラウザーがローカルにキャッシュしますが、最初のフットプリントを維持するのは困難になります。さらに、要求を減らし小さくした方が、帯域幅を低く抑え、レイテンシを低減し、バッテリ寿命を延ばすのに役立ちます。これらは、モバイル ブラウズには重要な要素です。こうした問題の対処に広く用いられている手法が、バンドルと縮小という 2 つの操作の組み合わせです。

今回は、ASP.NET MVC 4 で利用できるソフトウェア ツール独特の観点から、CSS ファイルのバンドルと縮小について取り上げます。今回は、前回の「ASP.NET MVC 4 でモバイル向けに最適化したビューを作成する (第 2 部): WURFL を使用する」(msdn.microsoft.com/magazine/dn342866) の続きです。

バンドルと縮小の基礎

バンドルとは、多数の個別リソースを 1 つのダウンロード可能なリソースにまとめるプロセスです。たとえば、アドホックのエンドポイントへの HTTP 要求を 1 つにすることで、ローカル コンピューターにダウンロードする複数の JavaScript や CSS のファイルからのバンドルを構成します。一方、縮小はリソースに適用する変換です。具体的には、縮小は想定する機能を変えることなく、テキストベースのリソースから不要な文字をすべて取り除きます。識別子の短縮、機能のエイリアシング、およびコメント、空白文字、改行の削除を行います。つまり、一般に、多くの場合読みやすさのために追加され、場所を占め、特に機能を果たさないすべての文字を取り除きます。

バンドルと縮小は一緒に適用できますが、プロセスは独立しています。必要に応じて、バンドルの作成か、個別ファイルの縮小のどちらかのみを行います。ただし、通常運用サイトでは、CSS と JavaScript のファイルをすべてバンドルおよび縮小しない理由はありません (よく知られたコンテンツ デリバリ ネットワーク (CDN) で使用される jQuery などの共通リソースは除きます)。ただし、デバッグ時はまったく異なります。縮小またはバンドルされたリソースは、読み取りとステップ実行が非常に難しいため、デバッグ時にバンドルと縮小を有効にするのは望ましくありません。

多くのフレームワークは、いくらか異なるレベルの拡張性と別の機能セットでバンドルおよび縮小のサービスを提供します。ほとんどの場合、提供する機能はすべて同じなので、どれを選択するかは単純に好みの問題です。ASP.NET MVC 4 アプリケーションを作成する場合、バンドルと縮小には Microsoft ASP.NET Web Optimization Framework を選択するのが自然です。これは、NuGet パッケージ (bit.ly/1bS8u4B、英語) から利用できます (図 1 参照)。


図 1 Microsoft ASP.NET Web Optimization Framework のインストール

CSS バンドルの使用

私の見たところ、CSS バンドルのしくみを理解するには、完全に空の ASP.NET MVC プロジェクトから開始するのが最善の方法です。つまり、Empty Project テンプレートを使って新しいプロジェクトを作成し、使用しない参照とファイルを削除します。次に、いくつかの CSS ファイルとリンクするレイアウト ファイルを追加します。

ページを表示し、Fiddler または Internet Explorer 開発者ツールでネットワーク アクティビティを監視すると、並列に進行する 2 つのダウンロードを確認できます。これが既定の動作です。

ASP.NET MVC 4 では、新しい Styles.Render 機能を使ってよりコンパクトな方法で以前のマークアップを書き直せます。

@Styles.Render(
   "~/content/styles/site.css",
   "~/content/styles/site.more.css")

System.Web.Optimization の下に位置する Styles クラスは、最初の印象よりもずっと強力です。Styles.Render メソッドはバンドルもサポートします。つまり、このメソッドにはさまざまなオーバーロードがあり、その 1 つは CSS URL の配列を受け取ります。ただし、別のオーバーロードは以前作成したバンドルの名前を受け取ります (詳細についてはすぐ後で説明します)。この場合、単一の 要素を削除し、すべてのスタイル シートをバンドルまたは縮小して返す自動生成された URL を指すようにします。

CSS バンドルの作成

多くの場合、global.asax でプログラミングしてバンドルを作成します。構成コードの ASP.NET MVC 4 パターンに応じて、App_Start フォルダーの下に BundleConfig クラスを作成し、そこから静的初期化メソッドを公開します。

BundleConfig.RegisterBundles(BundleTable.Bundles);

CSS バンドルは、単純なスタイル シートのコレクションです。以下に、前述した 2 つの CSS ファイルを 1 つのダウンロードにまとめるために必要なコードを示します。

public class BundleConfig
 {
   public static void RegisterBundles(BundleCollection bundles)
   {
     // Register bundles first
     bundles.Add(new Bundle("~/mycss").Include(
       "~/content/styles/site.css",
       "~/content/styles/site.more.css"));
     BundleTable.EnableOptimizations = true;
   }
 }

新しい Bundle クラスを作成し、ビューまたは Web ページからバンドルを参照するのに使用する仮想パスをコンストラクターに渡します。後から Path プロパティを使って仮想パスを設定することもできます。CSS ファイルをバンドルと関連付けるには、Include メソッドを使用します。このメソッドは、仮想パスを表す文字列の配列を受け取ります。前の例のように、CSS ファイルを明示的に示すか、次のようにパターン文字列を示します。

bundles.Add(new Bundle("~/mycss")
   .Include("~/content/styles/*.css");

Bundle クラスには IncludeDirectory メソッドも含まれており、これにより特定の仮想ディレクトリへのパスを示し、パターンに一致する文字列と Boolean フラグによりサブディレクトリの検索も可能になります。

上記のコード スニペットで BundleTable クラスに設定された EnableOptimization Boolean プロパティは、バンドルを明示的に有効にする必要があることを示します。プログラムで有効にしないと、バンドルは機能しません。既に述べたように、バンドルは最適化の 1 つの形式なので、運用中のサイトに最も意味を持ちます。EnableOptimization プロパティは、運用時に必要なバンドルをセットアップする便利な方法ですが、サイトをデバッグ モードでコンパイルするまで無効にすべきです。これは以下のコードで実行することもできます。

if (!DEBUG)
 {
   BundleTable.EnableOptimizations = true;
 }

さらに高度なバンドル機能

CSS バンドルに関する限り、他に重要なものは縮小ぐらいしかありません。ただし、BundleCollection クラスは、スクリプト ファイルのバンドルにも使用できる汎用のクラスです。特に、BundleCollection クラスには、CSS ファイルではなくスクリプト ファイルをバンドルする際に最も役立ちますが、言及すべき機能がいくつかあります。

最初の機能は、順序付けです。BundleCollection クラスには、IBundleOrderer 型の Orderer というプロパティがあります。名前から明らかですが、Orderer はダウンロードのためにファイルをバンドルする実際の順序を決めるコンポーネントです。既定の順序付けツールは DefaultBundleOrderer クラスです。このクラスは、BundleCollection の FileSetOrderList プロパティで設定する順序でファイルをバンドルします。FileSetOrderList は、BundleFileSetOrdering クラスのコレクションになるよう設計されています。各 BundleFileSetOrdering クラスがファイルのパターン (jquery-* など) を定義し、このクラスを FileSetOrderList に追加する順序によってファイルの実際の順序が決まります。たとえば、既定の構成では、すべての jQuery ファイルは常に Modernizr ファイルの前にバンドルされます。

DefaultBundleOrderer クラスが CSS ファイルに及ぼす影響はもっと限定的です。Web サイトに「reset.css」または「normalize.css」というファイルがあると、これらのファイルをすべての CSS ファイルの前に自動的にバンドルし、reset.css は常に normalize.css に先行します。スタイル シートのリセットまたは正規化になじみがないかもしれませんが、こうしたスタイル シートの目的はすべての HTML 要素のスタイル属性の標準セットを提供することで、これによりページはフォント、サイズ、余白などのブラウザー固有の設定を継承しなくなります。両方の種類の CSS ファイルに推奨コンテンツがありますが、実際のコンテンツは自分で決められます。プロジェクトにこれらの名前のファイルがある場合、ASP.NET MVC 4 は何よりも早くバンドルされるように追加の作業を行います。既定の順序付けツールをオーバーライドし、定義済みのバンドル ファイル セットの順序を無視する場合、2 つの方法があります。まず、バンドルごとに機能する独自の順序付けツールを作成します。以下に示すのは、定義済みの順序を単に無視する例です。

public class PoorManOrderer : IBundleOrderer
 {
   public IEnumerable OrderFiles(
     BundleContext context, IEnumerable files)
   {
      return files;
   }
 }

これは次のように使用します。

var bundle = new Bundle("~/mycss");
 bundle.Orderer = new PoorManOrderer();

さらに、以下のコードを使ってすべての順序をリセットできます。

bundles.ResetAll();

この場合、既定の順序付けツールと前に示した初歩的な人工の順序付けツールのどちらを使用しても結果は同じです。しかし、ResetAll はスクリプトの順序もリセットするので注意が必要です。

もう 1 つのさらに高度な機能は無視一覧機能です。これは BundleCollection クラスの IgnoreList プロパティで定義し、インクルードを選択したが無視するファイルを指定するパターン マッチング文字列を定義します。ASP.NET MVC 4 で実装されたバンドルの主な利点は、フォルダーのすべての JavaScript ファイル (*.js) を 1 回の呼び出しで取得できることです。ただし、*.js は vsdoc.js ファイルなど、ダウンロードの必要のないファイルとも一致します。IgnoreList の既定の構成は、最も一般的なシナリオに対応し、カスタマイズの余地も残します。

CSS バンドルの実際

CSS バンドルは、強力な最適化機能ですが、実際にどのように機能するでしょう。次のコードについて考えてみましょう。

var bundle = new Bundle("~/mycss");
 bundle.Include("~/content/styles/*.css");           
 bundles.Add(bundle);

対応する HTTP トラフィックを図 2に示します。


図 2 複数の CSS ファイルのバンドルの 2 つ目の要求

最初の要求はホームページ用です。2 つ目の要求は特定の CSS ファイルを指しませんが、content/styles フォルダーのすべての CSS ファイルを含むバンドルを表します (図 3 参照)。


図 3 バンドルされた CSS の例

縮小の追加

Bundle クラスが行うのは、複数のリソースを 1 つにまとめ、1 回のダウンロードで取得し、キャッシュすることのみです。

ただし、図 3のコードが示すように、ダウンロードされたコンテンツは読みやすさのために空白文字や改行文字がたくさん含まれています。ただし、ブラウザーには読みやすさは必要なく、図 3 の CSS コードは次のように短くしたコード文字列とまったく同じです。

<code class="html">html,body{font-family:'segoe ui';font-size:1.5em;margin:10px}
<code class="html">  html,body{background-color:#111;color:#48d1cc}

この例で操作しているシンプルな CSS では、縮小はそれほど大きな問題ではありません。ただし、縮小された CSS は大きなスタイル シートを利用するビジネス サイトには非常に適しています。

縮小を追加するために必要なのは、Bundle クラスを StyleBundle に置き換えることだけです。StyleBundle は、非常にシンプルで、Bundle を継承し、別のコンストラクターで構成されるだけです。

public StyleBundle(string virtualPath)
   : base(virtualPath, new IBundleTransform[] { new CssMinify() })
 {
 }

Bundle クラスには、IBundleTransform オブジェクトの一覧を受け取るコンストラクターがあります。これらの変換は、コンテンツに順番に適用されます。StyleBundle クラスは、CssMinify 変換ツールを追加するだけです。CssMinify は、ASP.NET MVC 4 (およびそれ以降のバージョン) の既定の縮小ツールで、WebGrease 最適化ツールを基盤にしています (webgrease.codeplex.com、英語)。言うまでもなく、別の縮小ツールに切り替える場合に行う必要があるのは、IBundleTransform の実装であるクラスを取得し、Bundle クラスのコンストラクターを介して渡すことだけです。

不足機能がなくなる

ASP.NET MVC 4 でバンドルした Web Optimization Framework が追加する機能はちょっとしたものですが、まったくないのと比べるとずっと効果的です。単純に、リソースを縮小およびバンドルしない理由はありません。これまでは、ASP.NET プラットフォームにはネイティブ サポートが欠けているという弁明が可能でしたが、これは ASP.NET MVC 4 以降のバージョンにはもう当てはまりません。

ただし、CSS ファイルに関しては、スタイルの動的な生成というもう 1 つの側面についても検討する必要があります。ページに適用されるただの膜として設計された CSS は、CSS をプログラムで生成するために擬似プログラミング言語が導入され、より動的なリソースを提供するようになりました。次回はこれを取り上げます。

Dino Esposito は、『Architecting Mobile Solutions for the Enterprise』(Microsoft Press、2012 年)、および近日出版予定の『Programming ASP.NET MVC 5』(Microsoft Press) の著者です。JetBrains の .NET および Android プラットフォームのテクニカル エバンジェリストでもあります。世界各国で開催される業界のイベントで頻繁に講演しており、software2cents.wordpress.com(英語) や Twitter (twitter.com/despos、英語) でソフトウェアに関するビジョンを紹介しています。

この記事のレビューに協力してくれた技術スタッフの Christopher Bennage (マイクロソフト) に心より感謝いたします。
Christopher Bennage は、マイクロソフトの Patterns & Practices チームの開発者です。彼の仕事は、開発者にとって役立つプラクティスを見つけ、収集し、推奨することです。最近は、JavaScript および (簡単な) ゲーム開発に技術的な関心を持っています。彼のブログは、dev.bennage.com(英語) から参照できます。