背景
在做一键webpack转vite的工具中rollup打包升级时,发现在某个项目中,使用vite后本地冷启动很快了,但是使用rollup打包,在公司构建工具上打包部署需要180s左右,M1本地打包也在102s左右,打包时间过于长,打包时长长也说明体积有优化空间,所以开始研究怎么优化体积与打包时长
制定方向
- 体积的减少
- 打包速度的提升
前期准备
安装rollup-plugin-visualizer
查看包体积分布、各插件占比情况
一、安装rollup-plugin-visualizer
yarn add rollup-plugin-visualizer
二、使用rollup-plugin-visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer’;
return {
...
plugins: [
...
visualizer(),
...
],
...
}
三、执行打包
yarn build
执行打包后会在根目录生成stats.html文件,在浏览器中直接打开即可
四、rollup-plugin-visualizer视图
五、根据视图分析
-
root包大小在没优化前是10.22MB
-
vendor包大小在没优化前是9.33MB,依赖项占比87%
-
分析几个比较大的包,分别有
ant-design-vue
、lodash
、wcharts
- wcharts占2.88MB,
- ant-design-vue占1.87MB,
- lodash占用547KB,
,可以看到lodash被哪些文件引用,可以方便排查和修改
- wcharts占2.88MB,
-
重复包:wcharts下有echarts、然后还有单独的echarts包打入vendor,需要排查下项目内是否有重复引用
-
观察到lodash 和 lodash-es重复引用
六、策略
- 根据echarts重复引用的问题,将echarts引用删除
- 将重复的lodash库改为lodash-es引用
- ant-design-vue按需加载
- 因为wcharts的包比较大,占比28%,计划使用external将wcharts从vendor剥离,使用cdn形式引入,需要观察下白屏时间是否增加
- 路由懒加载
优化实现
一、删除重复引用
1、项目中main.js删除echarts的多余引用
2、将lodash改为lodash-es引用,lodash使用的是旧版es5模块语法,而lodash-es使用的是es6模块语法,像webpack、rollup这种打包工具可以通过tree shaking将多余的代码摇掉,lodash打完包是547KB,而lodash-es打完包是154KB,差距还是挺大的。
二、ant-design-vue 按需加载
项目中原用的全局引用antd-vue
,这里计划改成使用unplugin-vue-components/vite
来实现按需加载,插件原理大致是:识别template中的a-input、a-button等,然后在全局组件编译后通过正则拿到入参a-input转成AInput,交给解析器,之后修改vue编译后的代码,注入import { AInput } from ant-design-vue/es
的代码
安装
yarn add unplugin-vue-components -D
使用
import Components from 'unplugin-vue-components/vite’;
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers’;
return {
...
plugins: [
...
Components({
// ui库解析器,可以自定义
resolvers: [
AntDesignVueResolver({
// 参数配置可参考:https://github.com/antfu/unplugin-vue-components/blob/main/src/core/resolvers/antdv.ts
// 自动引入 ant-design/icons-vue中的图标,需要安装@ant-design/icons-vue
resolveIcons: true
})
]
})
...
],
...
}
此插件还可以使用其他的UI组件库,引入相对应的解析器就可以了,比如使用的是elemenPlus组件库,可以使用ElementPlusResolver解析器 , vant使用VantResolver, elementUI可以使用ElementUiResolver解析器,都可以实现按需加载,且不需要在单独文件中再次写引用语句。
遇到的问题
- 非模板组件例如message、nitification等组件使用失效
- unplugin-vue-components/vite 插件对于动态组件的引入识别异常
全局组件失效解决
- message、nitification等组件在全局单独引入,挂载在全局, 此方法最为稳妥,推荐
- 网上推荐的插件vite-plugin-style-import据说也可以解决这个问题,大致用法如下,但经过我的实验发现:首先这个插件需要下载1.xx版本,否则会启动报错,改为1.xx版本后实验message组件也是展示不出的,不推荐
import styleImport, {
AndDesignVueResolve,
VantResolve,
ElementPlusResolve
} from 'vite-plugin-style-import'
plugins: [
styleImport({
resolves: [
AndDesignVueResolve(),
VantResolve(),
ElementPlusResolve()
],
// 自定义规则
libs: [
{
libraryName: 'ant-design-vue',
esModule: true,
resolveStyle: (name) => {
return `ant-design-vue/es/${name}/style/index`
}
}
]
})
],
// 引用使用less的库要配置一下
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true
}
}
}
动态组件失效解决
针对动态组件不生效的问题,我的解决方案是建立了一个extra.js单独存放需要引入UI库的组件,例如按需加载后以下代码是加载不出a-input的,之前没问题是因为所有组件注册在了实例中
<component :is="AInput">
// extra.js 文件
import 'ant-design-vue/es/input/style/css';
import input from 'ant-design-vue/es/input';
const config = {AInput: input};
export default config;
// control.js
import extra from './extra';
for (const i in extra) {
app.component(i, extra[i]);
}
效果
实现了删除重复引用、lodash改为lodash-es、antd-vue按需加载后,总包大小从10.22MB降至7.49MB, 包体积减少26%左右的空间
打包速度从102s左右降至75s左右,提升速率26%左右
三、三方库使用cdn形式引入
external是什么?
防止将某些 import 的包打包到 bundle中,而是在运行时再去从外部获取这些扩展依赖,一般情况下是将大体积且基本无变动的第三方包处理为external,针对此项目最大的依赖包是wcharts,占比28%,wcharts是在echarts上二次封装的。
实现
有两种方式实现,一种是external+html配置cdn地址的方式,一种是引入vite-plugin-cdn-import插件形式配置cdn地址自动插入到html模板中
external+html
-
在配置文件中增加external
external:[wchars]
-
在html中增加cdn引入,地址来自xingtu的文档:xingtu.58xinghuo.com/pages/wchar…
<script src="https://c.58cdn.com.cn/git/cdn/echarts/5.1.1/echarts.min.js"></script> <script src="https://wos2.58cdn.com.cn/MnOjIhGfMnSn/wcharts/wcharts-1.0.16-umd.min.js"></script>
vite-plugin-cdn-import方式
import importToCDN from 'vite-plugin-cdn-import';
plugins:[
importToCDN({
modules: [
{
name: 'echarts', // 包名称
var: 'echarts', // 在项目引用的名称
path: 'https://c.58cdn.com.cn/git/cdn/echarts/5.1.1/echarts.min.js' // cdn地址
},
{
name: '@dpd/wcharts',
var: 'Wcharts',
path: 'https://wos2.58cdn.com.cn/MnOjIhGfMnSn/wcharts/wcharts-1.0.16-umd.min.js'
}
]
})
]
效果
因为这个项目是共建项目,所以在external生效之前采集了包大小数据
root包大小8.41MB,vendor包大小7.35MB,打包速度在43s左右
通过处理external后,root包大小为4.99MB,vendor包为3.92MB, 打包速度降至 25s左右,在x86上打包大概在70s左右
包体积较之前减少了40.6% ,打包速度较之前提升了43.4%
白屏时间
我们使用DOMContentLoaded,即当一个 HTML 文档被加载和解析完成,来记录白屏时间,来观察处理external后的白屏时间是否有增加
在已缓存多次刷新情况下计算,处理了external后DOMContentLoaded时间都保持在1s内,且均值在517ms( 采集次数10次 ) ,处理external前的DOMContentLoaded均值在936ms,白屏时间也有所提升
四、图片压缩
使用vite-plugin-imagemin插件进行图片优化
import viteImagemin from 'vite-plugin-imagemin'
plugins: [
viteImagemin({
optipng: {
optimizationLevel: 3, // png图片的质量分,从0-7,0是最小的影响,不会对图片进行改变,7是最大的影响,中间逐步递增对图片的改变
},
mozjpeg: {
quality: 20, // jpeg的质量分,0-100
}
})
]
详细的options见:github.com/vbenjs/vite…
安装插件打包后会给出优化的百分比,可以直观的看到优化效果
五、路由懒加载
实现路由懒加载有两种形式
1、直接import
{
path: '/NewMarketing/signIn',
name: 'Market',
component: () => import('../views/designer/index.vue')
}
2、defineAsyncComponent方法,vue3新定义的异步组件方法
{
path: '/NewMarketing/signIn',
name: 'Market',
component: () => defineAsyncComponent(() => import('../views/designer/index.vue'))
}
这两种方式都会将组件独立分chunk引用,所以这里需要注意如果是完全前后端分离,可以使用路由懒加载的形式去优化,如果不是完全分离的话需要兼容模板的入口js、入口css
总体效果
在经过了删除重复引用、改用小体积工具包、按需加载、external的优化后,包总体积从之前的10.22MB降至4.99MB,ARM架构打包速度从102s左右降至25s左右,X86架构上打包速度从180s左右降至70s左右,包大小基本减少了一半,打包速度较之前提升了75%左右,在X86上打包速度较之前提升了61%左右。
总结
- 优化前需要收集数据,webpack可以安装webpack-bundle-analyzer,rollup安装rollup-plugin-visualizer直观的查看项目的包情况,能直观看到哪些包的体积较大需要优化。
- 有些优化是可以在创建项目初期就规定好,比如UI组件库按需引入、如果使用像lodash中的方法较少可以自己定义,无需引入,如果引入的话也引入lodash-es
- 包体积较大且基本无更新的三方包可以external处理,能大大缩小包体积,减少打包速度
- 注意在处理UI组件库按需加载时,如果之前是全局加载,需要考虑动态组件、message这种全局挂载组件的处理