文章目录
前言
标题取得好,看客少不了,哈哈哈哈哈哈
我前两年有个误区,把性能优化局限在业务逻辑的视角,比如串行改并行、删除无用逻辑、降低时间复杂度,但是事实证明这些改变虽然有效,但可能作用不大,而且对于不太熟悉业务背景的新人来说,这几个短时间内难以上手,那么如何“表现”自己呢?
上面说的是战术问题,其实我们可以暂时抛开战术不谈,思考战略方向,从宏观技术方案上着手,让你即使不太了解业务背景,也能“秀”一下技术,听起来是不是就很牛逼?要的就是事半功倍的效果。
(顺便贴个内推码,欢迎投递或转发)
1. 图片压缩
1.1 背景
目前业界常用的体积较小的图片格式是 webp、avif,对于一些中大厂,内部框架一般支持在图片 url 上修改后缀即可使用 webp 格式。当然,如果你所在公司没有支持,也可以自行配置 webpack 打包出经过压缩的图片。
以我司为例,static.tripcdn.com 域名下的 jpg、png 图片的 url 后面直接加 _.webp 后缀来实现 webp 压缩,而我所维护的项目有很多本地图片,默认打包后的图片后缀保持不变。那么如何改造能实现 webp 压缩呢?
1.2 方案分析
- css 中引用的外部图片 使用 postcss-url 这个 loader 判断 jpg,png 图片资源的 url 是否可用 webp 压缩,如果可以,直接在这步改成加 _.webp 后缀。
- css 中打包的本地图片链接 自定义 webpack plugin 对于 css 打包产物中引用的本地图片资源,且未被 base64 编码的图片,在 jpg、png 图片路径后面加上 _.webp 后缀。
1.3 压缩前后体积对比
图片链接压缩前体积压缩后体积压缩前请求耗时压缩后请求耗时选座头图https://aw-s.tripcdn.com/modules/ibu/online-flight/images/pic_seat_head_691e0c922e.png91.1kb15.5kb166ms88ms值机头图https://aw-s.tripcdn.com/modules/ibu/online-flight/images/pic_check_in_head_999912f6ba.png62kb12.1kb129ms77ms退改包头图https://aw-s.tripcdn.com/modules/ibu/online-flight/images/pic_illustration_e7d54913c7.png48.7kb7kb115ms83msCO2 背景图https://aw-s.tripcdn.com/modules/ibu/online-flight/images/carbon-offset-bg_de33cb6b5a.png45.9kb16.7kb111ms85ms服务包头图https://aw-s.tripcdn.com/modules/ibu/online-flight/images/pic-service-charge_3ea4a0c822.png26.5kb5.9kb96ms80msairHelp 背景图https://aw-s.tripcdn.com/modules/ibu/online-flight/images/airhelp_bg_eef31ebce1.png23.6kb2.7kb87ms78ms
2. 分包加载
2.1 背景
我们项目里有个 ProductList 这个组件,它里面根据接口下发数据判断应该渲染哪些产品,其中有 12 种类型的产品。
2.2 方案分析
以前 ProductList 自己单独是一个 chunk,但其实很多产品接口如果没下发时,是没有必要加载对应的产品的 js、css、图片资源的,所以适合将每个产品作为一个 chunk,将庞大的组件合理拆分 chunk。
2.3 效果对比
TTFB 平均降低了 30ms 左右,0.66s → 0.62s;
FCP 平均降低 300ms 左右,2.79s → 2.48s;
LCP 平均降低 1300ms 左右,3.24s → 1.94s。
(公司性能数据是采样的,所以出现了FCP > LCP,有点诡异,等有空我再研究一下子)
3. css 本地图片内嵌 base64 策略修改
3.1 背景
项目中以前的打包策略是将小于 10 kb 的所有本地图片转化为内联 base64 编码。
css 中有多处 base64 编码的图片,有多个弊端:
- 因为每次发布几乎都会改样式,所以都会导致 css文件的哈希值变更,而这些图片其实是不变的,没用上缓存的能力。
- 一些样式可能是没被使用的,其加载的图片也会被通过内嵌 base64 编码 打包到 css 文件中。
3.2 方案分析
将策略改成小于 1 kb 的本地图片采用内嵌 base 64 编码,其他都输出成图片文件,优点如下:
- css 文件体积减少 30% 左右。
- 图片按需加载,且本地图片可以采用 webp 压缩。
- 图片不变时,能用上浏览器缓存。
3.3 优化前后效果对比
4. 检查 treeshaking 是否生效
4.1 背景
我发现我们项目引用了某个 npm 包里的 Insurance 、HotelCandy 组件,但是没有 tree shaking,hotelCandy 这个包也包含了 insurance 的代码,导致打包产物臃肿,如图:
如果这些不够直观,那就看 js.map,可以清晰地看到保险组件,哪些文件被打包进来了
4.2 方案分析
❌ 方案一:在引入时显式声明引入路径为 esm 规范的打包产物,而不是 commonJS 规范的打包产物。
// 以前的写法import{ HotelCandyModule }from'@ctrip/ct-x-product-component-online';// 修改后的写法import HotelCandyModule from'@ctrip/ct-x-product-component-online/es/components/hotel-candy';
这个改法虽然能解决 tree shaking 不生效的问题,但是不利于长期维护,因为后续开发还是可能会写成老的写法,最好是能从配置层面解决。
✅ 方案二:
// 在组件库里,增加配置。// 如果不配置 sideEffects 字段,Webpack 可能会保守地认为所有文件都有副作用,从而无法移除未使用的代码。这会导致 tree shaking 无法正常工作,未使用的代码仍然会被打包。{"name":"@ctrip/ct-x-product-component-online","version":"1.2.77","description":"cupress构建生成的react组件库","main":"lib/index.js","module":"es/index.js","types":"lib/index.d.ts","sideEffects":false,// 此条为新增}
4.3 优化前后体积对比
优化后的打包产物分析图:
5. 懒加载
5.1 现状分析
当前,产品的所有依赖项都是通过同步导入的方式引入的。在静态打包过程中,构建工具会分析这些依赖关系,并通过 tree shaking 技术移除未使用的代码,从而将精简后的代码打包到最终的 JavaScript 文件中。
但是,有些模块不可能在首屏展示,这种模块的渲染时机是在用户进行某种操作后,所以适合懒加载。
5.2 方案分析
❌ 方案一:使用动态 import 函数
该方案缺点是不会自动处理加载状态。需要手动处理加载状态和错误。
// 示例代码如下import React,{ useState, useEffect }from'react';functionApp(){const[Component, setComponent]=useState(null);useEffect(()=>{import('./LazyComponent').then((module)=>{setComponent(()=> module.default);});},[]);if(!Component){return<div>Loading...</div>;}return<Component />;}
✅ 方案二:基于项目是使用 react18 的前提,所以可以用 Suspense、React.lazy。(要使用 Suspense 和 React.lazy,需要确保项目依赖的 React 版本至少是 16.6.0。)
React.lazy:用于定义一个懒加载的组件。它接受一个函数,该函数调用 import 动态导入组件。
Suspense:用于包裹懒加载的组件,并提供一个加载状态(fallback)在组件加载过程中显示。
// 示例代码import React,{ Suspense, lazy }from'react';const LazyComponent =lazy(()=>import('./LazyComponent'));functionApp(){return(<Suspense fallback={<div>Loading...</div>}><LazyComponent /></Suspense>);}
5.3 效果对比
6. 预加载
6.1 现状分析
做了产品列表 拆分后,确实可以降低资源请求耗时,但是从填写页跳到下个页面时,因为请求下个页面的产品的 js、css、图片需要一定时间,所以页面间跳转时,给用户的体验不够丝滑了。其实是 FCP 变小了,但 LCP 变大了。
6.2 方案分析
寻找合适的时机提前加载下个页面的产品
❌ 方案一:
点击下一步时
✅ 方案二:
用户滑动到页面底部时
监听滚动事件
✅判断下一步按钮是否可见
因为点击下一步时,也会发起网络请求,为了不影响这一步的转化率,综合起来考虑,适合选方案二。
进一步分析,Intersection Observer API 来判断底部元素是否可见通常比使用 scroll 事件监听器更好,主要原因如下:
- 性能:
Intersection Observer:它是由浏览器优化的 API,专门用于检测元素的可见性。它在后台进行计算,并且只在元素的可见性发生变化时触发回调函数。这减少了不必要的计算和回调调用,从而提高了性能。
Scroll 事件监听器:它会在每次滚动事件发生时触发回调函数,这可能会导致频繁的计算和回调调用,尤其是在滚动频繁的情况下,可能会影响页面的性能。
- 兼容性:
现代浏览器都支持 Intersection Observer API,有良好的兼容性。
6.3 优化前后对比
同一份 接口 数据,在浏览器模式为 高速 4G + 停用缓存 的情况下对比:
优化前
优化后
7. 未完待续……
其他
其实还有很多是这个项目的前人已经做过的优化措施,比如缓存、gzip压缩、字体文件压缩,还有一些是不适合本项目的优化措施,比如 font-spider 等。性能优化这个事情还是得具体问题具体分析。
参考文档:
https://webpack.docschina.org/api/module-methods/
https://webpack.docschina.org/guides/tree-shaking/
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
写在最后
如果友友们有什么好的想法或者实践时遇到了什么问题,欢迎在评论区或者私信我交流~~
版权归原作者 聆听花开CS 所有, 如有侵权,请联系我们删除。