Analysis of the principle of converting Vue2 code to Vue3-eventHub

Analysis of the principle of converting Vue2 code to Vue3-eventHub


A few days ago, we released "Alimama has made a new tool to help you change the Vue2 code to Vue3" . This article shares one of the conversion rules: eventHub (or eventBus) conversion ideas.

Vue's official migration plan:

1. First introduce eventHub

1.1 eventHub of Vue2

eventHub is an event center shared between components. It is used in Vue as a bridge for component communication, sending messages to eventHub, and other modules obtain corresponding data by subscribing to this eventHub.

Official website introduction:

Vue instances can be used to trigger processing functions that are added instructionally by the event-triggered API (on,on, off and $once). event hub, used to create the implementation of global event listeners available throughout the application:

EventHub.js// const eventHub = new new Vue () Export default eventHub duplicated code
//ChildComponent.vue import eventHub from './eventHub' export default { mounted () { //Add eventHub listener eventHub.$on( 'custom-event' , () => { console .log( 'Custom event triggered!' ) }) }, beforeDestroy () { //Remove eventHub listener eventHub.$off( 'custom-event' ) } } Copy 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 example. $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 monitor the events emitted by the component itself. There is no migration method for this use case.

2. Migration plan

2.1 The choice of eventHub third party library

Vue officially provides an alternative solution: using a third-party library, "eventHub mode can be replaced with an external library that implements the event trigger interface, such as mitt or tiny-emitter ." I studied mitt and tiny-emitter. Although mitt has a higher star number, it does not support the $once method and needs to be implemented by a combination of other methods. In order to reduce the cost of migration, I choose to use tiny-emitter.

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

2.2 Selection of migration tools

Faced with more than N projects, one hundred and eighty files, and various eventHub implementations, manual replacement is definitely not an achievable solution. The better solution is to deconstruct the code based on AST (Abstract Syntax Tree), modify and output files in batches according to established rules.

I recommend GoGoCode, a powerful and easy- to-start tool for handling AST

GoGoCode provides an API similar to JQuery to manipulate AST objects. Lowers the threshold for using AST, helping developers to free themselves from tedious AST operations

It is the most powerful assistant for code migration.

In addition, Amway is an indispensable tool for AST analysis: , using it we can easily view the AST syntax tree structure of a certain piece of 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 code

3.2 Things to do

  1. Locate the script tag
    Method to extract their object name
  2. Object name, find the code snippet that declares it
  3. Overwrite several obsolete methods previously provided by eventHub with the tiny_emitter object
  4. Add tiny_emitter reference
  5. File output

The conversion effect is as follows:

3.3 Commencement

Install GoGoCode

npm install gogocode copy the code

Initialize the AST object of the Vue file

//Read the content of the file as an ast object let ast = $( `The content of the vue file to be converted` , { parseOptions : { language : 'vue' } }) //object script nodes positioned ast the let scriptAST = ast.find ( '<script> </script>' ) copy the code

Conversion logic: to achieve "3.2 things to be done"

//1. Traverse script content $on, $off, $once and $emit scriptAST.find([ `$_$1.$on($_$2)` , `$_$1.$off($_$2)` , `$_$1.$once($_$2)` , `$_$1.$emit($_$2)` ]).each( hubAST => { //1.1 Use hubAST.attr('callee.object. name') API, extract the object name of ehb.$xxx(...) if (hubAST.attr( '' )) { //2. Find the eventHub declaration position let definitions = scriptAST.find( ` import ${hubAST.attr( '' )} from'$_$'` ) const eventHub = `Object.assign( ${ hubAST.attr('' )} ,{ $on: (...args) => tiny_emitter.on(...args), $once: (...args) => tiny_emitter.once(...args), $off: (...args) =>, $emit: (...args) => tiny_emitter.emit(...args), }); ` //3. Traverse the declaration position and use the after API to overwrite the previous eventHub object definitions.each( def => { if (!scriptAST.has(eventHub)) { def.after(eventHub) } }) } //4. Add a reference to tiny_emitter if (!scriptAST.has( `import tiny_emitter from'tiny-emitter/instance'` )) { scriptAST.prepend( `import tiny_emitter from'tiny-emitter/instance';\n` ) } }) Copy code

Finally, the output AST object is a string:

return ast.generate() Copy code

Specific implementation code: Playground

4. Final summary

These codes are used the AST operation implemented by GoGoCode :

  1. $.find API is used to locate
    , Output AST object
  2. xxx.attr used to extract the attributes of the AST object, such as in this code
    ehb.$on( ......)
    Object name
  3. Use the has API to determine whether a node already exists in the AST
  4. xxx.after adds an AST object after the corresponding AST
  5. xxx.prepend adds an AST object before the corresponding AST

Are there any familiar operations?

Here are some ideas for the implementation of eventHub conversion and the usage of GoGoCode. I hope to get your opinions and suggestions. If you find a code problem, please send us an issue

Thank you for reading, and have a nice day!