早期 Web 开发时有一种说法请求太多会影响页面性能如果您知道有什么技巧可以减少网页所触发的 HTTP 请求数尽可能使用这些方法吧随着网页中越来越多地填充了丰富的视觉内容下载 CSS脚本和图像等相关资源所需的成本显着攀升毫无疑问在大多数情况下这些资源可由浏览器缓存到本地但保持最初占用的空间会是非常困难的此外减少请求的数量和大小也有助于确保低带宽占用降低延迟以及延长电池寿命在移动浏览中这些都是关键因素解决这些问题最普遍接受的方法包含了两个操作的结合捆绑和缩小
在本文中我将从 ASPNET MVC 提供的软件工具的独有角度出发介绍 CSS 文件的捆绑和缩小这一话题延续了我之前的专栏在 ASPNET MVC 中创建为移动设备优化的视图第 部分使用 WURFL(msd/magazine/dn)
捆绑和缩小的基础知识
捆绑是将多个不同资源汇总成为单个可下载资源的过程例如一个捆绑中可能包括多个 JavaScript 或 CSS 文件您可以通过向某个特定端点发送单个 HTTP 请求将这些内容下载到本地计算机另一方面缩小是对资源应用的转换具体而言缩小是从基于文本的资源中删除所有不必要字符的过程这一过程不会改变预期功能这意味着缩短标识符对函数使用别名以及删除注释空格字符和新行一般来说添加这些所删除字符通常是为了提升可读性但是会占用空间并且实际不起到任何功能用途
捆绑和缩小可同时应用但过程彼此独立根据需要您可以决定只创建捆绑或者只缩小单独文件不过通常在生产站点上没有理由不捆绑和缩小所有 CSS 及 JavaScript 文件当然也有一些例外诸如 jQuery 等公共资源可能会位于众所周知的内容交付网络 (CDN) 上但是在调试时这就是完全不同的情况经过缩小或者捆绑的资源非常难于阅读和单步执行因此您可能不希望启用捆绑和调试
许多框架提供了捆绑和缩小服务其可扩展性水平略有差别并且采用了不同的功能集大多数情况下它们提供了相同的功能因此选择哪一种纯属个人偏好如果您正在编写 ASPNET MVC 应用程序则捆绑和缩小的选择自然就是 Microsoft ASPNET Web Optimization Framework该框架在 NuGet 程序包 (bitly/bSuB) 中提供如图 中所示
图 安装 Microsoft ASPNET Web Optimization Framework
使用 CSS 捆绑
就我认为了解 CSS 捆绑机制的最佳方法是从一个真正的空 ASPNET MVC 项目开始着手这意味着使用空项目模板来创建新项目并删除未使用的引用和文件接下来假定您添加链接多个 CSS 文件的布局文件
<link rel="stylesheet"
href="@UrlContent("~/content/styles/sitecss")"/>
如果使用 Fiddler 或 Internet Explorer 开发者工具显示页面并监视其网络活动您会发现有两个并行下载 这是默认行为
请注意在 ASPNET MVC 中您可以使用新的 StylesRender 工具以紧凑得多的方式重写以前的标记
@StylesRender(
"~/content/styles/sitecss"
"~/content/styles/sitemorecss")
位于 SystemWebOptimization 下的 Styles 类的功能远比第一眼看到的要强大 StylesRender 方法也支持捆绑 这意味着该方法有多个重载其中一个接受 CSS URL 数组 不过另一个重载接受以前创建的捆绑的名称(稍后将进一步介绍) 在本例中它发出单个 <link> 元素并使其指向自动生成的 URL该 URL 返回经过捆绑或缩小的所有样式表
创建 CSS 捆绑
通常在 globalasax 中以编程方式创建捆绑 按照配置代码的 ASPNET MVC 模式您可能需要在 App_Start 文件夹下创建 BundleConfig 类并在其外部公开静态初始化方法
BundleConfigRegisterBundles(BundleTableBundles);
一个 CSS 捆绑只是一个样式表集合 此处是将上述两个 CSS 文件分组到单个下载中的代码
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
// Register bundles first
bundlesAdd(new Bundle("~/mycss")Include(
"~/content/styles/sitecss"
"~/content/styles/sitemorecss"));
BundleTableEnableOptimizations = true;
}
}
创建新 Bundle 类将用于从视图或网页中引用捆绑的虚拟路径传递到构造函数中 也可以在以后通过 Path 属性来设置虚拟路径 要将 CSS 文件与捆绑关联需要使用 Include 方法 此方法接受表示虚拟路径的字符串数组 您可以按上例中所示明确指示 CSS 文件也可以按此处所示指示模式字符串
bundlesAdd(new Bundle("~/mycss")
Include("~/content/styles/*css");
Bundle 类还有 IncludeDirectory 方法该方法可用于指示给定虚拟目录的路径还可使用模式匹配字符串和布尔标志来启用对子目录的搜索
在之前代码片段中看到的在 BundleTable 类上设置的 EnableOptimization 布尔属性表示需要显式启用捆绑 如果未以编程方式启用则捆绑无法正常工作 如前所述捆绑是一种优化方式因此当站点是生产站点时通常很有意义 EnableOptimization 属性是一种设置捆绑的简便方法应在生产中使用不过以调试模式编译站点时应该禁用它 您甚至可以使用以下代码
if (!DEBUG)
{
BundleTableEnableOptimizations = true;
}
更多高级捆绑功能
就 CSS 捆绑而言除了缩小之外没有多少其他相关内容 不过BundleCollection 类是一个通用类也可用于捆绑脚本文件 特别是BundleCollection 类有几个功能值得注意虽然它们在捆绑脚本文件而非 CSS 文件时最有用
第一个功能是排序 BundleCollection 类有一个名为 Orderer 的属性其类型为 IBundleOrderer 正如您所见orderer 是一个负责确定您希望在捆绑文件以供下载时使用的实际顺序的组件 默认 orderer 是 DefaultBundleOrderer 类 此类按照 BundleCollection 属性 FileSetOrderLis 的设置所得到的顺序来捆绑文件 FileSetOrderList 设计作为 BundleFileSetOrdering 类的集合 这些类中的每一个都定义文件的模式(例如 jquery*)BundleFileSetOrdering 类添加到 FileSetOrderList 的顺序确定了文件的实际顺序 例如在使用默认配置时所有 jQuery 文件始终捆绑在 Modernizr 文件之前
DefaultBundleOrderer 类对 CSS 文件的影响则有限得多 如果您的网站中有名为resetcss或normalizecss的文件则这些文件将自动捆绑在您的任意 CSS 文件之前并且 resetcss 始终要优先于 normalizecss 如果您不熟悉重置/规范化样式表那么这些样式表可以为所有 HTML 元素提供标准样式属性集这样您的页面不会继承特定于浏览器的设置例如字体字号和边距 虽然这两种类型的 CSS 文件都有一些推荐的内容但实际内容取决于您 如果您的项目中有这些名称的文件则 ASPNET MVC 会通过额外方式确保这些文件捆绑在所有其他内容之前 如果您希望覆盖默认 orderer 并忽略预定义的捆绑文件集排序可以使用两个选项 首先可以针对每次捆绑创建自定义 orderer 下面是一个简单地忽略预定义排序的示例
public class PoorManOrderer : IBundleOrderer
{
public IEnumerable<FileInfo> OrderFiles(
BundleContext context IEnumerable<FileInfo> files)
{
return files;
}
}
您可以按下面所示的方式使用
var bundle = new Bundle("~/mycss");
bundleOrderer = new PoorManOrderer();
此外可以使用下列代码重置所有排序
bundlesResetAll();
在本例中使用默认 orderer 的效果与之前穷人 orderer 的效果相同 不过请注意ResetAll 还将重置脚本排序
另一个更高级的功能是忽略列表 它通过 BundleCollection 类的 IgnoreList 属性针对已选中包含的文件(但实际上应该忽略)定义模式匹配字符串 在 ASPNET MVC 中实现捆绑的主要优势是可以在单次调用中获取文件夹中的所有 JavaScript 文件 (*js) 但是有可能 *js 还会匹配您不希望下载的文件例如 vsdocjs 文件 IgnoreList 的默认配置考虑到了大多数常见情况同时也允许自定义
运行中的 CSS 捆绑
CSS 捆绑是一种强大的优化功能但是在实际过程中是如何工作的? 请考虑使用以下代码
var bundle = new Bundle("~/mycss");
bundleInclude("~/content/styles/*css");
bundlesAdd(bundle);
图 中显示了对应的 HTTP 通信
图 第二个请求是有多个 CSS 文件的捆绑
第一个请求针对是主页第二个请求不指向特定 CSS 文件而是引用了包含 content/styles 文件夹中所有 CSS 文件的捆绑(请参阅图 )
图 捆绑 CSS 示例
添加缩小功能
只有在将多个资源打包到一起以便通过单个下载捕获并进行缓存时才需要使用 Bundle 类
正如图 中的代码所示下载的内容中添加了空白和新行字符以便于阅读不过浏览器并不关心可读性对于浏览器来说图 中的 CSS 代码完全等同于以下缩小代码中的字符串
htmlbody{fontfamily:;segoe ui;;fontsize:em;margin:px}
htmlbody{backgroundcolor:#;color:#dcc}
对于我在此例中使用的简单 CSS缩小功能可能并没有起到多大作用 但是对于具有大量样式表的商务站点缩小 CSS 就非常有意义
如何添加缩小功能? 非常简单就像使用 StyleBundle 替换 Bundle 类一样 StyleBundle 出奇的简单它继承自 Bundle仅由不同的构造函数组成
public StyleBundle(string virtualPath)
: base(virtualPath new IBundleTransform[] { new CssMinify() })
{
}
Bundle 类具有可接受 IBundleTransform 对象列表的构造函数 这些转换将逐个应用于内容 StyleBundle 类仅添加 CssMinify 转换器 CssMinify 是 ASPNET MVC (和更高版本)的默认缩小器基于 WebGrease 优化工具 (webgreas) 无需赘言如果您希望切换到不同缩小器则需要做的就是获取类(IBundleTransform 的实现)并通过类 Bundle 的构造函数进行传递
再也不用找托辞
随 ASPNET MVC 捆绑的 Web Optimization Framework 添加了少量功能不过这些都是非常有用的功能 这就没有任何理由不去缩小和捆绑资源 到目前为止可能的托辞都是 ASPNET 平台中缺少原生支持 ASPNET MVC 和更高的版本中不再是这种情况
不过对于 CSS 文件而言还有另一个方面需要考虑动态生成样式 CSS 最初只是设计作为应用到网页的外观效果现在正在转变成为更加动态的资源已经引入了一些伪编程语言来以编程方式生成 CSS 这些主题将在下次介绍