背景
使用lighthouse对首页进行"体检"时,发现首屏渲染慢。 chrome给出的其中一个建议是,可以减少未被使用的js文件。

除了main和vendor包较大之外,还有另一个需要注意的是,echarts在首页并没有引入,为什么在首屏加载时会用到
可以考虑两个方向:
- webpack对项目进行打包时,分包策略不是我们想要的,也许是依赖分析不在我们的意料之中;
- 树摇失败,没有摇掉没用到的包;
下一步我对打包后的资源进行分析,发现了两个问题:
- 为什么会有两个lodash
- 影响首屏加载的main把首屏不需要的DyformGen也引进来了
另外从业务的角度分析
-
shared库的组件其实是可以缓存起来的,应该打成一个包
-
@babel/standalone其实只有一个页面在用,其实并不用打进vendor里,像这种只有一个模块在用的第三方库,可以单独打包,避免vendor一下子搞了太多不用的代码
-
首页并没有使用echarts,为什么打开首页时会下载这个包?
小结
需要解决的问题包括:
- echarts在首页并没有引入,为什么在首屏加载时会用到
- 为什么会有两个lodash
- 影响首屏加载的main.js里,把首屏不需要的DyformGen也引进来了
- (业务角度)shared库的组件其实是可以缓存起来的,应该打成一个包
- (业务角度)@babel/standalone其实只有动态表单在用,其实并不用打进vendor里,像这种只有一个模块在用的第三方库,可以单独打包,避免vendor一下子搞了太多不用的代码
问题1:Echarts首屏加载分析
疑问:为什么首页加载的时候被引入?
检查路由分割是否有问题,导致动态导入/代码切割出现bug
查看代码的组织结构,发现页面都是通过从后端拿到的路由数据,进行动态导入的; 且通过检查打包后各个page是否被拆分为单独包,确认路由分割没有失效。 动态导入代码如下:
javascript
rendComponent: Loadable(() => import('../lib/pages/login/index.tsx')),
webpack代码分割见:代码分离 | webpack 中文文档 (docschina.org)
通过打包可视化分析的时候发现,main.js主要是由dashboard.tsx(首页组件)和shared(通用组件)组成的。

结论:路由的动态分割应该没有问题,问题应该在于整个shared包几乎都被引入到main里了,而shared包里是引入了echarts的,这可能就是为什么首屏加载有echarts的原因。
而shared和项目代码是通过nx构造的monorepo组织起来的,难道是nx的问题?
nx的树摇问题和sideEffect标记
在nx的GitHub搜了一下,发现nx确实有树摇问题:github.com/nrwl/nx/iss...
解决方案:手动给包打上side-effect:false的标签
sideEffects配置:告知 webpack 去辨识
package.json
中的副作用
标记或规则,以跳过那些当导出不被使用且被标记不包含副作用的模块。见:优化(Optimization) | webpack 中文文档 (docschina.org)
然后发现确实解决了问题!main.js一下子从(stat)920.82kb降到86.03kb,且lighthouse的性能分数从73提升到84
同时,为了避免开发环境下热更新速度慢,仅在生产环境开启sideEffect检测

问题2:Loadash的按需加载问题
自上一步之后,lodash的总体积剩下

其他地方还捆绑了一些

解决方案:手动改成 import get from 'lodash/get'
大包(vendor)内容分析
大包分析:
- @babel/standalone,由于体积占比大的不是业务代码,是第三方库,而且只有个别页面在用,所以可以单独提取出来做缓存,但是不需要放到vendor里
需要拆包,见webpack拆包插件SplitChunksPlugin | webpack 中文文档 (docschina.org),其中我比较关注的配置是:
- splitChunks.minChunks 在拆分之前,模块必须在块之间共享的最小次数
- splitChunks.minSizeReduction 主块最小缩小尺寸
- splitChunks.maxSize
我的配置如下:

这样就把@babel/standalone拆分了出来

总结
问题分析:
- 树摇似乎失效了,首页加载的时候会将没有引用到的echart的js文件也下载下来
- 入口文件main.js过大,几乎包含了shared仓库里的所有内容
- 第三方库打包成的vendor.js过大,由于main.js一定会引用vendor.js,这样会导致首屏加载时,下载了很多没用到的vendor,但是由于可以击中缓存,所以这个问题比较小
解决方案:
- 修复nx失效问题,需要在mono仓库里配置各个仓库的sideEffect,声明该package没有副作用,从而使webpack进行树摇
- 对于只被一个模块引用的第三方库,webpack会将第三方库和业务代码打包在一起,如果这种第三方库体积较大的话,则将其拆分出来,使这种第三方库可以充分利用缓存
优化效果:
测试环境:本地将sms前端打包成镜像,在本地启动镜像,后端访问地址为10.53.0.207
- main入口从(stat)920.82kb降到(stat)86.03kb
- 通过配置,实现自动将echart、@babel/standalone之类的大chunk提取出来,无需单独配置
使用lighthouse对首页进行评估,优化前:
优化后:
