前端性能优化是一个老生常谈的问题,我写这篇博客的目的是记录自己的一点实践,没有什么高大上的东西,如果顺便能帮助到你,我将万分荣幸。
性能优化包含几个方面,比如减少http请求,当然现在http2有多路复用,其实很大程度上减缓了多个http请求的问题,但是话说回来,我相信大多数公司目前用的都是http1。
因此这依然是一个需要考虑的问题,比如字体图标火起来之前的雪碧图,js资源合并等等。
再比如减少首页资源包的大小,比如组件按需加载,其他依赖的按需加载等。
这次优化的原因是我们移动端项目的首屏加载肉眼可见的慢,已经被客户吐槽了,因此必须被提上日程。我们先来看看优化之前的首屏加载状况。
可以看到,首屏加载过程中产生了10个长任务,同时首屏数据加载完成需要2s以上,长任务中脚本的下载时间和解析时间占比非常大,原因是主 vender 包过大,最大的 js vender 包gzip压缩后仍然接近700kb。
本地用analyzer命令查看打包分析(需要先添加 依赖 并且在 配置中增加 命令):
突出的问题主要包括:
1、第三方库的共同依赖bn.js重复打包了7次
2、momentjs太重了,项目内只用了一些简单的日期格式化api,完全可以自己写一套轻量api去替换。
3、vconsole?调试工具不必打包到项目中,可走官方CDN,并且生产环境可以去掉
4、echarts、dingtalk-jsapi?等重型依赖没有按需引入
5、自研UI组件库的单个组件体积过大,同时存在单个组件多次打包的情况,需要优化打包策略
接下来我们针对上面的问题一个一个解决。
首先,因为 在递归查找依赖的时候会默认去第三方库的 目录中查找并单独打包,所以每个库只要对 有依赖就会单独打包一次。
解决这个问题其实也简单,就是告诉webpack,查找依赖时遇到bn.js就去?nodejs的命令执行目录的目录中查找依赖。
这样webpack不管遇到多少次bn.js都会识别为同一个依赖,也就只会打包一次了。
而我们要做的就是往webpack配置中的 目录中增加一行如下的配置:
这里的 是精髓所在,就是把依赖的查找目录指向当前?npm?命令的执行目录,也就是我们的项目根目录下的 ?目录。
我的项目是vue项目,因此只需要在?中的? 配置中增加这行配置,修改后的配置如下:
修改完这行配置后再执行命令查看结果,发现光这一项操作就把主 ?包的gzip体积缩小了 67kb,可真是小付出博取了大回报。
这一步我们提刀砍向了重型库?,这是一个日期格式化的库,有多重呢?上个图:
事实上我们项目中对 的依赖仅限于几个简单的日期格式化 api ,它功能很全(其实很大一部分体积来自语言包)但其实我们用不上,针对这种情况其实完全可以自己写几个api替换,然后把移除。
但是考虑到自己写api的可靠性和工作量问题,我选择了轻量级的dayjs来替换,的 api?和基本一样,因此替换起来可以说毫不费力,同时dayjs打包后的gzip体积只有2.7kb,比 直接小了 71kb
作为混合开发的应用,app内的真实环境调试必不可少,我们选择的是腾讯的 ,顺嘴提一句, ?的? 版本之前,在 vue3 项目中有循环报错的问题,会导致页面卡死,我到官方仓库提了,官方在版本修复了,这里要感谢官方团队的快速响应,点赞!
言归正传,我们对 的依赖仅限于非生产环境的调试,因此即使 挂了也仅仅影响调试,完全可以走官方 CDN ,而且要注意,生产环境不需要引入。但是我们原来的做法是直接把 ?加入到本地依赖,同时不管什么环境直接引入,只是判断在生产环境的时候不做初始化。
而 即使压缩打包后依然有52kb:
为此我们直接 ,?同时增加如下脚本文件,在?中这个脚本文件。
这个操作后主vendor包再次缩小52.5kb。
社区有众多功能全面的库,比如等图表库,、 等UI库,但大多数情况下我们只使用其中的部分功能。
UI组件库的按需引入以及路由的懒加载一般来说是前端项目的标配。我们的项目在创建之初就已经做过这件事。
所以这里只把精力放在我们项目中存在的问题上。
是一个大而全的组件库,像日常开发中我们可能为了省事直接一行代码全量引入了:
我们来看一下全量引入echarts在压缩打包后的体积有多大:
而事实上我们常规的项目一般只会用到柱图、饼图、折线图等几种常见且简单的图形。因此完全没必要全量引入了。我的做法是将必要的基础组件放在单独的文件中引入。
然后在需要使用图表的vue文件中引入暴露的?对象,再单独引入需要的图表类型?调用一次?
具体使用图表的 vue 文件
接下来逻辑代码中正常使用 ?就行了,一顿操作之后, 缩小了139kb,体积缩小一半不止。
这个库是钉钉多段统一的 ,我们只用到了其中一个更改页面title的小功能,因此?可以做按需加载。
具体操作官方文档都有,这里就不上代码了,直接上结果:
可以看到,我们又节省了16kb的空间,不要小看这16kb,性能优化,毫秒必争!
除了第三方库的依赖会重复打包外,我们项目中自己写的工具文件或者组件存在重复打包的问题。webpack 有一套默认配置来判断一个依赖是否需要单独打包,但是默认选项不一定适合我们的项目。
我们可以通过 的? 选项来自定义适合我们的配置。
以上部分注释引用自掘金文章:如何使用 splitChunks 精细控制代码分割
在此感谢博主@前端论道,他的文章对 介绍的非常详细,感兴趣的可以去看看。
做完这一步我们发现很多重复打包的问题没有了,同时该合并的包合并了,这样不仅减小了包体积,还轻微减少了http请求的数量,可以说是一举多得。
从项目打包分析中很容易看到,我们的自研UI组件库单个单个组件打包后体积非常大,而实际上我们的组件功能并不复杂,业务代码也不多,为啥会出现这样的情况呢?
其实不难想到,我们的UI库依赖了很多我们的项目中本来就有的依赖,比如 等等,而这些依赖在默认情况下会顺着依赖分析的路子找到组件库的 中,从而导致把这些依赖单独打包到了组件中。
针对这个问题,其实webpack提供了一个 配置来解决,官方文档是这么说的:
因此我们只需要在自研UI库项目中增加一些配置就能解决:
在根目录创建文件 /build/vuecli.lib.conf.js
这些配置能把里面列举的依赖从打包过程中排除出去,在 中再去外部获取。
此外,我们项目中安装的依赖要么在? 中,要么在 但其实 中还有另外一个 ?对象(可以翻译成同版本依赖)
这个配置对于插件库或者其他库的开发者来说非常有用,总体来说 的作用和 的 配置有重合,但又不完全一样。总结一下peerDependencies的特点:
不同的是声明的依赖在插件开发中正常安装就行了,在引用这个插件的项目打包时会把中列举的插件排除出去,仅在引用插件的项目中打包一次。
而的依赖你需要同时安装到中,这样你在开发插件的时候才能正常使用,外部项目打包的时候会根据外部项目是否显示声明依赖来决定是否安装和单独打包。
相同的是,外部项目必须有这些依赖才能体现它的价值。
我们这里单独把 、、 放入 中
好了,通过以上的两步操作,我们把自研UI库中和业务项目中的公共依赖抽离出来了,这样UI库的组件打包后的体积将会大大缩小。
最后,经过一顿操作后原来主 包和 两个包加起来的体积是 669kb + 99kb=768kb,优化后仅有497kb,并且减少了首屏的一次请求。同时自研UI库的重复打包问题和单个组件过大的问题也得到了解决。
最后的最后,我们来验收一下线上的效果:
长任务也从原来的10个缩减为5个,并且长任务耗时也大大缩减:
到这里,这一轮的性能优化就结束了,当然,此次 时长能获得这个效果不仅是前端优化的结果,和后端同学接口性能的优化分不开。
最后总结一下,其实上面所做的所有优化举措都是非常简单的操作,但是实际的项目中就是有很多人会做错,所以希望这篇文章能对你有所帮助。
020-88888888