前端性能优化第一部分 – 合并、压缩和缓存 – Node.JS节日季,第四部分

这是Mozilla身份团队的“A Node.JS Holiday Season”系列文章的第4部分,总共12部分。这是关于如何实现更好的前端性能的第一篇文章。

在本系列“A Node.JS Holiday Season”的这一部分,我们将讨论前端性能,并向您介绍我们在Mozilla中构建和使用的工具,以使Persona前端尽可能快。

我们将讨论connect-cachify,这是一个自动化前端性能中一些最重要的部分的工具。

但在我们这样做之前,让我们快速回顾一下,作为开发者,我们可以做些什么来使我们的解决方案在用户的机器上运行得尽可能流畅。如果您已经了解了所有关于性能优化的知识,可以随意继续到最后,看看connect-cachify如何帮助自动化您现在可能手动执行的一些操作。

客户端性能的三个C

网络上充满了与性能最佳实践相关的信息。虽然存在许多高级技术来微调网站的每一毫秒,但三个基本工具应该构成基础——合并、压缩和缓存。

合并

合并的目标是最小化向服务器发出的请求数量。服务器请求代价很高。建立HTTP连接所需的时间有时比传输数据本身所需的时间更长。每个请求都会增加查看网站的开销,并且在移动设备上尤其成问题,因为移动设备存在明显的连接延迟。您是否曾经在连接到Edge网络的手机上浏览购物网站,并在每个图像逐个加载时感到痛苦?这就是连接延迟的影响。

SPDY是一种构建在HTTP之上的新协议,旨在通过将资源请求合并到单个HTTP连接中来减少页面加载时间。不幸的是,目前只有最新版本的Firefox、Chrome和Opera支持此新协议。

尽可能地合并外部资源,尽管这种方法比较老旧,但它适用于所有浏览器,并且不会因SPDY的出现而降低性能。存在一些工具可以合并三种最常见的外部资源类型——JavaScript、CSS和图像。

JavaScript和CSS

具有多个外部JavaScript包含的网站应该考虑将脚本合并到单个文件中以用于生产环境。传统上,浏览器在下载和处理JavaScript时会阻止所有其他渲染。由于每个请求的JavaScript资源都带有一定的延迟,因此以下操作比需要的时间更慢

  
  
  
  

通过将四个请求合并为一个,浏览器因延迟而被阻塞的总时间将大大减少。

  

在开发过程中使用合并后的JavaScript可能非常困难,因此合并通常只在生产网站中进行。

与JavaScript类似,单个CSS文件也应该合并为单个资源以用于生产环境。过程相同。

图像

Data URI和图像精灵是减少请求图像数量的两种主要方法。

data: URI

Data URI是URL的一种特殊形式,用于将图像直接嵌入到HTML或CSS中。Data URI可以在img标签的src属性中使用,也可以在CSS中的background-image的url值中使用。由于嵌入的图像是base64编码的,因此它们需要更多字节,但比原始外部二进制图像少一个HTTP请求。如果包含的图像很小,则增加的字节大小通常可以通过减少HTTP请求数量来抵消。IE6和IE7都不支持Data URI,因此在使用它们之前,请了解您的目标受众。

图像精灵

只要无法使用Data URI,图像精灵就是一个很好的替代方案。图像精灵是将多个图像组合到一个较大的图像中的集合。组合图像后,CSS用于仅显示精灵的相关部分。许多工具可以根据图像集合创建精灵。

精灵的一个缺点在于维护方面。在精灵中添加、删除或修改图像需要对CSS进行相应的更改。

Sprite Cow帮助您获取精灵表中精灵的background-position、width和height,并提供易于复制的css代码。

删除多余字节 - 缩小、优化和压缩

合并资源以最大限度地减少HTTP请求的数量对于加快网站速度非常有帮助,但我们还可以做更多。合并资源后,应最大限度地减少传输到用户的字节数。最小化字节通常采用缩小、优化和压缩的形式。

JavaScript和CSS

JavaScript和CSS是文本资源,可以有效地缩小。缩小是一个转换原始文本的过程,通过消除与浏览器无关的任何内容来实现。对JavaScript和CSS的转换都从删除注释和额外空格开始。JavaScript缩小更加复杂。一些缩小器执行转换,将多字符变量名替换为单字符,删除不是严格必要的语言结构,甚至可以将整个语句替换为更短的等效语句。

UglifyJSYUICompressorGoogle Closure Compiler是三种流行的JavaScript缩小工具。

两个CSS缩小器包括YUICompressor和UglifyCSS

图像

图像通常包含可以在不影响其视觉质量的情况下删除的数据。删除这些多余的字节并不困难,但需要专门的图像处理工具。我们自己的Francois Marier撰写了两篇关于使用PNG使用GIF的博客文章。

Smush.it来自雅虎,是一个在线优化工具。ImageOptim是OSX的等效离线工具——只需将图像拖放到工具中,它就会自动减小图像大小。您无需执行任何操作——ImageOptim只需用更小的文件替换原始文件。

如果可以接受视觉质量的损失,则可以选择以更高的压缩级别重新压缩图像。

服务器也可以提供帮助!

即使在合并和缩小资源之后,还有更多工作要做。几乎所有服务器和浏览器都支持HTTP压缩。两种最流行的压缩方案是deflate和gzip。这两种方案都利用高效的压缩算法来减少字节数,然后再将其发送到服务器。

缓存

合并和压缩有助于首次访问我们网站的访客。第三个C,缓存,则有助于返回的访客。返回我们网站的用户不应重新下载所有资源。HTTP提供了两种广泛采用的机制来实现这一点,即缓存头和ETag。

缓存头有两种形式,适用于很少更改(如果有的话)的静态资源。这两种标题选项是ExpiresCache-Control: max-ageExpires标题指定必须重新请求资源的日期。max-age指定资源的有效秒数。如果资源具有缓存头,则浏览器仅在缓存过期日期过去后才会重新请求该资源。

ETag本质上是一个资源版本标签,用于验证资源的本地版本是否与服务器的版本相同。ETag适用于动态内容或随时可能更改的内容。当资源具有ETag时,它会告诉浏览器“检查服务器以查看版本是否相同,如果相同,则使用您已有的版本。”由于ETag需要与服务器进行交互,因此它不如完全缓存的资源高效。

缓存清除

使用基于时间/日期的缓存控制头而不是ETag的优势在于,只有在缓存过期后才会重新请求资源。这也是其最大的缺点。如果资源发生变化会怎样?缓存必须以某种方式清除。

缓存清除通常通过在资源URL中添加版本号来完成。对资源URL的任何更改都会导致缓存未命中,进而导致资源被重新下载。

例如,如果http://example.com/logo.png的缓存头设置为一年后过期,但徽标发生了更改,则已经下载该徽标的用户只能在一年后看到更新。可以通过在URL中添加某种版本标识符来解决此问题。

http://example.com/v8125/logo.png

或者

http://example.com/logo.png?v8125

当徽标更新时,使用新版本,这意味着徽标将被重新请求。

http://example.com/v8126/logo.png

或者

http://example.com/logo.png?v8126

Connect-cachify – 用于提供合并和缓存资源的NodeJS库

Connect-cachify是Mozilla开发的一个NodeJS中间件,它可以轻松地提供正确合并和缓存的资源。

在生产模式下,connect-cachify会提供预生成的生产资源,缓存过期时间为一年。如果不是在生产模式下,则会提供单独的开发资源,从而简化调试。Connect-cachify本身不执行合并和缩小,而是依赖于您在项目的构建脚本中执行此操作。

connect-cachify的配置通过setup函数完成。Setup接受两个参数,assetsoptionsassets是生产资源到开发资源的字典。每个生产资源都映射到其各个开发资源的列表。

  • options是一个可选的字典,可以采用以下值
  • prefix – 要附加到链接中的哈希之前的字符串。(**默认值:无**)
  • production – 布尔值,指示是否提供开发资源或生产资源。(**默认为true**)
  • root – 提供静态资源的完整限定路径。这与您发送到静态中间件的值相同。(**默认值:‘.’**)

connect-cachify使用示例

首先,假设我们有一个简单的HTML文件,我们希望与connect-cachify一起使用。我们的HTML文件包含三个CSS资源以及三个Javascript资源。

...

  Dashboard: Hamsters of North America 
  
  
  


  ...
  
  
  

...

设置中间件

接下来,在您的NodeJS服务器中包含connect-cachify库。创建您的生产资源到开发资源的映射并配置中间件。

...
// Include connect-cachify
const cachify = require('connect-cachify');

// Create a map of production to development resources
var assets = {
  "/js/main.min.js": [
    '/js/lib/jquery.js',
    '/js/magick.js',
    '/js/laughter.js'
  ],
  "/css/dashboard.min.css": [
    '/css/reset.css',
    '/css/common.css',
    '/css/dashboard.css'
  ]
};

// Hook up the connect-cachify middleware
app.use(cachify.setup(assets, {
  root: __dirname,
  production: your_config['use_minified_assets'],
}));
...

为了保持代码DRY,资产映射可以外部化到其自己的文件中,并用作connect-cachify和构建脚本的配置。

更新模板以使用cachify

最后,必须更新您的模板以指示应在何处包含生产JavaScript和CSS。JavaScript使用“cachify_js”助手包含,而CSS使用“cachify_css”助手包含。

...

  Dashboard: Hamsters of North America 
  <%- cachify_css('/css/dashboard.min.css') %>


  ...
  <%- cachify_js('/js/main.min.js') %>

...

Connect-cachify输出

如果在配置选项中将production标志设置为false,则connect-cachify将生成三个link标签和三个script标签,与原始标签完全相同。但是,如果将production标志设置为true,则只会生成每个标签中的一个。每个标签中的URL都将在其URL之前添加生产资源的MD5哈希。这用于缓存清除。当生产资源的内容发生变化时,其哈希也会发生变化,从而有效地清除缓存。

...

  Dashboard: Hamsters of North America 
  


  ...
  

...

这就是设置connect-cachify的全部内容。

结论

在寻求加速网站时,有很多简单的技巧可以应用。通过回归基础并使用三个C——连接、压缩和缓存——您将朝着改善网站加载时间和用户体验迈出一大步。Connect-cachify 帮助您在 NodeJS 应用程序中进行连接和缓存,但我们还可以做更多。在 NodeJS 假期系列的下一期中,我们将探讨如何生成动态内容并利用 ETagify 来继续提供最大程度可缓存的资源。

系列中的先前文章

这是 一个包含 12 篇关于 Node.js 的文章系列中的第四部分。之前的文章是


5 条评论

  1. Steve Souders

    很棒的文章!我很高兴您在 URL 前面添加了哈希值。最好 不要使用查询字符串来修改版本号

    2012 年 12 月 21 日 下午 4:45

    1. Lloyd Hilaiel

      感谢您的光临和参考,Steve!

      您是否有关于 Vary 标头和代理缓存的类似问题的参考?

      2013 年 1 月 31 日 上午 10:20

  2. srigi

    我真的很喜欢您的“辅助”cachify_css() 函数,它可以在开发模式下扩展链接。您能说明如何在使用 Jade 模板引擎的情况下实现此功能吗?

    2012 年 12 月 28 日 上午 6:06

    1. Lloyd Hilaiel

      嘿,srigi!

      我没有关于 Jade 的链接,但这是 .ejs 中的样子: https://github.com/mozilla/browserid/blob/56c2996b83b1bf89c64a6095689ecf6a6803a2ea/resources/views/layout.ejs

      2013 年 1 月 31 日 上午 10:16

  3. Andrew Griffiths

    信息不错。

    对于任何不使用 Express 的人,我编写了一个独立的 Node 脚本,用于处理版本控制,该脚本也使用 MD5 哈希进行编号。

    https://npmjs.net.cn/package/node-version-assets

    文档中提供了有关如何将其与 Grunt 集成的说明。

    2013 年 2 月 22 日 上午 6:02

本文的评论已关闭。