原文: Packaging for Performance

杰微刊 兼职译者缪晨翻译, 杰微刊 审校及发布。

最近有个很有趣的主题,是关于web应用的静态资源(JS/CSS)打包的。 在当今前端界Craig Silverstein’s 和 Rebecca Murphey’s 在这个主题上的几篇文章表现出了对打包本质的深刻理解。现在面对的主要问题是:JavaScript与CSS的打包策略(基于现有最佳性能的实践)在迁移到HTTP/2的时候是否需要做一些改变?虽然在HTTP/2中打包的作用——减少HTTP请求数,变得毫无作用,但实际上我们还没有迁移到HTTP/2。以上的几篇文章证明了这个事实。在eBay 几个月前准备将网站迁移到到HTTPS时做过类似的测试。在这篇文章当中,我将简单介绍下我们打包的方法,以及我们的方法对性能的提高。

eBay的绝大多数页面遵循一个本地模板来打包静态资源。一个页面中的所有CSS跟JavaScript都被各自打包成一个资源,CSS在head标签中加载,而JS在底部加载。虽然这在减少HTTP请求数上做的很好,但依然有可挖掘的空间——最主要的一点是更有效的利用浏览器的缓存。当用户访问eBay的页面时,所有没访问过的页面都需要下载整个JS与CSS,但其中包含了前面页面中用过的核心类库(如jQuery)。当我们计划迁移到HTTPS(和HTTP/2)时,我们意识到这种粗粒度的打包策略是不行的。我们及其他人的研究都表明,这种整个打包在一起再每个页面单独加载资源的方式,在性能的优化上并不好。我们需要一个平衡,这时我们提出了我们自己的打包解决方案。

Inception

我们首先标注出了所有eBay页面都会调用的核心JS和CSS,然后把他们聚合为一个资源。为了达到这个目的,我们建立了个内部的Node.js 模块,叫做Inception。这个模块包含了所有公用的JS和CSS模块,而且会被所有团队(eBay各个页面的所有者)作为依赖添加。被标注的核心JS类库为:jQuery, marko (模板引擎), marko-widgets (UI 组件抽象),以及内部的分析及跟踪类库。而CSS我们有我们自己的类库叫做Skin,从中我们提取了core、button、icons、dialog和form等模块。在eBay我们使用的打包工具叫作 Lasso。Inception模块作为Lasso的插件提供以下功能:

1、强制所有模块(购买,售卖,浏览,结算等)遵循准确的核心JS和CSS库的版本。不遵循话会构建失败。

2、将Inception 中的资源打包成一个URL,所有的模块都引用相同的URL,例如:inception-hashcode.js 和 inception-hashcode.css.

3、使各团队依然可以将Inception中的JS/CSS 作为他们自己模块依赖的一部分进行引用。Lasso的优化器会删除重复的加载,并确定只有一份会被发送到浏览器端。由于两个原因这个功能好到爆。首先,我们想要推广模块级的封装,因此当团队构建模块时,他们可以自由地将某个核心库作为依赖添加,而不必担心重复加载。这样可以保证这个模块可以独立运行。第二,各团队不需要追踪依赖是否在Inception当中。他们可以随意的添加依赖,工具可以处理这方面的优化。

现在有了Inception,我们可以看下我们得到的好处:

1、 浏览器缓存: 关于将所有资源打包进一个URL,上文提到了一个缺点,就是不能很好的利用浏览器缓存。Inception解决了这个问题。因为核心的JS和CSS类库(顺带一提,这是主要的负荷)在不同的模块上都采用同一个URL进行引用,这样在用户各种浏览eBay的过程中,浏览器缓存都被很有效的利用了。这个缓存大大提升了性能,特别是连接缓慢的情况下。附带的,新的浏览器通过各种方式支持 代码缓存,这样我们同时可以避免对Inception中的大量的JS进行重复的解析与编译。

2、库文件一致性: 在之前的打包系统中我们发现在各个模块中核心类库的版本缺乏一致。由于各个团队维护自己的核心类库,例如当用户从一个模块跳转到另一个的时候,使用了不用版本的jQuery或者按钮样式。实际结果并不只是UI不一致,而且实现也是不一致的。Inception修复了这个问题,因为它是一个统一维护核心类库的地方。

3、通向先进的Web应用: 当所有模块中的页面依赖于相同的核心类库时,他们支架你的过渡就变得非常容易,因为在浏览过程中只有这个应用单独需要的JS和 CSS需要下载。这样我们就可以使用 应用壳架构来构建我们的web应用,为将eBay建设成为一个先进Web应用铺平了道路,我们过去已经(在一个模块中) 探索 过一个类似的途径——使用 Structured Page Fragments 的方法,我们可以看到感知性能的明显提升。

4、简单的升级方式: 最后,Inception使我们可以在一个核心的地方将核心类库升级到一个更新的版本。Inception本身遵从 语义化版本,因此所有使用Inception的团队Inception 都可以根据一个语义的方式来获得更新。升级在之前是很有问题的,因为我们必须在挨个团队进行手工升级。

模块

现在核心类库已经由Inception来管理了,那页面中的其它资源呢?就是那些应用/模块中特定的CSS和JS。对于每个模块我们采用另一种打包的方式,我们把分为两组:常量和变量。

常量: 在所有的请求当中不变的CSS和JS 被定为常量。这些主要适用于各个模块中在不同的请求参数时不变的UI组件。常量模块打包成一个资源,这样又可以继续利用浏览器缓存。当一个用户重复浏览同一个页面的时候,这个包一般都会命中浏览器缓存,进而获得性能上的优势。

变量: 一小部分资源在各个页面中会根据请求的参数不同而变化。这些变化是由于实验,用户登录状态,业务逻辑等原因。这种资源被分入变量组,在运行时单独打包。这些只有极低的缓存命中率,可能每个会话都要通过网络重新下载。

总结

作为总结,每个页面会有6个资源包(3个JS和3个CSS),每个包都有各自的用途。所有的URL都根据内有进行哈希,这样缓存会自动过期。

1.Inception —打包核心JS和CSS,最高的负载。

2.常量 —打应用中包不变的CSS和JS。中级的负载。

3.变量 —打包应用中变化,最低的负载。

现在的状态下,这个打包策略貌似是最符合性能需求的。他在HTTP请求次数与浏览器缓存之间找到了一个正确的平衡点。明年我们将迁移到HTTP/2,我们也将继续改进这个方法,尝试更细粒度的打包方案,当然,性能是关键。

------------好久不见的分隔线------------

杰微刊旨在分享优质的内容。

我们水平有限,但理想高远。

也同样期待有理想的您对这个世界的贡献。

欢迎任何目的的联系。

欢迎关注杰微刊

09-09 11:56