原文:http://www.asp.net/mvc/overview/performance/bundling-and-minification
===============================================================================
Bundling 和 minification ASP.NET4.5 为了提高请求响应新增的2个技术. Bundling and minification 通过减少请求的数量(Boudling的功劳)、减少请求资源的大小(Minification的功劳)提高请求响应的效率
目前大部分浏览器会存在对一个域名同时请求数的限制(有的浏览器是最多6个 有的12个). 这意味着当有6个请求正在被处理的时候,其它的请求就得排队等待浏览器处理了. 在下面的图中可以看到使用ie开发工具展示的资源加载时间线.
灰色的部分表示这个请求因为浏览器请求并发数导致的等待发送时间. 黄色的部分表示这个请求被浏览器发送至服务器到服务器开始响应之间的耗费时间. 蓝色的部分表示接受服务器响应数据耗费的时间. 我们可以双击时间线查看详细的信息. 下面的图展示了加载/Scripts/MyScripts/JavaScript6.js的时间线.
Start 事件表示因为浏览器并发请求限制导致的请求排队事件. 在这个例子中请求排队等待了46 milliseconds.
Bundling
Bundling是ASP.NET4.5的新功能 使用它可以非常轻松的把多个文件合并成一个文件. 你可以用它把多个css打包成一个css,也可以把多个js打包成一个js. 越少的文件意味越少的http请求这能提高页面的加载速度.
下面的图片是使用了bundling和minification后加载上面例子中网页的时间线.
Minification
Minification 可以通过去掉不必要的空格、注释、使用更短的变量名来减小css或js的文件大小. 来看下使用minification前的js.
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/, '')); }
使用minification后, 代码变成了下面这样:
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% |
通过bundling我们可以看到发送至服务器的bytes有了显著的减少The bytes sent had a significant reduction with bundling as browsers are fairly verbose with the HTTP headers they apply on requests. 收到来自服务器的bytes变小了,因为大文件 (Scriptsjquery-ui-1.8.11.min.js and Scriptsjquery-1.7.1.min.js) 被最小化了.
Debugging Bundled and Minified JavaScript
在调试模式下调试js很容易,因为在调试模式下js文件不会被打包合并也不会最小化.
Controlling Bundling and Minification
通过修改web.config文件的debug属性可以使用或禁用Bundling 和 minification, debug 设为true
bundling 和 minification 被禁用.
<system.web> <compilation debug="true" /> <!-- Lines removed for clarity. --> </system.web>
只要设置debug为false就能启用 bundling 和 minification.我们还可以通过设置BundleTable的EnableOptimizations属性来覆盖web.config的设置.
<system.web> <compilation debug="true" /> <!-- Lines removed for clarity. --> </system.web>
EnableOptimizations 设为
true 或者把web.config的
debug设为false,否则文件将不会被bundled or minified. 在ASP.NET MVC中使用Bundling和Minification
在这小节中我们创建一个mvc项目展示怎么使用bundling and minification. 首先创建一个mvc项目叫MvcBM.
打开 App_StartBundleConfig.cs 文件 代码如下.
public static void RegisterBundles(BundleCollection bundles) { bundles.Add(new ScriptBundle("~/bundles/jquery").Include( "~/Scripts/jquery-{version}.js")); // Code removed for clarity. }
上面的代码创建了一个js包名字叫 ~/bundles/jquery 这包含了适当的文件 (that is debug or minified but not .vsdoc) 在Scripts 文件夹中, 这个包中包含了 "~/Scripts/jquery-{version}.js". 在MVC 4中, 这意味着一个可debug版本的文件 jquery-1.7.1.js 被加到了这个包中. 在发布模式下 jquery-1.7.1.min.js 被包含了进来. bundling 框架有下面几种默认的约定:
- 选择 “.min” 文件为release版本服务 当“FileX.min.js” 存在的情况下.
- 在debug环境下选择没有 “.min” 的版本.
- 忽略“-vsdoc” 文件 (例如 jquery-1.7.1-vsdoc.js), 这些是用来为智能提示服务的.
上面的The {version}
通配符被使用来自动创建合适版本的jQuery Bundler. 在这个例子中使用通配符的好处如下:
- 在不要修改bundling代码的情况下,使用 NuGet 更新最新版本的 jQuery.
- 自动选择全版本的或者是".min" 版本的
Using a CDN
下面的代码展示如果和从CDN上bundle 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 = "http://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. }
在上面的代码中,release模式下从CDN请求jQuery,在Debug模式下会请求本地的jQuery。 当使用CDN时为了预防加载CDN文件失败,我们应该有一个反馈机制。下面的代码展示了在加载CDN的jQuery时候我们改如何引用本地的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>
Creating a Bundle
Bundle 类的 Include 方法接收一个字符串的数组, 每个字符串都是一个资源的虚拟路径. 下面的代码位于 App_StartBundleConfig.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.
在views中使用Render方法就可以使用 Bundles , ( Styles.Render
用来使用CSS包 and Scripts.Render
用来使用JS包). 下面的代码位于 ViewsShared\_Layout.cshtml 展示了如何在视图页面使用打包的CSS和JS.
<!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 方法 可以接受多个 string 类型的参数 , 所以你可以在一行代码中添加多个包. 你可以使用 Url 方法 生成 资源的URL. 下面的代码展示如何使用Url 引用 Bundle.
<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
方法的 search pattern 接受 "*" 通配符.search string 是大小写敏感的. IncludeDirectory 方法 还可以设置是否去子目录中查找匹配
.
有这个项目,他有下面的js文件:
- ScriptsCommonAddAltToImg.js
- ScriptsCommonToggleDiv.js
- ScriptsCommonToggleImg.js
- ScriptsCommonSub1ToggleLinks.js
下面的表格展示对应的方法会加载哪些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 接口来自定义顺序 .
- 有的目录下的文件是为专们的页面准备的,如果在其它页面通配符也把这个文件包含了可能会出现js错误.
- css文件会import其它的文件这可能导致import的css文件被引用了多次. 举个例子下面的代码创建了个 bundle 打包 jQuery UI主题的CSS 文件 这会导致有的主题文件被加载了2次.
bundles.Add(new StyleBundle("~/jQueryUI/themes/baseAll") .IncludeDirectory("~/Content/themes/base", "*.css"));
Bundle 缓存
Bundles 默认会从创建开始缓存一年.
http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81
请求名为 AllMyScripts 的 bundle url包含一个查询参数 v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81. 查询参数 v 有一个值他被缓存所使用. 只要你的bundle没有改变, ASP.NET application 会始终通过v的这个值来请求 AllMyScripts bundle. 如果bundle中的任何文件发生了改变, the ASP.NET 会生成一个新的值, 这样浏览器就会去请求这个新的 bundle.