背景
VUE CLI 项目是基于 webpack 构建,随着业务代码越来越多,构建速度会越来越慢,包体积也会很大,而且 VUE CLI 逐渐不再维护了。而 Vite 构建工具在市场上越来越成熟,已经升级到了 Vite 4.0 版本,市场上的响应也比较好,新项目基本上使用 Vite 来搭建,旧项目升级 Vite 也有了很多的成功案例,为了让旧的项目也具有这种好的开发体验,统一技术栈,所以有必要升级 Vue-Cli 为 Vite,提高项目的开发效率
升级方案
项目比较小,可以将开发环境和生产环境打包都使用 Vite
打包
如果项目体积和业务比较大,一下子开发和打包升级,会带来一定的风险,所以采用渐进式升级
- 开发环境使用
Vite
构建,仍保留webpack
打包功能,先享受到 Vite 带来的友好开发体验 - 在开发环境和测试打包稳定后,再替换使用
Vite
打包生产代码
升级实现步骤
安装必要依赖
项目使用 vue3
,如果要支持 jsx
,先安装下面三个依赖
yaml
npm i vite @vitejs/plugin-vue @vitejs/plugin-vue-jsx -D
新增 vite.config.js 配置
Vite 的默认执行配置文件是 vite.config.js
,插件 plugins
是一个数组,插件安装是一个函数,直接调用执行
jsx
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [
vue(),
vueJsx()
]
})
创建 html 文件
Vite 默认会先加载 html
文件,然后根据入口文件,请求 js 文件,html
文件默认是在项目根目录下
推荐使用 vite 插件 vite-html-plugin
生成 html
,该插件具有功能
- HTML 压缩能力
- EJS 模版能力
- 多页应用支持
- 支持自定义
entry
- 支持自定义
template
根据模板+数据注入生成html
jsx
import { defineConfig } from 'vite'
import { createHtmlPlugin } from 'vite-plugin-html'
export default defineConfig({
plugins: [
createHtmlPlugin({
minify: true,
/**
* 在这里写entry后,你将不需要在`index.html`内添加 script 标签,原有标签需要删除
* @default src/main.ts
*/
entry: '/src/main.ts',
/**
* 如果你想将 `index.html`存放在指定文件夹,可以修改它,否则不需要配置
* @default index.html
*/
template: 'index.html',
/**
* 需要注入 index.html ejs 模版的数据
*/
inject: {
data: {
title: 'index', // 出现在模版中的 <%- title %>
injectScript: `<script src="./inject.js"></script>`, // 出现在模版中的<%- injectScript %>
},
tags: [
{
injectTo: 'body-prepend',
tag: 'div',
attrs: {
id: 'tag',
},
},
],
},
}),
],
})
html 文件
html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="/favicon.ico">
<title><%- title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%- title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
</html>
兼容webpack和vite的环境变量用法
vue-cli 环境变量使用 process.env
,然而 vite 不能识别 process
node 模块,解决办法使用vite插件 vite-plugin-env-compatible ,让vite可以使用webpack中读取环境变量的方式
yaml
process.env.VUE_APP_BASE_API
使用vite插件 vite-plugin-env-compatible ,让vite中可以使用webpack中读取环境变量的方式
jsx
import { defineConfig } from 'vite'
import envCompatible from 'vite-plugin-env-compatible'
export default defineConfig({
plugins: [
envCompatible()
]
})
兼容 commonjs 代码
Vite 使用 ESM 模块加载,项目中有用到commonjs规范的依赖,需要进行转换,解决办法使用 vite 插件 @originjs/vite-plugin-commonjs
jsx
// vite.config.js 忽略其他代码
import { viteCommonjs } from '@originjs/vite-plugin-commonjs'
export default ({ mode }) => {
return defineConfig({
plugins: [
// ...
viteCommonjs() // 兼容vite中的cjs导入语法
]
})
})
如果有 es 模块,优先使用 es 包,如 lodash 模块 使用 lodash-es
解决css预处理的问题
1、样式路径引用问题,在 vue-cli
样式绝对路径是通过 ~@
方式引用,在 vite
会报错
jsx
<style lang="scss" scoped>
@import '~@/styles/variables';
...
</style>
解决办法,使用 resolve.alias
路径别名
js
resolve: {
alias: [
{
find: '@',
replacement: path.resolve(__dirname, 'src')
},
{
find: '~@',
replacement: path.resolve(__dirname, 'src')
}
],
},
2、引用全局 SCSS变量
SCSS
变量为了避免在多个文件重复引入,在 vite
配置 scss 预处理器
jsx
import { defineConfig, normalizePath } from 'vite'
// 全局 scss 文件的路径
// 用 normalizePath 解决 window 下的路径问题
const variablePath = normalizePath(path.resolve('./src/styles/variables.scss'))
export default defineConfig({
// css 相关的配置
css: {
preprocessorOptions: {
scss: {
// additionalData 的内容会在每个 scss 文件的开头自动注入
additionalData: `@import "${variablePath}";`
}
}
}
})
在 variables.scss
文件,如果想让 js
也可以引用 scss
变量,会通过下面方式导出
scss
$mainColor: #2f54eb;
// 省略
:export {
mainColor: $mainColor;
...
}
在其他文件如 js 中引入相关变量
jsx
import variables from '@/styles/variables.scss'
console.log(variables.mainColor) // #2f54eb
上面在 vue-cli 是正常的,在 vite 会报错,表示已经在全局引入,原因是重复引入了
解决办法是,新建一个variables.module.scss
的文件,把 scss 变量定义在这个文件里,然后 variables.scss
引入这个变量文件
jsx
@import './variables.module.scss';
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
// JS 与 scss 共享变量,在 scss 中通过 :export 进行导出,在 js 中可通过 ESM 进行导入
:export {
menuText: $menuText;
...
}
修改 vite 中引入的样式文件 variables.scss
为 variables.module.scss
,这样可以避免冲突了
jsx
const variablePath = normalizePath(path.resolve('./src/styles/variables.module.scss'))
node 内置模块不兼容
vite 不支持 fs
, path
内置模块,下载 path-browserify
,修改引入方法
js
mport path from 'path' // 修改
import path from 'path-browserify'
require.context 自动注册 svg 报错
require.context
是 webpack API,vite 不支持 require
commonjs 规范,使用 createSvgIconsPlugin
插件解决
1、安装 createSvgIconsPlugin vite 插件
jsx
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
export default defineConfig({
plugins:[
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹(路径为存放所有svg图标的文件夹不单个svg图标)
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/svg')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]'
})
]
})
• 在入口文件 main.ts
中引入
jsx
import 'virtual:svg-icons-register'
需要注意是使用 svg 名称的拼接格式 symbolId: 'icon-[dir]-[name]'
,它是 icon + 目录 + 文件名
jsx
# src/icons
- icon1.svg # icon-icon1
- icon2.svg # icon-icon2
- icon3.svg # icon-icon3
- dir/icon1.svg # icon-dir-icon1
- dir/dir2/icon1.svg # icon-dir-dir2-icon1
可以测试,打印所有的 svg 组件
jsx
import ids from 'virtual:svg-icons-names'
// => ['icon-icon1','icon-icon2','icon-icon3']
server 代理配置
jsx
server: {
strictPort: false,
port: 8877, // 启动端口
open: true, // 自动打开浏览器
proxy: {
'/API': {
target: 'http://XXX',
changeOrigin: true
}
}
}
循环引用报错
如果项目引入不规范,会经常遇见 Cannot access '...' before initialization
报错提示,大概率是循环引用导致的问题,这时就要去代码里检查修改为正确的引入方式
有个 issue 讨论过这个问题
生产打包
包分析可视化工具
安装工具 rollup-plugin-visualizer
jsx
import { visualizer } from "rollup-plugin-visualizer";
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [visualizer()]
})
图片资源打包
打包压缩图片资源,使用 vite-plugin-imagemin
yaml
npm i vite-plugin-imagemin -D
jsx
viteImagemin({
gifsicle: {
optimizationLevel: 7,
interlaced: false
},
optipng: {
optimizationLevel: 7
},
mozjpeg: {
quality: 20
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: {
plugins: [
{
name: 'removeViewBox'
},
{
name: 'removeEmptyAttrs',
active: false
}
]
}
}),
algorithm
指定了压缩算法为 gzip
;
threshold
指定文件大于10240b(10kb)时才压缩文件;
verbose
禁止在控制台输出压缩结果;
deleteOriginFile
指定压缩完文件后删除源文件
压缩文件
压缩打包后的资源文件
jsx
npm i vite-plugin-compression -D
jsx
import { defineConfig } from 'vite
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins:[
viteCompression(
{
algorithm: 'gzip',
threshold: 10240,
verbose: false,
deleteOriginFile: true
}
)
]
})
分包策略
如何要减少包体积,配置代码分割,以 element-plus
拆包为例
jsx
build: {
outDir: 'dist',
chunkSizeWarningLimit: 500,
rollupOptions: {
output: {
manualChunks (id) {
if (id.includes('element-plus')) {
return // 不打入这个文件相关的东西
}
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString()
}
}
}
}
}