用Vue Demi同时支持Vue2和Vue3的方法
作者:街角小林 发布时间:2024-05-13 09:38:35
Vue Demi是什么
如果你想开发一个同时支持Vue2
和Vue3
的库可能想到以下两种方式:
1.创建两个分支,分别支持Vue2
和Vue3
2.只使用Vue2
和Vue3
都支持的API
这两种方式都有缺点,第一种很麻烦,第二种无法使用Vue3
新增的组合式 API
,其实现在Vue2.7+
版本已经内置支持组合式API
,Vue2.6
及之前的版本也可以使用@vue/composition-api插件来支持,所以完全可以只写一套代码同时支持Vue2
和3
。虽然如此,但是实际开发中,同一个API
在不同的版本中可能导入的来源不一样,比如ref
方法,在Vue2.7+
中直接从vue
中导入,但是在Vue2.6-
中只能从@vue/composition-api
中导入,那么必然会涉及到版本判断,Vue Demi就是用来解决这个问题,使用很简单,只要从Vue Demi
中导出你需要的内容即可:
import { ref, reactive, defineComponent } from 'vue-demi'
Vue-demi
会根据你的项目判断到底使用哪个版本的Vue
,具体来说,它的策略如下:
<=2.6
: 从Vue
和@vue/composition-api
中导出2.7
: 从Vue
中导出(组合式API
内置于Vue 2.7
中)>=3.0
: 从Vue
中导出,并且还polyfill
了两个Vue 2
版本的set
和del
API
接下来从源码角度来看一下它具体是如何实现的。
基本原理
当我们使用npm i vue-demi
在我们的项目里安装完以后,它会自动执行一个脚本:
{
"scripts": {
"postinstall": "node ./scripts/postinstall.js"
}
}
// postinstall.js
const { switchVersion, loadModule } = require('./utils')
const Vue = loadModule('vue')
if (!Vue || typeof Vue.version !== 'string') {
console.warn('[vue-demi] Vue is not found. Please run "npm install vue" to install.')
}
else if (Vue.version.startsWith('2.7.')) {
switchVersion(2.7)
}
else if (Vue.version.startsWith('2.')) {
switchVersion(2)
}
else if (Vue.version.startsWith('3.')) {
switchVersion(3)
}
else {
console.warn(`[vue-demi] Vue version v${Vue.version} is not suppported.`)
}
导入我们项目里安装的vue
,然后根据不同的版本分别调用switchVersion
方法。
先看一下loadModule
方法:
function loadModule(name) {
try {
return require(name)
} catch (e) {
return undefined
}
}
很简单,就是包装了一下require
,防止报错阻塞代码。
然后看一下switchVersion
方法:
function switchVersion(version, vue) {
copy('index.cjs', version, vue)
copy('index.mjs', version, vue)
copy('index.d.ts', version, vue)
if (version === 2)
updateVue2API()
}
执行了copy
方法,从函数名可以大概知道是复制文件,三个文件的类型也很清晰,分别是commonjs
版本的文件、ESM
版本的文件、TS
类型定义文件。
另外还针对Vue2.6
及一下版本执行了updateVue2API
方法。
updateVue2API
方法我们后面再看,先看一下copy
方法:
const dir = path.resolve(__dirname, '..', 'lib')
function copy(name, version, vue) {
vue = vue || 'vue'
const src = path.join(dir, `v${version}`, name)
const dest = path.join(dir, name)
let content = fs.readFileSync(src, 'utf-8')
content = content.replace(/'vue'/g, `'${vue}'`)
try {
fs.unlinkSync(dest)
} catch (error) { }
fs.writeFileSync(dest, content, 'utf-8')
}
其实就是从不同版本的目录里复制上述三个文件到外层目录,其中还支持替换vue
的名称,这当你给vue
设置了别名时需要用到。
到这里,Vue Demi
安装完后自动执行的事情就做完了,其实就是根据用户项目中安装的Vue
版本,分别从三个对应的目录中复制文件作为Vue Demi
包的入口文件,Vue Demi
支持三种模块语法:
{
"main": "lib/index.cjs",
"jsdelivr": "lib/index.iife.js",
"unpkg": "lib/index.iife.js",
"module": "lib/index.mjs",
"types": "lib/index.d.ts"
}
默认入口为commonjs
模块cjs
文件,支持ESM
的可以使用mjs
文件,同时还提供了可以直接在浏览器上使用的iife
类型的文件。
接下来看一下分别针对三种版本的Vue
具体都做了什么。
v2版本
Vue2.6
版本只有一个默认导出:
我们只看mjs
文件,cjs
有兴趣的可以自行阅读:
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api/dist/vue-composition-api.mjs'
function install(_vue) {
_vue = _vue || Vue
if (_vue && !_vue['__composition_api_installed__'])
_vue.use(VueCompositionAPI)
}
install(Vue)
// ...
导入Vue
和VueCompositionAPI
插件,并且自动调用Vue.use
方法安装插件。
继续:
// ...
var isVue2 = true
var isVue3 = false
var Vue2 = Vue
var version = Vue.version
export {
isVue2,
isVue3,
Vue,
Vue2,
version,
install,
}
/**VCA-EXPORTS**/
export * from '@vue/composition-api/dist/vue-composition-api.mjs'
/**VCA-EXPORTS**/
首先导出了两个变量isVue2
和isVue3
,方便我们的库代码判断环境。
然后在导出Vue
的同时,还通过Vue2
的名称再导出了一遍,这是为啥呢,其实是因为Vue2
的API
都是挂载在Vue
对象上,比如我要进行一些全局配置,那么只能这么操作:
import { Vue, isVue2 } from 'vue-demi'
if (isVue2) {
Vue.config.xxx
}
这样在Vue2
的环境中没有啥问题,但是当我们的库处于Vue3
的环境中时,其实是不需要导入Vue
对象的,因为用不上,但是构建工具不知道,所以它会把Vue3
的所有代码都打包进去,但是Vue3
中很多我们没有用到的内容是不需要的,但是因为我们导入了包含所有API
的Vue
对象,所以无法进行去除,所以针对Vue2
版本单独导出一个Vue2
对象,我们就可以这么做:
import { Vue2 } from 'vue-demi'
if (Vue2) {
Vue2.config.xxx
}
然后后续你会看到在Vue3
的导出中Vue2
是undefined
,这样就可以解决这个问题了。
接着导出了Vue
的版本和install
方法,意味着你可以手动安装VueCompositionAPI
插件。
然后是导出VueCompositionAPI
插件提供的API
,也就是组合式API
,但是可以看到前后有两行注释,还记得前面提到的switchVersion
方法里针对Vue2
版本还执行了updateVue2API
方法,现在来看一看它做了什么事情:
function updateVue2API() {
const ignoreList = ['version', 'default']
// 检查是否安装了composition-api
const VCA = loadModule('@vue/composition-api')
if (!VCA) {
console.warn('[vue-demi] Composition API plugin is not found. Please run "npm install @vue/composition-api" to install.')
return
}
// 获取除了version、default之外的其他所有导出
const exports = Object.keys(VCA).filter(i => !ignoreList.includes(i))
// 读取ESM语法的入口文件
const esmPath = path.join(dir, 'index.mjs')
let content = fs.readFileSync(esmPath, 'utf-8')
// 将export * 替换成 export { xxx }的形式
content = content.replace(
/\/\*\*VCA-EXPORTS\*\*\/[\s\S]+\/\*\*VCA-EXPORTS\*\*\//m,
`/**VCA-EXPORTS**/
export { ${exports.join(', ')} } from '@vue/composition-api/dist/vue-composition-api.mjs'
/**VCA-EXPORTS**/`
)
// 重新写入文件
fs.writeFileSync(esmPath, content, 'utf-8')
}
主要做的事情就是检查是否安装了@vue/composition-api
,然后过滤出了@vue/composition-api
除了version
和default
之外的所有导出内容,最后将:
export * from '@vue/composition-api/dist/vue-composition-api.mjs'
的形式改写成:
export { EffectScope, ... } from '@vue/composition-api/dist/vue-composition-api.mjs'
为什么要过滤掉version
和default
呢,version
是因为已经导出了Vue
的version
了,所以会冲突,本来也不需要,default
即默认导出,@vue/composition-api
的默认导出其实是一个包含它的install
方法的对象,前面也看到了,可以默认导入@vue/composition-api
,然后通过Vue.use
来安装,这个其实也不需要从Vue Demi
导出,不然像下面这样就显得很奇怪:
import VueCompositionAPI from 'vue-demi'
到这里,就导出所有内容了,然后我们就可以从vue-demi
中导入各种需要的内容了,比如:
import { isVue2, Vue, ref, reactive, defineComponent } from 'vue-demi'
v2.7版本
接下来看一下是如何处理Vue2.7
版本的导出的,和Vue2.6
之前的版本相比,Vue2.7
直接内置了@vue/composition-api
,所以除了默认导出Vue
对象外还导出了组合式API
:
import Vue from 'vue'
var isVue2 = true
var isVue3 = false
var Vue2 = Vue
var warn = Vue.util.warn
function install() {}
export { Vue, Vue2, isVue2, isVue3, install, warn }
// ...
和v2
相比,导出的内容是差不多的,因为不需要安装@vue/composition-api
,所以install
是个空函数,区别在于还导出了一个warn
方法,这个文档里没有提到,但是可以从过往的issues中找到原因,大致就是Vue3
导出了一个warn
方法,而Vue2
的warn
方法在Vue.util
对象上,所以为了统一手动导出,为什么V2
版本不手动导出一个呢,原因很简单,因为这个方法在@vue/composition-api
的导出里有。
继续:
// ...
export * from 'vue'
// ...
导出上图中Vue
所有的导出,包括version
、组合式API
,但是要注意这种写法不会导出默认的Vue
,所以如果你像下面这样使用默认导入是获取不到Vue
对象的:
import Vue from 'vue-demi'
继续:
// ...
// createApp polyfill
export function createApp(rootComponent, rootProps) {
var vm
var provide = {}
var app = {
config: Vue.config,
use: Vue.use.bind(Vue),
mixin: Vue.mixin.bind(Vue),
component: Vue.component.bind(Vue),
provide: function (key, value) {
provide[key] = value
return this
},
directive: function (name, dir) {
if (dir) {
Vue.directive(name, dir)
return app
} else {
return Vue.directive(name)
}
},
mount: function (el, hydrating) {
if (!vm) {
vm = new Vue(Object.assign({ propsData: rootProps }, rootComponent, { provide: Object.assign(provide, rootComponent.provide) }))
vm.$mount(el, hydrating)
return vm
} else {
return vm
}
},
unmount: function () {
if (vm) {
vm.$destroy()
vm = undefined
}
},
}
return app
}
和Vue2
的new Vue
创建Vue
实例不一样,Vue3
是通过createApp
方法,@vue/composition-api
插件polyfill
了这个方法,所以针对Vue2.7
,Vue Demi
手动进行了polyfill
。
到这里,针对Vue2.7
所做的事情就结束了。
v3版本
Vue3
相比之前的版本,最大区别是不再提供一个单独的Vue
导出:
import * as Vue from 'vue'
var isVue2 = false
var isVue3 = true
var Vue2 = undefined
function install() {}
export {
Vue,
Vue2,
isVue2,
isVue3,
install,
}
// ...
因为默认不导出Vue
对象了,所以通过整体导入import * as Vue
的方式把所有的导出都加载到Vue
对象上,然后也可以看到导出的Vue2
为undefined
,install
同样是个空函数。
继续:
// ...
export * from 'vue'
// ...
没啥好说的,直接导出Vue
的所有导出内容。
继续:
// ...
export function set(target, key, val) {
if (Array.isArray(target)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
target[key] = val
return val
}
export function del(target, key) {
if (Array.isArray(target)) {
target.splice(key, 1)
return
}
delete target[key]
}
最后polyfill
了两个方法,这两个方法实际上是@vue/composition-api
插件提供的,因为@vue/composition-api
提供的响应性API
实现上并没有使用Proxy
代理,仍旧是基于Vue2
的响应系统来实现的,所以Vue2
中响应系统的限制仍旧还是存在的,所以需要提供两个类似Vue.set
和Vue.delete
方法用来给响应性数据添加或删除属性。
来源:https://blog.csdn.net/sinat_33488770/article/details/128294084
猜你喜欢
- 今天也碰到了el表达式无法解析的事情,于是在网上查询了下,大多说是因为web.xml中声明的版本问题于是收集了如下版本:web-app_2_
- 以下是一些Python实用技巧和工具,希望能对大家有所帮助。交换变量x = 6y = 5x, y = y, xprint x>>
- 本文实例讲述了Python编码类型转换方法。分享给大家供大家参考,具体如下:1:Python和unicode为了正确处理多语言文本,Pyth
- 开始之前当然要导入模块啦:>>> import pymongo下一步,必须本地mongodb服务器的安装和启动已经完成,才
- 在接口测试学习过程中,遇到了利用requests库进行文件下载和上传的问题。同样,在真正的测试过程中,我们不可避免的会遇到上传和下载的测试。
- Django中请求的生命周期HTTP请求及服务端响应中传输的所有数据都是字符串步骤用户在浏览器中输入url时,浏览器会生成请求头和请求体发给
- commands模块的适用commands模块是python的内置模块,他共有三个函数,使用help(commands)可以查看到FUNCT
- Python 是一种代表简单思想的语言,其语法相对简单,很容易上手。不过,如果就此小视 Python 语法的精妙和深邃,那就大错特错了。本文
- 有时候完全备份, 当还原的时候说不时数据库文件不让还原, 解决办法: 可以直接复制数据库文件, xxx.mdf 和 xxx.ldf (实际复
- 1、sqldmo SQLDMO是操作SQLServer的理想的方式,如果您的数据库是SQLServer就可以考虑使用这种方式。在C#中使用S
- Python2.6 开始,新增了一种格式化字符串的函数 str.format(),它增强了字符串格式化的功能。基本语法是通过 {} 和 :
- Q1:PEP8是什么?Python之禅(import this)是什么?这题是考察你对编码规范的认识,无论是自己写代码还是在团队中写代码,了
- rabbitmq中文翻译的话,主要还是mq字母上:Message Queue,即消息队列的意思。前面还有个rabbit单词,就是兔子的意思,
- 这是python编写的用于测试网站访问速率的代码片段,可以输出打开某url的时间,访问100次的平均时间,最大时间和最小时间等等import
- 2020年4月4日,是个特殊的日子,我们看到朋友圈很多灰化的图片.今天我们就聊聊图片灰度处理这事儿.PIL的基本概念:PIL中所涉及的基本概
- 当一台计算机上有多个网卡时,需要选择对应IP地址的网卡进行发送数据包或者接受数据包。1、选择网卡发包(应用scapy):plface=con
- 问题提出最近,使用tqdm模块,对于大文件的阅读进行进度监控。然而我发现有个问题,即在tqdm模块使用一定没错的情况下,进度条死活打印不出来
- 导语🎁哈喽!哈喽!我是木木子😎,今日游戏更新——超级玛丽华丽上线🎊啦!“超级玛丽”有多少人还记得这款经典游戏?对于90、00后应该不大熟悉,
- SQL Server2005数据项的分拆与合并:参考示例如下:-- ====================================
- Tkinter库制作记事本现在为了创建这个记事本,你的系统中应该已经安装了 Python 3 和 Tkinter。您可以根据系统要求下载合适