前言
本文介绍在实际项目中进行打包优化过程
目前评分 good
npm install web-vitals
在App.vue加入如下代码测试网页性能指标
import { onLCP, onINP, onCLS, onFCP, onTTFP } from 'web-vitals/attribution'onCLS(console.log)
onINP(console.log)
onLCP(console.log)
onFCP(console.log)
onTTFB(console.log)
刷新网页,我们可以看到在console出现指标评分如下:
背景
项目目前的技术栈环境如下:
vite@5.0.10
vue@3.3.13
pinia@2.1.7
ant-design-vue@4.0.8
vue-router@4.2.5
现状分析
优化的主要难点还是从哪里开始去优化,如果不清楚自己的项目问题出现在哪里,就显得比较盲目,不知道优化哪里,那在优化之前我们先分析一下我的文件依赖。
1. 安装插件 rollup-plugin-visualizer
npm install rollup-plugin-visualizer --save-dev
2. 修改vite.config.js
import { visualizer } from 'rollup-plugin-visualizer'export default defineConfig(({ command, mode }) => {return {plugins: [visualizer({open: true})]}
})
3. 生成报告
在终端重新运行打包命令
npm run build
打包结束后,会自动打开报告页面如下图所示:
我们看到占体积较大的是第三方依赖如:z-render
(ECharts 底层库 ZRender ), clay-gl
( ECharts -gl 底层库ClayGL), e-charts
,echarts-gl
,ant-design-vue
,highcharts
,vxe-table
。总体积是9.77MB
方案
1. Echarts 按需加载
在 Echarts
的官方文档我们看到有关按需引入依赖的说明 按需引入Echarts
我们在项目中使用Echarts和Echarts-GL主要是为了绘制3D曲面图,如下图所示:
1.1 使用 Canvas 或者 SVG 渲染
使用 Canvas 或者 SVG 渲染
如果你是按需引入,则需要手动引入需要的渲染器
import * as echarts from 'echarts/core';
// 可以根据需要选用只用到的渲染器
import { SVGRenderer, CanvasRenderer } from 'echarts/renderers';echarts.use([SVGRenderer, CanvasRenderer]);
然后,我们就可以在代码中,初始化图表实例时,传入参数 选择渲染器类型:
// 使用 Canvas 渲染器(默认)
var chart = echarts.init(containerDom, null, { renderer: 'canvas' });
// 等价于:
var chart = echarts.init(containerDom);// 使用 SVG 渲染器
var chart = echarts.init(containerDom, null, { renderer: 'svg' });
1.2 按需引入组件
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from 'echarts/core';
// 引入标题,提示框,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {TitleComponent,TooltipComponent,DatasetComponent,TransformComponent,VisualComponent
} from 'echarts/components';
// 标签自动布局、全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features';
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from 'echarts/renderers';// 注册必须的组件
echarts.use([TitleComponent,TooltipComponent,DatasetComponent,TransformComponent,LabelLayout,UniversalTransition,CanvasRenderer
]);
1.3 按需引入echarts-gl
ECharts-GL
是 ECharts
的扩展包,提供 3D
绘图、地球可视化和 WebGL 加速
在 Echarts-GL
的 Github
主页我们看到按需引入的指引
import * as echarts from 'echarts/core';
import { SurfaceChart } from 'echarts-gl/charts';
import { Grid3DComponent } from 'echarts-gl/components';echarts.use([SurfaceChart,Grid3DComponent
]);
按需引入组件时,有关控件的说明没有在官网文档中给出,需要阅读源码去寻找需要导入的组件。
1.4 总结
本项目按需引入Echarts和Echarts-GL,所作的全部修改如下:
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from 'echarts/core'
// 引入标题,提示框,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {TitleComponent,TooltipComponent,DatasetComponent,TransformComponent,VisualMapComponent
} from 'echarts/components'
// 标签自动布局、全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features'
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from 'echarts/renderers'
// Minimal Import echarts-gl 模块
// import 'echarts-gl'
import { SurfaceChart } from 'echarts-gl/charts'
import { Grid3DComponent } from 'echarts-gl/components'// 注册必须的组件
echarts.use([TitleComponent,TooltipComponent,DatasetComponent,TransformComponent,VisualMapComponent,LabelLayout,UniversalTransition,CanvasRenderer,SurfaceChart,Grid3DComponent
]);
1.5 效果
重新打包
npm run build
得到report如下:
我们可以看到打包总体积从9.77MB减小到了8.03MB,减少了1.74MB,减少的值相对于初始值9.77MB减少了17.8%
2. 按需引入 ant-design-vue
组件库
ant-design-vue
默认支持基于 ES modules
的 tree shaking
自动按需引入组件,如果你使用的是 Vite ,我们推荐使用 unplugin-vue-components
2.1 unplugin-vue-components
该插件帮助开发者按需自动导入 Vue
组件
2.1.1 安装
npm install unplugin-vue-components -D
2.1.2 用法
像平常一样在模板中使用组件,它将按需导入组件,不再需要再导入 (import
) 组件或者注册 (register
) 组件。如果您异步注册父组件(或路由懒加载),则自动导入的组件将与其父组件一起进行代码拆分。
它会自动将:
<template><div><HelloWorld msg="Hello Vue 3.0 + Vite" /></div>
</template><script>export default {name: 'App',}
</script>
转换成:
<template><div><HelloWorld msg="Hello Vue 3.0 + Vite" /></div>
</template><script>import HelloWorld from './src/components/HelloWorld.vue'export default {name: 'App',components: {HelloWorld,},}
</script>
注意: 默认情况下,此插件将导入src/components路径中的组件。您可以使用选项进行自定义dirs
2.1.3 按需导入组件库
认识这个插件真的有种相见恨晚的感觉,它不仅适用于Ant-design-vue,还有其他流行的UI库如Element Plus提供了内置的解析器,以下是它支持的解析器:
- Ant Design Vue
- Arco Design Vue
- BootstrapVue
- Element Plus
- Element UI
- Headless UI
- IDux
- Inkline
- Ionic
- Naive UI
- Prime Vue
- Quasar
- TDesign
- Vant
- Varlet UI
- VEUI
- View UI
- Vuetify — Prefer first-party plugins when possible: v3 + vite, v3 + webpack, v2 + webpack
- VueUse Components
- VueUse Directives
- Dev UI
示例用法:
// vite.config.js
import Components from 'unplugin-vue-components/vite'
import {AntDesignVueResolver,ElementPlusResolver,VantResolver,
} from 'unplugin-vue-components/resolvers'// your plugin installation
Components({resolvers: [AntDesignVueResolver(),ElementPlusResolver(),VantResolver(),],
})
2.1.4 配置项说明
Components({// 搜索组件的相对路径dirs: ['src/components'],// 组件有效的文件扩展名extensions: ['vue'],// 匹配要检测为组件的文件名的Glob正则表达式// 指定该项之后,`dirs` 和`extensions`选项都将被忽略// 如果你想要排除已被注册的组件,使用!通配符globs: ['src/components/*.{vue}'],// 搜索子目录deep: true,// 自定义组件的解析器resolvers: [],// 生成 `components.d.ts` 全局申明,也可以是自定义文件名的路径,如果已安装ts,那么默认值是truedts: false,// 子目录可以作为组件的namespace前缀directoryAsNamespace: false,// Collapse same prefixes (camel-sensitive) of folders and components// to prevent duplication inside namespaced component name.// works when `directoryAsNamespace: true`collapseSamePrefixes: false,// Subdirectory paths for ignoring namespace prefixes.// works when `directoryAsNamespace: true`globalNamespaces: [],// auto import for directives// default: `true` for Vue 3, `false` for Vue 2// Babel is needed to do the transformation for Vue 2, it's disabled by default for performance concerns.// To install Babel, run: `npm install -D @babel/parser`directives: true,// Transform path before resolvingimportPathTransform: v => v,// 允许组件覆盖具有相同名称的其他组件allowOverrides: false,// Filters for transforming targets (components to insert the auto import)// Note these are NOT about including/excluding components registered - use `globs` for thatinclude: [/\.vue$/, /\.vue\?vue/],exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],// 项目的vue版本,如未指定,将自动检测// 可以接受的值: 2 | 2.7 | 3version: 2.7,// Only provide types of components in library (registered globally)types: []
})
2.2 修改构建配置 vue.config.js
// vite.config.js
import { defineConfig } from 'vite';
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({plugins: [// ...Components({resolvers: [AntDesignVueResolver({importStyle: false, // css in js}),],}),],
});
2.3 自动转换
然后你可以在代码中直接引入 ant-design-vue
的组件,插件会自动将代码转化为 import { Button } from 'ant-design-vue'
的形式。
import { Button } from 'ant-design-vue';
2.4 效果
重新打包
npm run build
得到report如下:
我们可以看到打包总体积从8.03MB减小到了6.33MB,减少了1.7MB,减少的量相对于初始值9.77MB,减少了17.4%
3. 按需引入vxe-table
环境
vxe-table@4.6.0
vite@5.0.10
vxe-table 官方文档介绍有关按需加载
的内容,有两种方式,vite-plugin-lazy-import
和 unplugin-vue-components
根据项目的当前环境去选择
方案
1. vite-plugin-lazy-import
如果您使用了 vite
,借助插件 vite-plugin-lazy-import
可以实现按需加载模块
1.1 安装
npm install vite-plugin-lazy-import -D
1.2 配置构建工具
// vite.config.js
import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import'export default defineConfig({plugins: [// ...,lazyImport({resolvers: [VxeResolver({libraryName: 'vxe-table'})]})// ...]
})
2. unplugin-vue-components
如果您使用了 webpack
,借助插件 unplugin-vue-components
可以实现按需加载模块,减少文件体积
2.1 安装
npm install unplugin-vue-components @vxecli/import-unplugin-vue-components
2.2 配置构建工具
// vue.config.js
import Components from 'unplugin-vue-components/webpack'
import { VxeTableResolver } from '@vxecli/import-unplugin-vue-components'export default defineConfig({plugins: [// ...,Components({resolvers: [VxeTableResolver()]})]
})
3. 导入使用
以下是全量的组件及模块安装列表:
// ...
import {// 全局实例对象VXETable,// 可选表格模块// VxeTableFilterModule,// VxeTableEditModule,// VxeTableMenuModule,// VxeTableExportModule,// VxeTableKeyboardModule,// VxeTableValidatorModule,// VxeTableCustomModule,// 可选组件VxeIcon,VxeTable,VxeColumn,VxeColgroup,// VxeGrid,// VxeTooltip,// VxeToolbar,// VxePager,// VxeForm,// VxeFormItem,// VxeFormGather,// VxeCheckbox,// VxeCheckboxGroup,// VxeRadio,// VxeRadioGroup,// VxeRadioButton,// VxeSwitch,// VxeInput,// VxeSelect,// VxeOptgroup,// VxeOption,// VxeTextarea,// VxeButton,// VxeButtonGroup,// VxeModal,// VxeDrawer,// VxeList,// VxePulldown
} from 'vxe-table'
// ...// 导入默认的语言
import zhCN from 'vxe-table/es/locale/lang/zh-CN'// 导入主题变量,也可以重写主题变量
import 'vxe-table/styles/cssvar.scss'// 按需加载的方式默认是不带国际化的,自定义国际化需要自行解析占位符 '{0}',例如:
VXETable.setConfig({i18n: (key, args) => XEUtils.toFormatString(XEUtils.get(zhCN, key), args)
})function LazyVxeUITable(app) {// 可选表格模块// app.use(VxeTableFilterModule)// app.use(VxeTableEditModule)// app.use(VxeTableMenuModule)// app.use(VxeTableExportModule)// app.use(VxeTableKeyboardModule)// app.use(VxeTableValidatorModule)// app.use(VxeTableCustomModule)// 可选组件app.use(VxeIcon)app.use(VxeTable)app.use(VxeColumn)app.use(VxeColgroup)// app.use(VxeVxeGrid)// app.use(VxeTooltip)// app.use(VxeToolbar)// app.use(VxePager)// app.use(VxeForm)// app.use(VxeFormItem)// app.use(VxeFormGather)// app.use(VxeCheckbox)// app.use(VxeCheckboxGroup)// app.use(VxeRadio)// app.use(VxeRadioGroup)// app.use(VxeRadioButton)// app.use(VxeSwitch)// app.use(VxeInput)// app.use(VxeSelect)// app.use(VxeOptgroup)// app.use(VxeOption)// app.use(VxeTextarea)// app.use(VxeButton)// app.use(VxeButtonGroup)// app.use(VxeModal)// app.use(VxeDrawer)// app.use(VxeList)// app.use(VxePulldown)
}createApp(App).use(LazyVxeUITable).mount('#app')
采用方案
vite-plugin-lazy-import
安装
修改vite.config.js
import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import'export default defineConfig({plugins: [// ...,lazyImport({resolvers: [VxeResolver({libraryName: 'vxe-table'})]})// ...]
})
组件使用
import {// 可选组件VxeTable,VxeColumn
} from 'vxe-table'
// ...function LazyVxeUITable(app) {// 可选组件app.use(VxeTable)app.use(VxeColumn)
}createApp(App).use(LazyVxeUITable).mount('#app')
问题处理
console
有警告消息 Failed to resolve component: vxe-table-custom-panel
需要引入组件 VxeTableCustomModule
并注册,报错消失
最后改为:
import {// 可选组件VxeTable,VxeColumn,VxeTableCustomModule
} from 'vxe-table'
// ...function LazyVxeUITable(app) {// 可选组件app.use(VxeTable)app.use(VxeColumn)app.use(VxeTableCustomModule)
}createApp(App).use(LazyVxeUITable).mount('#app')
效果
我们可以看到打包总体积从6.33MB减小到了5.78MB,减少了0.55MB,减少的量相对于初始值9.77MB,减少了5.62%
总结
通过按需加载Echarts, Ant-design-vue和Vxe-table,我们将打包的体积从9.77MB减小到了5.78MB,一共减少了3.99MB,缩小到了初始值的59.16%
代码分割
我们在打包之后,遇到另外一个问题看到终端有一段提示文字:
(!) Some chunks are larger than 500 KB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/guide/en/#outputmanualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
build.chunkSizeWarningLimit
类型: number
默认: 500
块大小警告的限制(以 kbs 为单位)
这段提示提示一个vite打包的问题,指的是存在体积过大(大于500KB)的单个js文件,一般调高chunk size就能解决这个问题
解决方案
export default defineConfig({plugins: [],build: {chunkSizeWarningLimit: 1500,rollupOptions: {output: {manualChunks(id) {if (id.includes('node_modules')) {return id.toString().split('node_modules/')[1].split('/')[0].toString();}}}}}
})
效果
我们看到报错信息消失,且体积较大的两个文件也消失了,实际上是被分割成了多块js脚本