webpack2的vuejs老项目迁移到vite2.0的记录
最近 vite2 非常火,它基于浏览器原生ES模块加载的现代化构建工具,主要由两部分组成:
- 一个开发服务器,它利用 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)。
- 一套构建指令,它使用 Rollup 打包你的代码,预配置输出高度优化的静态资源用于生产。
为什么选用vite?
随着我们开始构建越来越多的雄心勃勃的应用程序,我们处理的 JavaScript 数量也呈指数级增长。大型项目包含数千个模块的情况并不少见。我们开始遇到基于 JavaScript 的工具的性能瓶颈:通常需要很长时间(有时甚至是几分钟!)才能启动开发服务器,即使使用 HMR,文件编辑也需要几秒钟才能在浏览器中反映出来。缓慢的反馈会极大地影响开发人员的生产力和幸福感。
Vite 旨在利用生态系统中的新进展解决上述问题:浏览器支持原生模块,越来越多 JavaScript 工具使用编译型语言编写。
项目背景
公司内部OA系统,基于vue2+webpack2开发,目前页面有上百个,每次启动开发或者编译都需要至少5分钟,非常慢。由于 vite 官方没有原生支持vue2.0,需要依赖于第三方插件,而且对于编译的稳定性和风险没办法保证,因此本次引入vite优先保证本地开发服务器的运行,尽量避免修改代码,编译还是由原来的webpack来执行。
引入vite
首先安装 vite、vite-plugin-vue2
$ npm i vite vite-plugin-vue2 sass --save-dev
新建配置文件 vite.config.js
import {defineConfig} from 'vite'
import {createVuePlugin} from 'vite-plugin-vue2'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
createVuePlugin({
jsx: true,
}),
],
resolve: {
extensions: ['.vue', '.js', '.json'],
alias: [
{find: "@", replacement: path.resolve(__dirname, './src')},
],
},
server: {
proxy: {}, // 原本项目的后端接口代理
base: '/index-vite.html', // 保留原本的 index.html,新建一个 index-vite.html
open: '/index-vite.html'
},
})
index-vite.html 并增加入口
<!DOCTYPE html>
<html>
<head>
<!-- 页面字符集 -->
<meta charset="utf-8" />
<!-- 360标签,页面需默认用极速核-->
<meta name="renderer" content="webkit" />
<!-- 禁止移动端点击输入框自动放大-->
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> -->
<!-- 使用Chrome内核来做渲染-->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<!-- 网页标题-->
<title>document</title>
</head>
<body>
<div id="app"></div>
<!-- 入口 -->
<script type="module" src="./src/index.js"></script>
</body>
</html>
为了尽量不对源代码进行修改,保证能正常编译项目,因此对代码的修改使用 vite-plugin transform
钩子来实现,能够在引入代码之前对代码进行修改。
参照项目中的旧代码,我们的插件主要需要提供以下几种对代码的转换:
module.exports
->export default
。- 懒加载
require
转成import('...')
。 - 代码中使用
require(...)
,需替换成临时变量script_inject_var__0
,然后在脚本作用域顶部引入变量:import script_inject_var__0 from '...'
。 - template中引入的require资源,例如
<img :src="require(...)"
-><img :src="tpl_inject_var__1"
、在 script 块中import tpl_inject_var__1 from '...'
,然后在组件计算属性/data属性中引入computed: {tpl_inject_var__1}
。 require.context
转换成import.meta.globEager
首先创建 ./vite/plugins/replace.js
import path from 'path'
import url from 'url'
const dirname = path.dirname(url.fileURLToPath(import.meta.url))
const srcDir = path.resolve(dirname, '../../src')
const enterDir = path.resolve(dirname, '../../enter')
function getPath(filepath) {
let sps = filepath.split('?', 2)
return sps[0]
}
function getExtension(filepath) {
return path.extname(getPath(filepath))
}
function replaceCode(code, filename, ext) {
let n = 0
let script, template, style
if (ext === '.vue') {
try {
template = code.match(/<template(?:.*?)>(?<template>.+)<\/template>/is).groups.template
} catch (ignore) {
}
try {
script = code.match(/<script(?:.*?)>(?<script>.+)<\/script>/is).groups.script
} catch (ignore) {
}
try {
style = code.match(/<style(?:.*?)>(?<style>.+)<\/style>/is).groups.style
} catch (ignore) {
}
} else if (ext === '.js') {
script = code
} else {
return {n, code}
}
let tplInjectVar = []
if (template) {
template = template.replace(/require\((.+?)\)/igm, function (_, match) {
n++
tplInjectVar.push(match)
return `tpl_inject_var__${tplInjectVar.length - 1}`
})
}
if (style) {
style = style.replace(/\/deep\//ig, function (match) {
n++
return '::v-deep'
})
}
let scriptInjectVar = []
if (script) {
// 替换 resolve => require(['@ resolve => require(['@/guide/guidePage.vue'], resolve)
// () => import('../src/guide/guidePage.vue'),
script = script.replace(/resolve\s*?=>\s*?require\(\[['"`]@(\/.*?)(["'`])],\s*?resolve\s*?\)/img, function (matched, m1, m2) {
n++
return `() => import(${m2}../src${m1}${m2})`
})
// 替换 resolve => require([
script = script.replace(/resolve\s*?=>\s*?require\(\[(['"`]@\/.*?["'`])],\s*?resolve\s*?\)/img, function (matched, m1) {
n++
return `() => import(${m1})`
})
script = script.replace(/require\((.+?)\)(.default)?/img, function (_, match) {
n++
scriptInjectVar.push(match)
return `script_inject_var__${scriptInjectVar.length - 1}`
})
scriptInjectVar.forEach((vvar, i) => {
n++
script = `import script_inject_var__${i} from ${vvar}\n${script}`
})
// 替换 module.exports =
script = script.replace(/module\.exports\s*?=/, function () {
n++
return 'export default '
})
}
if (ext === '.js') {
code = script
return {n, code}
}
if (ext === '.vue') {
tplInjectVar.forEach((vvar, i) => {
n++
script = `import tpl_inject_var__${i} from ${vvar}\n${script}`
})
if (tplInjectVar.length) { // 添加计算属性
let computedN = 0
script = script.replace(/(computed\s*?:\s*?){/is, function (match) {
n++
computedN++
let vvars = ''
tplInjectVar.forEach((vvar, i) => {
vvars += `tpl_inject_var__${i},`
})
return `${match}${vvars}`
})
if (!computedN) { // 没有计算属性,就加一个
script = script.replace(/\s*?export\s+?default\s+?{/is, function (match) {
n++
computedN++
let vvars = ''
tplInjectVar.forEach((vvar, i) => {
vvars += `tpl_inject_var__${i},`
})
return `${match}\ncomputed:{${vvars}},`
})
}
if (!computedN) {
console.warn('没有注入 computed!!')
}
}
let ret = code
if (template) {
ret = ret.replace(/(<template(?:.*?)>)(.+)(<\/template>)/is, function (_, m1, m2, m3) {
return `${m1}${template}${m3}`
})
}
if (script) {
ret = ret.replace(/(<script(?:.*?)>)(.+)(<\/script>)/is, function (_, m1, m2, m3) {
return `${m1}${script}${m3}`
})
}
if (style) {
ret = ret.replace(/(<style(?:.*?)>)(.+)(<\/style>)/is, function (_, m1, m2, m3) {
return `${m1}${style}${m3}`
})
}
return {n, code: ret}
}
}
export default function replacement() {
return {
enforce: 'pre',
transform(code, rawId) {
// 由于就这一个地方,因此就直接用路径判断,直接返回源码,就不做正则匹配替换了
if (rawId.endsWith('/views/liveMonitor/icons/index.js')) {
return {
code: `
import Vue from 'vue'
import SvgIcon from '../components/SvgIcon'// svg component
// register globally
Vue.component('svg-icon', SvgIcon)
let all = import.meta.globEager('./svg/*.svg')
`,
map: null,
}
}
let filepath = path.resolve(getPath(rawId))
let ext = getExtension(getPath(rawId))
if ((filepath.startsWith(srcDir) || filepath.startsWith(enterDir)) && ['.vue', '.js'].includes(ext)) {
let ret = replaceCode(code, filepath, ext)
if (ret.n === 0) {
return null
}
return {
code: ret.code,
map: null,
}
}
return null
}
}
}
在 vite.config.js
中引入插件
import replacement from './vite/plugins/replacement.js'
...
export default defineConfig({
plugins: [
replacement(),
createVuePlugin({
jsx: true,
}),
],
...
在 package.json scripts 中新增两项
"vite-dev": "vite"
至此可以启动 vite 了。
npm run vite-dev
总结
以上就是为项目引入 vite2,并且对原本代码0修改的过程记录。这里仅做抛砖引玉,读者可以自定义插件来对代码进行替换,对其他不兼容的语法打补丁。
本作品采用《CC 协议》,转载必须注明作者和本文链接
我按步骤操作后,elementUI的图表样式没有显示
github.com/originjs/webpack-to-vit...
这是我对旧项目进行转换时,搜索错误信息发现的一个 github 项目,它列出了一些转换项和错误修复方法,甚至可以一键转换旧项目到 vite 项目,非常棒,推荐一下!