w3ctech

前端性能优化の备忘录(2017版)

原文:https://www.smashingmagazine.com/2016/12/front-end-performance-checklist-2017-pdf-pages/

你是不是已经在使用progressive booting渲染方式?你觉得React以及Angular中的tree-shaking(译者注:从模块包中排除未使用的exports项)以及code-splitting(译者注:在分离点中依赖的模块会被打包到一起,可以异步加载)机制怎么样?你是不是也曾实践过Brotli以及Zopfli压缩算法、OCSP(译者注:OCSP的全称Online Certificate Status Protocol)分层模型以及针对HTTP头部压缩的HPACK算法?除IPv6、HTTP/2、service workers等常规性能优化技巧之外,你觉得资源优化小技巧(resource hints)客户端(特指浏览器)优化小技巧(client hints)以及css containment属性(译者注:让开发者控制浏览器的样式、布局、重绘的工作区域)怎么样?

在以前(Back in the day),大家对性能优化总是摆出一副事后诸葛亮(a mere afterthought)的样子。(举个栗子),就算它的工作内容只是对资源进行压缩、合并、优化或者对服务器配置文件(的配置项)做一些微调,(万万没想到的是😱),大家还是习惯性地把(针对性能优化的)排期拖到项目末尾。现在我们再回过头看,性能优化似乎变得更加重要啦。

针对性能进行优化不仅仅只是出于技术层面的忧虑(concern):真的是因为性能优化非常重要,尤其在(我们需要)工作流中引入性能优化环节,且需要根据性能优化建议来提示(我们)进行决策的情况下,性能优化就会显得尤为必要,另外性能必须是可以不断被量化、被监控以及被优化的。现在随着web应用的复杂性日益增加,(自然而然)就会给性能指标分析(keep track of)带来新的挑战,这是因为性能指标之间的差异性非常大,这取决于使用的设备、浏览器、协议、网络类型以及其它能够对性能产生影响的潜在因素(CDNISP、缓存(cache)、代理(proxy)、防火墙(firewall)、负载均衡(load balancer)以及服务器(server))。

所以,在(网站)项目刚启动时,我们就应该(对网站)采取(有助于网站)性能提升的手段,并且该过程一直持续到网站最终版本发布上线。在这段时间里,假设我们需要牢记这些优化手段,并且希望针对这些优化手段做一下总结,(内心os:天啦噜),这总结应该长啥样呀?往下阅读,你就会发现一篇写前端性能优化写的非常客观、很有用的年度总结:前端性能优化の备忘录2017版(简单来说,这是一篇能够确保让你解决类似如何让网站响应更加迅速、访问更加流畅等前端性能优化问题的年度好文)。

(你也可以只下载前端性能优化の备忘录PDF版本或者去Apple Pages下载前端性能优化の备忘录(0.236MB)。让我们一起愉快的优化前端性能吧!)

前端性能优化の备忘录(2017版)

虽说Micro-optimization可以让你的性能优化手段起作用,但是(我觉得大家)还是应该在思想层面上树立一个明确的目标,这是因为目标量化会影响整个项目期间所作出的任何决定,所以说树立明确的目标才是至关重要的。(至于如何解决前端性能问题),这里有很多不同的优化模型(model),不过我们下面将要讨论的(优化模型)内容是十分武断的,所以你只需要确保(在使用上述优化模型时)使用自己的优先次序就行啦。

准备好了的话,那就设立一个小目标吧

网站总是要快人一步(Be 20% faster than your fastest competitor)

根据psychological research指出,如果你想让用户感觉你网站(的载入速度)就是比其它网站快,那么你网站(的载入速度)至少要比其它网站快20%才行。(另外你会发现),全屏载入时间跟一些(用于衡量网站载入速度)指标如启动渲染时间(start render time)首次有效渲染时间(first meaningful paint)(在这段时间内,页面已经显示出主要内容)、交互时间(time to interactive)(指的是用户与页面或者单页应用(适合用在用户参与程度比较高的项目{appears to be ready enough that a user can interact with it})进行交互的时间)没有半毛钱关系。

open device实验室中,我们利用Moto G手机(低端机)、Samsung手机(中端机)以及各项参数都很平均的手机如Nexus 4来估测不同型号手机在常规的网络环境下(3G、4G、WIFI)的启动渲染时间(使用WebPagetest来估测该时间)以及首次有效渲染时间(使用Lighthouse来估测该时间)。

Lighthouse, a new performance auditing tool by Google

通过查看分析报告就可以分析出用户的用户行为。如果该分析报告只是在测试环节使用,那么允许你只模拟出90%的场景。(要知道完成一篇分析报告,首先要)收集数据,(然后将收集的数据)做成表格(spreadsheet),(并且根据自身的情况),剔除部分(20%)数据,接下去你就可以去设立完成一篇分析报告(例如性能开销(performance budgets))的小目标啦。此时(如果你恰好有机会去)对一些环节进行性能测试,又假如你记住了(测试过程中所花费的)性能开销,然后就开始尝试脚本最小化的方法,来让交互时间的数据更好看,接下来你就会发现你已经踏上了性能优化的“不归路”(a reasonable path)。

Performance budget builder by Brad Frost

给你的同事安利性能优化の备忘录,(不过在安利之前),(首先)要确保你团队的成员都熟悉备忘录中的优化策略,这样就能避免日后出现(备忘录优化策略)使用方法上的误区。在我们需要做决策的环节,性能优化の备忘录都会有相对应的性能优化小贴士,当项目的设计理念、用户体验(UX)以及视觉设计都敲定好了,接下来项目的成功,很大程度得益于前端工程师的积极参与。Map design的设计理念,很明显违背性能开销,(不过幸好)在性能优化の备忘录中定义了优先级。

控制响应时间在100ms,控制帧速在60帧/秒(100-millisecond response time, 60 frames per second)

RAIL performance model提出一些不错的性能优化指标:在用户初始化input输入框后,务必在100ms之内给出反馈。考虑到存在响应时间不足100ms的情况,页面最迟在50ms的时候,把控制权交给主线程。另外针对优化起来有压力的环节(如动画),最好的做法是,在你能够优化的地方,努力优化到极致;在你不能够优化的地方,努力让(性能开销)降至最低。

因此,动画的每一帧都应该控制在16ms以内,这样就可以实现60fps(ls/60 = 16.6ms),当然把每一帧控制在10ms以内更合适。要实现60fps的目标,因为要考虑到浏览器把(新的)画面绘制在屏幕上也是需要时间的,所以你的代码应该在16.6ms之前结束运行。be optimistic,并且合理利用16.6ms剩下的时间。很显然,这些(性能优化)指标适合用于优化运行时的性能(runtime performance),而不是用于优化载入时的性能(loading performance)

控制首次有效渲染时间在1.25s以内,控制SpeedIndex在1000以内(First meaningful paint under 1.25 seconds, SpeedIndex under 1000)

说实在话,这个目标实现起来可能有点难度。(对了),你的性能优化最终目标应该还是这些吧(控制启动渲染时间{start rendering time}在1s以内以及保证SpeedIndex低于1000{在网速最理想的情况下})。对于首屏绘制时间来说,至多可以被我们优化到1.25s。启动渲染时间被控制在3s以内,对于处于3G网络环境的移动端设备来说,这还是可以接受的。(实际得出的数据)只是稍高于上面给出的数据,(或许大家觉得)这结果还不错,不过我们还是应该尽可能的让获取的数据更好看。

环境搭建

做好构建工具的选型以及搭建好(适合自己的)构建工具(Choose and set up your build tools)

不要过分关注那些在现在看来很炫酷的技术栈,根据自己项目环境来选择构建工具(比如使用Grunt、Gulp、Webpack、PostCSS或者结合使用上述工具)。另外只要你的构建工具选型有更快的产出,并且在项目构建过程中没有遇到问题,(就可以说)你的构建工具选型就是好的。

渐进式提升(Progressive enhancement)

把(如何)渐进式提升性能作为前端架构以及项目部署的指导思想,这不失为一种安全的做法(a safe bet)。首先要设计出具有核心(用户)体验的产品,然后在(支持新特性的)浏览器使用新特性来提升用户体验,从而营造出极具弹性Web设计(resilient)的用户体验。假设你的网站都能在这些糟糕的环境下(烂屏幕、烂浏览器、渣网络以及运行超慢的电脑)跑的很快,那么在更好的环境下(好浏览器、好网络以及运行超快的电脑),你的网站只会跑的更快。

Angular、React、Ember以及其它前端框架(Angular, React, Ember and co)

喜欢某个前端框架的前提:该框架能够进行服务端渲染。在敲定使用(哪个)前端框架之前,一定要在框架开启服务端&客户端渲染模式的时候测试移动设备的启动(boot)时间(这是因为前端框架一旦确定,如果在后面使用框架的过程中出现性能问题,那时候想再换框架就会变得极其困难)。如果你确实需要使用JavaScript框架,一定要确保你对框架的选型是基于对这框架有一定的了解以及自己深思熟虑的结果。不同的框架不仅会对性能造成不同的影响,而且需要不同的优化策略来处理性能问题,所以你必须要对(自己所使用)框架的细节做到心中有数(understand all of the nuts and bolts of the framework)。等你有机会去开发web app的时候,推荐你去看PRPL pattern以及application shell architecture

PRPL stands for Pushing critical resource, Rendering initial route, Pre-caching remaining routes and Lazy-loading remaining routes on demand

An application shell is the minimal HTML, CSS, and JavaScript powering a user interface

使用AMP技术或者Instant Articles技术?(AMP or Instant Articles?)

基于项目的解决方案以及优先级,你可能想使用google的AMP技术或者facebook的Instant Articles技术。当然你也可以在不使用这些技术的前提下做到优化性能,不过AMP技术还会提供一套可靠的性能优化框架(基于免费的CDN网络),与此同时Instant Articles技术也会优化facebook网站性能。当然你也可以把AMP做成渐进式web AMP

合理利用CDN(Choose your CDN wisely)

基于你动态数据的规模,你可能会把一部份内容“外包”(outsource)给用静态站点生成工具如Jekyll生成的静态文件,接着把静态文件推送到CDN上,最后CDN只是提供静态文件的静态版本,所以这样就可以避免发起对数据库的读写请求。甚至你可以搞一个基于CDN的静态文件托管平台(static-hosting platform),(这样就可以)通过给页面添加可交互组件的方式来丰富你的页面,并以此作为性能提升的标志(JAMStack)。

请注意,CDN也是可以托管并卸载(offload)动态内容的,所以咱们没有必要把CDN的服务范围限定在静态资源。(另外需要你记住的是),不管你的CDN是否执行内容压缩(GZip)、内容转换、HTTP/2传输以及ESI(一种标记语言,可以用它把网页划分为单独的可缓存的实体)等操作,我们还是需要复核上述操作的,这是因为上述操作不仅会在CDN的edge处(服务器最接近用户的地方)聚合页面中的静态以及动态内容,也还会执行其它任务。

优化构建

如实(straight)设置优先级(Set your priorities straight)

知道自己第一步要干啥,(对自己来说)是件好事。例如,首先在页面中写好并运行请求(某目录下所有)资源(JS、图片、字体、第三方js脚本以及开销很大的模块{比如轮播图、复杂的信息图(infographics)以及多媒体内容})的代码,然后(使用浏览器的开发者工具Network选项卡)将这些资源进行分组。

先搞清楚资源(assets)可以分为几类(set up a spreadsheet),大致可以分为:

  • 针对传统浏览器定义的核心(core)型资源(比如可以完整访问的核心型内容)
  • 针对现代浏览器定义的增强(enhanced)型资源(比如增强型内容)
  • 其它(extras)型资源(指的是该资源可选且支持懒加载,如web字体、多余的样式、轮播脚本、媒体播放器、社交媒体(social media)按钮以及大图)。 我们发表了一篇关于“提升Smashing Magazine的性能”的博文,这里面会详细讲述有关于上述问题的解决办法。

使用“cutting-the-mustard”技术(Use the “cutting-the-mustard” technique)

使用cutting-the-mustard技术就能实现不同类型的浏览器载入不同类型的资源(传统浏览器载入核心型资源,现代浏览器载入增强型资源)。在载入资源时要严格遵守相应的规则:页面加载时应首先载入Core资源,然后在DomContentLoaded事件触发时载入Enhancement资源,最后在Load事件触发时载入Extras资源。

请注意,cutting-the-mustard技术是可以通过(安装在设备中的)浏览器来推断出该设备性能的好坏,然而该技术的有些细节在现在看来其实是没有实现的必要。举个例子,现在发展中国家生产(低配版)的安卓机不但会内置chrome浏览器,而且都能达到(技术标准所提出的)要求(虽说安卓机在内存以及CPU性能方面饱受诟病)。但你要知道,尽管我们现在还没找到可替代方案,(不过我们还是可以预见),使用cutting-the-mustard技术的限制会越来越多。

可以考虑micro-optimization以及progressive booting(Consider micro-optimization and progressive booting)

在你能看到app(渲染出的)页面内容之前,有些app可能需要一些时间来初始化app。所以在初始化app的过程中,我们可以使用以下方式来实现更快的产出。

最后,可以考虑用Optimize.js(其实是通过在外层添加立即执行函数的方式实现,不过这实现方式可能不再需要)来实现(在app初始时能够)快速载入。

Progressive booting means using server-side rendering to get a quick first meaningful paint, but also include some minimal JavaScript to keep the time-to-interactive close to the first meaningful paint

是采用客户端渲染还是采用服务端渲染?在这两个使用场景中,我们的目标都应该是实现progressive booting:先通过使用服务器端渲染快速完成首次有效渲染(first meaningful paint),浏览器再通过少量的JS代码就可以让交互时间接近于首次有效渲染时间。在时间允许或者项目需要的情况下,我们会考虑使用progressive boot来渲染app的部分页面。不幸的是,正如Paul Lewis强调的那样,一般来说,框架是不存在优先级的概念,所以很难通过库以及框架的方式来实现progressive booting。假设你时间和资源都充裕的话,就可以使用上述策略来实现boost性能的终极目标。

你会正确设置HTTP cache header吗?(Are HTTP cache headers set properly?)

(设置HTTP cache header的正确做法),其实是需要对已经正确设置的expirescache-controlmax-age以及其它HTTP缓存响应头(HTTP cache header)进行二次检查。一般而言,资源都应该被缓存下来,(除非资源在短时间内可能会变或者资源属于没有时间限制的(indefinitely)静态资源,你才可以对该资源不进行缓存处理),这样一来,在需要资源的时候,你只需要改变资源在URL中版本号。

如果可以的话,请使用Cache-control: immutable(其实是为了解决fingerprinted静态资源的缓存问题而被设计出来的,解决了客户端revalidation问题,截止2016年12月底,仅Firefox支持https事务中使用Cache-control: immutable)。当然你也可以参考这些Heroku’s primer on HTTP caching headers、Jake Archibald的缓存之最佳实践以及Grigorik的HTTP caching primer

控制第三方库(的引入),并使用异步的方式载入JavaScript(Limit third-party libraries, and load JavaScript asynchronously)

在用户请求网页的过程中,浏览器首先会去请求HTML内容,并根据HTML内容构建DOM,接着去请求CSS内容,并根据CSS内容构建CSSOM,然后通过匹配DOM结构以及CSSOM结构来创建渲染树(rendering tree)。在这个过程中,如果有JavaScript脚本需要被解析执行,那么浏览器就会停止渲染页面,直到JavaScript脚本被执行完,这样会造成浏览器延迟渲染CSS以及HTML。作为一名开发者,我们有责任告诉浏览器:“其实你是可以不等js执行完,然后就开始渲染页面的”。(让浏览器实现上述操作的)办法其实就是在HTML的script标签上添加defer以及async属性。

实践表明,我们应该更偏向于使用async(要让IE浏览器(包括IE9)支持defer,花费的代价太大啦(at a cost to users of Internet Explorer up to and including version 9),这是因为在IE浏览器中使用defer会破坏js之间的依赖)。另外,我们也要控制(因为)第三方库以及js框架(的引入)给项目带来的影响,尤其是在我们需要引入社交(social)分享按钮以及嵌入第三方的<iframe>(例如map组件)时需要特别注意。当然你也可以使用第三方社交分享按钮(例如SSBG分享按钮)或者用可交互map组件的链接代替map组件。

你会合理优化图片吗?(Are images properly optimized?)

要实现图片的响应式,请尽可能地使用带有srcsetsizes属性的HTML标签(如<picture>),代码使用如下:

<!--原文中没有下面代码,由译者自己添加,不要打我😭-->
<picture>
  <source media="(max-width:480px)" srcset="img/480x320.gif">
  <source media="(max-width:640px)" srcset="img/640x427.gif">
  <source media="(max-width:720px)" srcset="img/720x480.gif">
  <source media="(max-width:900px)" srcset="img/900x600.gif">
  <img src="img/900x600.gif" alt=""/>
</picture>

在你使用<picture>标签过程中,就可以(针对<picture>的srcset属性)最大限度使用WebP格式的图片,(当浏览器不支持<picture>标签时),需要准备好可替代方案(其实就是用JPEG图片替代,具体细节可以去看Andreas Bovens写的代码)或者使用HTTP中内容协商(content negotiation)机制(其实就是使用HTTPAccept请求头)。Sketch软件本身就支持WebP格式的图片,另外Photoshop可以通过借助WebP的Photoshop插件就能导出WebP格式的图片,当然你也可以有其它选择

Responsive Image Breakpoints Generator automates images and markup generation

你也可以使用client hints属性(查看浏览器的支持程度)。没有足够的资源来对响应式图片进行压测?(不要方),我们其实是可以使用响应式图片breakpoint generator工具或者使用能够自动优化图片的服务(Cloudinary)来解决上述问题的。经过很多测试用例之后,我们发现:单独使用srcsetsizes属性时会收到意想不到的结果。下面是Smashing Magazine针对图片优化的做法:我们会在图片名后面追加-opt后缀(例如brotli-compression-opt.png),所以无论在什么时候,大家只要看到图片名后面带有-opt,就可以认为该图片已被人优化过。

图片优化进阶(Take image optimization to the next level)

在开发登录页面过程中,最关键的部分就是实现大图的快速载入,(天啦噜😱,那如何实现上述功能呢)?方法如下:

  • 可以使用渐进式JPEG图片
  • 可以使用压缩工具对(不同格式的)图片进行压缩(例如,JPEG图片用mozJPEG(通过控制层级扫描(scan level)的方式来优化启动渲染时间)压缩、PNG图片用Pingo压缩、GIF图片用Lossy GIF压缩、SVG图片用SVGOMG压缩)。
  • 可以通过抹掉不必要的图片细节(通过给图片添加高斯模糊滤镜实现)来减小文件的大小,(好的实现方式其实有很多,不必太过于纠结),有可能你最后使用这种方式(不给图片加颜色或者使用纯白、纯黑的图片)来减小文件的大小😂。
  • 对于图片只是用做背景图的需求来说,完全可以使用PhotoShop导出(质量在0-10%)的图片。

纳尼,你还是觉得上面列出的方法都不够好😡?(画个圈圈诅咒你),当然你也可以使用多张背景图的技巧来提高对图片性能感知的能力。

你会优化web字体?(Are web fonts optimized?)

在使用web字体的过程中,很有可能出现字体不再包含这种字形或者字体不再支持额外特性的情况,(肿么办😭)?没关系的,其实你可以让设计师(type foundry)给你Web字体系列的子集(subset)字体,或者在你用开源字体实现字体文件最小化(实现的方式很多,例如在字体中收录一些用来表示特殊语调的Latin字符)时,定制属于自己的子集(subset)字体😬。(总体上看),浏览器对WOFF2的支持度还是不错的,当浏览器不支持WOFF2的时,你需要准备好可替代方案(其实就是用WOFF字体、OTF字体替代WOFF2字体)。当然你也可以从Zach Leatherman的字体载入策略综合指南选出一些(针对字体优化的)策略,并且也可以使用service worker来达到字体缓存持久化。(内心os:这好麻烦啊),有没有针对字体优化的速成手册呀😇?(还别说,你运气可真好),Pixel Ambacht就写过一些关于如何快速入门字体优化的教程,并且拥有关于如何让你的字体正确载入的第一手资料。 Zach Leatherman’s Comprehensive Guide to Font-Loading Strategies provides a dozen of options for bettern web font delivery

如果你遇到自己的服务器上没有某种字体,需要引入第三方字体库的情况,请务必使用Web Font Loader载入字体。要知道字体出现FOUT(Flash of Unstyled Text)的情况要好于出现FOIT(Flash of Invisible Text);浏览器会立即使用备用(fallback)字体来渲染文本,并且通过异步的方式载入(用@font-face声明的)字体,当然你也可以通过loadCSS来完成上述操作,貌似你也可以通过在本地安装系统字体的方式来完成上述操作

利用(能够对“缓存进行感知”的)服务器快速推送critical CSS文件(Push critical CSS quickly)

为了保证能够让浏览器快速渲染出网页,(大家总结出)一些常见的做法:把所有用于首屏(可见区域)渲染的CSS文件整合成一个文件(该CSS文件被称之“critical CSS”或者“above-the-fold CSS”),然后以<style>的行内形式内嵌到<head>标签里面,这样做是会减少critical渲染路径(rendering path)的roundtrip。在(浏览器)慢启动的期间,由于HTTP数据包大小的限制,因此critical CSS文件大小不能超过14KB。如果(critical CSS)文件大小超过14KB,浏览器将会通过额外的roundtrip来拿到更多的样式文件。虽说CriticalCSSCritical也能帮你完成上述操作,不过这都是基于你(可能)需要对每个页面都使用CriticalCSS或者Critical为前提。如果可能的话,可以考虑使用Filament Group总结出的conditional inlining approach

有了HTTP/2协议,critical CSS就可以用单个CSS文件存储啦,并通过服务器推送CSS文件的传输方式来让HTML文件瘦身。有时候你需要处理这些异常情况:

  • 对服务器推送支持不够
  • 存在缓存问题(需要了解详情,可以去看Hooman Beheshti的演讲PPT第114页)

说实在话,(上面所描述的)push服务的负能量爆棚(be negative),另外网络缓冲区溢出也会导致(真实存在文档流中的)frame的内容不被分发的局面。由于TCP慢启动机制的存在,所以在网络环境还不错的情况下,服务器推送将会变得更加高效。接下来,你或许需要得出基于HTTP/2服务器推送的缓存感知算法。记住,尽管新的cache-digest规范不支持这种手动构建能够对“缓存进行感知”服务器的做法,不过从目前来看,我们还是应该手动构建能够对“缓存进行感知”服务器。

使用tree-shaking以及code-splitting机制来减轻负载(Use tree-shaking and code-splitting to reduce payloads)

在你进行项目构建的过程中,Tree-shaking机制能够帮你清理生产环境中的冗余代码。例如,你可以利用Webpack2中的Tree-Shaking机制来清理冗余的exports代码或者使用UnCSSHelium工具来清理冗余的CSS代码。或许你也想知道究竟如何才能写出高效的CSS选择器以及究竟如何才能避免自己写出冗余、低性能的CSS代码

code splitting机制实际上是Webpack的一个特性,该特性能够将你的代码块分成多个“chunk”,并且能够做到对“chunk”按需载入。另外,一旦你在代码块中定义好分割点(split point),那么Webpack就会帮你处理好分割点之间的依赖关系,而且还会输出相对应的文件。在项目中使用Webpack的split point特性之后,(我们发现)该特性不仅能够实现对文件的瘦身,而且还能对(所需要的)代码做到按需载入。

值得注意的是,通过对比Browserify之后,我们发现:“用Rollup来export代码会取得更加不错的效果”。还有就是,你可能需要用到Rollupify(Browserify进行transform时需要用到的工具,能够将ES6模块转成CommonJS模块),这是因为小型规模的模块(module)能够起到意想不到的优化效果,(至于究竟可以优化到哪种程度),这取决于你选择的打包工具以及模块系统(AMD、CommonJS、ES6模块)。

提升渲染性能(Improve rendering performance)

首先,可以通过使用css containment属性的方式来达到隔离(性能开销大的)组件的目的。举个🌰:

  • 在浏览器渲染样式时,限制其渲染的范围
  • 在浏览器布局时,限制其布局的范围
  • 限制对(canvas范围之外)组件的绘制(paint)工作
  • 限制对第三方组件的使用 然后,确保自己做到以下两点:
  • 在页面滚动的过程中,不会出现卡顿、延迟的现象
  • 在元素产生动画的过程中,不会出现卡顿、延迟的现象

因为只有这样,你才有可能让帧速维持在60fps的水平。万一(帧速)实在是达不到60fps,那你至少也要让帧速维持在15fps-60fps的水平。 最后,推荐使用CSS属性will-change(该属性能够在元素的属性改变之前通知浏览器)。

当然,我们同样需要估测浏览器在处于运行时渲染模式下的性能(例如,使用开发者工具栏中的渲染工具查看渲染情况)。为了(更好地)开始测试,可以去听Paul Lewis关于(如何)优化浏览器渲染的免费公开课。另外我们这里也有Sergey Chikuyonok写的一篇关于如何正确的开启GPU动画

优化网络环境能够加快网络传输的速度(Warm up the connection to speed up delivery)

  • 使用skeleton screen或者使用懒加载的方式载入字体或者HTTP开销很大的组件,例如,视频、iframe、轮播图
  • 为节省时间,可以针对下面列出的操作使用资源优化的小技巧
    • dns-prefetch(能够让浏览器在后台进程执行一次DNS查询),<link rel="dns-prefetch" href="//fonts.googleapis.com"
    • preconnect(能够让浏览器在后台进程发起一次握手(DNS,TCP,TLS)),
    • prefetch(能够让浏览器发起对资源的请求),<link rel="prefetch" href="http://daker.me/2013/05/hello-world.html"/>
    • prerender(能够让浏览器在后台进程渲染出特定的页面),<link rel="prerender" href="http://daker.me/2013/05/hello-world.html"/>
    • preload(在不执行资源的前提下,预先拿到该资源),<link rel="preload" as="script" href="map.js" media="(min-width: 601px)">

值得注意的是:在实践过程中,由于浏览器对这些东西支持不一,因此对于preconnect来说,你要更倾向于使用dns-prefetch,另外在使用prefetchprerender的过程中,你需要特别小心,这是因为使用prerender的场景(指的是你要很清楚地知道用户下一步所做的决策,例如购买渠道)很特殊。

HTTP/2

为HTTP/2环境的搭建做好准备

在Google鼓励(所有使用HTTP的)网站做好HTTPS的迁移以及Chrome会认定(所有使用HTTP的)网页为不安全的大背景下,你需要做出是死守HTTP1.1还是拥抱HTTP/2的决定。从目前来看,浏览器对HTTP/2支持度还是不错的;事实就摆在那里(it isn’t going anywhere),而且在大多数情况下,使用HTTP/2是一种很明智的做法。虽说用好HTTP1.1也很重要,(不过你不要高兴太早),你的网站迟早是需要迁移到HTTP/2环境的。最重要的是,在使用HTTP/2的过程中,你可以利用service worker以及HTTP/2的服务器推送功能来获取更显著的性能提升(至少从长远看)。

Eventually, Google plans to label all HTTP pages as non-secure, and change the HTTP security indicator to the red triangle that Chrome uses for broken HTTPS. (Image source)

在项目进行HTTPS改造的过程中,遭遇的风险到底有多大,这完全取决于HTTP/1.1项目的用户(专指那些还在使用低版本操作系统或者使用低版本浏览器的用户)基数,万一(HTTP/1.1项目的)用户基数很大的话,那你就需要针对这类用户构建并发送符合HTTP2规范的报头。不过需要注意的是:同时进行HTTPS迁移操作以及(针对HTTP协议的)构建操作,可能会让上述操作变得复杂、耗时。另外接下来讲述的内容,主要是针对以前切过HTTP/2环境或者对切HTTP/2环境不是很熟的读者。

正确部署HTTP/2(Properly deploy HTTP/2)

重要的事情说两遍,在你开始使用HTTP/2请求资源之前,需要搞清楚你以前是如何请求资源的。另外需要你在载入大模块以及并行载入小模块之间找到一个平衡点。 一方面,你可能不愿意把所有资源合并到一起,而是希望把所有视图都分散到(小型)模块里面,然后在项目构建的过程中完成对(小型)模块的压缩,最后通过“scount” approach以及异步的方式来分别实现对模块的引用及载入。这样的话,就不存在文件改变需要(重新)引入整个样式文件或者重新下载JavaScript脚本的情况。 另一方面,HTTP/2环境下打包js文件时存在问题,这是因为向浏览器发送很多JS小文件的过程中会存在很多问题。 首先,文件压缩的优势被破坏。在压缩大文件的过程中,借助目录重用的特点,达到优化性能的目的,然而单个文件就不能。虽说有方法可以解决上述问题,但是这些方法在现在看来有点激进。 其次,浏览器不能针对一些工作流进行优化。例如,在Chrome浏览器,IPCs与资源数成线性关系,因此(向浏览器发送)数以百计的文件资源会导致浏览器产生运行时开销。

To achieve best results with HTTP/2, consider to load CSS progressively, as suggested by Chrome’s Jake Archibald.

你仍然可以尝试通过渐进的方式载入CSS。很显然,这种做法不利于HTTP/1.1的用户,因此在部署HTTP/2的过程中,你可能需要针对不同的浏览器创建并发送(该浏览器支持)HTTP协议的报头,这(还只)是部署过程中稍微复杂的地方。不过你可以通过HTTP/2 connection coalescing(可以让你在HTTP/2环境使用域名共享)来避开这些,不过在现实中很难实现。 肿么办?如果你简单了解过HTTP/2,(就应该知道),(一次请求)发送10个数据包似乎是不错的折中做法(上述做法即使是放到以前的浏览器身上,效果也不会太差)。(话虽如此),不过我们还是需要通过实验、测试等手段来帮网站在载入大模块以及并行载入小模块之间寻求合适的平衡点。

务必保证服务器的安全性(Make sure the security on your server is bulletproof)

在所有支持HTTP/2的浏览器都支持(run over)TLS安全协议的大背景下,假设你遇到以下问题:

  • 浏览器出现安全警告⚠️,不过我想屏蔽这些安全警告,肿么办(在线等,挺急的)😭?
  • 访问网页发现某些组件失效,肿么办(在线等,挺急的)😭?

(办法还是有的),那就是仔细检查是否正确✅设置HTTP请求头部如strict-transport-security使用Snyk工具排除已知的漏洞以及使用SSL Server Test网站来检查证书是否失效。 你还没有(把项目的线上环境)切成HTTPS?(好吧,真心对你无语😓),那你就先看一下HTTPS标准从入门到精通,(起码对HTTPS有个初步印象),然后就是保证所有(从外部引入的)插件以及埋点(js)脚本的载入都是走HTTPS协议的,不过这点对于跨站脚本来说,这几乎是不可能。最后就是在发起HTTP请求同时设置strict-transport-security以及content-security-policyHTTP请求头。

服务器以及部署的CDN都支持HTTP/2吗?(Do your servers and CDNs support HTTP/2?)

不同的服务器以及CDN对HTTP/2支持度也不一样,不过你可以使用TLS工具检查服务器支持的特性或者快速浏览(下面给出的)图表,这样你就可以知道(自己在用的)服务器的性能到底如何,并且可以知道该服务器到底支持哪些新特性。

Is TLS Fast Yet? allows you to check your options for servers and CDNs when switching to HTTP/2.

对于Brotli或者Zopfli压缩算法,你实践了没有?(Is Brotli or Zopfli compression in use?)

去年,Google开源了Brotli(其实是一种无损的数据格式,目前已(广泛)被Chrome、Firefox、Opera支持)。在实践过程中,(我们发现),用Brotli算法压缩比用Gzip算法以及用Deflate算法压缩更有效率。Brotli能否实现渐进式压缩,这取决于你给Brotli的配置,另外你压缩的越慢会导致最终输出的压缩率越高。用Brotli算法解压也很快,因为这是Gooler写的算法呀,浏览器只有在用户访问HTTPS网站时才会处理(用Brotli压缩算法处理过的)响应头,这没毛病,没有必要大惊小怪,当然我们也承认里面肯定有出于技术考虑的成分。另外需要我们注意⚠️的是,现在很多的服务器都不会内置Brotli服务,而且在没有对NGINX源码或者Ubuntu源码编译的情况下,创建Brotli服务可以说是非常难的。不过你还是可以在使用service worker的情况下激活Brotli服务,甚至是在不支持Brotli服务的CDN激活Brotli服务。 或者,你可以先调研一下Zopfli压缩算法的用法(能够将数据编码成Deflate、Gzip、Zlib数据格式),对于经常使用Gzip算法压缩的资源来说,(很大程度)得益于能够改善Deflate编码格式的Zopfli压缩算法,因为(用Zopfli算法压缩过后的)文件能够比同样用Zlib算法压缩的文件小3%-8%。另外需要我们注意⚠️的是,用Zopfli算法压缩这些文件花的时间比用Zlib算法压缩这些文件花的时间的80倍还多。这也是为什么只有在资源经历压缩前后改变不大的情况下才推荐使用Zopfli压缩算法,一旦决定对文件使用压缩算法进行压缩,接下来就会出现文件被多次下载的场景。

你激活OCSP stapling了吗?(Is OCSP stapling enabled?)

激活服务器的OCSP stapling之后,然后你就可以加速TLS握手过程。OCSP协议其实是作为CRL协议的另一种实现而被创建出来的。OCSP协议以及CRL协议在过去常被用于检查SSL认证是否被激活等事情。另外OCSP协议既不需要浏览器在下载方面花太多的时间,也不需要在搜索认证信息方面花太多的时间,因此OCSP可以减少TLS握手所需的时间。

你上IPv6了吗?(Have you adopted IPv6 yet?)

IPv4地址空间被分配的所剩无几,以及大多数的移动网络都会用上IPv6(在美国,IPv6覆盖率已经达到临界值50%)的大背景下,通过更新DNS解析IPv6规则的方式来保证将来不会出错,这不失为一种靠谱的做法。而且只需要保证在跨网络的时候能够支持dualstack(允许IPv4服务、IPv6服务并行)。毕竟在设计IPv6之初,人家就没有考虑向下兼容😔。另外研究也发现:正因为IPv6自带NDP以及路由优化(的buffer),所以能够让网站的载入速度提升10%到15%。

对于(针对HTTP响应头压缩的)HPACK压缩算法,你实践了吗?(Is HPACK compression in use?)

如果你在用HTTP/2,那么请你认真检查有关于服务器实现HPACK算法(旨在减少不必要的HTTP开销)的具体细节。考虑到出现(HTTP/2)服务器的时机有点晚以及(上述服务器)对HTTP/2规范的支持度不够等因素,所以大家应该以🌰(example)的心态来看待HPACK实践。(关于如何实践HPACK),我们强烈推荐大家使用H2spec工具。服务器成功开启HPACK服务,画面是酱紫的

H2spec (View large version) (Image source)

用service worker来处理缓存问题或者将service worker作为网络(出现问题时)的应急方案,合适吗?(Are service workers used for caching and network fallbacks?)

你要知道,(在有网以及服务器已被优化的情况下),用户从服务端拿数据是不可能比用户从本地拿缓存来的快。如果你的网站已经切到HTTPS,推荐你使用pragmatist-service-worker(不但可以(让你)通过service worker cache来缓存静态资源、离线资源(比如离线的页面),而且可以让你从缓存中拿数据)。当然,你也可以去看Jake的Offline Cookbook或者去看Udacity开设的免费公开课“离线web应用”。浏览器对serviceworker支持度怎么样?你要的答案在这里,不管浏览器对service worker支持度怎么样,性能优化的备用方案仍然是基于网络的。

在做好测试的同时部署好监控

监控混合内容出现的⚠️(警告)(Monitor mixed-content warnings)

如果你是最近才把服务器从HTTP迁到HTTPS,那请你务必监控这些在混合内容出现频率很高或者出现频率很低的⚠️(警告)。(你运气还真好),还可以使用Report-URI.io工具来完成上述监控。另外你也可以使用Mixed Content Scan工具扫描(能够支持HTTPS访问的)网站是否存在混合内容(mixed content)。

DevTool的开发工作流就是优化吗?(Is your development workflow in DevTools optimized?)

(和DevTool打交道的)工程师的开发工作流如下:在DevTool选中调试工具选项卡,然后把调试工具上所有的🔘(按钮)都点一下。(不过这是有前提的),首先要保证你是知道如何使用开发者工具来分析渲染性能以及如何使用控制台工具来输出结果。Umar Hansa最近时间都在忙于准备自己的PPT以及演讲(帮解答大家在使用开发者工具中存在不理解的知识点,以及帮大家科普一些关于如何用开发者工具进行调试以及性能测试的小技巧)。

在浏览器开代理或者浏览器的版本比较低的环境下,你测试过吗?(Have you tested in proxy browsers and legacy browsers?)

不是说在Chrome以及Firefox浏览器环境下测试通过就行啦,你应该去了解在浏览器开代理或者浏览器的版本比较低的环境下网站背后的运行机制。UC浏览器以及Opera mini在亚洲都占有较大的市场份额(在亚洲的市场份额已经达到35%)。为了避免自己以后被网络坑哭,建议你去了解一下自己(感兴趣)国家的平均网速。对于需要进行网络节流测试或者需要对高DPI设备进行仿真测试的用户来说,BrowserStack是个不错的选择,因为BrowserStack不仅可以完成上述操作,而且也可以完成真机上的测试。

(对性能开销大的环节),是否可以做到持续监控?(Is continuous monitoring set up?)

部署私密的WebPageTest测试环境,真的有助于快速地构建出测试用例。接下来,应该是对性能开销大的环节建立起自动报警机制。然后在需要对(与业务相关的)指标进行监控的情况下,设置自定义的标记。另外可以去调研SpeedCurve(在网络出现超时的情况下,可以监控性能的改变),或者使用New Relic(用于获取WebPagetest不能提供的),当然也可以去体验SpeedTrackerLighthouse以及Calibre工具

w3ctech微信

扫码关注w3ctech微信公众号

共收到0条回复