Code reuse is a very common thing, if it is the reuse of public code that is easy to say, directly made of an internal private library, if you want to use it, install the NPM package on the line, but the reuse of business code is not good to make a package, generally is copy and paste

I usually write code, if you feel a certain section of the business code seeing others have written before, so considering the business priorities, as long as someone else’s code is not too bad, I tend to give priority to copy other people’s code, save oneself write it again Then I met a problem, the company currently front projects mostly vue, no ts that early, Was gradually introducing ts later for the new project, so the new project using the vue – ts, and want to copy the old code in general are not introduced ts, admittedly, the two can be compatible with the existing, but for me, with a slight code cleanliness, or don’t want to see the same project code mixed with ts and ts of two kinds of writing, so as long as there is time, I will try to manually convert the old code to the TS specification

The difficulty was not much, but each copy had to be manually rotated. After rotating a lot, I suddenly fell into a deep thought, I seemed to repeat myself, and COULD not bear it, so I decided to write a tool that could automatically convert VUe-JS into VUe-TS

The code for this tool has been posted on Github, and I have made it into an NPM package for easy use. If you are interested, you can try it yourself

@babel

Babel is the first thing that comes to mind when it comes to JS syntax conversion. Babel has long provided rich and perfect PARSING and anti-parsing tools for JS syntax

@babel/parser

@babel/ Parser is a tool responsible for parsing JS syntax, which can be understood as converting JS syntax into AST, convenient for developers to customize processing, and supporting various JS syntax through plugins. For example, ES6, ES7, TS, Flow, JSX, and even experimental Language proposals

Such as:

const code = 'const a = 1'
const ast = require("@babel/parser").parse(code)
Copy the code

The transformed AST is an object and the data structure describes the const a = 1 expression

Iterating through the AST gives you information about all the currently parsed JS syntax, and you can modify it as well

@babel/generator

@babel/generator is used to convert the AST parsed by @babel/ Parser back to string JS code

const code = 'const a = 1; '
const ast = require("@babel/parser").parse(code)
const codeStr = require('@babel/generator').default(ast).code
code === codeStr // => true
Copy the code

other

@babel/parser, @babel/ Generator, and @babel/traverse will be used together. The first two are already covered. With @babel/traverse, Its main purpose is to iterate over ast generated by @babel/ Parser and provide methods that save developers from having to make their own judgments

But the program I’m writing here, because I don’t need too much parsing, I’m not going to use @babel/traverse, and I’m going to go through the AST as I want

In addition, Babel also provides a number of other tool libraries and help libraries, generally not very useful, you can see the documentation for more details

The following operations in this article are basically performed on the AST converted by @babel/ Parser and the code string parsed by @babel/ Generator

props

The vue official website provides an introduction to props

Therefore, the following ways of writing props are compliant:

export default {
  props: ['size'.'myMessage'].props: {
    a: Number.b: [Number.String].c: 'defaultValue'.d: {
      type: [Number.String]
    }
    e: {
      type: Number.default: 0.required: true.validator: function (value) {
        return value >= 0}}}}Copy the code

The above conversion to TS corresponds as follows:

export default class YourComponent extends Vue {
  @Prop() readonly size: any | undefined
  @Prop() readonly myMessage: any | undefined
  @Prop({ type: Number }) readonly a: number | undefined
  @Prop([Number.String]) readonly b: number | string | undefined@Prop() readonly c! : any @Prop({type: [Number.String] }) readonly d: number | string | undefined
  @Prop({ type: Number.default: 0.required: true.validator: function (value) {
    return value >= 0} }) readonly e! : number }Copy the code

The only types of props are Array

and object

An array type

The Array

type is easy to do, just one conversion template:

@Prop() readonly propsName: any | undefined
Copy the code

Just go through the props of type Array

and replace the propsName with the real value

Object type

We added some strings to the array template, mainly the @prop argument:

@Prop({ type: typeV, default: defaultV, required: requiredV, validator: validatorV }) readonly propsName: typeV
Copy the code

Each property of the props object is a propsName, which is defined. The propsName can be a type, which can be a single type (for example, Number) or an array of types (for example, [Number, String]). It may be an object with at least 0 or at most 4 attributes. If the object has an attribute named type, the value of this attribute also needs to determine the single type and type array, and the other attributes simply take the original values

We need to handle type regardless of whether the props object’s property value is an object or a type, so a special method to handle type is handlerType

That way, if it’s type, handlerType is handled directly; If it is an object, the property of the object is iterated and the property is found to be type, then handlerType is called. Otherwise, it is simply used as @prop’s argument

data

Vue official website for the introduction of data in data

The type of data can be Object or Function.

export default {
  data: {
    a: 1
  },
  data () {
    return {
      a: 1}},data: function () {
    return {
      a: 1}}}Copy the code

The above conversion to TS corresponds as follows:

export default class YourComponent extends Vue {
  a: number = 1
}
Copy the code

So it’s pretty straightforward to take each property of the data return value object as a property of class, as if it were converted

However, data could also be written like this:

export default {
  data () {
    const originA = 1
    return {
      a: originA
    }
  }
}
Copy the code

When data is a Function type, it is possible to run a piece of code before a return. The result of this code may affect the value of data

This isn’t uncommon, so it shouldn’t be ignored, but what about code before return? What I can do is put the code before the return into the Created lifecycle function, and then reassign each data after the created code. For example, in the case of the above code, to ts, I can do this:

export default class YourComponent extends Vue {
  a: any = null
  created () {
    const originA = 1
    this.a = originA
  }
}
Copy the code

Therefore, this involves the modification of data to created data. It can be considered to force data to be processed first, but I have a look, in fact, it is not complicated to write two pieces of logic here, so I will not strictly stipulate the processing order

model

The introduction of model on vUE’s official website is in Model

Props is referenced in the model, so the use of the model needs props

export default {
  model: {
    prop: 'checked'.event: 'change'
  },
  props: {
    checked: {
      type: Boolean}}}Copy the code

The above conversion to TS corresponds as follows:

export default class YourComponent extends Vue {
  @Model('change', { type: Boolean}) readonly checked! : boolean }Copy the code

If @model is used to declare props, there is no need to declare props in @prop. Therefore, I arranged the processing sequence, processing Model first, and then processing props. When processing props, Filter out the props already declared in the model

If you want to handle the props before the model, you need to handle the props before the model. If you want to handle the props before the model, you need to handle the props before the model. If you want to handle the props before the model, you need to handle the props. These two sections of logic are more complicated than the above data effect on creation, so I will directly deal with them in order to save myself trouble

computed

The vUE website introduces the model in Computed

All of the following computed methods are correct

export default {
  computed: {
    a () { return true },
    b: function () { return true },
    d: {
      get () { return true },
      set: function (v) { console.log(v) }
    }
  }
}
Copy the code

The ue-property-decorator does not provide a decorator specifically for computed, because ES6’s GET /set syntax itself can replace computed as follows:

export default class YourComponent extends Vue {
  get a () { return true }
  get b () { return true },
  get d (){ return true },
  set d (v) { console.log(v) }
}
Copy the code

In addition, computed actually supports arrow functions:

export default {
  computed: {
    e: (a)= > { return true}}}Copy the code

However, the class syntax of get/set does not support arrow functions, so it is not easy to convert. In addition, because arrow functions change the direction of this, and for computed data, arrow functions are generally not recommended for computed data because of attributes on the current VUE instance. You can get the current vUE instance on the first argument of the arrow function, but that’s a bit redundant, so I’ll skip the arrow function here and give you a hint when you run into a computed arrow function

watch

Vue’s official website introduces watch in Watch

The following are legal ways to write watch:

export default {
  watch: {
    a: function (val, oldVal) {
      console.log('new: %s, old: %s', val, oldVal)
    },
    / / the method name
    b: 'someMethod'.// This callback is called whenever the property of any object being listened on changes, no matter how deeply nested it is
    c: {
      handler: function (val, oldVal) { / *... * / },
      deep: true
    },
    // This callback will be invoked immediately after the listening starts
    d: {
      handler: 'someMethod'.immediate: true
    },
    e: [
      'handle1'.function handle2 (val, oldVal) { / *... * / },
      {
        handler: function handle3 (val, oldVal) { / *... * / },
        immediate: true}].// watch vm.e.f's value: {g: 5}
    'e.f': function (val, oldVal) { / *... * /}}}Copy the code

The above conversion to TS corresponds as follows:

export default class YourComponent extends Vue {
  @Watch('a')
  onAChanged(val: any, oldVal: any) {}
  @Watch('b')
  onBChanged (val: any, oldVal: any) {
    this.someMethod(val, oldVal)
  }
  @Watch('c', { deep: true })
  onCChanged (val: any, oldVal: any) {}
  @Watch('d', { deep: true })
  onDChanged (val: any, oldVal: any) {}
  @Watch('e')
  onE1Changed (val: any, oldVal: any) {}
  @Watch('e')
  onE2Changed (val: any, oldVal: any) {}
  @Watch('e', {immediate: true })
  onE3Changed (val: any, oldVal: any) {}
  @Watch('e.f')
  onEFChanged (val: any, oldVal: any) {}
}
Copy the code

There are a lot of ways to write it, so there must be a lot of branches

Each attribute under Watch is a VUE response value required for watch. The value of these attributes can be strings, functions, objects and arrays, and there are four types in total

Where, the string type is equivalent to calling the method in the current Vue instance, the function type is calling this function, relatively simple; For the object type, it has three attributes: handler, deep, and immediate. All three attributes are optional. The value of handler is a function or string. For array types, each array item, its actually quite so string type, function, types and object types of polymerization, so in fact as long as the deal with the three types, array types directly over the array, the type of each array item must be within the three types, corresponding treatment method according to the type of call.

This is the main part of the function, but we also need to consider the form of the handler function. The following functions are legal:

export default {
  watch: {
    a: function {},
    b () {},
    c: (a)= > {},
    d: async function {},
    e: async() => {}}}Copy the code

Not only in watch, but in other vue instance properties, such as created, computed, and so on, wherever functions are possible, you need to take these notation into account. Of course, there are Generator functions, but I won’t consider them here. There are better async/await functions available. Why Generator

methods

All vue instance methods exist as attributes of methods. Each method is a function, so you just need to take all the methods under methods and convert them to class methods. There is not much work to do. You can also support async/await, which should be taken into account

lifeCycle

Vue has many lifecycle hook functions, as well as some third-party hook functions, such as vue-router:

const vueLifeCycle = ['beforeCreate'.'created'.'beforeMount'.'mounted'.'beforeUpdate'.'updated'.'activated'.'deactivated'.'beforeDestroy'.'destroyed'.'errorCaptured'.'beforeRouteEnter'.'beforeRouteUpdate'.'beforeRouteLeave']
Copy the code

These hook functions are functions, just like methods

component

This is a little bit easier. Let’s convert it and splice it

export default {
  components: {
    a: A,
    B
  },
}
Copy the code

The above conversion to TS corresponds as follows:

@Component({
  components: {
    a: A,
    B
  }
})
export default class TransVue2TS extends Vue {}
Copy the code

So you map all the properties of the original components

mixins

Vue official website for the introduction of mixins in mixins

The value type is Array

export default {
  mixins: [A, B]
}
Copy the code

The above conversion to TS corresponds as follows:

export default class TransVue2TS extends Mixins(A.B) {}
Copy the code

Extends Vue is changed to extends Mixins, and the parameters of Mixins are all the items of the original Mixins

provide && inject

When I was thinking about how to deal with these two, I looked at the official website of VUE. The official website said this about these two:

Provide and Inject mainly provide use cases for high-level plug-in/component libraries. Direct use in application code is not recommended.

To be clear, it is not recommended to use the business code, because it is not conducive to data tracking. You can use mature vueBus or Vuex instead, which is generally not used. I wrote this conversion program also to convert the business code. So I didn’t do anything with these two attributes, and if you find them in your code, you’ll be prompted to do it manually

emit && ref

Both of these are just sort of syntactic sugar things that can be left alone

File processing

The above is for a.vue file detailed processing logic, want to access the actual file and folder processing, naturally, not the file read and update operation, which involves node file processing content, but not complicated, not to say

NPM package

After writing the code, to simplify the process, I packaged it into an NPM package and uploaded it to NPM. If you want to use it, just download the package and type in the command line

npm i transvue2ts -g
Copy the code

Transvue2ts = transvue2ts = transvue2ts = transvue2ts = transvue2ts = transvue2ts = transvue2ts = transvue2ts = transvue2ts = transvue2ts = transvue2ts = transvue2ts Handle E:\project\testA\ SRC \test.vue file:

transvue2ts E:\project\testA\ SRC \test.vue => Output path: E:\project\testA\src\testTs.vue
Copy the code

Process all.vue files in E:\project\testA\ SRC folder:

transvue2ts E:\project\testA\ SRC => Output path: E:\project\testA\srcTs
Copy the code

For a single file, it must end in. Vue. The converted file is output to the same directory with the original file name + Ts, for example, index.vue => indexts.vue. For the file directory, the program will recursively traverse the file directory, find all. Vue files in this folder and convert them to a new folder in the same directory according to the original directory structure, for example, / SRC => /srcTs

conclusion

As cumbersome as this conversion process may seem, it can be summarized in three steps:

  • List all that needs to be transformedvue-jsGrammar and its varied ways of writing
  • listjs-tsTransformation mappings between grammars
  • Write syntax conversion code

It’s essentially a translator that translates vue-JS syntax into VUe-TS syntax, and the hard part is finding all the syntactic mappings between the two and knowing what to do with them, so it’s actually a lot of manual labor, right

As long as you understand how this works, it’s actually the same thing to switch from VUE to Wepy or React to wechat. They’re both translators and manual work, but some of them are very easy, like moving a few bricks, while some of them are hard and require brain work