捆绑和缩小

作者 :Rick Anderson

捆绑和缩小是 ASP.NET 4.5 中可用于缩短请求加载时间的两种方法。 捆绑和缩小通过减少对服务器的请求数和减少请求的资产的大小, (CSS 和 JavaScript.)

当前大多数主要浏览器将每个主机名 同时连接 数限制为 6 个。 这意味着,在处理 6 个请求时,针对主机上的其他资产请求将由浏览器排队。 在下图中,IE F12 开发人员工具网络选项卡显示示例应用程序的“关于”视图所需的资产的计时。

B/M

灰色条形显示等待六个连接限制的浏览器排队请求的时间。 黄色条表示第一个字节的请求时间,即发送请求并从服务器接收第一个响应所花费的时间。 蓝色条形显示从服务器接收响应数据所需的时间。 可以双击资产以获取详细的计时信息。 例如,下图显示了加载 /Scripts/MyScripts/JavaScript6.js 文件的计时详细信息。

显示 A S P dot NET 开发人员工具网络选项卡的屏幕截图,其中资产请求 URL 位于左侧列,其计时位于右侧列。

上图显示了 Start 事件,该事件提供了由于浏览器限制同时连接数而使请求排队的时间。 在这种情况下,请求已排队 46 毫秒,等待另一个请求完成。

捆绑

捆绑是 ASP.NET 4.5 中的一项新功能,可以轻松地将多个文件合并或捆绑到单个文件中。 可以创建 CSS、JavaScript 和其他捆绑包。 文件越少意味着 HTTP 请求越少,这可以提高首页加载性能。

下图显示了前面显示的“关于”视图的相同计时视图,但这次启用了捆绑和缩小。

显示 I E E F 12 开发人员工具上资产的计时详细信息选项卡的屏幕截图。突出显示了 Start 事件。

缩小

缩小对脚本或 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 重命名
imageTagAndImageID n
imageContext t
imageElement i

捆绑和缩小的影响

下表显示了单独列出所有资产与在示例程序中使用捆绑和缩小 (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 规则 ”菜单中选择“ 性能 ”,然后选择“ 模拟调制解调器速度”。)

调试捆绑和缩小的 JavaScript

在开发环境中调试 JavaScript 很容易, (Web.config文件中的 compilation Element 设置为 debug="true" ) ,因为 JavaScript 文件未捆绑或缩小。 还可以调试发布版本,其中 JavaScript 文件已捆绑和缩小。 使用 IE F12 开发人员工具,使用以下方法调试缩小捆绑包中包含的 JavaScript 函数:

  1. 选择“ 脚本 ”选项卡,然后选择“ 开始调试 ”按钮。
  2. 使用“资产”按钮选择包含要调试的 JavaScript 函数的捆绑包。
    显示 I E E F 12 开发人员工具的“脚本”选项卡的屏幕截图。突出显示了“搜索脚本”输入框、捆绑包和 Java 脚本函数。
  3. 通过选择显示“ 配置”按钮图标的“配置”按钮图像,然后选择“ 设置 JavaScript 格式”来设置缩小的 JavaScript 的格式
  4. “搜索脚本 ”输入框中,选择要调试的函数的名称。 在下图中,在“搜索脚本”输入框中输入了 AddAltToImg
    显示 I E E F 12 开发人员工具的“脚本”选项卡的屏幕截图。突出显示了“搜索脚本”输入框,其中输入了“向 lmg 添加 Alt”。

有关使用 F12 开发人员工具进行调试的详细信息,请参阅 MSDN 文章 使用 F12 开发人员工具调试 JavaScript 错误

控制捆绑和缩小

通过在 Web.config 文件中的 compilation Element 中设置 debug 属性的值来启用或禁用捆绑和缩小。 在以下 XML 中, debug 设置为 true,因此禁用捆绑和缩小。

<system.web>
    <compilation debug="true" />
    <!-- Lines removed for clarity. -->
</system.web>

若要启用捆绑和缩小,请将 debug 值设置为“false”。 可以使用 类上的 BundleTable 属性替代Web.config设置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;
}

注意

除非 EnableOptimizationstrueWeb.config 文件中的 compilation Element 中的 debug 属性设置为 false,否则不会捆绑或缩小文件。 此外,不会使用文件的 .min 版本,将选择完整的调试版本。 EnableOptimizations重写Web.config文件中编译元素中的调试属性

对 ASP.NET Web Forms和网页使用捆绑和缩小

通过 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-{version}.js"));
         // Code removed for clarity.
}

前面的代码创建名为 ~/bundles/jquery 的新 JavaScript 捆绑包,其中包含调试或缩小但不是 的所有相应 (。vsdoc) Scripts 文件夹中与野生卡字符串“~/Scripts/jquery-{version}.js”匹配的文件。 对于 ASP.NET MVC 4,这意味着使用调试配置时,文件 jquery-1.7.1.js 将添加到捆绑包中。 在发布配置中,将添加 jquery-1.7.1.min.js 。 捆绑框架遵循几个常见约定,例如:

  • 存在FileX.min.jsFileX.js 时,选择“.min”文件进行发布。
  • 选择非“.min”版本进行调试。
  • 忽略“-vsdoc”文件 (,例如 jquery-1.7.1-vsdoc.js) ,它们仅由 IntelliSense 使用。

{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.
}

在上面的代码中,将在发布模式下从 CDN 请求 jQuery,并在调试模式下本地提取 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>

创建捆绑包

BundleInclude方法采用字符串数组,其中每个字符串都是资源的虚拟路径。 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"));

提供 BundleIncludeDirectory 方法,用于将目录中的所有文件添加到 (,还可以选择添加与搜索模式匹配的所有子目录) 。 BundleIncludeDirectory 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 和 Scripts.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 异步 属性。 以下代码演示如何使用 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 搜索模式可以接受一个“*”通配符作为最后一个路径段中的前缀或后缀。 搜索字符串不区分大小写。 方法 IncludeDirectory 具有搜索子目录的选项。

请考虑具有以下 JavaScript 文件的项目:

  • Scripts\Common\AddAltToImg.js
  • Scripts\Common\ToggleDiv.js
  • Scripts\Common\ToggleImg.js
  • Scripts\Common\Sub1\ToggleLinks.js

dir imag

下表显示了使用通配符添加到捆绑包的文件,如下所示:

调用 添加的文件或引发异常
包括 (“~/Scripts/Common/*.js”) AddAltToImg.jsToggleDiv.js 、ToggleImg.js
包括 (“~/Scripts/Common/T*.js”) 无效模式异常。 仅允许在前缀或后缀上使用通配符。
包括 (“~/Scripts/Common/*og.*”) 无效模式异常。 只允许使用一个通配符。
包括 (“~/Scripts/common/T*”) ToggleDiv.jsToggleImg.js
包括 (“~/Scripts/Common/*”) 无效模式异常。 纯通配符段无效。
IncludeDirectory (“~/Scripts/Common”、“T*”) ToggleDiv.jsToggleImg.js
IncludeDirectory (“~/Scripts/Common”, “T*”, true) ToggleDiv.jsToggleImg.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 (强制完全刷新,为每个 bundle 生成 HTTP 200 响应。)

下图显示了 Fiddler 响应窗格的“ 缓存 ”选项卡:

fiddler 缓存图像

请求
http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81
适用于捆绑包 AllMyScripts ,包含查询字符串对 v=r0sLDicvP58AIXN\_mc3QdyVvVj5euZNzdsa2N1PKvb81。 查询字符串 v 具有值标记,该标记是用于缓存的唯一标识符。 只要捆绑包不更改,ASP.NET 应用程序就会使用此令牌请求 AllMyScripts 捆绑包。 如果捆绑包中的任何文件发生更改,ASP.NET 优化框架将生成一个新令牌,保证浏览器对捆绑包的请求将获取最新的捆绑包。

如果运行 IE9 F12 开发人员工具并导航到以前加载的页面,IE 会错误地显示对每个捆绑包和返回 HTTP 304 的服务器发出的条件 GET 请求。 可以在博客文章 使用 CDN 和过期以提高网站性能中了解 IE9 在确定是否发出条件请求时出现问题的原因。

LESS、CoffeeScript、SCSS、Sass 捆绑。

捆绑和缩小框架提供了一种处理中间语言(如 SCSSSassLESSCoffeescript)的机制,并将缩小等转换应用于生成的捆绑包。 例如,若要将 .less 文件添加到 MVC 4 项目,请执行以下操作:

  1. 为 LESS 内容创建一个文件夹。 以下示例使用 Content\MyLess 文件夹。

  2. .less NuGet 包 无点 添加到项目。
    NuGet 无点安装

  3. 添加实现 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. 使用 LessTransformCssMinify 转换创建 LESS 文件捆绑包。 将以下代码添加到 RegisterBundlesApp\_Start\BundleConfig.cs 文件中的 方法。

    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”作为前缀。 这将防止可能的 路由冲突

更新捆绑包中的一个文件后,将为捆绑查询字符串参数生成一个新令牌,并且下次客户端请求包含捆绑包的页面时,必须下载完整捆绑包。 在单独列出每个资产的传统标记中,将仅下载已更改的文件。 经常更改的资产可能不适合捆绑。

捆绑和缩小主要缩短第一个页面请求加载时间。 请求网页后,浏览器会将资源缓存 (JavaScript、CSS 和图像) ,因此捆绑和缩小不会在请求同一页面或请求相同资产的同一站点上的页面时提供任何性能提升。 如果未在资产上正确设置 expires 标头,并且未使用捆绑和缩小,浏览器新鲜度启发将在几天后将资产标记为过时,并且浏览器将要求对每个资产发出验证请求。 在这种情况下,捆绑和缩小在第一页请求后会提高性能。 有关详细信息,请参阅博客 使用 CDN 和过期以提高网站性能

使用 CDN 可以缓解每个主机名同时连接 6 个的浏览器限制。 由于 CDN 的主机名与托管站点不同,因此来自 CDN 的资产请求不会计入托管环境的六个同时连接限制。 CDN 还可以提供常见的包缓存和边缘缓存优势。

捆绑应按需要捆绑包的页进行分区。 例如,Internet 应用程序的默认 ASP.NET MVC 模板创建独立于 jQuery 的 jQuery 验证捆绑包。 由于创建的默认视图没有输入且不发布值,因此它们不包括验证捆绑包。

命名空间 System.Web.OptimizationSystem.Web.Optimization.dll中实现。 它利用 WebGrease 库 (WebGrease.dll) 缩小功能,进而使用 Antlr3.Runtime.dll

我使用 Twitter 快速发布和共享链接。 我的 Twitter 句柄是@RickAndMSFT

其他资源

作者