バンドルと縮小
作成者: Rick Anderson
バンドルと縮小は、ASP.NET 4.5 で要求の読み込み時間を短縮するために使用できる 2 つの手法です。 バンドルと縮小により、サーバーへの要求の数を減らし、要求された資産 (CSS や JavaScript など) のサイズを減らすことで、読み込み時間が短縮されます。
現在の主要なブラウザーのほとんどは、各ホスト名あたりの同時接続数を 6 に制限しています。 つまり、6 つの要求が処理されている間は、ホスト上の資産に対する追加の要求はブラウザーによってキューに入れられます。 次の図では、IE F12 開発者ツールのネットワーク タブに、サンプル アプリケーションの [バージョン情報] ビューで必要な資産のタイミングが表示されています。
灰色のバーには、ブラウザーによって、6 つの接続制限を待機している要求がキューに登録された時間が示されています。 黄色のバーは、最初のバイトへの要求時間です。つまり、要求を送信してサーバーから最初の応答を受信するのにかかった時間です。 青いバーには、サーバーから応答データを受信するのにかかった時間が示されています。 資産をダブルクリックすると、詳細なタイミング情報を取得できます。 たとえば、次の図は、/Scripts/MyScripts/JavaScript6.js ファイルを読み込むタイミングの詳細を示しています。
上の図は、Start イベントを示しています。これは、ブラウザーが同時接続の数を制限しているために要求がキューに入った時間を示します。 この場合、要求は、別の要求が完了するまで 46 ミリ秒間キューに入れられました。
バンドル
バンドルは、複数のファイルを 1 つのファイルに簡単に結合またはバンドルできるようにする、ASP.NET 4.5 の新機能です。 CSS、JavaScript、その他のバンドルを作成できます。 ファイルの数が少ないほど、HTTP 要求が少なくなり、最初のページの読み込みパフォーマンスが向上します。
次の図は、前に示した [バージョン情報] ビューと同じタイミング ビューを示していますが、今回はバンドルと縮小が有効になっています。
縮小
縮小では、不要な空白やコメントの削除、変数名の 1 文字への短縮など、スクリプトや css に対してさまざまなコード最適化が実行されます。 次の JavaScript 関数を考えてみます。
AddAltToImg = function (imageTagAndImageID, imageContext) {
///<signature>
///<summary> Adds an alt tab to the image
// </summary>
//<param name="imgElement" type="String">The image selector.</param>
//<param name="ContextForImage" type="String">The image context.</param>
///</signature>
var imageElement = $(imageTagAndImageID, imageContext);
imageElement.attr('alt', imageElement.attr('id').replace(/ID/, ''));
}
縮小後、関数は次のように縮小されます。
AddAltToImg = function (n, t) { var i = $(n, t); i.attr("alt", i.attr("id").replace(/ID/, "")) }
コメントと不要な空白の削除に加えて、次のパラメーターと変数名の名前が次のように変更 (短縮) されました。
元の画像サイズ | 変更後の名前 |
---|---|
imageTagAndImageID | n |
imageContext | t |
imageElement | i |
バンドルと縮小の影響
次の表は、サンプル プログラムで、すべての資産を個別に一覧表示する場合と、バンドルと縮小 (B/M) を使用する場合の重要な違いをいくつか示しています。
B/M を使用 | B/M なし | 変更点 | |
---|---|---|---|
ファイル要求 | 9 | 34 | 256% |
送信 KB | 3.26 | 11.92 | 266% |
受信 KB | 388.51 | 530 | 36% |
読み込み時間 | 510 MS | 780 MS | 53% |
ブラウザーが要求に適用する HTTP ヘッダーはかなり詳細であるため、送信されたバイト数はバンドルで大幅に削減されました。 最大のファイル (Scripts\jquery-ui-1.8.11.min.js と Scripts\jquery-1.7.1.min.js) が既に縮小されているため、受信したバイト数の削減量はそれほど大きくありません。 注: サンプル プログラムのタイミングでは、Fiddler ツールを使用して低速ネットワークをシミュレートしました。 (Fiddler の [Rules]\(ルール\) メニューで [Performance]\(パフォーマンス\) を選択し、[Simulate Modem Speeds]\(モデム速度のシミュレート\) を選択します)。
バンドルおよび縮小された JavaScript のデバッグ
JavaScript ファイルがバンドルまたは縮小されていないため、開発環境 (Web.config ファイルの compilation 要素が debug="true"
に設定されている) で JavaScript をデバッグするのは簡単です。 JavaScript ファイルがバンドルされて縮小されているリリース ビルドをデバッグすることもできます。 IE F12 開発者ツールを使用して、次の方法で、縮小バンドルに含まれる JavaScript 関数をデバッグします。
- [スクリプト] タブを選択し、[デバッグの開始] ボタンを選択します。
- 資産のボタンを使用してデバッグする JavaScript 関数を含むバンドルを選択します。
- 縮小された JavaScript の書式を設定するには、 Configuration ボタン を選択し、 Format JavaScript を選択します。
- [Search Script]\(スクリプトの検索\) 入力ボックスで、デバッグする関数の名前を選択します。 次の図では、[Search Script]\(スクリプトの検索\) 入力ボックスに「AddAltToImg」が入力されています。
F12 開発者ツールを使用したデバッグの詳細については、F12 開発者ツールを使用した JavaScript エラーのデバッグに関する MSDN の記事を参照してください。
バンドルと縮小の制御
バンドルと縮小は、Web.config ファイルの compilation 要素でデバッグ属性の値を設定することで有効または無効になります。 次の XML では、debug
が true に設定されているため、バンドルと縮小は無効になっています。
<system.web>
<compilation debug="true" />
<!-- Lines removed for clarity. -->
</system.web>
バンドルと縮小を有効にするには、debug
の値を "false" に設定します。 Web.config 設定は、BundleTable
クラスの EnableOptimizations
プロパティでオーバーライドできます。 次のコードでは、バンドルと縮小を有効にし、Web.config ファイル内の設定をオーバーライドします。
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
// Code removed for clarity.
BundleTable.EnableOptimizations = true;
}
Note
EnableOptimizations
が true
でないかぎり、または Web.config ファイル内の compilation 要素内のデバッグ属性が false
に設定されていない限り、ファイルはバンドルまたは縮小されません。 さらに、ファイルの .min バージョンは使用されず、完全なデバッグ バージョンが選択されます。 EnableOptimizations
は、Web.config ファイルの compilation 要素のデバッグ属性をオーバーライドします
ASP.NET Web Forms と Web ページでのバンドルと縮小の使用
- Web ページについては、Web ページ サイトへの Web 最適化の追加に関するブログ エントリを参照してください。
- Web Forms については、バンドルと縮小の Web Forms への追加に関するブログ エントリを参照してください。
ASP.NET MVC でのバンドルと縮小の使用
このセクションでは、バンドルと縮小を調べるための ASP.NET MVC プロジェクトを作成します。 まず、既定値を変更せずに、MvcBM という名前の新しい ASP.NET MVC インターネット プロジェクトを作成します。
App\_Start\BundleConfig.cs ファイルを開き、バンドルの作成、登録、構成に使用する RegisterBundles
メソッドを調べます。 次のコードは、RegisterBundles
メソッドの一部を示しています。
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
// Code removed for clarity.
}
上記のコードでは、~/bundles/jquery という名前の新しい JavaScript バンドルが作成されます。このバンドルには、ワイルドカード文字列 "~/Scripts/jquery-{version}.js" と一致する Scripts フォルダー内のすべての適切な (デバッグまたは縮小は行われますが、.vsdoc ではない) ファイルが含まれています。 ASP.NET MVC 4 の場合、これはデバッグ構成を使用して、ファイル jquery-1.7.1.js がバンドルに追加されることを意味します。 リリース構成では、jquery-1.7.1.min.js が追加されます。 バンドル フレームワークは、次のようないくつかの一般的な規則に従います。
- FileX.min.js と FileX.js が存在する場合に、リリース用の ".min" ファイルを選択します。
- デバッグ用の ".min" 以外のバージョンを選択します。
- IntelliSense でのみ使用される "-vsdoc" ファイル (jquery-1.7.1-vsdoc.js など) を無視します。
上記の {version}
ワイルドカード マッチングは、Scripts フォルダーに適切なバージョンの jQuery を含む jQuery バンドルを自動的に作成するために使用されます。 この例では、ワイルドカードを使用すると、次の利点があります。
- NuGet を使用して、ビュー ページ内の上記のバンドル コードまたは jQuery 参照を変更せずに、新しい jQuery バージョンに更新できます。
- デバッグ構成の完全なバージョンとリリース ビルドの ".min" バージョンを自動的に選択します。
CDN の使用
次のコードでは、ローカル jQuery バンドルを CDN jQuery バンドルに置き換えます。
public static void RegisterBundles(BundleCollection bundles)
{
//bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
// "~/Scripts/jquery-{version}.js"));
bundles.UseCdn = true; //enable CDN support
//add link to jquery on the CDN
var jqueryCdnPath = "https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js";
bundles.Add(new ScriptBundle("~/bundles/jquery",
jqueryCdnPath).Include(
"~/Scripts/jquery-{version}.js"));
// Code removed for clarity.
}
上記のコードでは、リリース モードの間に jQuery が CDN から要求され、jQuery のデバッグ バージョンがデバッグ モードでローカルにフェッチされます。 CDN を使用する場合は、CDN 要求が失敗した場合に備えてフォールバック メカニズムが必要です。 レイアウト ファイルの末尾にある次のマークアップ フラグメントは、CDN が失敗した場合に jQuery を要求するために追加されたスクリプトを示しています。
</footer>
@Scripts.Render("~/bundles/jquery")
<script type="text/javascript">
if (typeof jQuery == 'undefined') {
var e = document.createElement('script');
e.src = '@Url.Content("~/Scripts/jquery-1.7.1.js")';
e.type = 'text/javascript';
document.getElementsByTagName("head")[0].appendChild(e);
}
</script>
@RenderSection("scripts", required: false)
</body>
</html>
バンドルの作成
Bundle クラスの Include
メソッドは文字列の配列を受け取ります。ここで、各文字列はリソースへの仮想パスです。 App\_Start\BundleConfig.cs ファイルの RegisterBundles
メソッドの次のコードは、バンドルに複数のファイルを追加する方法を示しています。
bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
"~/Content/themes/base/jquery.ui.core.css",
"~/Content/themes/base/jquery.ui.resizable.css",
"~/Content/themes/base/jquery.ui.selectable.css",
"~/Content/themes/base/jquery.ui.accordion.css",
"~/Content/themes/base/jquery.ui.autocomplete.css",
"~/Content/themes/base/jquery.ui.button.css",
"~/Content/themes/base/jquery.ui.dialog.css",
"~/Content/themes/base/jquery.ui.slider.css",
"~/Content/themes/base/jquery.ui.tabs.css",
"~/Content/themes/base/jquery.ui.datepicker.css",
"~/Content/themes/base/jquery.ui.progressbar.css",
"~/Content/themes/base/jquery.ui.theme.css"));
Bundle クラスの IncludeDirectory
メソッドは、ディレクトリ (および必要に応じてすべてのサブディレクトリ) 内にある、検索パターンに一致するすべてのファイルを追加するために提供されます。 Bundle クラスの IncludeDirectory
API を次に示します。
public Bundle IncludeDirectory(
string directoryVirtualPath, // The Virtual Path for the directory.
string searchPattern) // The search pattern.
public Bundle IncludeDirectory(
string directoryVirtualPath, // The Virtual Path for the directory.
string searchPattern, // The search pattern.
bool searchSubdirectories) // true to search subdirectories.
バンドルは、Render メソッド (CSS では Styles.Render
、JavaScript では Scripts.Render
) を使用してビューで参照されます。 Views\Shared\_Layout.cshtml ファイルの次のマークアップは、既定の ASP.NET インターネット プロジェクト ビューが CSS および JavaScript バンドルを参照する方法を示しています。
<!DOCTYPE html>
<html lang="en">
<head>
@* Markup removed for clarity.*@
@Styles.Render("~/Content/themes/base/css", "~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
@* Markup removed for clarity.*@
@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
</body>
</html>
Render メソッドは文字列の配列を受け取るので、1 行のコードに複数のバンドルを追加できます。 通常は、資産を参照するために必要な HTML を作成する Render メソッドを使用します。 Url
メソッドを使用すると、資産を参照するためにマークアップを必要とせずに、資産への URL を生成できます。 新しい HTML5 async 属性を使用するとします。 次のコードは、Url
メソッドを使用して modernizr を参照する方法を示しています。
<head>
@*Markup removed for clarity*@
<meta charset="utf-8" />
<title>@ViewBag.Title - MVC 4 B/M</title>
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<meta name="viewport" content="width=device-width" />
@Styles.Render("~/Content/css")
@* @Scripts.Render("~/bundles/modernizr")*@
<script src='@Scripts.Url("~/bundles/modernizr")' async> </script>
</head>
ワイルドカード文字 "*" を使用したファイルの選択
Include
メソッドで指定された仮想パスと IncludeDirectory
メソッド内の検索パターンは、最後のパス セグメントのプレフィックスまたはサフィックスとして 1 つの "*" ワイルドカード文字を受け取ることができます。 検索文字列では大文字と小文字が区別されません。 IncludeDirectory
メソッドには、サブディレクトリを検索するオプションがあります。
次の JavaScript ファイルを含むプロジェクトについて考えてみましょう。
- Scripts\Common\AddAltToImg.js
- Scripts\Common\ToggleDiv.js
- Scripts\Common\ToggleImg.js
- Scripts\Common\Sub1\ToggleLinks.js
次の表に示すように、ワイルドカードを使用してバンドルに追加されたファイルを示します。
Call | 追加されたファイルまたは発生した例外 |
---|---|
Include("~/Scripts/Common/*.js") | AddAltToImg.js、ToggleDiv.js、ToggleImg.js |
Include("~/Scripts/Common/T*.js") | 無効なパターンの例外。 ワイルドカード文字は、プレフィックスまたはサフィックスでのみ使用できます。 |
Include("~/Scripts/Common/*og.*") | 無効なパターンの例外。 ワイルドカード文字は 1 つだけ使用できます。 |
Include("~/Scripts/Common/T*") | ToggleDiv.js、ToggleImg.js |
Include("~/Scripts/Common/*") | 無効なパターンの例外。 ワイルドカードのみのセグメントは無効です。 |
IncludeDirectory("~/Scripts/Common", "T*") | ToggleDiv.js、ToggleImg.js |
IncludeDirectory("~/Scripts/Common", "T*", true) | ToggleDiv.js、ToggleImg.js、ToggleLinks.js |
通常、バンドルに各ファイルを明示的に追加することは、次の理由から、ファイルのワイルドカード読み込みよりも優先されます。
ワイルドカードによるスクリプトの追加は、既定でアルファベット順に読み込まれますが、この処理は、通常は必要ありません。 CSS ファイルと JavaScript ファイルは、多くの場合、特定の (アルファベット以外の) 順序で追加する必要があります。 このリスクを軽減するには、カスタムの IBundleOrderer 実装を追加しますが、各ファイルを明示的に追加するとエラーが発生しやすくなります。 たとえば、今後、IBundleOrderer の実装の変更が必要になる可能性のあるフォルダーに新しい資産を追加する場合があります。
ワイルドカードの読み込みを使用してディレクトリに追加されたビュー固有のファイルを、そのバンドルを参照するすべてのビューに含めることができます。 ビュー固有のスクリプトがバンドルに追加されると、バンドルを参照する他のビューで JavaScript エラーが発生する可能性があります。
他のファイルをインポートする CSS ファイルでは、インポートされたファイルが 2 回読み込まれます。 たとえば、次のコードは、ほとんどの jQuery UI テーマ CSS ファイルを 2 回読み込んだバンドルを作成します。
bundles.Add(new StyleBundle("~/jQueryUI/themes/baseAll") .IncludeDirectory("~/Content/themes/base", "*.css"));
ワイルドカード セレクター "*.css" は、Content\themes\base\jquery.ui.all.css ファイルを含むフォルダー内の各 CSS ファイルを取り込みます。 jquery.ui.all.css ファイルは、他の CSS ファイルをインポートします。
バンドル キャッシュ
バンドルは、バンドルの作成時から 1 年後に HTTP Expires ヘッダーを設定します。 以前に表示されたページに移動すると、IE がバンドルに対して条件付き要求を行っていない、つまり、バンドルに対する IE からの HTTP GET 要求がなく、サーバーからの HTTP 304 応答がないことが Fiddler に示されます。 F5 キーを使用して各バンドルに対して条件付き要求を IE に強制できます (その結果、各バンドルに対して HTTP 304 応答が返されます)。 ^F5 を使用して完全な更新を強制できます (バンドルごとに HTTP 200 応答が返されます)。
次の図は、Fiddler 応答ウィンドウの [Caching]\(キャッシュ\) タブを示しています。
要求
http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81
は、バンドル AllMyScripts 用であり、クエリ文字列ペア v=r0sLDicvP58AIXN\_mc3QdyVvVj5euZNzdsa2N1PKvb81 が含まれています。 クエリ文字列 v には、キャッシュに使用される一意の識別子である値トークンがあります。 バンドルが変更されない限り、ASP.NET アプリケーションはこのトークンを使用して AllMyScripts バンドルを要求します。 バンドル内のファイルが変更された場合、ASP.NET 最適化フレームワークは新しいトークンを生成し、バンドルに対するブラウザー要求が最新のバンドルを取得することを保証します。
IE9 F12 開発者ツールを実行し、以前に読み込まれたページに移動すると、IE では、各バンドルに対して行われた条件付き GET 要求と、HTTP 304 を返すサーバーが誤って表示されます。 IE9 で条件付き要求が行われたかどうかの判断に問題がある理由については、「CDN と有効期限を使用して Web サイトのパフォーマンスを向上させる」のブログ エントリを参照してください。
LESS、CoffeeScript、SCSS、Sass のバンドル。
バンドルと縮小のフレームワークは、SCSS、Sass、LESS、Coffeescript などの中間言語を処理し、縮小などの変換を結果のバンドルに適用するメカニズムを提供します。 たとえば、MVC 4 プロジェクトに .less ファイルを追加するには、次のようにします。
LESS コンテンツのフォルダーを作成します。 次の例では、Content\MyLess フォルダーを使用します。
.less NuGet パッケージ dotless をプロジェクトに追加します。
IBundleTransform インターフェイスを実装するクラスを追加します。 .less 変換の場合は、次のコードをプロジェクトに追加します。
using System.Web.Optimization; public class LessTransform : IBundleTransform { public void Process(BundleContext context, BundleResponse response) { response.Content = dotless.Core.Less.Parse(response.Content); response.ContentType = "text/css"; } }
LessTransform
と CssMinify 変換を使用して LESS ファイルのバンドルを作成します。 App\_Start\BundleConfig.cs ファイルのRegisterBundles
メソッドに次のコードを追加します。var lessBundle = new Bundle("~/My/Less").IncludeDirectory("~/My", "*.less"); lessBundle.Transforms.Add(new LessTransform()); lessBundle.Transforms.Add(new CssMinify()); bundles.Add(lessBundle);
LESS バンドルを参照するすべてのビューに次のコードを追加します。
@Styles.Render("~/My/Less");
バンドルに関する考慮事項
バンドルを作成するときに従う良い慣例は、バンドル名にプレフィックスとして "bundle" を含める方法です。 これにより、ルーティングの競合を回避できます。
バンドル内の 1 つのファイルを更新すると、バンドル クエリ文字列パラメーターに対して新しいトークンが生成され、次にクライアントがバンドルを含むページを要求するときに完全なバンドルをダウンロードする必要があります。 各資産が個別に一覧表示される従来のマークアップでは、変更されたファイルのみがダウンロードされます。 頻繁に変更される資産は、バンドルの候補として適していない可能性があります。
バンドルと縮小を使用すると、主に最初のページ要求の読み込み時間が短縮されます。 Web ページが要求されると、ブラウザーは資産 (JavaScript、CSS、画像) をキャッシュするため、バンドルと縮小によって、同じページを要求する場合や、同じ資産を要求する同じサイト上のページを要求する場合のパフォーマンスが向上することはありません。 資産に期限切れヘッダーを正しく設定せず、バンドルと縮小を使用しない場合、ブラウザーの更新頻度のヒューリスティックは数日後に古い資産としてマークし、ブラウザーでは各資産の検証要求が必要になります。 この場合、最初のページ要求の後に、バンドルと縮小によりパフォーマンスが向上します。 詳細については、「CDN と有効期限を使用して Web サイトのパフォーマンスを向上させる」のブログを参照してください。
CDN を使用すると、ホスト名ごとに 6 つの同時接続のブラウザー制限を軽減できます。 CDN にはホスティング サイトとは異なるホスト名があるため、CDN からの資産要求は、ホスティング環境への 6 つの同時接続の制限にカウントされません。 CDN では、一般的なパッケージ キャッシュとエッジ キャッシュの利点も提供できます。
バンドルは、それらを必要とするページでパーティション分割する必要があります。 たとえば、インターネット アプリケーションの既定の ASP.NET MVC テンプレートでは、jQuery とは別の jQuery 検証バンドルが作成されます。 作成された既定のビューには入力がなく、値がポストされないため、検証バンドルは含まれません。
System.Web.Optimization
名前空間は、System.Web.Optimization.dll で実装されます。 縮小機能には WebGrease ライブラリ (WebGrease.dll) が活用され、ここでは Antlr3.Runtime.dll が使用されます。
Twitter で簡単な投稿やリンクを共有しています。 Twitter のハンドル: @RickAndMSFT
その他のリソース
- ビデオ: バンドルと最適化 (作成者: Howard Dierking)
- Web ページ サイトへの Web 最適化の追加。
- Web Forms へのバンドルと縮小の追加。
- Web ブラウズにおけるバンドルと縮小のパフォーマンスへの影響 Henrik F Nielsen @frystyk
- CDN と有効期限を使用して Web サイトのパフォーマンスを向上させる (作成者: Rick Anderson @RickAndMSFT)
- RTT を最小限に抑える (ラウンドトリップ時間)
共同作成者
- Hao Kung
- Howard Dierking
- Diana LaRose