声明
请注意,笔者在写本文时,已经完成了升级改造工作,故部分错误可能无法通过截图呈现,不过笔者会尽可能用语言描述清楚
一次失败的尝试
由于所要改造项目是公司运行六七年的老项目了,内容之大之重可以想象,纯靠人力的话,光是想想就头大
所谓遇事不决先百度,像这类迁移工具必定是有人已经做过了的,于是发现了gogocode
首先,使用npm
安装该工具
ts
npm install gogocode-cli -g
接着进行语法转换
在项目根目录执行如下命令
ts
// src是要转换的源
gogocode -s ./src -t gogocode-plugin-vue -o ./src-out
然后静静等待它转换完成
下一步,对相关依赖做升级,gogocode
也提供了相关指令
ts
gogocode -s package.json -t gogocode-plugin-vue -o package.json
这一步执行之后,使用yarn
重新安装依赖
ts
yarn
这样工具帮我们处理的工作就完成了,我们先来跑一下lint
看看,如下有3k+
此时,我已经有点打退堂鼓了
先不急,咱们先来看看它转出来的效果
首先,它对package.json
转换后,vue
的版本是3.0.0
。我为什么要特意强调这个呢?
因为这说明了两个问题:
1.现在vue
都3.3
了,它却还在用3.0
,这说明大概率它已经鲜有维护更新了
2.3.3
的语法与3.0
大概率会存在出入,也就是说,它的转换结果未必可信
现在,我们再来看具体的sfc
文件的转换
- props
我们知道,vue3.3
中props
已经被扁平化了,是不需要通过特定的props
属性进行传递的,但gogocode
对这一部分并未处理
- attrs
attrs
也有类似的问题
- style样式
工具对源码转换后,会导致style
标签内的样式错乱,具体来说应该是它无法识别css
代码中的注释导致的
- 格式
除了上述的语法转换问题外,还有其他的,此处不再一一阐述。其实真正让我决定放弃的是,它转换出来感觉有点乱,且对源码的侵入性太大
比如eventBus
,修改后,无法和原来一样调用,需要先导入,再使用,且必须手动传入组件实例,伪代码如下
ts
import {$on,$emit,$off} from '../xx/utils'
$on(instance,key,callback)
这成功给笔者造成了将近一千个error
,因为项目里用到了400
多次
具体来说,每一个文件都会触发相对路径必须在绝对路径之后引入
和变量未被使用
这两个error
半自动化
现在,笔者打算自己手动修正。不过在开始前,先说原则,原则就是,能不修改原来代码的就尽量不修改,能保证原有调用格式的就尽量保证原来的使用方式
loaders
一些通用的调整,可以通过webpack
的loader
来自动化
- filters
vue3
中已经剔除了filters
选项,需要将其移动到methods
中
不过,代码有点多,咱这文章毕竟也不是卖钱的小册,所以就只讲一下思路,这也是最重要的:
首先,通过正则提取出script
标签的内容部分,然后将其ast
化,在通过节点树找到export default
部分并将其提取出来转换成对象
转对象的时候,由于部分代码可能是export default
外部的,比如
ts
const yyy = 'spp'
export default {
data(){
return xxx:yyy
}
}
这部分直接去解析会报错,因此需要进行try...catch并在捕获到错误时递归
ts
function parse(){
try{
...
}catch(err){
if(err){
parse()
}
}
}
接着判断对象中是否存在filters
属性,如果有,就对该属性做遍历,将其key
提取到methods
中,形式大概是这样
ts
export default {
methods:{
filtersKey:filtersValue
}
}
这个filtersValue
对应的就是原来的过滤函数,要把它生成到script
标签中,具体来说,是import
语句和export
之间
ts
function filtersValue(){
...
}
下一步,就是去template
模板中查找使用过滤函数的地方进行替换。这利用正则可以很轻松的完成
- props
在vue2
中可以这样声明props
ts
export default {
props:{
xxx:'some value'
}
}
在vue3
中会报错。因此需要对此进行转换。具体来说,就是找到props对象,分别对它的key对应的值进行识别。如果是对象或函数则不处理,如果是基本数据类型,则转换成如下形式
ts
{
type:'原值所对应的数据类型',
default:'some value'
}
这里比较容易出错的点是关于字符串类型的获取,需要手动拼接上引号
完整代码如下
- bus通信
众所周知,vue3
中已经剔除了eventBus
,无法再通过new
实例的方式来实现跨级通信
但第三方插件又无法保证this
指向,一般需要手动传入
但前文我们说过,要尽可能保证原调用方式不变
因此,需要在loader
中提取并添加
这里的注意点在于loader
的执行时机,必须要保证它在源代码被转换之前。否则添加的this
与回调函数内的this
大概率不是一个。如下,是经过转换后的代码,在回调中使用的this
其实并不是实际传入的this
ts
var this1 = this
this.$bus.$on(key,this,()=>{
this1.xxx = 'spp'
})
fixed
还有一些是可以在全局去做兼容的,它们的目的是与改造前的代码行为尽可能保持一致
- bus通信
在前文loader中虽然解决了eventBus的this指向问题,但还没有找到可替代的包
幸运的是,笔者在之前实现的web-localstorage-plus中实现了该功能
首先,使用web-localstorage-plus
提供的接口来代替bus
功能
接着将其挂载到app.config.globalProperties
上,这是vue3中提供的类似Vue.prototype
挂载方式
又不幸的是,web-localstorage-plus
目前其实并不支持接收this
参数,且回调函数也不支持传递多个参数
因此,还需要对这两个接口进行重写。如下,接受参数二并将其挂载到第三个函数参数上,然后在触发on
注册的监听函数时手动使用call
修改其this
指向,并将参数使用展开运算符传递以支持多个参数传递
- vue-router
在笔者的业务中,对单点登录进行了校验,并在无权限时跳转到指定的页面,伪代码如下
ts
// 获取权限
this.$router.onReady(...)
// 校验权限
this.$router.beforeEach(...)
这两行代码有两个问题
一个是vue-router4.x
中已经废弃了onReady
,需要改成isReady
代替
另一个问题是,当页面刷新或执行window.open
时,其表现与vue-router3.x
不一致
在vue-router3.x
中刷新并不会触发beforeEach
钩子,但vue-router4.x
中会触发
这就导致,会触发权限的重新获取,而此时beforeEach
钩子执行时还拿不到权限数据导致跳转到noAuth
页面
因此,需要对beforeEach
钩子进行重写。如下,我们在页面刷新或window.open
被执行时向本地设置缓存,并在beforeEach
钩子中判断是否存在缓存标记,存在则什么都不做,否则正常跳转路由
- $children
vue3
中已经剔除了$children
接口,需要自己手动实现一份查找逻辑。如下,只需要按指定的格式进行递归即可
- $filters
对于注册的全局过滤器,现在统一调整到app.config.globalProperties
上
- view-ui-plus
之前默认注册到全局的loading现在已经没有了,需要根据业务需求做调整
其他
剩下的,就是需要手动调整的其他语法了
- vue-router
1-h函数
在vue4.x
版本中应该已经不支持render
函数了,由于笔者公司项目有且仅有这一处对组件的使用,故修改成了默认方式
实际上,更准确的写法应该是这样
2-注册方式
4.x
中已经不需要使用new
关键字了,取而代之的是createRouter
接口。另外,mode
属性也被history
替代了
ts
import { createRouter, createWebHashHistory } from 'vue-router';
export default createRouter({
history: createWebHashHistory(),
routes:[...]
})
- vuex
1-注册方式
与vue-router
类似,通过createStore
替换。
ts
import { createStore } from 'vuex';
export default createStore({...})
2-日志引入
它的日志插件的导入方式要改成下边这样
ts
import { createLogger } from 'vuex';
- view-ui-plus
对于js
模块中使用的消息提示需要手动按需导入
ts
import { Message } from 'view-ui-plus'
Message.error('...')
- h函数
h函数的变动大致有四个
1-props与attrs
现在不需要在显示的指定这两个属性了,可以直接平铺传递
ts
h('div',{
//props:{
// xxx:'spp'
//}
xxx:'spp'
})
2-事件绑定
现在也已经剔除了on
属性,而需要改成onEventType
的形式
ts
h('div',{
//on:{
// click:()=>{}
//}
onClick(){}
})
3-slots
插槽需要改成函数形式
ts
h('div',{
//slot:'slotName'
slotName(){}
})
4-组件渲染
现在使用h
函数渲染组件有两种方式,笔者采用的方式如下
首先将要导入的组件挂载到全局
ts
import { LoadingBar } from 'view-ui-plus';
app.component('LoadingBar', LoadingBar);
然后借助vue3
提供的resolveComponent
来执行导入
ts
import { resolveComponent } from 'vue';
h(resolveComponent('LoadingBar'))
- $set
基于proxy
的v3
已经不需要$se
t或$delete
了,现在直接进行修改即可
- slot
v3
中的插件必须使用template
,且写法有变更,如下,现在可以合写成一个了
ts
<template
//slot="createTime"
//slot-scope="scope"
v-slot="scope"
>
...
</template>
- 三方组件库
这种每个人的不一样,笔者就不贴了,只说下笔者的处理方式
1-找vue3
版本
有些组件库是支持vue3
版本的,这部分直接yarn
即可
2-修改源码
如果没有v3
版本,就利用patch-pkg
自己修改源码做兼容
3-copy源码
对于比较复杂的库,笔者是找到其源代码copy
了一份,然后在项目中引入并修改
- vue-loader
项目启动后,页面元素之间的间隙会失效,配置如下