vue2.x老项目vue-cli升级vite
由于vue2.x的老项目越来越庞大,导致每次启动的时候要等差不多一分钟的时间,vite4不是都出来了嘛,所以就在琢磨如何将老项目升级到vite,秒启动项目。
升级vite首先要分析的是vue-cli和vite的差异有哪些地方。
vue-cli和vite的差异
依赖引入方式
vite中统一采用import的方式引入依赖和资源,vue-cli采用的webpack,故可能会有代码用required去引入依赖,这部分就需要处理一下。
index.html的处理
在vue-cli中,项目入口文件一般是放在public
文件夹中,并且解析html采用的是webpack的html-webpack-plugin
插件来进行的,vite默认是不用插件处理html文件的。所以html-webpack-plugin中的一些占位符语法就不能在vite中出现,比如:
<link rel="icon" href="<%= BASE_URL %>favicon-alone.png">
这种代码vite不用插件就无法识别,会报错。
.vue文件引入路径
在vue-cli中,引入vue文件不需要写全.vue
的后缀名,但是vite默认是需要后缀名的,虽然可以通过配置文件省略掉,但是建议还是补全后缀名,这对vscode解析vue代码起到帮助作用。
对环境变量的命名规范和使用
vue-cli和vite环境变量都可以采用.env
文件去存储,但是在变量的命名方式上有很大差异。比如vue-cli是VUE_APP_
前缀的环境变量,vite则是采用VITE_
的变量前缀。
环境变量使用方面,vue-cli是采用process.env.xxx
来使用;vite则是采用import.meta.env.xxx
来使用。
解决方案
required依赖引入方式
处理required引入的方式,可以采用vite插件来完成,安装@originjs/vite-plugin-commonjs
pnpm add @originjs/vite-plugin-commonjs -D
vite.config.js
import { viteCommonjs } from '@originjs/vite-plugin-commonjs'
...
plugins: [
viteCommonjs()
]
...
index.html的处理
将原项目中public/index.html
移动到根目录,安装vite-plugin-html并进行相应的配置即可。
pnpm add vite-plugin-html -D
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%- title %></title>
<%- injectScript %>
</head>
import { defineConfig, Plugin } from 'vite'
import vue from '@vitejs/plugin-vue'
import { createHtmlPlugin } from 'vite-plugin-html'
export default defineConfig({
plugins: [
vue(),
createHtmlPlugin({
minify: true,
/**
* After writing entry here, you will not need to add script tags in `index.html`, the original tags need to be deleted
* @default src/main.ts
*/
entry: 'src/main.ts',
/**
* If you want to store `index.html` in the specified folder, you can modify it, otherwise no configuration is required
* @default index.html
*/
template: 'public/index.html',
/**
* Data that needs to be injected into the index.html ejs template
*/
inject: {
data: {
title: 'index',
injectScript: `<script src="./inject.js"></script>`,
},
tags: [
{
injectTo: 'body-prepend',
tag: 'div',
attrs: {
id: 'tag',
},
},
],
},
}),
],
})
处理.vue文件引入路径
可以写个nodejs脚本去处理这种问题,不然人工一个个去改可太费时费力了。主要思路说一下,首先如何识别到vue引入路径的代码呢。其实核心思想就是利用工具去讲代码解析成AST树,然后再进行相应的替换操作。
项目下可能会出现引入vue文件的代码,只会在.js和.vue文件中,那么对这两种文件进行解析即可。
解析.js文件可以利用@babel/core
这个包,解析.vue文件可以用@vue/compiler-sfc
这个包。注意,@vue/compiler-sfc
只能把vue文件中的script代码搞出来,最后还是得用babel去解析成AST。具体代码如下:
import fs from 'fs-extra'
import { parseAsync } from '@babel/core'
import { parse } from '@vue/compiler-sfc'
import path from 'path'
// 这个是你的项目的完整路径下的src
const base = `/Users/xxx/xxxx/src`
async function addFix(fileurl) {
let content = fs.readFileSync(fileurl, 'utf-8')
const replaceAt = string => string.replace(/^@(.*)/, `${base}$1`)
let c, d, importNodes;
if(fileurl.endsWith('.vue')) {
c = parse(content)
const script = c.descriptor.script || c.descriptor.scriptSetup
if(!script) return
d = await parseAsync(script.content)
importNodes = d.program.body.filter(({ type }) => type === 'ImportDeclaration')
.map(v => v.source.value)
} else if(fileurl.endsWith('.js')) {
d = await parseAsync(content)
importNodes = d.program.body
.filter(({type}) => type === 'VariableDeclaration')
.map(({declarations}) => declarations[0])
.filter(v => v.init.body?.callee?.type === 'Import')
.map(v => (v.init.body.arguments[0].value))
.concat(
d.program.body
.filter(({type}) => type === 'ImportDeclaration')
.map(({source}) => source.value)
)
} else return
importNodes.map(v => ({source: v, rep: readFile(replaceAt(v), v, fileurl)}))
.forEach(v => {
if(v.rep) {
content = content.replace(v.source, v.rep)
}
})
fs.writeFileSync(fileurl, content, 'utf-8')
}
/**
*
* @param {string} p
*/
function loop(p) {
if(fs.lstatSync(p).isDirectory()) {
fs.readdirSync(p).forEach(v => {
loop(path.resolve(p, v))
})
} else {
addFix(p)
}
}
loop(base)
/**
*
* @param {string} url
* @param {string} p
*/
function readFile(url, p, fileurl, flag = false) {
if(url.endsWith('.vue')) return false
if(fs.pathExistsSync(`${url}.js`)) return false
if(fs.pathExistsSync(`${url}.vue`)) return `${p}.vue`
if(fs.pathExistsSync(url) && fs.lstatSync(url).isDirectory() && fs.pathExistsSync(`${url}/index.vue`)) return `${p}/index.vue`
if(p.startsWith('./') && !flag) {
const u = path.resolve(fileurl, '../', p)
return readFile(u, p, fileurl, true)
}
return false
}
处理环境变量
处理环境变量就比补全.vue后缀简单一些了,因为无需解析AST,直接文字硬匹配替换就好了。
此外,不一定要替换环境变量前缀,vite现在支持自定义环境变量前缀了,通过配置envPrifix 来实现。
envPrifix: 'VUE_APP_'
唯一需要处理的就是使用环境变量的代码。
import fs from 'fs-extra'
import path from 'path'
// 这个是你的项目的完整路径下的src
const base = `/Users/xxx/xxxx/src`
/**
*
* @param {string} fileurl
*/
function replace(fileurl) {
const prefix = path.extname(fileurl)
if(!['.vue', '.js'].includes(prefix)) return
let content = fs.readFileSync(fileurl, 'utf-8')
const reg1 = /process\.env\.NODE_ENV (===|==) ('|")development('|")/g
const reg2 = /process\.env\.NODE_ENV !(=|==) ('|")production('|")/g
const reg3 = /process\.env\.NODE_ENV (===|==) ('|")production('|")/g
const reg4 = /process\.env\.NODE_ENV !(=|==) ('|")development('|")/g
const reg5 = /process\.env\./g
content = content.replace(reg1, 'import.meta.env.DEV')
content = content.replace(reg2, 'import.meta.env.DEV')
content = content.replace(reg3, 'import.meta.env.PROD')
content = content.replace(reg4, 'import.meta.env.PROD')
content = content.replace(reg5, 'import.meta.env.')
fs.writeFileSync(fileurl, content, 'utf-8')
}
/**
*
* @param {string} p
*/
function loop(p) {
if(fs.lstatSync(p).isDirectory()) {
fs.readdirSync(p).forEach(v => {
loop(path.resolve(p, v))
})
} else {
replace(p)
}
}
loop(path.resolve(base, 'src'))
完整更改后的vite.config.js配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue2'
import { nodePolyfills } from 'vite-plugin-node-polyfills'
import { viteCommonjs } from '@originjs/vite-plugin-commonjs'
import babel from 'vite-plugin-babel'
import dns from 'dns'
dns.setDefaultResultOrder('verbatim')
export default defineConfig({
plugins: [
vue(),
viteCommonjs(),
babel({
filter: /^.*node_modules\/element-ui.*\.js?$/,
}),
nodePolyfills({
// Whether to polyfill `node:` protocol imports.
protocolImports: true,
}),
],
base: './',
resolve: {
extensions: ['.js', '.vue'],
alias: [{ find: /^@\/(.*)$/, replacement: '/src/$1' }],
},
})
关于proxy代理的配置,跟vue-cli的配置语法类似,可以直接抄过来。
转载自:https://juejin.cn/post/7200673854862884923