preface

A few days ago, we published a new tool to help you change the Vue2 code to Vue3. In this article, we shared one of the conversion rules: the conversion of eventHub (or eventBus).

Vue official migration plan: v3.cn.vuejs.org/guide/migra…

1. Introduce eventHub

1.1 Vue2 eventHub

EventHub is an event center shared between components and is used in Vue as a bridge between components to send messages to eventHub, which other modules subscribe to get their data.

About the official website:

Vue instances can be used to trigger handlers (on, ON, ON, off, and $once) that are added instructively by the event-firing API. An implementation of the Event Hub, used to create global event listeners available throughout the application:

// eventHub.js
const eventHub = new Vue()
export default eventHub
Copy the code
// ChildComponent.vue
import eventHub from './eventHub'

export default {
  mounted() {
    // Add eventHub listener
    eventHub.$on('custom-event'.() = > {
      console.log('Custom event triggered! ')})},beforeDestroy() {
    // Remove the eventHub listener
    eventHub.$off('custom-event')}}Copy the code

1.2 eventHub in Vue3

However, in Vue 3, the $ON, $OFF, and $once methods are officially removed

We completely removed the $ON, $OFF, and $once methods from the instance. $EMIT is still included in the existing API because it is used to trigger event handlers added declaratively by the parent component. In Vue 3, it is no longer possible to use these apis to listen for events emitted by the component itself from within the component, and there is no way to migrate the use case.

2. Migration solution

2.1 Selection of eventHub tripartite library

Vue offers an alternative: using third-party libraries, “eventHub patterns can be replaced with external libraries that implement event trigger interfaces, such as Mitt or Tiny-Emitter.” Mitt and Tiny-Emitter are studied. Although mitt has a higher number of stars, it does not support the $once method and needs to be combined with other methods. To reduce migration costs, I chose to use Tiny-Emitter.

A tiny (less than 1k) event emitter library.

2.2 Selection of migration tools

With N + projects, 180 files, and various eventHub implementations, manual replacement is certainly not an implementable solution. A better approach is to deconstruct the code based on the AST (Abstract syntax tree), modify it in batches according to established rules, and then export the file.

GoGoCode, a handy tool for handling AST power, is recommended

GoGoCode provides a jquery-like API for manipulating AST objects. Lower the threshold of using AST, help developers from the tedious AST operation free

Is the most powerful aid to code migration.

In addition, amway can not do AST analysis without a tool: AstExplorer.net, using it we can easily view the AST syntax tree structure of a certain code

3. Code migration

3.1 Write a Demo to be converted

<template>
  <div>
    A:{{num}}
  </div>
</template>
<script>
import ehb from './EventHub';
export default {
  name: 'B'.mounted() {
    // Add eventHub listener
    ehb.$on('inc'.() = > {
      this.num += 1;
    });
    // Add eventHub listener
    ehb.$once('inc'.() = > {
      this.num * 100;
    });
  },
  beforeUnmount() {
    ehb.$off('inc'); }};</script>
Copy the code

3.2 Things to do

  1. Locate inside the script tag$on $off $once $emitMethod to extract their object names
  2. Object name, find the code segment that declares it
  3. Using tiny_Emitter objects to overlay several of eventHub’s previously deprecated methods
  4. Add tiny_emitter reference
  5. The output file

The conversion effect is as follows:

3.3 starts

The installationGoGoCode

npm install gogocode
Copy the code

Initializes the AST object of the Vue file

// Read file contents as ast objects
let ast = $('Contents of vUE file to be converted', {parseOptions: { language: 'vue'}})// Locate the script node of the AST object
let scriptAST = ast.find('<script></script>') 
Copy the code

Transformation logic: Implement “3.2 Things to Do”

$on, $off, $once and $emit
scriptAST.find([`$_$1.$on($_$2)`.`$_$1.$off($_$2)`.`$_$1.$once($_$2)`.`$_$1.$emit($_$2)`]).each(hubAST= > {
    Attr ('callee.object.name'); ehb.$XXX (...) The object name
    if (hubAST.attr('callee.object.name')) {
        // 2. Find the eventHub declaration location
        let definitions = scriptAST.find(`import ${hubAST.attr('callee.object.name')} from '$_$'`)
        const eventHub = `Object.assign(${ hubAST.attr('callee.object.name')},{ $on: (... args) => tiny_emitter.on(... args), $once: (... args) => tiny_emitter.once(... args), $off: (... args) => tiny_emitter.off(... args), $emit: (... args) => tiny_emitter.emit(... args), }); `
        // 3. Iterate through the declaration location, using the After API to overwrite the previous eventHub object after the declaration
        definitions.each(def= > {
            if(! scriptAST.has(eventHub)) { def.after(eventHub) } }) }// 4. Add tiny_emitter references
    if(! scriptAST.has(`import tiny_emitter from 'tiny-emitter/instance'`)) {
        scriptAST.prepend(`import tiny_emitter from 'tiny-emitter/instance'; \n`)}})Copy the code

The final output AST object is a string:

return ast.generate()
Copy the code

Concrete implementation code: playground

4. To conclude

This code uses the AST operations implemented by GoGoCode:

  1. $.findAPI is used to locate$on $off $once $emitTo output the AST object
  2. xxx.attrUsed to extract properties of AST objects, such as in this codeehb.$on( ...... )Object nameehb
  3. Use the HAS API to determine whether a node already exists in the AST
  4. XXX. After Adds an AST object to the corresponding AST
  5. Xxx. prepend adds an AST object to the corresponding AST

Are very familiar with the operation there!

If you find a problem with the eventHub conversion, please send us an issue github.com/thx/gogocod…

Thanks for reading and have a great day!