原文地址:http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification
免责申明:本文为翻译,仅用于学习交流,版权归原作者所有 (Rick Anderson)
更多说明:本着自学加深记忆之目的翻译此文,不用于任何商业用途
ASP.NET MVC 4教程:捆绑和压缩
By Rick Anderson|June 13, 2012
捆绑和压缩是你可以在ASP.NET 4.5中使用技术,用于改善请求的加载时间.捆绑和压缩通过减少向服务端请求的次数和减小请求资源的大小(如CSS和JavaScript)来改善加载时间.
许多主流浏览器限制并发连接数为6个/主机.那意味着当6个请求都正在处理时,其它访问资源的请求会被浏览器排队.在下图中,IE F12 开发人员工具Network标签显示了示例应用程序的About视图请求资源的时间线.

灰色条表示被浏览器排队的请求等待这6个连接限制解除占用的时间.黄色条代表从发送第一个字节的请求时间,就是说,发送请求到收到服务端第一个响应的时间.蓝色条表示从服务端收到响应数据的时间.在资源上单击面进入详细的时间线信息.例如,下图显示加载/Scripts/MyScripts/JavaScript6.js文件的详细时间线.
Start事件给出的时间是请求因为浏览器并发连接限制而排队所占用的时间.在这里,等待另一个请求完成排队了46毫秒.
捆绑
捆绑是ASP.NET 4.5中一个新功能,它让合并或捆绑多个文件为一个文件变得简单.你可以创建CSS和JavaScript捆绑.更少的文件代表更少的HTTP请求,它能提高首页加载性能.
下图显示和上图一样的About视图的时间线,不过次启用了捆绑和压缩.
压缩
压缩为script或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 |
捆绑和压缩的效果
下表展示了在同一程序中逐一列出所有资源和使用捆绑和压缩(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头相当冗余.接收字节数缩减不是很大的原因是最大的文件(Scripts\jquery-ui-1.8.11.min.jsand Scripts\jquery-1.6.2.min.js)已经压缩过.注意:示例程序的时间线使用了Fiddler工具来模拟一个慢速网络.(从Fiddler的Rules菜单,选择Performance然后选择Simulate Modem Speeds.)
调试捆绑和打包的JavaScript
在开发环境调试是很容易的(在Web.config中将compilation节设置为debug="true")因为JavaScript文件没有捆绑和压缩.你也可以在生成发布中调试捆绑和压缩过的JavaScript文件.使用IE F12开发者工具,用下面的方法调试捆绑压缩过的JavaScript函数.
- 选中Script标签,点击Start debugging按钮.
- 用assets按钮选择你要调试的捆绑和压缩过的JavaScript函数.

3. 选择Configuration按钮来格式化压缩的JavaScript,然后选择Format JavaScript.
![]()
4. 在Search Script输入框,输入调试函数名称.如下图所示,在Search Script里输入AddAltToImg
使用F12开发者工具调试的更多信息,请参见MSDN文章Using the F12 Developer Tools to Debug JavaScript Errors.
控制捆绑和压缩
通过配置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-1.*")); // Code removed for clarity. BundleTable.EnableOptimizations = true; }
在ASP.NET MVC中使用捆绑和压缩
本节我们将创建一个ASP.NET MVC项目来讲解捆绑和压缩.首先,创建一个新的名为MvcBM的ASP.NET MVC internet项目,不改变任何默认设置.
打开App_Start\BundleConfig.cs文件查看用来创建,注册,配置捆绑的RegisterBundles方法.下面代码展示了RegisterBundles方法的一部分.
public static void RegisterBundles(BundleCollection bundles) { bundles.Add(new ScriptBundle("~/bundles/jquery").Include( "~/Scripts/jquery-1.*")); // Code removed for clarity. }
上述代码创建了一个新的名为~/bundles/jquery的JavaScript绑定,它包含通配符字符串"~/Scripts/jquery-1.*"匹配的Scripts文件夹下的文件(可以是debug或minified除了.vsdoc).对ASP.NET MVC 4 RC来说,这意味着在调试模式, jquery-1.6.2.js文件将会被添加到捆绑中.在发布模式,则是jquery-1.6.2.min.js.捆绑框架遵循下面的惯例:
- 当同时存在"FileX.min.js"和"FileX.js"文件时为发布模式选择".min"文件.
- 为调试模式选择不带".min"版本.
- 忽略"-vsdoc" 文件(例如jquery-1.6.2-vsdoc.js),它们只用于智能提示.
上面的通配符用来自动创建一个Script文件夹下匹配1.*版本的jQuery捆绑.示例中,使用通配符带来下面的好处:
- 允许你在视图页中不改变之前的捆绑代码和jQuery引用更新jQuery新版本.
- 自动为debug选择完整版,为release选择".min"版
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 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方法接收一个字符串数组参数,因此你可以在一行代码中添加多个捆绑.
使用"*"通配符筛选文件
Include方法中的虚拟路径和IncludeDirectory方法中的查找规则接受一个"*"通配符作为路径最后部分的前缀或后缀.查找字符串不区分大小写.IncludeDirectory方法可选择在子目录中查找.
试想一个项目中有下面的JavaScript文件:
- Scripts\Common\AddAltToImg.js
- Scripts\Common\ToggleDiv.js
- Scripts\Common\ToggleImg.js
- Scripts\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") | Invalid pattern exception. The wildcard character is only allowed on the prefix or suffix. |
| Include("~/Scripts/Common/*og.*") | Invalid pattern exception. Only one wildcard character is allowed. |
| "Include("~/Scripts/Common/T*") | ToggleDiv.js, ToggleImg.js |
| "Include("~/Scripts/Common/*") | Invalid pattern exception. A pure wildcard segment is not valid. |
| 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文件.
捆绑缓存
当捆绑创建的时捆绑设置HTTP过期头值为一年.如果你浏览之前访问过的页面,Fiddler显示IE并没有有条件请求该捆绑,也就是说,IE没有HTTP GET请求该捆绑并且服务端没有HTTP 304响应.可以使用F5来强制IE为每一个捆绑进行有条件请求(每一个捆绑都返回HTTP 304响应).通过使用^F5来完全刷新(每一个捆绑都返回HTTP 200响应).
下图展示Caching标签Fiddler响应面板:
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.你可以阅读博客为什么IE就是否产生一个有条件请求或没有请求做出错误决定Using CDNs and Expires to Improve Web Site Performance.
捆绑LESS, CoffeeScript, SCSS, Sass
捆绑和压缩框架提供一种机制来支持中间语言像SCSS,Sass,LESS 或 Coffeescript,允许转换为捆绑.例如,添加.less文件到你的MVC4项目:
- 创建一个文件夹用于存放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"; } }
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"前缀是一个好习惯.它将避免一个可能的routing conflict.
只要捆绑中的某个文件更新,就会为捆绑查询字符串参数生成一个新的标识并且在客户端下一次请求一个包含该捆绑的页面时被完全下载.每个资源文件单独列出,只有改变的文件会被下载.改变频繁的资源不适合捆绑.
捆绑和压缩主要减少了首页请求加载时间,一旦网页被请求,浏览器将缓存资源(JavaScript,CSS和images),因此请求同一页面或同一站点页面请求相同资源时捆绑和压缩不会带来任何加速性能.如果你没有正确设置资源的过期头值并且你没有使用捆绑和压缩,几天后浏览器会将资源标记为过期并为每一个资源发送验证请求.这种情况下,在首页请求后捆绑和压缩提供了性能增长.更多细节参见Using CDNs and Expires to Improve Web Site Performance.
浏览器限制并发连接数为6个/主机能通过CDN缓解.原因是CDN有一个与你网站不同的主机名,从CDN请求资源将有利于6个并发连接限制到你的主机环境.CDN还提供通用包缓存有边界增强缓存.
捆绑应当被使用它们的页面侵害.例如,默认的Internet应用程序ASP.NET MVC模板为jQuery生成一个jQuery验证捆绑.因为默认视图创建时并没有包含输入和提交值,因此它们不需要包含验证捆绑.
System.Web.Optimization命名空间实现于System.Web.Optimization.DLL.它影响WebGrease库(WebGrease.dll)的压缩功能,依次使用Antlr3.Runtime.dll.
贡献者
- Hao Kung
- Howard Dierking
- Diana LaRose
By Rick Anderson, Rick Anderson works as a programmer writer for Microsoft, focusing on ASP.NET MVC, jQuery and Entity Framework. He enjoys working with the top contributors in the ASP.NET MVC forum.
posted on
浙公网安备 33010602011771号