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 transformed
vue-js
Grammar and its varied ways of writing - list
js-ts
Transformation 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