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:

  1. There are plenty of community practices that can distinguish versions directly from repository names.

Disadvantage:

  1. For example, v2.x supports Vue2 and v3.x supports Vue3.
  2. 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.
  3. 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:

  1. There is no mental burden on the developer, and the development experience is the same as when developing a Vue project.
  2. 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:

  1. 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:

  1. The render method in Vue2 provides a createElement method, usually used as h.

  2. 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:

  1. Ancestors components<Ancestor>Listen for events during the lifecycle
created() {
  this.$on('event', handler)
}
Copy the code
  1. Subcomponents are constantly passing through$parentFinds 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:

  1. Ancestor component usageemitter.onInstead 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
  1. The method by which the child component dispatches events fromparent.$emittoparent.emitter.emit.
parent.emitter.emit(event, args);
Copy the code

Program source code

Github Repository: github.com/Leecason/vu…

IO /vue-rough-n…

summary

  1. We can usevue-demiTo develop third-party packages that support both Vue2 and VUe3, with low development and migration costs.
  2. usevue-demiThe development experience is consistent with the usual development of Vue, and the mental burden is small.
  3. vue-demiIt provides us with additional apisisVue2isVue3Is used to determine the current environment.
  4. Implementing the event bus in Vue3 requires third-party packages such asmitt 或 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 ~