介绍
为什么接入vite?
Vite 支持原生 ESM 模块(依托现代浏览器)+ 文件缓存,显著降低了应用的启动和重新构建时间。 其本质上是一个在开发环境为浏览器按需提供文件的 Web Server。
接入要求
vite运行环境需要node 14.18+ / 16+
为什么只在开发环境接入?
- node版本升级,某些依赖包可能会存在兼容问题
- 相比webpack技术比较新,与现有项目结合,可能会产生一些奇怪的问题
- 直接上生产环境风险比较高,建议先用于本地开发提效
数据对比
| vue-cli | vite | |
|---|---|---|
| 冷启动 | 25s | 1.5s |
| 热更新 | 4s | 1s |
| 更改环境变量后重启 | 60s | 1.5s |
接入指引(踩坑记录)
兼容vue2
使用 vite 插件兼容 Vue2.7:@vitejs/plugin-vue2
注意:插件
vite-plugin-vue2只针对2.6及以下版本
// vite.config.js
import vue from '@vitejs/plugin-vue2'
export default defineConfig({
plugins: [
vue() // 兼容vue2.7
],
})
兼容jsx语法
vue2.7 提供的Composition Api 内部使用了jsx的语法,需要使用 vite 插件兼容:@vitejs/plugin-vue2-jsx
// vite.config.js
import vueJsx from '@vitejs/plugin-vue2-jsx'
export default defineConfig({
plugins: [
vueJsx() // 兼容jsx语法
],
})
兼容process.env语法
与webpack不同,Vite 在一个特殊的 import.meta.env 对象上暴露环境变量。这里主要解决两个问题:
- 兼容webpack和vite的环境变量用法
使用vite插件 vite-plugin-env-compatible ,让vite可以使用webpack中读取环境变量的方式,再配合envPrefix配置,让vite可以读取到VUE_APP_开头的环境变量。
// vite.config.js
import { defineConfig } from 'vite'
import envCompatible from 'vite-plugin-env-compatible'
export default defineConfig({
plugins: [
envCompatible() // 兼容process.env获取环境变量
],
envPrefix: ['VUE_APP_']
})
- vite.config.js中不能读取到环境变量
业务代码在vite环境中运行,可直接通过import.meta.env获取环境变量。vite.config.js 在 node 环境中运行,是无法直接通过import.meta.env获取环境变量,需要通过vite的 loadEnv 或process.env获取。
// vite.config.js
import { defineConfig, loadEnv } from 'vite'
import { createHtmlPlugin } from "vite-plugin-html";
export default ({ mode }) => {
function getEnv(key) {
return loadEnv(mode, process.cwd(),'')[key]
}
return defineConfig({
base: getEnv('VUE_APP_BASE_URL'), // 部署应用包时的基本URL,'/'表示静态目录的根目录
plugins: [
createHtmlPlugin({ // 该插件用于编译时在index.html中注入title
inject: {
data: {
title: process.env.VUE_APP_SYSTEM === 'MANAGE' ? '管理平台' : '作业平台',
},
},
})
],
})
})
兼容CommonJS模块化
vite 通过拦截原生 ESModule 加载的 http 请求来实现按需编译,预编译机制只会对 node_modules 里面的包通过esbuild 进行打包预编译,业务代码不支持 commonJs 的 require、exports、modules.exports语法。
使用 vite 插件兼容 commonjs 语法:@originjs/vite-plugin-commonjs
// vite.config.js
import { viteCommonjs } from '@originjs/vite-plugin-commonjs'
export default defineConfig({
plugins: [
viteCommonjs() // 兼容commonjs
],
})
兼容require.context语法
webpack使用 require.context() 来动态查找文件内容,vite不支持。有两种修改方式:
- 将
reqiure.context()修改为import.meta.globEager() - 使用插件兼容:@originjs/vite-plugin-require-context
// vite.config.js
import viteRequireContext from '@originjs/vite-plugin-require-context'
export default defineConfig({
plugins: [
viteRequireContext(), // 兼容require.context
],
})
兼容sass /deep/语法
wepack中使用的node-sass,深度选择器为/deep/,在sass中不兼容,需要使用自定义插件将 /deep/ 替换为 ::v-deep
其次,在使用scss时如果使用了scss变量,需将变量声明文件添加到css预处理选项中。
// vite.config.js
// ...
function transformScss () {
return {
name: 'vite-plugin-transform-scss',
enforce: 'pre',
transform(src, id) {
if (
/\.(js|ts|tsx|vue)(\?)*/.test(id) &&
id.includes('lang.scss') &&
!id.includes('node_modules')
) {
return {
code: src.replace(/\/deep\//gi, '::v-deep'),
};
}
},
};
}
export default defineConfig({
plugins: [
transformScss(), // 兼容node-sass /deep/写法
],
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "@/assets/styles/scss/var.scss";',
}
},
},
})
无模块默认导出
webpack会对引入的模块进行包装,即使没有导出模块也不会报错,但是在vite下会报错。可使用自定义插件,对没有模块默认导出的文件,增加默认导出
function transformFileExport() {
return {
name: 'vite-plugin-transform-file', // 文件未设置默认导出时使用
transform(src, id) {
if (
id.includes('.vite/deps/pdfjs-dist_build_pdf__worker__entry') // 未设置默认导出的文件
) {
const newCode = src.replace('export', 'export default')
return {
code: newCode,
};
}
},
};
}
export default defineConfig({
plugins: [
transformFileExport(), // 设置模块默认导出
],
})
解决循环引用问题
vite在热更新时,如果检测到循环引用,会直接失败,通过通过自定义插件,拷贝引用循环中的依赖到.vite缓存目录下,替换引用该依赖文件的路径指向.vite目录下,中断循环链。
import fs from 'fs';
import path from 'path';
function transformCircularReference () {
return {
name: 'vite-plugin-circular-reference',
enforce: 'pre',
buildStart() {
fs.cpSync(
path.resolve(__dirname, './src/fileName'), // 循环引用的起点文件
path.resolve(__dirname, './node_modules/.vite/fileName'),
{
recursive: true,
force: true,
},
);
},
transform(src, id) {
if (//src/fileName/index.ts$/i.test(id)) {
const code = src.replace(
/(?<=')./src(?=/)/gi,
'/node_modules/.vite/fileName/src',
);
return {
code,
};
}
},
};
}
export default defineConfig({
plugins: [
transformCircularReference(), // 解决循环引用问题
],
})
解决path报错问题
vite 源码中设定了不允许在客户端代码中访问内置模块代码
Error:Module "path" has been externalized for browser compatibility. Cannot access "path.resolve" in client code.
使用
import path from 'path-browserify'
替换
import path from 'path'
解决@vue/compiler-sfc对vue-property-decorator兼容问题
部分使用 vue-property-decorator 语法的组件可能会因为@vue/compiler-sfc兼容问题编译报错
[vite] Internal Server Error
Cannot overwrite across a split point
at MagicString.overwrite (D:\workspace\project\node_modules\@vue\compiler-sfc\dist\compiler-sfc.js)
使用
<script>
@Component({})
class CustomerComponent extends Vue {
...
}
export default CustomerComponent
</script>
替换
<script>
@Component({})
export default class CustomerComponent extends Vue {
...
}
</script>
解决node-sass 和 sass 版本问题
webpack环境下对css sass的支持主要依赖于:
- sass-loader
- node-sass(作为sass-loader依赖)
因为 sass-loader和 node-sass 版本不匹配可能会导致一些奇怪的bug,社区提供了一些版本搭配:
// package.json
"node-sass": "4.14.1",
"sass-loader": "7.1.0",
node-sass对node环境有明确要求,v4.14.1对应node版本最高为node v14。
vite环境下对css sass的支持主要依赖于:
- sass(封装了dart-sass)
vite运行环境需要node 14.18+/16+,所以升级node到v16以后,node-sass将无法运行。
如果目前项目里面使用的node-sass,接入vite后需要安装sass。两者会产生node版本冲突,目前的解决方案是:
- 在node v14下安装项目依赖
- 在node v16下运行vite项目
完整配置
// vite.config.js
import fs from 'fs';
import path from 'path'; // vite.config.js在node环境下运行,这里可以使用path
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue2'
import vueJsx from '@vitejs/plugin-vue2-jsx'
import envCompatible from 'vite-plugin-env-compatible'
import { createHtmlPlugin } from "vite-plugin-html";
import { viteCommonjs } from '@originjs/vite-plugin-commonjs'
import viteRequireContext from '@originjs/vite-plugin-require-context'
function resolve (dir) {
return path.join(__dirname, dir)
}
function transformScss () {
return {
name: 'vite-plugin-transform-scss',
enforce: 'pre',
transform(src, id) {
if (
/\.(js|ts|vue)(\?)*/.test(id) &&
id.includes('lang.scss') &&
!id.includes('node_modules')
) {
return {
code: src.replace(/\/deep\//gi, '::v-deep '),
};
}
},
};
}
function transformFileExport() {
return {
name: 'vite-plugin-transform-file-export', // 文件未设置默认导出时使用
transform(src, id) {
if (
id.includes('.vite/deps/pdfjs-dist_build_pdf__worker__entry') // 未设置默认导出的文件
) {
const newCode = src.replace('export', 'export default')
return {
code: newCode,
};
}
},
};
}
function transformCircularReference () {
return {
name: 'vite-plugin-transform-circular-reference',
enforce: 'pre',
buildStart() {
fs.cpSync(
path.resolve(__dirname, './src/fileName'), // 循环引用的起点文件
path.resolve(__dirname, './node_modules/.vite/fileName'),
{
recursive: true,
force: true,
},
);
},
transform(src, id) {
if (//src/fileName/index.ts$/i.test(id)) {
const code = src.replace(
/(?<=')./src(?=/)/gi,
'/node_modules/.vite/fileName/src',
);
return {
code,
};
}
},
};
}
export default ({ mode }) => {
function getEnv(key) {
return loadEnv(mode, process.cwd(),'')[key]
}
return defineConfig({
base: getEnv('VUE_APP_BASE_URL'), // 部署应用包时的基本URL,'/'表示静态目录的根目录
publicDir: 'assets', // 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。
plugins: [
vue(), // 兼容vue2
vueJsx(), // 兼容jsx语法
envCompatible(), // 兼容process.env获取环境变量
viteCommonjs(), // 兼容commonjs
viteRequireContext(), // 兼容require.context
transformScss(), // 兼容node-sass /deep/语法
transformFileExport(), // 设置模块默认导出
transformCircularReference(), // 解决循环引用问题
createHtmlPlugin({ // 编译时在index.html中注入title
inject: {
data: {
title: title: process.env.VUE_APP_SYSTEM === 'MANAGE' ? '管理平台' : '作业平台',
},
},
})
],
envPrefix: ['VUE_APP_'],
resolve: {
alias: [
{ find: '@', replacement: resolve('src') },
{ find: '@types', replacement: resolve('src/types/ index') },
],
extensions: ['.mjs', '.js', '.ts','.vue', '.jsx', '.tsx', '.json', , '.less'] // 需要忽略的文件后缀
},
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "@/assets/styles/scss/var.scss";',
}
},
},
server: {
port: 8080, // 端口号
hmr: {
overlay: false
},
proxy: {
// ...
}
},
})
}