大前端之性能优化

表格1 性能优化原则分类css

目前大多数前端团队能够利用yui compressor或者google closure compiler等压缩工具很容易作到“精简Javascript”这条原则;一样的,也可使用图片压缩工具对图像进行压缩,实现“图像优化”原则。这两条原则是对单个资源的处理,所以不会引发任何工程方面的问题。不少团队也经过引入代码校验流程来确保实现“避免css表达式”和“避免重定向”原则。目前绝大多数互联网公司也已经开启了服务端的Gzip压缩,并使用CDN实现静态资源的缓存和快速访问;一些技术实力雄厚的前端团队甚至研发出了自动CSS Sprites工具,解决了CSS Sprites在工程维护方面的难题。使用“查找-替换”思路,咱们彷佛也能够很好的实现“划分主域”原则。html

咱们把以上这些已经成熟应用到实际生产中的优化手段去除掉,留下那些尚未很好实现的优化原则。再来回顾一下以前的性能优化分类:前端

优化方向git

优化手段github

请求数量web

合并脚本和样式表,拆分初始化负载浏览器

请求带宽缓存

移除重复脚本性能优化

缓存利用服务器

添加Expires头,配置ETag,使Ajax可缓存

页面结构

将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出

 

表格2 较难实现的优化原则

如今有不少顶尖的前端团队能够将上述还剩下的优化原则也都一一解决,但业界大多数团队都还没能很好的解决这些问题。所以,本文将就这些原则的解决方案作进一步的分析与讲解,从而为那些尚未进入前端工业化开发的团队提供一些基础技术建设意见,也借此机会与业界顶尖的前端团队在工业化工程化方向上交流一下彼此的心得。

静态资源版本更新与缓存

如表格2所示,“缓存利用”分类中保留了“添加Expires头”和“配置ETag”两项。或许有些人会质疑,明明这两项只要配置了服务器的相关选项就能够实现,为何说它们难以解决呢?确实,开启这两项很容易,但开启了缓存后,咱们的项目就开始面临另外一个挑战:如何更新这些缓存。

相信大多数团队也找到了相似的答案,它和《高性能网站建设指南》关于“添加Expires头”所说的原则同样——修订文件名。即:

最有效的解决方案是修改其全部连接,这样,全新的请求将从原始服务器下载最新的内容。

思路没错,但要怎么改变连接呢?变成什么样的连接才能有效更新缓存,又能最大限度避免那些没有修改过的文件缓存不失效呢?

先来看看如今通常前端团队的作法:

或者

你们会采用添加query的形式修改连接。这样作是比较直观的解决方案,但在访问量较大的网站,这么作可能将面临一些新的问题。

一般一个大型的web应用几乎天天都会有迭代和更新,发布新版本也就是发布新的静态资源和页面的过程。以上述代码为例,假设如今线上运行着index.html文件,而且使用了线上的a.js资源。index.html的内容为:

此次咱们更新了页面中的一些内容,获得一个index.html文件,并开发了新的与之匹配的a.js资源来完成页面交互,新的index.html文件的内容所以而变成了:

好了,如今要开始将两份新的文件发布到线上去。能够看到,index.html和a.js的资源其实是要覆盖线上的同名文件的。无论怎样,在发布的过程当中,index.html和a.js总有一个前后的顺序,从而中间出现一段或大或小的时间间隔。对于一个大型互联网应用来讲即便在一个很小的时间间隔内,都有可能出现新用户访问。在这个时间间隔中,访问了网站的用户会发生什么状况呢?

  1. 若是先覆盖index.html,后覆盖a.js,用户在这个时间间隙访问,会获得新的index.html配合旧的a.js的状况,从而出现错误的页面。
  2. 若是先覆盖a.js,后覆盖index.html,用户在这个间隙访问,会获得旧的index.html配合新的a.js的状况,从而也出现了错误的页面。

这就是为何大型web应用在版本上线的过程当中常常会较集中的出现前端报错日志的缘由,也是一些互联网公司选择加班到半夜等待访问低峰期再上线的缘由之一。此外,因为静态资源文件版本更新是“覆盖式”的,而页面须要经过修改query来更新,对于使用CDN缓存的web产品来讲,还可能面临CDN缓存攻击的问题。咱们再来观察一下前面说的版本更新手段:

咱们不难预测,a.js的下一个版本是“1.0.1”,那么就能够刻意构造一串这样的请求“a.js?v=1.0.1”、“a.js?v=1.0.2”、……让CDN将当前的资源缓存为“将来的版本”。这样当这个页面所用的资源有更新时,即便更改了连接地址,也会由于CDN的缘由返回给用户旧版本的静态资源,从而形成页面错误。即使不是刻意制造的攻击,在上线间隙出现访问也可能致使区域性的CDN缓存错误。

此外,当版本有更新时,修改全部引用连接也是一件与工程管理相悖的事,至少咱们须要一个能够“查找-替换”的工具来自动化的解决版本号修改的问题。

对付这个问题,目前来讲最优方案就是基于文件内容的hash版本冗余机制了。也就是说,咱们但愿工程师源码是这么写的:

可是线上代码是这样的:

其中”_82244e91”这串字符是根据a.js的文件内容进行hash运算获得的,只有文件内容发生变化了才会有更改。因为版本序列是与文件名写在一块儿的,而不是同名文件覆盖,所以不会出现上述说的那些问题。同时,这么作还有其余的好处:

  1. 线上的a.js不是同名文件覆盖,而是文件名+hash的冗余,因此能够先上线静态资源,再上线html页面,不存在间隙问题;
  2. 遇到问题回滚版本的时候,无需回滚a.js,只须回滚页面便可;
  3. 因为静态资源版本号是文件内容的hash,所以全部静态资源能够开启永久强缓存,只有更新了内容的文件才会缓存失效,缓存利用率大增;
  4. 修改静态资源后会在线上产生新的文件,一个文件对应一个版本,所以不会受到构造CDN缓存形式的攻击

虽然这种方案是相比之下最完美的解决方案,但它没法经过手工的形式来维护,由于要依靠手工的形式来计算和替换hash值,并生成相应的文件。这将是一项很是繁琐且容易出错的工做,所以咱们须要借助工具。咱们下面来了解一下fis是如何完成这项工做的。

首先,之因此有这种工具需求,彻底是由web应用运行的根本机制决定的:web应用所需的资源是以字面的形式通知浏览器下载而聚合在一块儿运行的。这种资源加载策略使得web应用从本质上区别于传统桌面应用的版本更新方式。为了实现资源定位的字面量替换操做,前端构建工具理论上须要识别全部资源定位的标记,其中包括:

  • css中的@import url(path)、background:url(path)、backgournd-image:url(path)、filter中的src
  • js中的自定义资源定位函数,在fis中咱们将其规定为__uri(path)。
  • html中的<script src=”path”>、<link href=”path”>、<imgsrc=”path”>、已经embed、audio、video、object等具备资源加载功能的标签。

为了工程上的维护方便,咱们但愿工程师在源码中写的是相对路径,而工具能够将其替换为线上的绝对路径,从而避免相对路径定位错误的问题(好比js中须要定位图片路径时不能使用相对路径的状况)。

fis的资源定位设计思想

fis有一个很是棒的资源定位系统,它是根据用户本身的配置来指定资源发布后的地址,而后由fis的资源定位系统识别文件中的定位标记,计算内容hash,并根据配置替换为上线后的绝对url路径。

要想实现具有hash版本生成功能的构建工具不是“查找-替换”这么简单的。咱们考虑这样一种状况:

资源引用关系

因为咱们的资源版本号是经过对文件内容进行hash运算获得,如上图所示,index.html中引用的a.css文件的内容其实也包含了a.png的hash运算结果,所以咱们在修改index.html中a.css的引用时,不能直接计算a.css的内容hash,而是要先计算出a.png的内容hash,替换a.css中的引用,获得了a.css的最终内容,再作hash运算,最后替换index.html中的引用。

这意味着构建工具须要具有“递归编译”的能力,这也是为何fis团队不得不放弃gruntjs等task-based系统的根本缘由。针对前端项目的构建工具必须是具有递归处理能力的。此外,因为文件之间的交叉引用等缘由,fis构建工具还实现了构建缓存等机制,以提高构建速度。

在解决了基于内容hash的版本更新问题以后,咱们能够将全部前端静态资源开启永久强缓存,每次版本发布均可以首先让静态资源全量上线,再进一步上线模板或者页面文件,不再用担忧各类缓存和时间间隙的问题了!

相关文章
相关标签/搜索