打包和壓縮使你可以在ASP.NET 4.5中用來改善請求加載時間的兩項技術。打包和壓縮通過減少向服務器發送請求的次數以及減少請求資源的大小(例如CSS和JavaScript)來改善加載時間。
當前大多數主流的瀏覽器將每個同名主機的併發連接數限制爲6。這就意味着當有6個請求正在被處理時,瀏覽器向主機發送的額外請求將會被加載到隊列。在下圖中,IE F12開發者工具中網絡選項卡顯示了向示例應用程序中請求About視圖中資源的所需時間。
灰色進度條顯示了瀏覽器在6個連接限制的情況下請求所需的排隊時間(開始:從最初創建請求到發送請求之間的時間)。黃色進度條是第一個字節所到達的請求時間,即發送請求和接收來自服務器的第一個響應的時間(請求:接收到第一個字節所需的時間。發送請求並接收服務器的第一個響應所需花費的時間)。藍色進度條顯示了接收服務器響應數據所需要的時間(響應:接收服務器的響應所花費的時間)。你可以雙擊一個資源查看詳細的時間信息。例如,下圖中顯示了加載 /Scripts/MyScripts/JavaScript6.js 文件的時間詳情。
上面的圖片顯示了開始事件,給出了由於瀏覽器限制連接數量請求所需的排隊時間。在這種情況下,請求被排隊46毫秒等待另一個請求完成。
打包(Bundling)
打包(Bundling)是ASP.NET 4.5的一個新特性,它使得將多個文件合併成一個文件變得更容易。你可以創建CSS,JavaScript和其他包。更少的文件意味着更少的HTTP請求,這樣可以改善第一頁的加載性能。
下面的圖片顯示了之前顯示的關於About視圖的相同時序圖,但是這一次啓用了打包和壓縮。
壓縮(Minification)
壓縮是對腳本或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/, "")) }
除了刪除註釋和不必要的空白之外,一下參數和變量名被重新命名(縮短)如下:
Original | Renamed |
imageTagAndImageID | n |
imageContext | t |
imageElement | i |
打包和壓縮的影響(Impact Of Bundling and Minification)
下表列出了在示例程序中單獨請求資源和使用打包和壓縮(B/M)後請求資源之間的幾個重要區別。
Using B/M | Without B/M | Change | |
File Requests | 9 | 34 | 256% |
KB Sent | 3.26 | 11.92 | 266% |
KB Received | 388.51 | 530 | 36% |
Load Time | 510 MS | 780 MS | 53% |
因爲之前瀏覽器發送請求時的HTTP頭文件的非常冗長,使用了打包(bundle)之後發送的字節明顯減少了(The bytes sent had a significant reduction with bundling as browsers are fairly verbose with the HTTP headers they apply on requests.此句感覺翻譯的不通順,原句在此,請博友幫忙譯一下,謝謝!)。由於大的文件(Scripts/jquery-ui-1.8.11.min.js和Scripts/jquery-1.7.1.min.js)已經被壓縮過了(已經是.min文件了)所以所接收的字節減少並沒有那麼大。注意:示例程序中的計時是使用了Fiddler工具來模擬慢速網絡。(從Fiddler Role 菜單中,選擇Performance然後選擇Simulate Modem Speeds。)
調試打包和壓縮的JavaScript(Debugging Bundled and Minified JavaScript)
在開發環境(Web.config文件中的compilation 元素要設置debug="true")中調試是非常容易的,因爲JavaScript文件沒有被打包和壓縮過。你也可以調試一個發佈版本,在這個版本中,你的JavaScript文件是打包和壓縮過的。使用IE F12開發者工具,你可以使用下面的方法調試一個包含在壓縮包中的JavaScript函數:
1,選擇腳本(Scrip)選項卡,然後選擇開始調試(Start debugging)按鈕。
2,使用assets按鈕選擇包含要調試的JavaScript函數的包。
3,通過選擇配置按鈕(Configuration button),然後選擇格式JavaScript(Format JavaScript),來格式化壓縮的JavaScript。
4,在搜索腳本(Search Scrip)輸入框中,搜索你想要調試的函數名。在下圖中,搜索腳本(Search Scrip)輸入框中鍵入的是AddAltToImg。
使用F12開發者工具調試的更多信息,請參考MSDN文章 使用F12開發者工具調試JavaScript錯誤。
控制打包和壓縮(Controlling Bundling and Minification)
可以通過在Web.config文件的compilation 元素中設置debug屬性的值來禁用或啓用打包和壓縮功能。在下面的XML文件中,debug被設置爲“true”來禁用打包和壓縮。
<system.web> <compilation debug="true" /> <!-- Lines removed for clarity. --> </system.web>
若要啓用打包和壓縮,則設置debug的值爲“false”。你可以用BundleTable類中的EnableOptimizations屬性來覆蓋Web.config中的設置。下面的代碼啓用了打包和壓縮並覆蓋了在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; }
注意:除非EnableOptimizations是true,或者Web.config文件中compilation 元素的debug屬性設置爲false,文件纔會被打包或壓縮。此外,.min版本的文件不會被使用,完整的debug版本將會被選種。EnableOptimizations會覆蓋Web.config文件中compilation 元素的debug屬性。
在ASP.NET Web Forms和Web Pages中使用打包和壓縮(Using Bundling and Minification with ASP.NET Web Forms and Web Pages)
- Web Pages,請看Adding Web Optimization to a Web Pages Site。
- Web Forms,請看Adding Bundling and Minification to Web Forms。
ASP.NET MVC中使用打包和壓縮(Using Bundling and Minification with ASP.NET MVC)
在本節中,我們將創建一個ASP.NET MVC項目研究打包和壓縮。首先,創建一個新的ASP.NET MVC Internet項目,命名爲MvcBM,無需更改任何缺省值。
打開App_Start\BundleConfig.cs文件查看RegisterBundles方法,它被用來創建,註冊和配置bundles。下面的代碼展示了RegisterBundles方法的一部分。
public static void RegisterBundles(BundleCollection bundles) { bundles.Add(new ScriptBundle("~/bundles/jquery").Include( "~/Scripts/jquery-{version}.js")); // Code removed for clarity. }
前面的代碼創建了一個名爲~/bundle/jquery的新JavaScript包,它包含了與通配符字符串“~/Script/jquery-{version}.js”匹配的JavaScript文件夾中的所有合適的文件(即debug或壓縮(minified)而非.vsdoc)文件。在ASP.NET MVC 4中,這意味着可以是一個調試文件,jquery-1.7.1.js文件將會被加載到bundle包中。在發佈配置中,jquery-1.7.1.min.js將會被加到Bundle中。Bundling框架遵循以下幾個常見的約定:
- 發佈版本中,當“FileX.min.js”和“FileX.js”都存在時,“.min”文件將會被選中。
- 調試版本中,不會選擇“.min”版本。
- 忽略“-vsdoc”文件(例如jquery-1.7.1-vsdoc.js),它常被用作智能提示。
上面顯示的{version}通配符用於自動創建一個Jquery包,並在JavaScript文件夾中使用適當的jquery版本。在本例中,使用通配符有以下好處:
- 允許你用NuGet更新到新的jQuery版本,而不需要改變前面的bundling代碼或你的視圖頁面中的jQuery引用。
- debug配置時自動選擇完整版本,發佈版本自動選擇“.min”版本。
用CDN(Using a CDN)
下面的代碼用一個CDN jQuery包替換本地的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請求失敗,應該有一個備用機制。在layout文件末尾下面的標記片段顯示了在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(Create a Bundle)
Bundle類的Include方法參數是一個字符串數組,每個字符串都是一個資源的虛擬路徑。下面的代碼來自於App_Start\BundleConfig.cs文件的RegisterBundles方法,展示瞭如何向一個bundle添加多個文件:
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方法在視圖中引用,(Styles.Render用於CSS,Script.Render用於JavaScript)。下面的標記是Views\Shared\_Layout.cshtml文件中的,展示了默認的ASP.NET internet項目視圖如何引用打包的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方法採用了字符串數組作爲參數,因此你可以在一行代碼上添加多個包。通常你會需要使用Render方法來創建必要的HTML來引用該資源。你也可以使用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>
使用“*”通配符選擇文件(Using the "*" Wildcard Character to Select Files)
在Include方法中指定的虛擬路徑和IncludeDirectory方法中的搜索模式都可以在最後一個路徑段中接收一個“*”通配符作爲前綴或後綴。搜索字符串不區分大小寫。IncludeDirectory方法有搜索子目錄的選項。
思考一個具有以下JavaScript文件的項目:
- Script\Common\AddAltToImg.js
- Script\Common\ToggleDiv.js
- Script\Common\ToggleImg.js
- Script\Common\Sub1\ToggleLinks.js
下表顯示了使用通配符添加到包中的文件:
Call | Files Added or Exception Raised |
Include("~/Scripts/Common/*.js") | AddAltToImg.js,ToggleDiv.js,ToggleImg.js |
Include("~/Scripts/Common/T*.js") | 無效的異常模式。通配符只允許作爲前綴或後綴。 |
Include("~/Scripts/Common/*og.*") | 無效的異常模式。只允許一個通配符。 |
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會導致導入的文件加載兩次。例如,下面的代碼創建了一個包,其中大多數JQuery UI主題的CSS文件加載了兩次。
bundles.Add(new StyleBundle("~/jQueryUI/themes/baseAll") .IncludeDirectory("~/Content/themes/base", "*.css"));
通配符選擇器“*.css”會選擇文件夾中的每個CSS文件,包括Content\themes\base\jquery.ui.all.css文件。而jquery.ui.all.css文件已經導入了其他CSS文件。
包緩存(Bundle Caching)
當包(bundle)被創建時Bundles設置了HTTP過期頭(HTTP Expires Header)爲一年。如果你導航到一個先前查看的頁面,Fiddler顯示IE沒有對bundle進行條件請求,也就是說,IE對包(bundle)沒有HTTP GET請求,也沒有HTTP 304響應來自服務器。你可以使用F5鍵(結果每個包(bundle)都有一個HTTP 304響應)來強制IE對每個bundle做出一個有條件的請求。你可以用F5來強制完全刷新(結果每個包(bundle)都有一個HTTP 200響應)。
下面的圖片顯示了Fiddler響應窗格的緩存(Caching)選項卡:
該請求:
http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81
是對AllMyScripts包的請求幷包含了一個查詢字符串對,v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81。查詢字符串v具有一個值令牌,它是用於緩存的唯一標識。只要包(bundle)不改變,ASP.NET 應用程序將會用這個令牌(v)去請求AllMyScripts包。如果該包(bundle)裏的任何文件發生改變,ASP.NET 優化(optimization)框架將會生成一個新的令牌,確保瀏覽器對該包的請求得到最新的包。
如果你運行IE9 F12開發者工具然後導航到一個先前加載的頁面,IE錯誤的顯示對每個包的條件GET請求,並服務器返回HTTP 304。你可以閱讀爲什麼IE9在確定是否有條件請求時存在問題的博客使用CDNs和過期時間來改善Web站點的性能(Using CDNs and Expires to Improve Web Site Performance)。
LESS,CoffeeScript,SCSS,Sass 打包(LESS,CoffeeScript,SCSS,Sass Bundling)
打包和壓縮框架提供了一種機制來處理諸如SCSS,Sass,LESS 或 CoffeeScript 等中間語言,並應用轉換,例如縮小到結果包(resulting bundle)。例如,添加 .less 文件到你的MVC 4項目中:
1,創建一個你的LESS內容文件夾。下面的例子用的是Content\MyLess文件夾。
2,添加 .less NuGet包dotless到你的項目。
3,添加一個類來實現IBundleTransform接口。爲變換(transform).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"; } }
4,使用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);
5,在需要引用LESS包的視圖中添加以下代碼。
@Styles.Render("~/My/Less");
包注意事項(Bundle Considerations)
在創建包時遵循的一個好習慣是將“bundle”包含在bundle名稱的前綴中。這將防止可能的路由衝突。
當你在一個包中更新一個文件時,將爲bundle查詢字符串參數生成一個新的令牌,並在下一次客戶端請求包的頁面時下載完整的包。在傳統的標記中,每個資源是被單獨列出的,只有更改後的文件纔會被下載。對經常變化的資源進行打包(bundling)可能不是好的選擇。
打包和壓縮主要是改善第一個頁面的請求加載時間。一旦請求了一個網頁,瀏覽器就會緩存這些資源(JavaScript,CSS和圖像),因此打包和壓縮在請求相同頁面或請求同一站點上的相同資源時不會提供任何性能的提升。如果在你的資源上你沒有正確的設置過期頭(expires header),並且你不使用打包和壓縮,瀏覽器的新鮮感(freshness heuristic)將會在幾天後標記資源過期,並且瀏覽器對每個資源需要驗證請求。在這種情況下,打包和壓縮在第一個頁面請求之後提供了性能提升。欲知詳情,請參考使用CDNs和過期時間來改善Web站點的性能。
通過使用CDN,可以緩解每個主機名對6個併發連接的瀏覽器限制。因爲CDN和你的主機站點有不同的主機名,來自CDN的資源請求不會對你的託管環境的6個併發連接限制進行計數。CDN還可以提供常用的包緩存和邊緣緩存優勢。
包(bundles)應該根據頁面對它們的需要進行分割。例如,默認的ASP.NET MVC模板創建了與jQuery分離的jQuery驗證包。因爲創建的默認視圖沒有輸入,也沒有post的值,所以它們不包含驗證包。
System.Web.Optimization命名空間是在System.Web.Optimization.DLL程序集中實現的。它利用WebGrease庫(WebGrease.dll)來實現壓縮(minification)功能,而這反過來又使用了Antlr3.Runtime.dll。
其他資源(Additional Resources)
- Video:Bundling and Optimizing by Howard Dierking
- Adding Web Optimization to a Web Pages Site.
- Adding Bundling and Minification to Web Forms.
- Performance Implications of Bundling and Minification on Web Browsing by Henrik F Nielsen @frystyk
- Using CDNs and Expires to Improve Web Site Performance by Rick Anderson @RickAndMSFT
- Minimiza RTT(round-trip times)
參考文獻
原文:https://docs.microsoft.com/en-us/aspnet/mvc/overview/performance/bundling-and-minification