Vue3项目打包优化

前言

本文介绍在实际项目中进行打包优化过程

目前评分 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-chartsecharts-glant-design-vuehighchartsvxe-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-GLECharts 的扩展包,提供 3D 绘图、地球可视化和 WebGL 加速

Echarts-GLGithub 主页我们看到按需引入的指引

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 modulestree 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提供了内置的解析器,以下是它支持的解析器:

示例用法:

// 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,那么默认值是true
  dts: 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 resolving
  importPathTransform: 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 that
  include: [/\.vue$/, /\.vue\?vue/],
  exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],

  // 项目的vue版本,如未指定,将自动检测
  // 可以接受的值: 2 | 2.7 | 3
  version: 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-importunplugin-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脚本
在这里插入图片描述

相关推荐

  1. vue 项目优化打包时间

    2024-07-11 03:32:02       53 阅读
  2. Vue3 打包优化与部署

    2024-07-11 03:32:02       35 阅读
  3. vue项目打包体积大优化之-productionSourceMap设置

    2024-07-11 03:32:02       56 阅读
  4. Webpack打包vue项目

    2024-07-11 03:32:02       50 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-11 03:32:02       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-11 03:32:02       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-11 03:32:02       58 阅读
  4. Python语言-面向对象

    2024-07-11 03:32:02       69 阅读

热门阅读

  1. 解决Spring Boot中的安全漏洞与防护策略

    2024-07-11 03:32:02       21 阅读
  2. 二、Python日志系统之watchDog监控日志

    2024-07-11 03:32:02       22 阅读
  3. 如何预防SQL注入

    2024-07-11 03:32:02       22 阅读
  4. 1、预处理

    2024-07-11 03:32:02       23 阅读
  5. Jmeter进阶-接口自动化

    2024-07-11 03:32:02       17 阅读
  6. 在 Ubuntu 上玩转 WordPress

    2024-07-11 03:32:02       24 阅读
  7. Redis 数据过期及淘汰策略

    2024-07-11 03:32:02       21 阅读
  8. VSCode 推荐插件列表(都安装到Remote SSH上)

    2024-07-11 03:32:02       18 阅读
  9. bug——多重定义

    2024-07-11 03:32:02       23 阅读