Open pikou1995 opened 3 years ago
在一系列测试网络应用质量的工具中,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。
webpack + antd
这是一个典型的网站,总共分为四个页面,包含了一些常见的组件,分别为主页,表单,表格和图表。样式和 Network 瀑布流如下:
这里使用的 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 本身支持 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按需引入:
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组件为例:
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 才会去请求。
/form
此时 main.css 和 main.js 优化减少到了,而 Form 和其他组件对应的 js 和 css 都从 main 里面拆分了出来。
vue 框架也支持 AsyncComponent来实现懒加载
在 demo 里我们给静态服务加上 gzip,把 koa-static 替换成 koa-static-cache,默认以 level=6 压缩。
level=6
由于 koa-static-cache 不支持 index.html,所以我们需要加上 index.html,即通过 http://localhost:8080/index.html 访问。
compression-webpack-plugin 可以在打包的时候就生成 gzip 后的资源,可以根据实际情况选择对应的压缩方式。
实际上用户的体验可能会因为网络通畅,资源缓存而更好,也可能因为网络拥堵,服务器压力大等情况更差。 但我们经过了下面的几个方向的尝试,确实提高了web应用的质量:
当然这只是茫茫多优化中的一小部分而已,再好的程序总是会有优化的空间的。良好的用户体验不仅仅需要速度快,还要浅显易懂的设计,友善的引导,清晰的出错提示等等。
CDN 服务商一般都能提供更快的网络和更低的延迟,让用户就近获取资源,缩短资源下载时间,减少自身服务器的压力。而且CDN 可以避免和 api 共用同一个域名,浏览器请求时不会携带 cookie 等 http 头,减少请求时间。
一般现代浏览器在同一个 origin 同时最大链接数是 6。打开多个标签也共享这6个限制。html,css,font,js,图片,ajax,下载文件等 http 请求等都受到限制( websocket 除外)。
有些应用依赖项过多的,想使用 DllPlugin 和 splitChunks.cacheGroups 等功能拆分 js 文件的需要注意,同步的 js 拆出来加载时依然是同步的,如果超过最大链接数反而会成为瓶颈。可以考虑转换成动态引入 import()。
服务端渲染是个值得尝试的技术,配合 css 的按需加载,可以让浏览器在 js 加载和执行前就渲染出页面。使用时要注意做好缓存和用户隔离。
HTTP/2其中的特性比如 HTTP 头压缩, 服务端推送, 多路复用等,可以减少资源传输的时间。HTTP/2 的支持率达到了 96% 以上 。
除了网站可能没有遵循最佳实践,也可能是 Lighthouse 版本小于 v6,在 v5 中 CPU 是4x slowdown并且模拟了更高的延迟和更低的吞吐量。
可能你使用了 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
组件为例:这时候 FCP 从 1.6s 降到了 1.3s。点击 View Original Trace 查看 perfermence:
可以看到浏览器只请求了首页的 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。