Vue Demi是如何让你的库同时支持Vue2和Vue3的

news2024/11/26 10:19:42

Vue Demi是什么

如果你想开发一个同时支持Vue2Vue3的库可能想到以下两种方式:

1.创建两个分支,分别支持Vue2Vue3

2.只使用Vue2Vue3都支持的API

这两种方式都有缺点,第一种很麻烦,第二种无法使用Vue3新增的组合式 API,其实现在Vue2.7+版本已经内置支持组合式APIVue2.6及之前的版本也可以使用@vue/composition-api插件来支持,所以完全可以只写一套代码同时支持Vue23。虽然如此,但是实际开发中,同一个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版本的setdel 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)
// ...

导入VueVueCompositionAPI插件,并且自动调用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**/

首先导出了两个变量isVue2isVue3,方便我们的库代码判断环境。

然后在导出Vue的同时,还通过Vue2的名称再导出了一遍,这是为啥呢,其实是因为Vue2API都是挂载在Vue对象上,比如我要进行一些全局配置,那么只能这么操作:

import { Vue, isVue2 } from 'vue-demi'

if (isVue2) {
  Vue.config.xxx
}

这样在Vue2的环境中没有啥问题,但是当我们的库处于Vue3的环境中时,其实是不需要导入Vue对象的,因为用不上,但是构建工具不知道,所以它会把Vue3的所有代码都打包进去,但是Vue3中很多我们没有用到的内容是不需要的,但是因为我们导入了包含所有APIVue对象,所以无法进行去除,所以针对Vue2版本单独导出一个Vue2对象,我们就可以这么做:

import { Vue2 } from 'vue-demi'

if (Vue2) {
  Vue2.config.xxx
}

然后后续你会看到在Vue3的导出中Vue2undefined,这样就可以解决这个问题了。

接着导出了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除了versiondefault之外的所有导出内容,最后将:

export * from '@vue/composition-api/dist/vue-composition-api.mjs'

的形式改写成:

export { EffectScope, ... } from '@vue/composition-api/dist/vue-composition-api.mjs'

为什么要过滤掉versiondefault呢,version是因为已经导出了Vueversion了,所以会冲突,本来也不需要,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方法,而Vue2warn方法在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
}

Vue2new Vue创建Vue实例不一样,Vue3是通过createApp方法,@vue/composition-api插件polyfill了这个方法,所以针对Vue2.7Vue 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对象上,然后也可以看到导出的Vue2undefinedinstall同样是个空函数。

继续:

// ...
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.setVue.delete方法用来给响应性数据添加或删除属性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/83851.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Redis】Redis跳表与实现源码解析(Redis专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &#x1…

Tomcat下载和安装

下载 Tomcat官网网址 我选择的是8&#xff0c;根据自己需要选择不同版本 选择64位下载 下载有点慢&#xff0c;等一会儿就行 安装 首先确认安装了JDK&#xff1a;命令行窗口输入java -version 配置Tomcat环境变量 配完之后验证是否成功。 winR->cmd->输入startup.bat…

什么是社交新零售?社交新零售的底层商业又是逻辑是什么?

一千个品牌商心中&#xff0c;有一千个新零售——自从马云2016年10月提出新零售的概念后&#xff0c;电商平台、传统商超、电商品牌、线下品牌&#xff0c;汹涌而至&#xff0c;都想搭上“新零售快车”。 新零售模式是依靠于大数据的开发应用&#xff0c;国内新零售模式发展多年…

Node.js快速入门

一、简介 1、什么是Node.js 简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js是一个事件驱动I/O服务端JavaScript环境&#xff0c;基于Google的V8引擎&#xff0c;V8引擎执行Javascript的速度非常快&#xff0c;性能非常好。 2、Node.js有什么用 如果你是一个前端程序…

【有料c++题目周刊 | 第一期】希腊诸神

文章目录第一题&#xff1a;珀耳修斯点金题目描述输入描述输入示例输出描述输出示例解题思路&C题解第二题&#xff1a;女神赫拉题目描述输入描述输入示例输出描述输出示例解题思路&C题解第一题&#xff1a;珀耳修斯点金 题目描述 某希腊神话故事中&#xff0c;有一个…

物联网通信技术|课堂笔记week2-2|9月7日·21日

sudo ifconfig bridge101 hw ether 9e:3e:53:38:45:66 目录 Linux网络管理命令 (1)route (2)ip (3)netstat (4)ping ​​​​​​两台电脑连起来后ping不通? (5)telnet (6)ssh (7)wget Linux网络管理命令 (1)route 路由为互联网的中转站 静态路由 动态路由 rout…

Go代码审计学习(一)

文章目录Vulnerability-goapp/assets// 根目录/login/new/top/profile/profile/edit/upload/post /timeline/timeline/searchpost/adminconfirm /adminlogin /adminusersCSRF网上有关Go的代码审计好少哇&#xff0c;能找到的文章也不多&#xff0c;害&#xff0c;没办法也得学 …

网络协议—应用层的HTTP协议

URL&#xff0c;叫作统一资源定位符。之所以叫统一&#xff0c;是因为它是有格式的。HTTP 称为协议&#xff0c;www.163.com 是一个域名&#xff0c;表示互联网上的一个位置。正是因为这个东西是统一的&#xff0c;所以当你把这样一个字符串输入到浏览器的框里的时候&#xff0…

软件测试是要学习什么技能?

推荐阅读&#xff1a; 2022年简历石沉大海&#xff0c;软件测试行业当前找工作有多难&#xff1f; [内部资源] 想拿年薪30W的软件测试人员&#xff0c;这份资料必须领取~ 在确定软件开发可行的状态下&#xff0c;对软件是要实现的各个功能做到详细分析。要求分析阶段是一个很…

两起并购!深兰科技完成自动驾驶新能源车产业生态链布局

近日&#xff0c;深兰科技集团全资控股的熊猫汽车(上海)有限公司分别与一汽凌源、湖南加立减新能源科技正式签约&#xff0c;完成了对这两家公司的并购&#xff0c;为企业自动驾驶新能源汽车产业生态链的建设蓝图添上了浓重的一笔。 此次被深兰科技并购的一汽凌源汽车制造有限公…

Java二维数组拓展练习

例 1 对角线输出&#xff1a; 已知一个n*n的矩阵&#xff08;n<20&#xff09;&#xff0c;把矩阵二条对角线上的元素值加上10&#xff0c;然后输出这两条新对角线上的所有元素之和。 注意当n为奇数时两条对角线交叉的那个元素改变时只需要改变一次&#xff0c;累加的时候…

46.for循环嵌套之九九乘法表

46.for循环嵌套之九九乘法表 文章目录46.for循环嵌套之九九乘法表1. 目标任务2. 完成第1行输出2. 分析前3行规律3. 编写第1行代码4. 编写前2行代码5. 调整输出格式6. 完成总代码1. 目标任务 本节的任务是利用for循环嵌套编写九九乘法表。 1*11 1*22 2*24 1*33 2*36 3*39 1…

语音识别之语音激活(VAD)检测(一)

导读 语音激活检测(Vioce Activation Detection)简称VAD&#xff0c;用来检测语音信号是否存在。VAD技术在语音领域中应用非常的广泛&#xff0c;在语音识别中我们可以对长语音通过VAD来检测出语音信号的空隙&#xff0c;通过这个空隙来分割语音&#xff0c;将长语音切分成短语…

正大国际期货:如何摆脱炒黄金期货被套单?

投资失败无非两点&#xff1a;一是自身原因&#xff0c;自己没有经验&#xff0c;盲目的操作肯定会造成亏损&#xff1b;二是指导老师的实力问题&#xff0c;指导老师对行情的方向把握不准&#xff0c;经常喊反弹&#xff0c;造成你的亏损。想要走得快&#xff0c;就独自上路&a…

Python每日一练 09——多文件操作

Python每日一练 09——多文件操作 文章目录Python每日一练 09——多文件操作一、单文件拆分为多个文件二、多文件合并为单文件1、获取数据文件名2、获取股票名列表3、读单支股票数据4、读多支股票数据5、多文件中数据合并到一个文件一、单文件拆分为多个文件 我们写一个txt文件…

数字图像处理(入门篇)九 图像数据预处理之滤波

目录 1 模板运算 &#xff08;1&#xff09;模板卷积 &#xff08;2&#xff09;模板排序 2 均值滤波 &#xff08;1&#xff09;代码 &#xff08;2&#xff09;结果图 3 高斯滤波 &#xff08;1&#xff09;代码 &#xff08;2&#xff09;结果图 4 中值滤波 &#…

IB数学HL/SL考试大纲简单介绍

IB数学难度怎么样&#xff1f;IB数学内部评估和外部评估是怎么评分的&#xff0c;IB数学HL和SL的考试大纲&#xff1f; 下面介绍IB数学框架体系IB数学SL考试大纲 SL数学为必修课 考试内容相对比较基础 代数&#xff08;Algebra&#xff09;&#xff1a;涉及数列问题&#xff…

CentOS虚拟机搭建Hive环境

注&#xff1a;本文是对 https://www.bilibili.com/video/BV1CU4y1N7Sh 的实践。 关于如何搭建Hadoop集群&#xff0c;请参考我另一篇文档。 环境 CentOS 7.7JDK 8Hadoop 3.3.0Hive 3.1.2 准备 确认Hadoop的 etc/hadoop/core-site.xml 文件包含如下配置&#xff1a; <…

3D建模师做多了女人会不会找不到老婆?次世代美少女战士建模流程讲解

什么是次世代&#xff1f; 次世代是个舶来语&#xff0c;“次世代游戏”指代和同类游戏相比下更加先进的游戏&#xff0c;即“下一代游戏”。 次世代是利用高模烘焙的法线贴图回帖到低模上&#xff0c;让低模在游戏引擎里可以及时显示高模的视觉效果。模型面数比较高&#xf…

Python实现PSO粒子群优化卷积神经网络CNN回归模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 PSO是粒子群优化算法&#xff08;Particle Swarm Optimization&#xff09;的英文缩写&#xff0c;是一…