在一系列测试网络应用质量的工具中,Lighthouse 脱颖而出。并且它集成进了 chrome 中,便利性也很高。
在 Lighthouse6 中,FCP 是六个主要指标中第一个指标,它测量的是用户第一次访问网页时浏览器需要多长时间呈现第一块 DOM 内容。图片,非空白的<canvas>,SVG 被认为是 DOM 内容(不包括 iframe 内的)。也不会考虑浏览器缓存的情况。FCP 在 v6 版本比重占 15%(v5 占 20%),是其他指标Speed Index、Largest Contentful Paint、Time to Interactive的基础,合计占总评分的70%,是开始优化的第一步。
我自己写了一个 demo,其中总结了自己的优化经验,记录一个典型的 webpack + antd 的项目中怎么进行优化。下面的文章也是基于这个demo。
介绍demo
这是一个典型的网站,总共分为四个页面,包含了一些常见的组件,分别为主页,表单,表格和图表。样式和 Network 瀑布流如下:

使用Lighthouse
这里使用的 Chrome 版本是86.0.4240.111,Lighthouse 版本是6.2.0。

等demo启动好,打开开发者工具 Lighthouse 面板,设置好需要评估的选项,这里以Performance和Desktop为例,表示测试桌面端浏览器的性能。点击 Generate report 开始生成报告。第一次启动可能时间会久一些,Lighthouse 需要预热。稍作等候,这样就得到了我们的报告。

可以看到这里的瓶颈就是 FCP 2.5s。
Lighthouse 模拟了环境带宽和 CPU 性能,是为了统一标准和计算结果,并不代表目标用户的实际体验。

优化打包大小

我们知道同步 js 的加载和执行是会阻碍 DOM 渲染的, css 资源的优先级也是最高的,所以我们要把不需要的资源移除,减少打包的大小。下面我们借助 webpack-bundle-analyzer 分析打包结果。

鼠标悬浮在模块上会显示详情:stat size 是引用源码的大小,parsed size 是经过压缩后的大小,gzipped size 是 gzip 后的大小。面积越大在文件中的比例就越大。
可以看到所占比例最大的是 echarts,其次是 moment。我们查找相关文档,echarts 可以按需引入,moment 不需要的 locale 文件也可以移除。
按需引入
// src/components/chart.tsx
// 完整引入
import echarts from 'echarts';
// 按需引入
import echarts from 'echarts/lib/echarts'
import 'echarts/lib/chart/line'
import 'echarts/lib/chart/pie'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/legend'
完整引入大小:
按需引入大小:
可以看到改成按需引入后结果是让人惊喜的。除了 echarts,现在很多开源组件都支持了按需引入,有的是通过 ES modules 支持来 tree shaking。其他依赖也可以找到对应的文档,查看是否提供了按需引入的方法。
antd 的样式按需引入
antd 本身支持 ES modules的 tree shaking 的,样式也可以只引入需要的部分。
// 完整引入
// src/app.tsx
// 入口处完整引入样式
import 'antd/dist/antd.css'
// 按需引入
// src/app.tsx
// 入口处移除全部样式
- import 'antd/dist/antd.css'
// src/components/home.tsx
import { Carousel } from 'antd'
// 引入需要的样式
import 'antd/es/carousel/style/index.css'
但是这样太繁琐了,我们可以交给 babel-plugin-import 处理。只需按需引入 js 的部分,它会自动帮我们引入组件对应的 css。
// .babelrc
{
// ...
"plugins": [
// ...
[
"import",
{ "libraryName": "antd", "libraryDirectory": "es", "style": "css" }
]
]
}
css完整引入:
css按需引入:
只引入需要的 moment.js locales
antd 部分组件依赖了moment.js,使用 webpack 打包时会把 moment.js 和所有的 locales 都引入。我们可以使用 moment-locales-webpack-plugin 来移除不需要的 locales。
// webpack-config.js
const MomentLocalesPlugin = require('moment-locales-webpack-plugin')
module.exports = {
// ...
plugins: [new MomentLocalesPlugin({
localesToKeep: ['zh-cn'], // 保留需要的语言
})],
};
引入了所有的locales:
只引入需要的locales:
如果想进一步优化,可以用 day.js 替换 moment.js。
代码压缩
webpack 在 production模式 下默认启用 TerserPlugin 来精简 js,但是 css 还没压缩。下面我们用 css-minimizer-webpack-plugin 来压缩 css。
// webpack-config.js
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
}),
new CssMinimizerPlugin(),
],
},
};
css压缩前:
css压缩后:
阶段性成果

FCP成绩的提高,其他指标也随之提高。
组件懒加载
打开首页的时候,其他三个页面的组件也一并打包进了 main.js 和 main.css,从而会占用带宽和 CPU 资源,拖慢首页的加载。我们可以使用 webpack 的动态加载组件 import() 和 React.lazy 来实现代码拆分和懒加载。
以Form组件为例:
// 同步加载
import Form from './components/form'
<Route path="/form">
<Form />
</Route>
// 懒加载
import { Suspense, lazy } from 'react'
import { Spin } from 'antd'
const Form = lazy(
() =>
import(
/* webpackChunkName: "form" */
'./components/form'
)
)
<Route path="/form">
<Suspense fallback={<Spin />}>
<Form />
</Suspense>
</Route>
这时候 FCP 从 1.6s 降到了 1.3s。点击 View Original Trace 查看 perfermence:

可以看到浏览器只请求了首页的 main.js 和 main.css,而 Form 的资源要等到 url 变成 /form 才会去请求。

此时 main.css 和 main.js 优化减少到了
,而 Form 和其他组件对应的 js 和 css 都从 main 里面拆分了出来。
vue 框架也支持 AsyncComponent来实现懒加载
gzip 进一步优化资源下载速度
在 demo 里我们给静态服务加上 gzip,把 koa-static 替换成 koa-static-cache,默认以 level=6 压缩。
由于 koa-static-cache 不支持 index.html,所以我们需要加上 index.html,即通过 http://localhost:8080/index.html 访问。

compression-webpack-plugin 可以在打包的时候就生成 gzip 后的资源,可以根据实际情况选择对应的压缩方式。
总结
实际上用户的体验可能会因为网络通畅,资源缓存而更好,也可能因为网络拥堵,服务器压力大等情况更差。
但我们经过了下面的几个方向的尝试,确实提高了web应用的质量:
- 尽可能减少打包出来文件的体积
- 当前页面不需要的组件延迟加载
- 利用压缩充分利用带宽资源
当然这只是茫茫多优化中的一小部分而已,再好的程序总是会有优化的空间的。良好的用户体验不仅仅需要速度快,还要浅显易懂的设计,友善的引导,清晰的出错提示等等。
其他可以优化或需要注意的地方
CDN
CDN 服务商一般都能提供更快的网络和更低的延迟,让用户就近获取资源,缩短资源下载时间,减少自身服务器的压力。而且CDN 可以避免和 api 共用同一个域名,浏览器请求时不会携带 cookie 等 http 头,减少请求时间。
浏览器并发请求链接数
一般现代浏览器在同一个 origin 同时最大链接数是 6。打开多个标签也共享这6个限制。html,css,font,js,图片,ajax,下载文件等 http 请求等都受到限制( websocket 除外)。
有些应用依赖项过多的,想使用 DllPlugin 和 splitChunks.cacheGroups 等功能拆分 js 文件的需要注意,同步的 js 拆出来加载时依然是同步的,如果超过最大链接数反而会成为瓶颈。可以考虑转换成动态引入 import()。
Server Side Rendering(SSR)
服务端渲染是个值得尝试的技术,配合 css 的按需加载,可以让浏览器在 js 加载和执行前就渲染出页面。使用时要注意做好缓存和用户隔离。
HTTP/2
HTTP/2其中的特性比如 HTTP 头压缩, 服务端推送, 多路复用等,可以减少资源传输的时间。HTTP/2 的支持率达到了 96% 以上 。
可能会遇到的问题
为什么 Lighthouse 评分极低?
除了网站可能没有遵循最佳实践,也可能是 Lighthouse 版本小于 v6,在 v5 中 CPU 是4x slowdown并且模拟了更高的延迟和更低的吞吐量。
为什么 webpack-bundle-analyzer 结果没有 parsed size 和 gziped size?
可能你使用了 webpack5,笔者自测 webpack-bundle-analyzer@3.9.0 版本还没有完全适配 wepack5。
在一系列测试网络应用质量的工具中,Lighthouse 脱颖而出。并且它集成进了 chrome 中,便利性也很高。
在 Lighthouse6 中,FCP 是六个主要指标中第一个指标,它测量的是用户第一次访问网页时浏览器需要多长时间呈现第一块 DOM 内容。图片,非空白的<canvas>,SVG 被认为是 DOM 内容(不包括 iframe 内的)。也不会考虑浏览器缓存的情况。FCP 在 v6 版本比重占 15%(v5 占 20%),是其他指标Speed Index、Largest Contentful Paint、Time to Interactive的基础,合计占总评分的70%,是开始优化的第一步。
我自己写了一个 demo,其中总结了自己的优化经验,记录一个典型的
webpack + antd的项目中怎么进行优化。下面的文章也是基于这个demo。介绍demo
这是一个典型的网站,总共分为四个页面,包含了一些常见的组件,分别为主页,表单,表格和图表。样式和 Network 瀑布流如下:
使用Lighthouse
等demo启动好,打开开发者工具 Lighthouse 面板,设置好需要评估的选项,这里以Performance和Desktop为例,表示测试桌面端浏览器的性能。点击 Generate report 开始生成报告。第一次启动可能时间会久一些,Lighthouse 需要预热。稍作等候,这样就得到了我们的报告。
可以看到这里的瓶颈就是 FCP 2.5s。
优化打包大小
我们知道同步 js 的加载和执行是会阻碍 DOM 渲染的, css 资源的优先级也是最高的,所以我们要把不需要的资源移除,减少打包的大小。下面我们借助 webpack-bundle-analyzer 分析打包结果。
鼠标悬浮在模块上会显示详情:stat size 是引用源码的大小,parsed size 是经过压缩后的大小,gzipped size 是 gzip 后的大小。面积越大在文件中的比例就越大。
可以看到所占比例最大的是 echarts,其次是 moment。我们查找相关文档,echarts 可以按需引入,moment 不需要的 locale 文件也可以移除。
按需引入
完整引入大小:
按需引入大小:
可以看到改成按需引入后结果是让人惊喜的。除了 echarts,现在很多开源组件都支持了按需引入,有的是通过 ES modules 支持来 tree shaking。其他依赖也可以找到对应的文档,查看是否提供了按需引入的方法。
antd 的样式按需引入
antd 本身支持 ES modules的 tree shaking 的,样式也可以只引入需要的部分。
但是这样太繁琐了,我们可以交给 babel-plugin-import 处理。只需按需引入 js 的部分,它会自动帮我们引入组件对应的 css。
css完整引入:

css按需引入:
只引入需要的 moment.js locales
antd 部分组件依赖了moment.js,使用 webpack 打包时会把 moment.js 和所有的 locales 都引入。我们可以使用 moment-locales-webpack-plugin 来移除不需要的 locales。
引入了所有的locales:
只引入需要的locales:
代码压缩
webpack 在 production模式 下默认启用 TerserPlugin 来精简 js,但是 css 还没压缩。下面我们用 css-minimizer-webpack-plugin 来压缩 css。
css压缩前:

css压缩后:
阶段性成果
FCP成绩的提高,其他指标也随之提高。
组件懒加载
打开首页的时候,其他三个页面的组件也一并打包进了 main.js 和 main.css,从而会占用带宽和 CPU 资源,拖慢首页的加载。我们可以使用 webpack 的动态加载组件 import() 和 React.lazy 来实现代码拆分和懒加载。
以
Form组件为例:可以看到浏览器只请求了首页的 main.js 和 main.css,而
Form的资源要等到 url 变成/form才会去请求。此时 main.css 和 main.js 优化减少到了
,而
Form和其他组件对应的 js 和 css 都从 main 里面拆分了出来。gzip 进一步优化资源下载速度
在 demo 里我们给静态服务加上 gzip,把 koa-static 替换成 koa-static-cache,默认以
level=6压缩。总结
实际上用户的体验可能会因为网络通畅,资源缓存而更好,也可能因为网络拥堵,服务器压力大等情况更差。
但我们经过了下面的几个方向的尝试,确实提高了web应用的质量:
当然这只是茫茫多优化中的一小部分而已,再好的程序总是会有优化的空间的。良好的用户体验不仅仅需要速度快,还要浅显易懂的设计,友善的引导,清晰的出错提示等等。
其他可以优化或需要注意的地方
CDN
CDN 服务商一般都能提供更快的网络和更低的延迟,让用户就近获取资源,缩短资源下载时间,减少自身服务器的压力。而且CDN 可以避免和 api 共用同一个域名,浏览器请求时不会携带 cookie 等 http 头,减少请求时间。
浏览器并发请求链接数
一般现代浏览器在同一个 origin 同时最大链接数是 6。打开多个标签也共享这6个限制。html,css,font,js,图片,ajax,下载文件等 http 请求等都受到限制( websocket 除外)。
有些应用依赖项过多的,想使用 DllPlugin 和 splitChunks.cacheGroups 等功能拆分 js 文件的需要注意,同步的 js 拆出来加载时依然是同步的,如果超过最大链接数反而会成为瓶颈。可以考虑转换成动态引入 import()。
Server Side Rendering(SSR)
服务端渲染是个值得尝试的技术,配合 css 的按需加载,可以让浏览器在 js 加载和执行前就渲染出页面。使用时要注意做好缓存和用户隔离。
HTTP/2
HTTP/2其中的特性比如 HTTP 头压缩, 服务端推送, 多路复用等,可以减少资源传输的时间。HTTP/2 的支持率达到了 96% 以上 。
可能会遇到的问题
为什么 Lighthouse 评分极低?
除了网站可能没有遵循最佳实践,也可能是 Lighthouse 版本小于 v6,在 v5 中 CPU 是4x slowdown并且模拟了更高的延迟和更低的吞吐量。
为什么 webpack-bundle-analyzer 结果没有 parsed size 和 gziped size?
可能你使用了 webpack5,笔者自测 webpack-bundle-analyzer@3.9.0 版本还没有完全适配 wepack5。