Vite 是一种新的前端构建工具,能够显著提升前端开发体验,关于 Vite 更多的功能请前往官网。
对于 Vite 使用,如果是新项目,则可使用命令 pnpm create vite 创建;如果是使用 Vue CLI 工具创建的项目,则需要在其基础上进行改造。
本文记录 Vue CLI 项目迁移到 Vite 所遇到的坑。
迁移步骤
移除 Vue CLI
把与 Vue CLI 相关的依赖及配置进行移除,即 package.json 文件中 scripts,
原先 scripts 如下:
{
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
}
}
改动 scripts,如下:
{
"scripts": {
"serve": "vite",
"build": "vite build",
"lint": "vite lint",
}
}
修改入口文件
Vue CLI 入口默认是 src/main.js,而 Vite 的入口文件是 index.html,借用官方对 index.html 的解释:
Vite 将
index.html视为源码和模块图的一部分。Vite 解析<script type="module" src="...">,这个标签指向你的 JavaScript 源码。甚至内联引入 JavaScript 的<script type="module">和引用 CSS 的<link href>也能利用 Vite 特有的功能被解析。另外,index.html中的 URL 将被自动转换,因此不再需要%PUBLIC_URL%占位符了。
首先,需要将 public/index.html 移动到根目录,然后对其进行修改,即添加一行代码 <script type="module" src="/src/main.js"></script> 具体如下:
<!DOCTYPE html>
<html>
<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">
<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>
<!-- Vite将自动解析下面的js文件 -->
<script type="module" src="/src/main.js"></script>
</body>
</html>
环境变量不同
Vue CLI 和 Vite 的环境变量加载都是通过 dotenv 来实现,因此在命名约定上是一致的。但是它们之间有两点不同:
暴露方式
- Vue CLI 对外暴露的变量只有
NODE_ENV、BASE_URL和VUE_APP_开头的变量 - Vite 对外暴露的变量:
MODE、BASE_URL、PROD、DEV和以VITE_开头的变量
访问方式
- Vue CLI 通过
process.env对变量进行访问 - Vite 通过
import.meta.env对变量进行访问
因此,在迁移项目,需要对变量进行替换,
- 将以
VUE_APP_开头的变量替换为以VITE_开头的变量 - 将
process.env替换为import.meta.env
不能忽略自定义导入类型扩展名
使用 Vue CLI 时,在引入文件(如:.vue)时,可不用写后缀名,IDE 可自动识别,比如
import Test from '@views/Test'
但是在 Vite 中,需要加上后缀名,否则构建时会报错。
引入 Vite
安装 Vite 依赖
pnpm add -D vite
// or
npm install -D vite
为了让 Vite 能解析 Vue,需要安装相应的插件,官方也提供插件支持,具体如下:
pnpm add -D @vitejs/plugin-vue // Vue3
pnpm add -D vite-plugin-vue2 // Vue2
创建配置文件 vite.config.js
在根目录创建配置文件 vite.config.js,Vite 会自动加载配置文件,执行构建工作,配置具体如下:
import { fileURLToPath, URL } from 'url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
base: 'xxxx'
});
这是最基础的配置,根据项目需求可在此配置上进行扩展。
扩展配置 vite.config.js
proxy
在请求服务端接口时,我们都需要做一层代理来实现转发请求。在 Vue CLI 通过 proxy 配置,对于 Vite,也是通过 proxy 来转发的,具体配置如下:
import { defineConfig } from 'vite';
// https://vitejs.dev/config/
export default defineConfig({
server: {
host: 'xxx.xxx.xxxx',
proxy: {
'^/api': {
target: 'xxxx',
changeOrigin: true,
},
}
}
});
JSX
Vite 默认不支持 JSX,如果要支持 JSX,需要安装相关依赖及配置,对于 JSX 使用方式有两种:.jsx和 .vue 文件中使用 JSX 语法,以下介绍的是后者,具体如下:
- 安装
@vitejs/plugin-vue-jsx
pnpm add -D @vitejs/plugin-vue-jsx
- 修改
vite.config.js
import { defineConfig } from 'vite';
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vueJsx()],
});
- 在
.vue使用 JSX 语法的,需要在script标签添加标识jsx,即
<script setup lang="jsx"></script>
- 对于在
.js使用到 JSX 语法的,需要将其后缀名改为jsx。
CSS 全局变量
在项目中,我们会定义一些 CSS 变量,将其设置为全局变量,方便直接使用,而无需引入,具体配置如下:
import { defineConfig } from 'vite';
// https://vitejs.dev/config/
export default defineConfig({
css: {
preprocessorOptions: {
less: {
modifyVars: {
hack: `true; @import (reference) "${path.resolve('src/assets/xxxxx/variable.less')}"`
}
}
}
}
});
SVG 雪碧图
在 Vue CLI 项目中,使用 webpack 提供的 svg-sprite-loader 插件实现 SVG 雪碧图;而在 Vite 中,也可以通过插件来实现,目前采用的方案是编写插件读取、解析 SVG 文件,根据 symbol 规则重新生成,并将其插入到 body,具体如下:
- 编写Vite 插件
// 参考链接:https://juejin.cn/post/6932037172178616334
// src/plugins/index.js
import { readFileSync, readdirSync } from 'fs'
let idPerfix = ''
const svgTitle = /<svg([^>+].*?)>/
const clearHeightWidth = /(width|height)="([^>+].*?)"/g
const hasViewBox = /(viewBox="[^>+].*?")/g
const clearReturn = /(\r)|(\n)/g
function findSvgFile(dir) {
const svgRes = []
const dirents = readdirSync(dir, {
withFileTypes: true,
})
for (const dirent of dirents) {
if (dirent.isDirectory()) {
svgRes.push(...findSvgFile(dir + dirent.name + '/'))
} else {
const svg = readFileSync(dir + dirent.name)
.toString()
.replace(clearReturn, '')
.replace(svgTitle, ($1, $2) => {
let width = 0
let height = 0
let content = $2.replace(clearHeightWidth, (s1, s2, s3) => {
if (s2 === 'width') {
width = s3
} else if (s2 === 'height') {
height = s3
}
return ''
})
if (!hasViewBox.test($2)) {
content += `viewBox="0 0 ${width} ${height}"`
}
return `<symbol id="${idPerfix}-${dirent.name.replace(
'.svg',
''
)}" ${content}>`
})
.replace('</svg>', '</symbol>')
svgRes.push(svg)
}
}
return svgRes
}
export const svgPlugin = (path, perfix = 'icon') => {
if (path === '') return
idPerfix = perfix
const res = findSvgFile(path)
return {
name: 'svg-transform',
transformIndexHtml(html) {
return html.replace(
'<body>',
`
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
${res.join('')}
</svg>
`
)
},
}
}
vite.config.js引入插件
import { defineConfig } from 'vite';
import { svgPlugin } from './src/plugins/svg'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svgPlugin('./src/assets/xxx/svg/')],
});
vite.config.js 最终配置如下:
import { fileURLToPath, URL } from 'url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx'
import path from 'path'
import { svgPlugin } from './src/plugins/svg'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), vueJsx(), svgPlugin('./src/xxx/svg/')],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
host: 'xxx.xxx.xxxx',
proxy: {
'^/api': {
target: '',
changeOrigin: true,
},
}
},
css: {
preprocessorOptions: {
less: {
modifyVars: {
hack: `true; @import (reference) "${path.resolve('src/assets/xxx/variable.less')}"`
}
}
}
},
base: 'xxx'
});
报错信息收集
- TypeError: Failed to fetch dynamically imported module: