https://github.com/Findow-team/Blog/issues/11?utm_source=tuicool&utm_medium=referral
你开始使用渐进启动了么?是不是已经使用过React和Angular中tree-shaking和code-splitting两个工具?有没有用过Brotli、Zofli和HPACK这几种压缩技术,或者OCSP协议(在线证书状态协议)?知不知道资源提醒,客户端提醒和CSS containment一类的技术?了解IPv6,HTTP/2和Service Worker这些协议吗?
回想那些年,大家往往在完成了产品之后才会去考虑性能。常常把与性能相关的事情拖到项目的最后来做,所做的也不过是对服务器上的config
文件进行一些微调、串联、优化以及部分特别小的调整。而现在,技术已经有了翻天覆地的变化。
一个项目的性能是非常重要的,除了要在技术层面上注意,更要在项目的设计之初就开始考虑,这样才可以使性能的各种隐形需求完美的整合到项目中,随着项目一起推进。性能最好具有可量化、可监测以及可改动的特性。网络越来越复杂,对网络的监控也变得越来越难,因为监测的过程会受到包括设备、浏览器、协议、网络类型以及其他技术(CDN,ISP,缓存,代理服务器,防火墙,负载均衡器和服务器对性能的影响都很大)的很大影响。
下文是一份2017年的前端性能优化清单,阐述了作为前端开发人员,为了确保反馈速度以及浏览器兼容性我们需要考虑的问题。
(你也可以下载checklist PDF或者check in Apple Pages。优化万岁!)
微优化是保持性能最好的办法,但是又不能有太过明确的优化目标,因为过于明确的目标会影响在项目中做的每一个决定。以下是一些不同的模型,请按照自己舒服的顺序阅读。
根据一个心理学研究,你的网站最少在速度上比别人快20%,才能让用户感觉到你的网站比别人的更快。这个速度说的不是整个页面的加载时间,而是开始加载渲染的时间,首次有效渲染时间(例如页面需要加载主要内容的时间),或者交互时间(指的是页面或者应用中主要的页面加载完成,并主备好与用户进行交互的时间)。
在Moto G(或中端三星设备)和Nexus 4(比较主流的设备)上衡量开始渲染时间(用WebPagetest)以及首页有效渲染时间(用Lighthouse),最好是在一个开放的实验室中,使用规范的3G,4G和Wi-Fi链接。
Lighthouse,一个Google开发的新的性能审查工具
你可以通过你的分析报告看看你的用户处在哪个阶段,选取其中前90%的用户体验来做测试。接着收集数据,建一个表格,筛去20%的数据并预设一个目标(如:性能预算)。现在你可以将上述两个值进行对比检测。如果你始终维持着你的目标并且通过一点一点改变脚本去加快交互时间,那么上述方法就是合理可行的。
由Brad Frost创建的性能分析
和你的同事分享这份清单。首先要确保团队中的每个人都熟悉这份清单。项目中每一个决定都会影响性能,如果前端工程师们都在积极的参与项目概念,UX以及视觉设计的决定,这将会给整个项目带来巨大收益。地图设计的决定违背了性能理念,所以他在这份清单内的顺序有待考虑。
RAIL性能模型会为你提供一个优秀的目标,既尽最大的努力在用户初始操作后的100毫秒内提供反馈。为了达到这个目标,页面需要放弃权限,并将权限在50毫秒内交回给主线程。对于像动画一样的高压点,最好的方法就是什么都不做,因为你永远无法达到最小绝对值。
同理,动画的每一帧都需要在16毫秒内完成,这样才能保证每秒60帧(一秒/60=16.6毫秒),如果可以的话最好能在10毫秒内完成。因为浏览器需要一定的时间去在屏幕上渲染新的帧,而且你的代码需要在16.6毫秒内完成执行。要注意,上述目标应用于衡量项目的运行性能,而非加载性能。
纵使这个目标实现起来非常困难,你的最终目标也应该是让开始渲染时间低于1秒且速度指数低于1000(在网速快的地方)。对于首次有效渲染时间,上限最好是1250毫秒。对于移动端,3G下移动设备首次渲染时间低于3秒都是可以接受的。稍微高一点也没关系,但千万别高太多。
不要过多的关注当下最流行的工具,坚持选择适合自己开发环境的工具,例如Grunt、Gulp、Webpack、PostCSS,或者组合起来的工具。只要这个工具运行的速度够快,而且没有给你的维护带来太大问题,这就够了。
在构建前端结构的时候,应始终将渐进增强作为你的指导原则。首先设计并且构建核心体验,随后再完善那些为高性能浏览器设计的高级特性的相关体验,创建弹性体验。如果你的网页可以在使用低速网络、老旧显示器的很慢的电脑上运行飞快,那么在光纤高配电脑上它只会运行的更快。
最好使用那些支持服务器端渲染的框架。在使用某个框架钱,先记录服务器和客户端的引导时间,记得要在移动设备上测试,最终才能使用某个框架(因为面对的是性能问题,如果在使用某个框架后,再做修改是非常困难的)。如果你使用JavaScript框架,要保证你的选择是被广泛使用并且经过考验的。不同框架对性能有着不同程度的影响,同时对应着不同的优化策略,所以最好可以清楚的了解你要用的框架的每个方面。在写网页应用时可以先看看PRPL pattern和application shell architecture。
本图描述了PRPL pattern
上图是application shell,是一个小型的、由HTML,CSS和JavaScript构成的用户界面
根据你整体组织结构的优先顺序和策略,你可以考虑使用Google的AMP或Facebook的Instant Articles。要知道没有这些你也可以达到不错的性能,但是AMP可以提供一个性能不错的免费的内容分发网络框架(CDN),Instant Articles可以在Facebook上促进你的性能。你也可以建立progressive web AMP。
根据你的动态数据量,可以将一部分内容外包给静态网站生成工具,将它置于CDN上,从中生成一个静态版本,从而避免那些数据库的请求。也可以选择基于CDN的静态主机平台,通过交互组件丰富你的页面(JAMStack)。
注意CDN是否可以很好的处理(或分流)动态内容。没必要单纯的将你的CDN限制为静态。反复检查CDN是否执行了内容的压缩和转化,检查智能HTTP/2传输和缓存服务器(ESI),注意哪些静态或动态的部分处在CDN的边缘(最接近用户的服务器)。
首先应该弄清楚你想解决的问题是什么。检查一遍你所有的文件(JavaScript,图片,字体,第三方script文件以及页面中重要的模块,例如轮播,复杂信息图标和多媒体内容),并将他们分类。
列一个表格。明确浏览器上应该有的基础核心内容,哪些部分属于为高性能浏览器设计的升级体验,哪些是附加内容(那些不必要或者可以被延时加载的部分,例如字体,不必要的样式,轮播组件,播放器,社交网站入口,很大的图片)。更详细的细节请参考文章”Improving Smashing Magazine’s Performance‘’。
使用符合标准的技术向过时的浏览器提供核心体验,向老式浏览器提供增强体验, 同时对所加载的内容要有严格的把控。即首要加载核心体验部分,将增强部分放在DomContentLoaded
,把额外内容发在load
事件中。
以前我们可以通过浏览器的版本推断出设备的性能,但现在我们已经无法推测了。因为现在市场上很多廉价的安卓手机都不考虑内存限制和CPU性能,直接使用高版本的Chrome浏览器。一定要注意,在我们没有其他选择的时候,我们选择的技术同时可能成为我们的限制。
在一些应用中,可以在渲染页面前先初始化应用。最好先显示框架,而不是一个进度条或指示器。使用可以加速初始渲染时间的模块或技术(例如tree-shaking和code-splitting),因为大部分性能问题来自于应用引导程序的初始分析时间。还可以在服务器上提前编译,从而减轻部分客户端的渲染过程,从而快速输出结果。最后,考虑使用Optimize.js来加快初始加载速度,它的原理是包装优先级高的调用函数(虽然现在已经没什么必要了)。
渐进启动指的是使用服务器端渲染,从而快速得到首次有效渲染,这个渲染过程也包括小部分的JavaScript文件,目的是使交互时间尽可能的接近首次有效渲染时间。
到底采用客户端渲染还是服务器端渲染?不论哪种做法,我们的目标都是建立渐进启动:使用服务器端渲染可以得到很短的首次有效渲染时间,这个渲染过程也包括小部分的JavaScript文件,目的是使交互时间尽可能的接近首次有效渲染时间。接下来,尽可能的增加一些应用的非必要功能。不幸的是,正如Paul Lewis所说,框架基本上对开发者是没有优先级的概念的,因此渐进启动在很多库和框架上是很难实施的。如果你有时间的话,还是考虑使用策略去优化你的性能吧。
仔细检查一下例如expires
,cache-control
,max-age
以及其他HTTP缓存头是否被正确的使用。一般来说,资源不论在短时间(如果它会被频繁改动)还是不确定的时间内(如果它是静态的)都是可缓存的——你大可在需要的时候在URL中成改版本。
如果可能,使用为指纹静态资源设计的Cache-control:immutable,从而避免二次验证(2016年12月,只有FireFox在https://
处理中支持)。你可以使用,Heroku的primer on HTTP caching headers,Jake Archibald的 ”Caching Best Practices”,还有IIya Grigorik的HTTP caching primer作为指导。
当用户请求页面时,浏览器会抓取HTML同时生成DOM,然后抓取CSS并建立CSS对象模型,最后通过匹配DOM和CSS对象生成渲染树。在需要处理的JavaScript文件被解决之前,浏览器不会开始对页面进行渲染。作为开发者,我们要明确的告诉浏览器不要等待,直接开始渲染。具体方法是使用HTML中的defer
和async
两个属性。
事实上,defer
更好一些(因为对于IE9及以下用户对于IE9及以下用户,很有可能会中断脚本)。同时,减少第三方库和脚本的使用,尤其是社交网站的分享按键和嵌入(比如地图)。你可以使用静态的社交网站分享按键(例如SSBG的)和指向交互地图的静态链接去代替他们。
尽可能的使用带有srcset
,sizes
还有元素的响应式图片。你也可以利用
元素的WebP格式,用JPEG格式作为替补(参见Andreas Bovens的code snippet)或是使用内容协商(使用接受头)。Sketch原本就支持WebP,WebP图片可以直接被Photoshop的WebP plugin导出。当然也有很多其他方法。
响应图片断点生成器可自动处理图片
你也可以使用客户端提示,现在浏览器也可以做到。在用来生成响应图片的源文件过少时,使用响应图片断点生成器或类似Cloudinary的服务自动的优化图片。在很多案例中,单独使用sreset
和sizes
都带来了很大的收益。在本网站上,我们给文件添加-opt
后缀——例如brotli-compression-opt.png
;这样团队的每一个人就知道这些带着后最的图片是被优化过的。
当你在编写登陆界面的时候,发现页面上的图片加载的特别快,这时你需要确认一下JPEG格式文件是否已经通过mozJPEG(它可以操作扫描等级从而提高渲染时间)优化和压缩,PNG格式对应Pingo,GIF需要用到Lossy GIF,SVG要使用SVGOMG。对图片不重要的部分进行模糊处理(使用高斯模糊过滤器处理他们),从而减少文件大小,最后你可能还要去彩色化使图片变成黑白,从而减少更多的容量。对于背景图片,使用Photoshop保持0到10%的质量输出是绝对可以接受的。
如果你还觉得不够,那你可以通过多重背景图片技术来提高图片的感知性能。
你用来修饰网页字体的服务很有可能毫无用处,包括字形和额外的特性。如果你在使用开源的字体,尝试用字体库中某一个小的子集或是自己归类一个小的子集从而压缩文件大小(例如通过一些特殊的注音符号引用Latin)。WOFF2 support是个非常不错的选择,如果浏览器不支持,那你可以将WOFF和OTF作为备用。你也可以从Zach Leatherman的“Comprehensive Guide to Font-Loading Strategies”一文中选择一个合适的策略,然后使用服务器来缓存字体。如果想要快速入门,Pixel Ambacht的教程与案例会让你的字体变得尽然有序。
Zach Leatherman的“Comprehensive Guide to Font-Loading Strategies”提供了一打可以让字体传输变得更好的选项
如果你用的是第三方服务器主机,没办法自己在服务器上对字体进行操作,一定看看Web Font Loader。FOUT is better than FOIT中提到,在备选情况下立即渲染文本,并且异步加载字体——你也可以使用loadCSS实现这个。你可能也会避免本地OS上安装字体。
为了确保浏览器尽可能快的渲染你的页面,先收集页面首次可见部分的CSS文件(也叫决定性CSS或上半版CSS)进行渲染,然后将它加入页面的部分,从而避免重复操作。因为慢启动阶段对交换包大小的限制,你关键CSS文件的大小也被限制在14KB左右。如果高于这个值,浏览器需要重复一些步骤来获取更多的样式。关键CSS是允许你这样做的。可能对每个模板都需要这个操作。如果可能,考虑一下用Fiament Group用的条件内敛方法。
通过HTTP/2,关键CSS可以单独存为CSS文件,通过服务器传输,而且可以避免HTML膨胀。服务器传输缺乏连续支持,并且存在一些超高速缓存的问题(Hooman Beheshti演示的前144页)。事实上,这样会导致网络缓冲区膨胀。因为TCP的慢启动,服务器传输在稳定的连接下会更有效率。所以你可能需要建立带有缓存的HTTP/2服务器传输机制。但请记住,新的cache-digest
规则会否定手动建立的需要缓存的服务器的请求。
Tree-shaking是通过保留那些在项目过程中真正需要的代码从而清理你的构建过程的一种方式。你可以用Webpack 2来提出那些没用的住配置文件,用UnCSS或Helium从CSS中取出不需要的样式。同理,也可以考虑学习一下如何编写高效的CSS选择器,以及如何避免膨胀和高费的样式。
Code-splitting是另一个Webpack特性,它可以根据按需加载的块将你的代码分开。只要你在代码中定义了分离点,Webpack便会处理好相关的输出文件。它基本上能保证你初始下载的内容很小,而且在需求被添加时按需请求代码。
Rollup所展示的结果要比Browserify配置文件所显示的好得多。所以当我们想使用类似工具的时候,或许应该看看Rollupify,它将ECMAScript2015模块变成了一个更大的CommonJS模块——因为小模块没准有出乎意料的高性能成本,这源自于你对打包工具模块系统的选择。
使用类似CSS containment的方法对高消耗组建进行隔离,从而限制浏览器样式的范围,可以作用在为无canvas的浏览工作的布局和装饰工作中,或是用在第三方工具上。要确保页面滚动和出现动画效果时没有延迟,别忘了之前提到过的每秒60帧的原则。如果没办法做到,那就尽可能保证每秒帧数的大致范围在15到60帧。使用CSS中的will-change通知浏览器哪些元素和属性发生了变化。
也记得要衡量渲染执行中的性能(可以用DevTools)。可以参照Udacity上Paul Lewis的免费课程——浏览器渲染优化,作为入门。还有一篇Sergey Chikuyonok的文章讲了如何正确的用GPU动画。
使用页面框架,对高消耗组建延迟加载(字体,JS文件,循环播放,视频和内嵌框架)。使用资源提示来节省在dns-prefetch
(指的是在后台执行DNS检索),preconnect
(指要求浏览器在后台进行握手链接(DNS,TCP,TLS)),prerender
(指要求浏览器在后台对特定页面进行渲染),preload
(指的是提前取出暂不执行的源文件)。根据你浏览器的支持情况,尽量使用preconnect
来代替dns-prefetch
,而且在使用prefetch
和prerender
要特别小心——后者(prerender
)只有在你非常确信用户下一步操作的情况下再去使用(比如购买流程中)。
Google开始向着更安全网页的方向努力,并且将所有Chrome上的HTTP网页定义为“不安全”时,你或许应该考虑是继续使用HTTP/1.1,还是将目光转向HTTP/2环境。虽然初期投入比较大,但是使用HTTP/是大趋势,而且在熟练掌握之后,你可以使用service worker和服务器推送技术让行性能再提升一大截。
现在,Google计划把所有HTTP页面标记为不安全,并且将HTTP安全指示器设置为与Chrome用来拦截HTTPS的红色三角相同的图标。
使用HTTP/2的环境的缺点在于你要转移到HTTPS上,并且根据你HTTP/1.1用户的数量(主要指使用过时操作系统和过时浏览器的用户),你需要适应不同的建构过程,才能发送不同的建构。注意:不论是迁移还是新的构建过程都可能非常棘手而且耗时很多。
重申,使用HTTP/2协议之前,你需要彻底排查目前为止你所使用协议的情况。你需要在打包组建和同时加载很多小组间之间找到平衡。
一方面,你可能想要避免将很多资源链式链接,与其将你全部的界面分割成许多小模块,不如将他们压缩使之成为建构过程的一部分,之后给它们赋予可检索的信息并加载它们。这样的话,对一个文件将不再需要重新下载整个样式清单或JavaScript文件。
另一方面,封装是很有必要的,因为一次向浏览器发送太多JavaScript文件会出问题。首先,压缩会造成损坏。得益于dictionary reuse,压缩大文件不会对文件造成损害,压缩小文件则不然。确实有方法可以解决这个问题,但这不是本文讨论的范畴。其次,浏览器还没有优化到可以对类似工作流进行优化。例如,Chrome会引发进程间通信(IPCs),这些通信的数量与资源的数量成正比,而这成百上千个资源将会消耗大量的浏览器的执行时间。
Chrome的Jake Archibald建议,为了用HTTP/2达到最好的效果,考虑一下逐步加载CSS文件
当然你可以考虑逐步加载CSS文件。很显然,你这样做对HTTP/1.1的用户非常不利,所以你可能需要根据不同的浏览器建立多个版本来应对你的调度过程,这样就会使过程略微复杂。你也可以避免HTTP/2连接的合并,同时受益于HTTP/2来使用域名碎片,但是实现起来有些困难。
我们到底应该做什么呢?如果你粗略的用过HTTP/2,似乎成功的发送过10个左右的包(在老是浏览器上运行的也不错)。那你就能着手开始试验并且为你的网站找到平衡点。
所有的浏览器都支持HTTP/2并且使用TLS,这是有你可能想要避免安全警告,并删除页面上没用的元素。好好检查你的安全头部是否设置正确,排除已知的缺陷并检查证书。
如果还没有迁移到HTTP, 你那可以先看看HTTPS准则(The HTTPS-Only Standard)。确保所有外部插件和监视脚本都能被HTTPS正确加载,确保没有跨站脚本出现,HTTP脚本传输的安全头和内容安全头也都设置正确。
不同服务器和CDN对HTTP/2的兼容性不同,你可以使用TLS够快吗?一文来查看你有什么选择。
Is TLS Fast Yet?让你能看看你的服务器和CDN在使用HTTP/2的时候你能使用的工具
2015年,Google介绍了Brotli,一个新的开源无损数据格式,它已经被Chrome,Firefox和Opera很好的兼容了。具体使用时,Brotli所显示出的效率要远高于Gzip和Deflate。它根据不同的配置可能压缩的时候会比较慢,但是压缩速度慢最后会让压缩效率提高。而且解压起来非常快。因为这个算法来自Google,所以浏览器只在用户通过HTTPS访问网页的时候使用它,这个事情就不奇怪了。Brotli的隐患是它没办法在目前大部分服务器上预设,而且如果没有NGINX或者Ubuntu,部署起来还是有难度的。但其实你是可以在不支持它的CDN上使用Brotli(通过service worker)。
你可以看看Zopfli压缩算法作为备选,它将数据编码为Deflate,Gzip和Zlib格式。任何规范的Gzip压缩资源都受益于Zopfli改进了Deflate编码,因为文件会比Zlib压缩的最大文件小3%-8%。问题在于文件会消耗大概80倍的时间去压缩。这就是为什么在不怎么会变得资源上使用Zopfli是不错的选择,这样的文件一般都压缩一次,下载多次。
让服务器使用OCSP装订,可以提升你TLS握手的速度。线证书状态协议(OCSP)是作为证书废置列表协议的代替品被创造出来的。两个协议都可以用来检测SSL证书是否被取消。然而,OCSP不需要浏览器花时间下载和扫描证书信息的列表,所以它可以减少握手时间。
因为我们已经没什么IPv4的地址可用了,而且移动网络大都开始使用IPv6(美国已经有50%的入口采用IPv6),将你的DNS升级到IPv6为以后作打算是个不错的选择。要确保通网络可以支持双栈协议——它需要允许IPv6和IPv4同时运行。毕竟IPv6不只是做为后备计划的。而且研究显示,伴随邻居发现(NDP)和路由优化,使用IPv6的网站要比普通网站快10%到15%。
如果你在使用HTTP/2,看看你的服务器有没有执行HPACK对HTTP的响应头进行压缩,来减少不必要的消耗。因为HTTP/2服务器相对较新,所以有些像HPACK这样的规格目前还没有被支持。我们可以用H2spec来检查HPACK是否在工作。
H2spec的截图
没有经过优化的网络可以比用户机器的本地缓存跑得更快。如果你的网站在HTTPS上运行,你可以参考“实用主义者的service workers手册”,然后把静态资源存在service worker的缓存中,把离线预设(甚至离线页面)存在用户机器方便检索,这样比多次进行网络连接更有效。你还可以参考Jake的离线使用手册和免费的Udactiy课程“离线网络应用”。如果浏览器支持,那就再好不过了,预设就能在任何地方代替网络了。
如果你近期完成了HTTP到HTTPS的迁移,你可以利用类似Report-URI.io一类的对主动和被动的混合内容警告都进行监听。也可以利用混合内容扫描器来对你使用HTTPS的网页进行扫描。
选一个调试工具来对每一个按钮进行检查。确保自己明白如何分析渲染性能和控制台输出、明白如何调试JavaScript以及编辑CSS样式。Umar Hansa最近有一个关于使用DevTools调试和测试的分享,主要包括一些不常用的技巧和技术。
仅仅使用Chrome和Firefox测试是不够的。还应该看看你的网页在代理浏览器和过时浏览器上运行的怎么样。比如UC浏览器和Opera Min, 它们在亚洲市场的份额很高(高达35%)。在推广时,利用目标客户所在国家的平均网速来进行测试,避免日后出现大的误差。同样的,不仅要在节流网络以及模拟的高数据处理设备上进行测试,还要在真实设备上测试。
在进行快速、无限制的测试时,最好使用一个个人的WebPageTest实例。建立一个能自动预警的性能预算监听。建立自己的用户时间标记从而测量并监测具体商用的数据。使用SpeedCurve对性能的变化进行监控,同时利用New Relic获取WebPageTest没法提供的数据。SpeedTracker,Lighthouse和Calibre都是不错的选择。
这份清单综合性很强,几乎介绍了所有的可用的优化方式。那么,如果你只有一个小时进行优化,你应该干什么呢?让我们从中总结10个最有用的来。别忘了在你开始优化前和结束优化后,记录你的结果,包括开始渲染时间以及在3G,有限连接下的速度。
- 有线连接下,你的目标是将开始渲染时间降低至1s一下,而3G的目标是保持在3s一下,SpeedIndex值保持在1000一下。为开始渲染时间和交互时间做优化。
- 为你主要的模板准备关键CSS文件,将它们放在页面的
中(你可以使用14KB)。
- 对于你自己和第三方的脚本文件,尽可能的延迟加载它们——尤其是社交网站按钮,播放器和高消耗的JavaScript文件。
- 使用更快的
dns-lookup
,preconnect
,prefetch
,preload
和prerender
添加资源提示,从而加快传输速度。- 将字体一类属性作为子集,异步加载(或者使用系统字体代替)。
- 优化图片,并考虑在关键页中使用WebP(例如登陆页面)。
- 确保HTTP的缓存头和安全头都被正确的设置。
- 在服务器上使用Brotli或Zopfli压缩算法。(如果不支持这两个,尝试一下Gzip)
- 如果HTTP/2可用,使用HPACK压缩算法,并监听混合内容的警告。如果你在使用LTS,就可以使用OCSP装订。
- 如果可能,将类似字体,JavaScript文件以及图片缓存在service worker缓存中——事实上越多越好!
文中提到的一些优化可能超出了你工作的范围,有的可能超出了你的预算,有的甚至对你正在使用的有些过时的代码判了死刑。但没关系,本文只是一个普通大纲(希望能做到综合性强),你应该根据自己的工作环境列一份适合自己的清单。最重要的,在开始优化之前,先对项目中存在的问题有一个明确的了解。最后,祝大家2017年优化顺利!