网页性能优化 - 加载速度
本文最后更新于:几秒前
测试工具:
- http://webpagetest.org/
- Chrome-devtools-lighthouse
- …
性能关键词
- First Contentful Paint:FCP,首屏渲染时间
- Largest Contentful Paint : LCP,最大内容渲染时间
- Speed Index:代表页面内容渲染所消耗的时间
- Time to Interactive:TTI,用户与页面可交互时间
- Total Blocking Time:TBT,衡量从FCP到TTI之间主线程被阻塞时长的总和
主线程执行的任务分为长任务和短任务。规定持续时间超过50ms的任务为长任务,低于50ms的任务为短任务。长任务中超过50ms的时间被认为是“阻塞”的,因此,TBT是所有长任务中阻塞时间的总和。
- Cumulative Layout Shift:累计布局偏移,指网页布局在加载期间的偏移量
并发可以加快速度?
一个文件通过一个连接传输快,还是通过多个连接传输快?显然在「多连接传输的收益 > 建立连接的成本」条件成立下,必然是多连接传输快,也就是并发。
浏览器对同源HTTP/1.1连接的并发个数有限制,典型值是6,测试表明Chrome和Firefox都是这个值。根据实际情况充分利用最大连接数,可以让速度更快,文件分片上传/下载就是常见的情况。
解决最大连接数有常见几种方案
- 域名分片(就是多搞些不同的域名,打破同源条件)
- websocket(限制数相对较高)
- HTTP/2多路复用
说明
下面评测结果均为Chrome浏览器开发者工具内置的LightHouse得出
Q: 不同服务器硬件条件不一,LightHouse如何保证结果的稳定?
A: 它会模拟出一个尽可能相同的环境,然后自动模拟用户访问。但是受当时的服务器状态和网络条件影响,仍然会有测试误差。
FCP 优化
Gzip动态压缩
- 这是未开启压缩进行测试的结果截图
需要加载的各个文件都比较大,首次加载十分缓慢;二次加载时有缓存的支持,表现相对较好。
一般网页加载使用的文件大小在200kb左右,以便利用并发请求加快网页的加载速度(HTTP/1.1)。
查看Network
可以看到需要处理的文件Coding.js
、student.js
、Personal.js
;
查看产物目录下report.html
,使用Gzip
压缩可以大幅度减少文件的体积
- 进行Nginx的配置,开启
Gzip
压缩
1 |
|
- 配置后,查看是否生效
LightHouse
进行测试
开启Gzip压缩后测试:文件的体积大幅度减小,首屏加载时间FCP也从4s变为了1.2s
Gzip静态压缩
上面提及了Gzip的动态压缩,在请求到达的时候匹配到相应的压缩规则进行压缩,尽可能的降低文件大小来提高加载速度,实际上就是一个用服务器计算性能换取网络性能的操作。
假设缓存失效,大量访问涌入服务器将会占用大量的计算资源用以压缩;而静态资源文件没有变化,无需每次访问都重新压缩,于是就可以有「一次压缩,多次使用」的方法,即「静态压缩」。
- 配置Nginx服务器,开启静态压缩
1 |
|
- 配置webpack,输出经过gzip压缩的产物
安装compression-webpack-plugin
插件yarn add compression-webpack-plugin -D
1 |
|
注意:出现Cannot read property 'tapPromise' of undefined
错误提示的请降低插件的版本
- 部署服务器,查看是否生效
- 使用
LightHouse
进行测试
加载速度没有明显的变化,想想应该是存在并发量才能观察出来差异,这里不再深入
第三方库按需引入
使用element-ui、echarts等第三方库时,可以根据相应的文档使用按需引入,减少无用代码打包
第三方库使用CDN引入
student.js
的加载直接影响FCP,而Coding.js
和Personal.js
通过prefetch
进行预加载
所以想进一步提高首次加载速度,需要考虑优化student.js
vue-cli下自带prefetch和preload的优化,优化时可以关闭相应的优化便于查看首次加载文件
单页应用时:config.plugins.delete(‘prefetch’)
多页应用时:config.plugins.delete(‘prefetch-XXX’) 关闭对应页面XXX的prefetch插件
- 查看
report.html
中student.js
的构成
可以看到element-ui
的体积占用较高;结合项目实际使用情况,项目内已使用的组件较多,按需加载优化效果不明显;而组件库作为必要依赖且一般不会变化,可以考虑抽离使用模板HTML引入(又称CDN引入)。
有什么作用?使用外部免费CDN,加载速度较快,可以减少服务器的负载;公共依赖,利用缓存可以提高加载速度;但是需要注意的是外部免费CDN存在宕机(见附录)等隐患,可以通过切换备用CDN恢复,若不能接受此类情况,也可以把文件放在自己的服务器上;
1 |
|
- 配置webpack,在
build
的时候不打包element-ui
1 |
|
此时需要注意项目中非Vue组件下引入组件库的方式都需要改为全部引入,避免webpack识别不了
1 |
|
- 再次
build
,查看report.html
文件,组件库成功抽离
- 部署到服务器上,使用
LightHouse
进行测试
利用外部CDN加载,首次加载速度FCP又有部分提高
假设不使用外部CDN引入,加载速度会受限于服务器条件
路由懒加载
build
时路由的每个组件各自打包,使用**动态导入**
1 |
|
异步组件
动态组件 & 异步组件 — Vue.js 实际上也是上面提到的动态导入
以Coding页面为例
![](https://static.chanx.tech/image/tefc3_0.png
在使用路由懒加载后,按路由级别对js进行了拆包,每一个路由都是一个新的js,但是仍然存在单个路由过多组件导致的包体积过大问题。像上图Coding.js
经过gzip压缩
后还有2.9MB
,严重影响到页面的首次加载和渲染(进入Coding页需要等待2-3s加载),需要对这个文件进行下一步的优化;
查看report.html
,观察文件内各个内容的占用;
首先看到整个文件可以大致分为四块:sv.js
、ace-builds
、swiper
、splitpanes
;分别代表的是可视化面板、编辑器、测试数据面板、分割面板组件。
Coding.js
中引入可视化面板、堆栈面板、监视面板。它们在调试状态下并不会被使用到,也就是说页面首次加载时加载了部分无用内容。使用异步组件将这几个面板拆出来,让它们在进入调试状态下才去加载。
1 |
|
拆完包之后我们打开页面验证一下:
可以看到在进入页面时多加载了几个js文件,单个文件的体积比较小,没有超过200K
Coding页面的首次加载优化已经算完成了,继续看看调试状态下的加载
点击调试后开始加载这几个组件,visual-pane
因为sv.js
的存在所以体积还是相对较大;
后续还可以对sv.js
进行优化:使用CDN引入、在sv.js
构建的时候拆成多个包
这里没有对
sv.js
进行下一步优化的原因是:
sv.js
处于快速迭代状态,为了方便该模块开发者测试,所以暂不考虑构建成多个包;- 使用CDN引入,一般适合变化不多的静态资源
当然,还是有方案解决的,感兴趣可以了解一下monorepo
或者其他,这里不再赘述
拆包时需要注意拆出的模块是否存在代码耦合,比如说加载完成后执行某个函数,这是需要进行处理的:import(“XXX”).then(() => { console.log(“加载完成执行的函数”); })
部署后使用LighntHouse进行测试
比较优化前后的测试结果:FCP从1.3s到1.0s,LCP从8s到2.7s
使用HTTP/2
HTTP/2下二进制和流的特性可以加快网站的访问速度,需要服务器和浏览器的支持
目前主流浏览器均已支持HTTP/2,服务器完成支持即可,需要配置HTTPS、Nginx
SourceMap
生产环境把sourcemap
关了也能减小部分文件大小
总结
全文主要围绕网页加载速度,选定单页应用(SPA)常见的首屏渲染(FCP)问题作为优化点。
上面的几个点基本都在围绕「文件大小」进行优化,尽可能的减少单个文件体积,实际上还有隐藏在打包阶段进行的tree shaking(删除无用代码)
、uglily(压缩)
、内联资源等进行的优化;
另外就是算法、网络通信上提升传输效率,像上面提到的gzip、http2、cdn,还有dns、缓存等。
更进一步,网页加载速度不只是资源文件的下载速度,其实还包括下载后资源文件的解析、渲染等,这些牵扯到浏览器的运行机制,优化难度更大更复杂。
思考
- CDN利用缓存提高速度,而不同CDN之间的缓存不能共用,有没有什么好的办法?
若浏览器支持类似contenthash形式的缓存,那么所有第三方缓存将会打通,所有引入第三方的网站访问速度都会提升。
路由懒加载和异步组件实际上都是将单文件变为多文件,即「拆包」;
- 那是不是首页变快了,后面每次点击都需要等待加载?用户体验不是更差?
这个跟应用实际情况有关系。拆包之后按需加载确实加快了首次访问,后续要等待加载这个却是可以优化的。Vue-cli项目打包默认加载prefetch和preload插件,利用
**prefetch**
和**preload**
属性,浏览器可以在后台进行无感的预加载,后续加载的时候就是使用cache
了,反而让用户体验变得更好。当然,不排除某些情况下预加载没完成,这个时候做一个loading
状态也不会影响体验。
- 拆包那么爽,那我全拆了不就行了吗?
拆包前先考虑原来的代码是否已经优化过,删除无用的代码和模块,无必要的拆包会增加文件数量和维护成本。如果拆出的包过小,反而会影响加载速度(HTTP/1.1),不然为什么小图片加载会有雪碧图方案呢?
附录
2021-12-20
jsdelivr挂掉https://www.v2ex.com/t/823281
国内节点挂了至少八小时,影响了BootCDN(笑)、echarts、部分npm包等…很严重