This article was first published on the public account “Xiao Li’s Front-end cabin”, welcome to follow ~
background
I developed a Vue plugin in my spare time when the market was Vue2 era. Vue3 has become such an inevitable trend that it is necessary to upgrade the library to support Vue3 in order to make the project have a longer life cycle.
Upgrade package
Plan 1: two warehouses
Following the example of Vue, build two warehouses, one for V2 and one for V3, named XXX and XXX-Next.
Advantage:
- There are plenty of community practices that can distinguish versions directly from repository names.
Disadvantage:
- For example, v2.x supports Vue2 and v3.x supports Vue3.
- Two sets of code need to be maintained at the same time. In addition, the engineering part of the warehouse is the same, and there is a lot of duplicate code.
- If you need to support new features or adjust build-related changes later, you need to deal with both sides of the code, which is costly.
Plan two: two branches
Similar to scheme 1, two branches, V2 and V3, are built in the warehouse to support two versions of Vue respectively.
Advantages and disadvantages are the same as plan 1, except that only one warehouse is required, but the maintenance cost is also very high.
Both of these solutions require maintaining two sets of code, so is there a solution that can be done with just one set of code
Scheme 3: Use vue-demi
What is thevue-demi
?
Vue-demi is a development tool that allows you to develop a common VUE library that supports both Vue2 and vu3 without worrying about the version installed by users. The official warehouse was developed by ANTfu, a core member of the Vue team. Vueuse, Vue-Charts and other packages use it.
For those interested, check out vue-Demi’s introduction
Method of use
Any VUe-related apis are no longer imported from the original Vue, but from vue-Demi.
import { ref, reactive, defineComponent } from 'vue-demi'
Copy the code
The rest of the code is just de-coding and publishing as usual when developing Vue!
Vue – demi principle
The simpler the code is to use, the more it is worth exploring the underlying principles. So what exactly does Vue-Demi have in mind?
Vue-demi takes advantage of NPM’s Postinstall hook. After the user installs all the packages, the script starts checking for installed Vue versions and returns the corresponding code based on the Vue version. When using Vue2, @vue/ composition-API will be installed automatically if it is not installed.
Some of the core code is extracted below:
const Vue = loadModule('vue'); / / load the vue
function switchVersion(version, vue) {
// Copy the index file
copy('index.cjs', version, vue);
copy('index.mjs', version, vue);
copy('index.d.ts', version, vue);
if (version === 2) {
updateVue2API(); // On Vue2, install @vue/composition-api}}// Determine the version number and write the corresponding file to vue-demi export file
if (Vue.version.startsWith('2.')) {
switchVersion(2);
} else if (Vue.version.startsWith('3.')) {
switchVersion(3);
}
Copy the code
Back to the scheme:
Advantage:
- There is no mental burden on the developer, and the development experience is the same as when developing a Vue project.
- Only one set of code needs to be maintained, and two large versions of the code base will not be maintained at the same time. Development costs are low.
Disadvantage:
- Developers using Vue2 will need an additional installation
@vue/composition-api
, slightly increasing the code size.
conclusion
To make the project cheap and quick to support Vue3 (and to try some new wheels).
Finally, I chose option three: vue-Demi.
The migration process
The installationvue-demi
npm i vue-demi
# or
yarn add vue-demi
Copy the code
Add vue and @vue/composition-api to peerDependencies in package.json.
{
"dependencies": {
"vue-demi": "latest"
},
"peerDependencies": {
"@vue/composition-api": 1 "" ^ 1.0.0 - rc.."vue": "^ 2.0.0 | | > = 3.0.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true}},"devDependencies": {
"vue": "^ 3.0.0"}},Copy the code
Code transformation
Vue plug-in
Before modification:
const MyPlugin = {
/**
* install function
* @param {Vue} Vue
* @param {Object} options* /
install (Vue, options = {}) {
... // Handle the plug-in's default arguments
// Globally register components
Vue.component('my-component', MyComponent); }};export default MyPlugin;
Copy the code
Since the install method of the plug-in in Vue3 no longer passes the Vue constructor, but an app instance, all you need to do is change the parameter name: Vue -> app.
After transforming:
const MyPlugin = {
/**
* install function
* @param {App} app
* @param {Object} options* /
install (app, options = {}) {
... // Handle the plug-in's default arguments
// Globally register components
app.component('my-component', MyComponent); }};export default MyPlugin;
Copy the code
Vue single file component
To support Vue3, we need to use Vue3’s new syntax as much as possible. Also, to keep code changes as small as possible, I didn’t use the Setup API this time.
Component definition
Before modification:
The code is the Vue2 component definition syntax, which defines a component object and exports it externally by default.
export default {
name:...props:...watch:... };Copy the code
In Vue3, we use defineComponent, a new API for TypeScript type derivation, to wrap the component object.
The difference here is that defineComponent needs to be imported from Vue-demi.
After transforming:
import { defineComponent } from 'vue-demi'; // Need to be imported from 'vue-demi'
export default defineComponent({
name:...props:...watch:... });Copy the code
Render function
Before modification:
-
The render method in Vue2 provides a createElement method, usually used as h.
-
To get the current default slot VNode in the Render method, we can use this.$slot.default.
render(h){...const slot = this.$slots.default; // Default slot
return h('div'.null, slot); // Wrap the incoming default slot content in div
}
Copy the code
The RENDER method in Vue3 no longer provides h method and needs to be imported from Vue. Again, this comes from vue-demi.
To get the default slots, call this.$slots.default() as a method.
But this.$slots.default cannot be imported from vue-demi and is associated with the vue version of the current runtime.
Vue-demi provides us with two additional apis, isVue2 and isVue3, for determining the current environment.
After transforming:
import { h, isVue2 } from 'vue-demi'; // Need to be imported from 'vue-demi'
render(h2){...// vue2
if (isVue2) {
const slot = this.$slots.default; // Default slot
return h2('div'.null, slot);
}
// vue3
const slot = this.$slots.default(); // Default slot
return h('div'.null, slot);
}
Copy the code
Cross component communication
Before modification:
We can easily implement the EventBus using $emit and $ON in Vue2. In my library, the child component needs to send events to the specified ancestor component. I borrowed the Element-UI implementation using $emit and $ON:
- Ancestors components
<Ancestor>
Listen for events during the lifecycle
created() {
this.$on('event', handler)
}
Copy the code
- Subcomponents are constantly passing through
$parent
Finds the specified ancestor component and uses itparent.$emit.call(parent, event, args)
Dispatch events to ancestral elementals.
// Dispatches events to the specified ancestor component
export default defineComponent({
...
methods: {
$_dispatchComponent(componentName, event, args) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
// Iterate through the loop to find components with the same name
while(parent && (! name || name ! == componentName)) { parent = parent.$parent;if(parent) { name = parent.$options.name; }}if (parent) {
parent.$emit.call(parent, event, args); // When found, send the event}}},},Copy the code
In Vue3, the implementation of the event bus is no longer able to use Vue’s own API due to the removal of $on.
We need to use third-party libraries to do this, such as Mitt or Tiny-Emitter. Here, I choose Mitt. The API is sufficient and relatively light.
After transforming:
- Ancestor component usage
emitter.on
Instead of$on
:
import mitt from 'mitt';
export default defineComponent({
...
created() {
this.emitter = mitt();
this.emitter.on(event, handler); // Listen on events
},
beforeUnmount() {
this.emitter.all.clear(); // Unbind events}})Copy the code
- The method by which the child component dispatches events from
parent.$emit
toparent.emitter.emit
.
parent.emitter.emit(event, args);
Copy the code
Program source code
Github Repository: github.com/Leecason/vu…
IO /vue-rough-n…
summary
- We can use
vue-demi
To develop third-party packages that support both Vue2 and VUe3, with low development and migration costs. - use
vue-demi
The development experience is consistent with the usual development of Vue, and the mental burden is small. vue-demi
It provides us with additional apisisVue2
和isVue3
Is used to determine the current environment.- Implementing the event bus in Vue3 requires third-party packages such as
mitt
或tiny-emitter
.
❤ ️ support
If this article has been helpful to you, please support me by clicking 👍. Your “likes” are the motivation for my creation.
As for me, I am currently a front-line developer of Bytedance, working for four and a half years. I use React in my work and like Vue in my spare time.
At ordinary times, I will share and summarize the thinking and practice of front-end work in depth from time to time. The public account “Xiao Li’s Front-end cabin”, thank you for your attention ~