-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bde8aed
commit 3b0545a
Showing
17 changed files
with
6,994 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,285 @@ | ||
# samples-plus-ts | ||
# vue3+vite 如何打包优化 | ||
|
||
## 前言 | ||
首先 npm run bulid 看一下打包之后的目录结构情况 | ||
|
||
## Project Setup | ||
![image](src/assets/readme/1.png) | ||
|
||
```sh | ||
npm install | ||
我们可以看出,第三方依赖包文件在没有处理的情况下是非常大的,而且随着版本的迭代、业务的增多,第三方依赖也会越来越多,打包出来的文件也会越来越大,从而造成网页在首次进入时比较缓慢。针对这个问题,首先想到的是分包的解决方案,将一个大文件分割成多个小文件,在第一次进入网页时可以提升加载速度。 | ||
|
||
## 区块分割(chunk split) | ||
|
||
根据 vite 官方文档提示,在vite的配置文件,通过 build.rollupOptions 的配置进行手动配置,代码如下: | ||
``` | ||
// vite.config.ts | ||
import { fileURLToPath, URL } from 'node:url' | ||
import { defineConfig } from 'vite' | ||
import vue from '@vitejs/plugin-vue' | ||
import DefineOptions from 'unplugin-vue-define-options/vite' | ||
// https://vitejs.dev/config/ | ||
export default defineConfig({ | ||
plugins: [vue(), DefineOptions() ], | ||
resolve: { | ||
alias: { | ||
'@': fileURLToPath(new URL('./src', import.meta.url)) | ||
} | ||
}, | ||
build: { | ||
rollupOptions: { | ||
output: { | ||
manualChunks(id: any) { | ||
// 最小化拆分包 | ||
if (id.includes('node_modules')) { | ||
return id.toString().split('node_modules/')[1].split('/')[0].toString() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}) | ||
``` | ||
|
||
通过上述优化,再一次进行 npm run bulid 打包,看一下结果 | ||
|
||
![image](src/assets/readme/2.png) | ||
|
||
这时候可以看出来,这样根据依赖分别拆分,打包出来的文件数量变多,单个文件的大小变小了。 | ||
在这个基础上可以继续扩展,例如: | ||
|
||
### 1、根据文件分类和重命名,把css、png、js分开 | ||
|
||
``` | ||
rollupOptions: { | ||
output: { | ||
// 用于命名代码拆分时创建的共享块的输出命名 | ||
chunkFileNames: `assets/chunk/[name]-[hash].js`, | ||
// 用于从入口点创建的块的打包输出格式 | ||
entryFileNames: `assets/entry/[name]-[hash].js`, | ||
// 用于输出静态资源的命名,打包后的目录中可能会出现png、jpg、svg、ttf、gif等目录。 | ||
assetFileNames: `assets/[ext]/[name]-[hash].[ext]`, | ||
manualChunks(id: string) { | ||
// 最小化拆分包 | ||
if (id.includes('node_modules')) { | ||
return id.toString().split('node_modules/')[1].split('/')[0].toString() | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
通过chunkFileNames、entryFileNames、assetFileNames实现分包和重命名,效果如下 | ||
|
||
![image](src/assets/readme/3.png) | ||
|
||
### 2、将业务模块分块打包,把第三方插件和实际业务模块分开 | ||
|
||
根据实际项目的具体情况分模块,比如我的 views 中,一个文件夹就是一个模块;我还想让css和图片就统一在assets中,重新规划一下文件,代码如下: | ||
|
||
``` | ||
build: { | ||
rollupOptions: { | ||
output: { | ||
// 按照一个模块一个文件,而且与第三方区分开 | ||
chunkFileNames: (chunkInfo) => { | ||
const { name, isDynamicEntry } = chunkInfo | ||
if (isDynamicEntry) { | ||
return `js/views/${name}-[hash].js` | ||
} | ||
return `js/vendor/${name}-[hash].js` | ||
}, | ||
// 用于从入口点创建的块的打包输出格式 | ||
entryFileNames: `js/[name]-[hash].js`, | ||
// assetFileNames 不设置 所有css和图片文件就默认在assets中 | ||
// assetFileNames: `assets/[ext]/[name]-[hash].[ext]`, | ||
manualChunks(id: string) { | ||
// 最小化拆分包 | ||
if (id.includes('node_modules')) { | ||
return id.toString().split('node_modules/')[1].split('/')[0].toString() | ||
} | ||
// 一个模块只要一个文件,如果需要一个vue一个文件,以下代码可忽略 | ||
if (id.includes('/src/views/')) { | ||
return id.toString().split('/src/views/')[1].split('/')[0] | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
效果如下: | ||
|
||
![image](src/assets/readme/4.png) | ||
|
||
|
||
### 3、第三方代码分包后,过于琐碎,只单独提出个别大文件 | ||
|
||
例如:我要拆分出来vue、element-plus、fit2cloud-ui-plus、axios | ||
|
||
``` | ||
build: { | ||
rollupOptions: { | ||
output: { | ||
// 用于命名代码拆分时创建的共享块的输出命名 | ||
chunkFileNames: (chunkInfo) => { | ||
const { name, isDynamicEntry } = chunkInfo | ||
if (isDynamicEntry) { | ||
return `js/views/${name}-[hash].js` | ||
} | ||
return `js/vendor/${name}-[hash].js` | ||
}, | ||
// 用于从入口点创建的块的打包输出格式 | ||
entryFileNames: `js/[name]-[hash].js`, | ||
// 用于输出静态资源的命名,打包后的目录中可能会出现png、jpg、svg、ttf、gif等目录。 | ||
// assetFileNames: `assets/[ext]/[name]-[hash].[ext]`, | ||
manualChunks(id: string) { | ||
// 根据不同模块 | ||
if (id.includes('/src/views/')) { | ||
return id.toString().split('/src/views/')[1].split('/')[0] | ||
} | ||
if (id.includes('node_modules')) { | ||
if (id.includes('vue')) { | ||
return 'vue' | ||
} else if (id.includes('element-plus')) { | ||
return 'element-plus' | ||
} else if (id.includes('fit2cloud-ui-plus')) { | ||
return 'fit2cloud-ui-plus' | ||
} else if (id.includes('axios')) { | ||
return 'axios' | ||
} | ||
return 'vendor' | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
效果如下: | ||
|
||
![image](src/assets/readme/5.png) | ||
|
||
当然 vendor 和 vue 仍然可以进一步拆分,根据需要。 | ||
|
||
如果项目中有 echart 或者 富文本编辑器,只是想把这些单独打包,并不想复杂分类,那么也可以像以下代码这样,简洁配置 | ||
``` | ||
rollupOptions: { | ||
output: { | ||
manualChunks: { | ||
editor: ['mavon-editor', '@kangc/v-md-editor'] | ||
}, | ||
}, | ||
}, | ||
``` | ||
此外还有一些第三方的拆分插件工具可以,比如:vite-plugin-chunk-split。 | ||
|
||
### 4、删除不必要的依赖项或者使用 CDN | ||
|
||
可以通过 rollup-plugin-visualizer 插件进行可视化分析,找出哪些依赖项占用了最多的空间。然后,您可以考虑删除不必要的依赖项或将其替换为更轻量级的库。 | ||
|
||
``` | ||
import { visualizer } from 'rollup-plugin-visualizer' | ||
export default defineConfig({ | ||
plugins: [ | ||
// ... | ||
visualizer() | ||
// ... | ||
], | ||
}); | ||
``` | ||
依赖项也可以引入外部CDN库???? | ||
|
||
## 打包压缩 | ||
|
||
对文件进一步压缩,让文件更小,以下是打包压缩的几种方法: | ||
|
||
### 1、build.minify | ||
|
||
### Compile and Hot-Reload for Development | ||
vite官网有介绍,build.minify 包括两个模式,'terser' 和 'esbuild',build.minify开启以后,默认是为 esbuild,它比 terser 快 20-40 倍,压缩率只差 1%-2%。当设置为 'terser' 时必须先安装 Terser。 | ||
|
||
```sh | ||
npm run dev | ||
``` | ||
npm add -D terser | ||
``` | ||
|
||
然后用 build.terserOptions 对 terser 进行配置。 | ||
但是也要注意,在 lib 模式下使用 'es' 时,build.minify 选项不会缩减空格,因为会移除掉 pure 标注,导致破坏 tree-shaking。 | ||
|
||
### Type-Check, Compile and Minify for Production | ||
``` | ||
build: { | ||
minify: 'terser', | ||
// 去掉 console、debug | ||
terserOptions: { | ||
compress: { | ||
drop_console: true, | ||
drop_debugger: true, | ||
}, | ||
}, | ||
... | ||
} | ||
``` | ||
对比一下配置 terser 之后,打包出来的文件大小 | ||
|
||
```sh | ||
npm run build | ||
![image](src/assets/readme/6.png) | ||
|
||
文件确实小了那么一些些,可以根据项目需要选择是否用这种模式打包。也可以区分出来开发环境和生产环境的配置,避免影响调试。 | ||
|
||
### 2、vite-plugin-compression | ||
|
||
vite-plugin-compression 插件,可以自动在构建时进行 gzip 压缩,并支持多种压缩算法。 | ||
|
||
安装插件 | ||
|
||
``` | ||
npm install vite-plugin-compression -D | ||
``` | ||
然后在 vite.config.ts 中进行如下配置: | ||
``` | ||
plugins: [ | ||
vue(), | ||
viteCompression({ | ||
// gzip静态资源压缩配置 | ||
verbose: true, // 是否在控制台输出压缩结果 | ||
disable: false, // 是否禁用压缩 | ||
threshold: 10240, // 启用压缩的文件大小限制 | ||
algorithm: 'gzip', // 采用的压缩算法 | ||
ext: '.gz', // 生成的压缩包后缀 | ||
}), | ||
], | ||
``` | ||
|
||
### Lint with [ESLint](https://eslint.org/) | ||
![image](src/assets/readme/7.png) | ||
|
||
此时打包文件夹中多出几个 gz 结尾的文件,文件大小会比没压缩之前小了一些些,但是此方法也会存在一些缺点: | ||
- 压缩的静态文件需要消耗一定的 CPU 和内存资源; | ||
- 针对个别大文件可以进行进一步压缩,不是很大的文件不建议这样处理,因为浏览器解压时间可能大于请求原来资源的时间,反而适得其反; | ||
- 在某些情况下,压缩算法可能会导致压缩后的文件无法正常解压或运行,因此需要进行充分的测试和验证。 | ||
|
||
|
||
此外也有其他压缩插件,比如 rollup-plugin-gzip,功能是一样的,缺点也是增加 CPU 消耗;配合vite的内置压缩,更推荐 vite-plugin-compression。也有图片和 css 的压缩工具 vite-plugin-imagemin 等等。 | ||
|
||
## 三、懒加载和异步组件 | ||
|
||
通过懒加载模块避免一次加载所以模块,然后每个模块中还可以使用异步加载,加速网站响应速度,提高用户体验。 | ||
|
||
### 1、defineAsyncComponent | ||
|
||
在 Vue 2.x 中,声明一个异步组件只需这样: | ||
|
||
``` | ||
const asyncPage = () => import('./views/home.vue') | ||
``` | ||
|
||
在 Vue 3.x 中,声明一个异步组件只需这样: | ||
|
||
``` | ||
import { defineAsyncComponent } from 'vue' | ||
const child = defineAsyncComponent(() => import('@/components/async-component-child.vue')) | ||
``` | ||
|
||
```sh | ||
npm run lint | ||
``` | ||
const AsyncComponent = defineAsyncComponent({ | ||
loader: () => import('./AsyncComponent.vue'), | ||
delay: 200, | ||
timeout: 3000 | ||
}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* eslint-disable */ | ||
/* prettier-ignore */ | ||
// @ts-nocheck | ||
// Generated by unplugin-auto-import | ||
export {} | ||
declare global { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* eslint-disable */ | ||
/* prettier-ignore */ | ||
// @ts-nocheck | ||
// Generated by unplugin-vue-components | ||
// Read more: https://github.com/vuejs/core/pull/3399 | ||
import '@vue/runtime-core' | ||
|
||
export {} | ||
|
||
declare module '@vue/runtime-core' { | ||
export interface GlobalComponents { | ||
BackButton: typeof import('./src/components/back-button/index.vue')['default'] | ||
ElButton: typeof import('element-plus/es')['ElButton'] | ||
ElCard: typeof import('element-plus/es')['ElCard'] | ||
ElCol: typeof import('element-plus/es')['ElCol'] | ||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] | ||
ElContainer: typeof import('element-plus/es')['ElContainer'] | ||
ElIcon: typeof import('element-plus/es')['ElIcon'] | ||
ElInput: typeof import('element-plus/es')['ElInput'] | ||
ElMenu: typeof import('element-plus/es')['ElMenu'] | ||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] | ||
ElRow: typeof import('element-plus/es')['ElRow'] | ||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] | ||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] | ||
ElTag: typeof import('element-plus/es')['ElTag'] | ||
LabelValue: typeof import('./src/components/view-card/label-value/index.vue')['default'] | ||
LayoutContent: typeof import('./src/components/layout-content/index.vue')['default'] | ||
RouterLink: typeof import('vue-router')['RouterLink'] | ||
RouterView: typeof import('vue-router')['RouterView'] | ||
ViewCard: typeof import('./src/components/view-card/index.vue')['default'] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
/// <reference types="vite/client" /> | ||
declare module 'path-browserify' | ||
declare module 'path-browserify' |
Oops, something went wrong.