用Vue?Demi同時(shí)支持Vue2和Vue3的方法
Vue Demi是什么
如果你想開發(fā)一個(gè)同時(shí)支持Vue2
和Vue3
的庫可能想到以下兩種方式:
1.創(chuàng)建兩個(gè)分支,分別支持Vue2
和Vue3
2.只使用Vue2
和Vue3
都支持的API
這兩種方式都有缺點(diǎn),第一種很麻煩,第二種無法使用Vue3
新增的組合式 API
,其實(shí)現(xiàn)在Vue2.7+
版本已經(jīng)內(nèi)置支持組合式API
,Vue2.6
及之前的版本也可以使用@vue/composition-api插件來支持,所以完全可以只寫一套代碼同時(shí)支持Vue2
和3
。雖然如此,但是實(shí)際開發(fā)中,同一個(gè)API
在不同的版本中可能導(dǎo)入的來源不一樣,比如ref
方法,在Vue2.7+
中直接從vue
中導(dǎo)入,但是在Vue2.6-
中只能從@vue/composition-api
中導(dǎo)入,那么必然會(huì)涉及到版本判斷,Vue Demi就是用來解決這個(gè)問題,使用很簡(jiǎn)單,只要從Vue Demi
中導(dǎo)出你需要的內(nèi)容即可:
import { ref, reactive, defineComponent } from 'vue-demi'
Vue-demi
會(huì)根據(jù)你的項(xiàng)目判斷到底使用哪個(gè)版本的Vue
,具體來說,它的策略如下:
<=2.6
: 從Vue
和@vue/composition-api
中導(dǎo)出2.7
: 從Vue
中導(dǎo)出(組合式API
內(nèi)置于Vue 2.7
中)>=3.0
: 從Vue
中導(dǎo)出,并且還polyfill
了兩個(gè)Vue 2
版本的set
和del
API
接下來從源碼角度來看一下它具體是如何實(shí)現(xiàn)的。
基本原理
當(dāng)我們使用npm i vue-demi
在我們的項(xiàng)目里安裝完以后,它會(huì)自動(dòng)執(zhí)行一個(gè)腳本:
{ "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.`) }
導(dǎo)入我們項(xiàng)目里安裝的vue
,然后根據(jù)不同的版本分別調(diào)用switchVersion
方法。
先看一下loadModule
方法:
function loadModule(name) { try { return require(name) } catch (e) { return undefined } }
很簡(jiǎn)單,就是包裝了一下require
,防止報(bào)錯(cuò)阻塞代碼。
然后看一下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() }
執(zhí)行了copy
方法,從函數(shù)名可以大概知道是復(fù)制文件,三個(gè)文件的類型也很清晰,分別是commonjs
版本的文件、ESM
版本的文件、TS
類型定義文件。
另外還針對(duì)Vue2.6
及一下版本執(zhí)行了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') }
其實(shí)就是從不同版本的目錄里復(fù)制上述三個(gè)文件到外層目錄,其中還支持替換vue
的名稱,這當(dāng)你給vue
設(shè)置了別名時(shí)需要用到。
到這里,Vue Demi
安裝完后自動(dòng)執(zhí)行的事情就做完了,其實(shí)就是根據(jù)用戶項(xiàng)目中安裝的Vue
版本,分別從三個(gè)對(duì)應(yīng)的目錄中復(fù)制文件作為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" }
默認(rèn)入口為commonjs
模塊cjs
文件,支持ESM
的可以使用mjs
文件,同時(shí)還提供了可以直接在瀏覽器上使用的iife
類型的文件。
接下來看一下分別針對(duì)三種版本的Vue
具體都做了什么。
v2版本
Vue2.6
版本只有一個(gè)默認(rèn)導(dǎo)出:
我們只看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) // ...
導(dǎo)入Vue
和VueCompositionAPI
插件,并且自動(dòng)調(diào)用Vue.use
方法安裝插件。
繼續(xù):
// ... 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**/
首先導(dǎo)出了兩個(gè)變量isVue2
和isVue3
,方便我們的庫代碼判斷環(huán)境。
然后在導(dǎo)出Vue
的同時(shí),還通過Vue2
的名稱再導(dǎo)出了一遍,這是為啥呢,其實(shí)是因?yàn)?code>Vue2的API
都是掛載在Vue
對(duì)象上,比如我要進(jìn)行一些全局配置,那么只能這么操作:
import { Vue, isVue2 } from 'vue-demi' if (isVue2) { Vue.config.xxx }
這樣在Vue2
的環(huán)境中沒有啥問題,但是當(dāng)我們的庫處于Vue3
的環(huán)境中時(shí),其實(shí)是不需要導(dǎo)入Vue
對(duì)象的,因?yàn)橛貌簧?,但是?gòu)建工具不知道,所以它會(huì)把Vue3
的所有代碼都打包進(jìn)去,但是Vue3
中很多我們沒有用到的內(nèi)容是不需要的,但是因?yàn)槲覀儗?dǎo)入了包含所有API
的Vue
對(duì)象,所以無法進(jìn)行去除,所以針對(duì)Vue2
版本單獨(dú)導(dǎo)出一個(gè)Vue2
對(duì)象,我們就可以這么做:
import { Vue2 } from 'vue-demi' if (Vue2) { Vue2.config.xxx }
然后后續(xù)你會(huì)看到在Vue3
的導(dǎo)出中Vue2
是undefined
,這樣就可以解決這個(gè)問題了。
接著導(dǎo)出了Vue
的版本和install
方法,意味著你可以手動(dòng)安裝VueCompositionAPI
插件。
然后是導(dǎo)出VueCompositionAPI
插件提供的API
,也就是組合式API
,但是可以看到前后有兩行注釋,還記得前面提到的switchVersion
方法里針對(duì)Vue2
版本還執(zhí)行了updateVue2API
方法,現(xiàn)在來看一看它做了什么事情:
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之外的其他所有導(dǎo)出 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
之外的所有導(dǎo)出內(nèi)容,最后將:
export * from '@vue/composition-api/dist/vue-composition-api.mjs'
的形式改寫成:
export { EffectScope, ... } from '@vue/composition-api/dist/vue-composition-api.mjs'
為什么要過濾掉version
和default
呢,version
是因?yàn)橐呀?jīng)導(dǎo)出了Vue
的version
了,所以會(huì)沖突,本來也不需要,default
即默認(rèn)導(dǎo)出,@vue/composition-api
的默認(rèn)導(dǎo)出其實(shí)是一個(gè)包含它的install
方法的對(duì)象,前面也看到了,可以默認(rèn)導(dǎo)入@vue/composition-api
,然后通過Vue.use
來安裝,這個(gè)其實(shí)也不需要從Vue Demi
導(dǎo)出,不然像下面這樣就顯得很奇怪:
import VueCompositionAPI from 'vue-demi'
到這里,就導(dǎo)出所有內(nèi)容了,然后我們就可以從vue-demi
中導(dǎo)入各種需要的內(nèi)容了,比如:
import { isVue2, Vue, ref, reactive, defineComponent } from 'vue-demi'
v2.7版本
接下來看一下是如何處理Vue2.7
版本的導(dǎo)出的,和Vue2.6
之前的版本相比,Vue2.7
直接內(nèi)置了@vue/composition-api
,所以除了默認(rèn)導(dǎo)出Vue
對(duì)象外還導(dǎo)出了組合式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
相比,導(dǎo)出的內(nèi)容是差不多的,因?yàn)椴恍枰惭b@vue/composition-api
,所以install
是個(gè)空函數(shù),區(qū)別在于還導(dǎo)出了一個(gè)warn
方法,這個(gè)文檔里沒有提到,但是可以從過往的issues中找到原因,大致就是Vue3
導(dǎo)出了一個(gè)warn
方法,而Vue2
的warn
方法在Vue.util
對(duì)象上,所以為了統(tǒng)一手動(dòng)導(dǎo)出,為什么V2
版本不手動(dòng)導(dǎo)出一個(gè)呢,原因很簡(jiǎn)單,因?yàn)檫@個(gè)方法在@vue/composition-api
的導(dǎo)出里有。
繼續(xù):
// ... export * from 'vue' // ...
導(dǎo)出上圖中Vue
所有的導(dǎo)出,包括version
、組合式API
,但是要注意這種寫法不會(huì)導(dǎo)出默認(rèn)的Vue
,所以如果你像下面這樣使用默認(rèn)導(dǎo)入是獲取不到Vue
對(duì)象的:
import Vue from 'vue-demi'
繼續(xù):
// ... // 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
創(chuàng)建Vue
實(shí)例不一樣,Vue3
是通過createApp
方法,@vue/composition-api
插件polyfill
了這個(gè)方法,所以針對(duì)Vue2.7
,Vue Demi
手動(dòng)進(jìn)行了polyfill
。
到這里,針對(duì)Vue2.7
所做的事情就結(jié)束了。
v3版本
Vue3
相比之前的版本,最大區(qū)別是不再提供一個(gè)單獨(dú)的Vue
導(dǎo)出:
import * as Vue from 'vue' var isVue2 = false var isVue3 = true var Vue2 = undefined function install() {} export { Vue, Vue2, isVue2, isVue3, install, } // ...
因?yàn)槟J(rèn)不導(dǎo)出Vue
對(duì)象了,所以通過整體導(dǎo)入import * as Vue
的方式把所有的導(dǎo)出都加載到Vue
對(duì)象上,然后也可以看到導(dǎo)出的Vue2
為undefined
,install
同樣是個(gè)空函數(shù)。
繼續(xù):
// ... export * from 'vue' // ...
沒啥好說的,直接導(dǎo)出Vue
的所有導(dǎo)出內(nèi)容。
繼續(xù):
// ... 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
了兩個(gè)方法,這兩個(gè)方法實(shí)際上是@vue/composition-api
插件提供的,因?yàn)?code>@vue/composition-api提供的響應(yīng)性API
實(shí)現(xiàn)上并沒有使用Proxy
代理,仍舊是基于Vue2
的響應(yīng)系統(tǒng)來實(shí)現(xiàn)的,所以Vue2
中響應(yīng)系統(tǒng)的限制仍舊還是存在的,所以需要提供兩個(gè)類似Vue.set
和Vue.delete
方法用來給響應(yīng)性數(shù)據(jù)添加或刪除屬性。
到此這篇關(guān)于用Vue Demi同時(shí)支持Vue2和Vue3的方法的文章就介紹到這了,更多相關(guān)Vue Demi同時(shí)支持Vue2和Vue3內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VUE : vue-cli中去掉路由中的井號(hào)#操作
這篇文章主要介紹了VUE : vue-cli中去掉路由中的井號(hào)#操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09vue項(xiàng)目中l(wèi)ess的一些使用小技巧
前段時(shí)間一直在學(xué)習(xí)vue,開始記錄一下遇到的問題,下面這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目中l(wèi)ess的一些使用小技巧,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09結(jié)合axios對(duì)項(xiàng)目中的api請(qǐng)求進(jìn)行封裝操作
這篇文章主要介紹了結(jié)合axios對(duì)項(xiàng)目中的api請(qǐng)求進(jìn)行封裝操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09