由于vue2.x的老项目越来越庞大,导致每次启动的时候要等差不多一分钟的时间,vite4不是都出来了嘛,所以就在琢磨如何将老项目升级到vite,秒启动项目。
升级vite首先要分析的是vue-cli和vite的差异有哪些地方。
vue-cli和vite的差异 依赖引入方式 vite中统一采用import的方式引入依赖和资源,vue-cli采用的webpack,故可能会有代码用require去引入依赖,这部分就需要处理一下。
index.html的处理 在vue-cli中,项目入口文件一般是放在public
文件夹中,并且解析html采用的是webpack的html-webpack-plugin 插件来进行的,vite默认是不用插件处理html文件的。所以html-webpack-plugin中的一些占位符语法就不能在vite中出现,比如:
1 <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
来使用。
解决方案 require依赖引入方式 处理required引入的方式,可以采用vite插件来完成,安装@originjs/vite-plugin-commonjs
1 pnpm add @originjs/vite-plugin-commonjs -D
vite.config.js
1 2 3 4 5 6 7 import { viteCommonjs } from '@originjs/vite-plugin-commonjs' ... plugins : [ viteCommonjs () ] ...
index.html的处理 将原项目中public/index.html
移动到根目录,安装vite-plugin-html并进行相应的配置即可。
1 pnpm add vite-plugin-html -D
1 2 3 4 5 6 7 <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>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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 , entry : 'src/main.ts' , template : 'public/index.html' , 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。具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 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 来实现。
唯一需要处理的就是使用环境变量的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import fs from 'fs-extra' import path from 'path' const base = `/Users/xxx/xxxx/src` 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' ) } 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配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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 ({ protocolImports : true , }), ], base : './' , resolve : { extensions : ['.js' , '.vue' ], alias : [{ find : /^@\/(.*)$/ , replacement : '/src/$1' }], }, })
关于proxy代理的配置,跟vue-cli的配置语法类似,可以直接抄过来。