After finishing the project, add the Github link
Typescript is becoming a popular choice for front-end engineers to develop projects, and I have several Vue projects written in JS that I’m currently rewriting in TS. The number of pages alone, not to mention the number of components, was over 100, and it would have been a huge and tedious task to rewrite so many Vue files one by one. In fact, before this also manually converted several projects, found that the conversion process is mostly repetitive labor, is possible through the program to achieve automatic conversion. Of course, converting from JS to TS inevitably leads to typing problems, so it requires only repetitive work, and manual processing when type information is really needed.
When writing projects with TS, you can use two different code styles:
- The Vue. Extend method is used.
- Use the class syntax with the vue-property-decorator implementation.
Which plan should be chosen is a matter of opinion. What I did was approach 2. Why did you choose it? Wouldn’t it be easy to rewrite it if you used method 1? Method 2 was chosen because of the heavy use of this in Vue and the more intuitive use of the class form — everything is on the class instance (which is probably just something I like to mess with (´ · ᴗ · ‘).
Implementation approach
The idea is as simple as putting an elephant in a refrigerator:
- Convert old code into an AST (Abstract Syntax Tree).
- Change the AST to class form. (Type information cannot be filled in completely, you can use any or choose not to fill in)
- Convert the AST into new code.
For more information on what an abstract syntax tree is, you can look it up on the web (I think it’s necessary to have some understanding of abstract syntax trees). In simple terms, JS code can be represented in a tree structure called an abstract syntax tree. Such as:
function foo() {
return a + b;
}
Copy the code
The corresponding AST might look something like the following, a simple tree structure (although this is a big simplification, it is much more complex).
If you want to change b to C in your code, you just need to change the nodes in the tree, for example:
You can then use the modified AST to generate the code.
Conversion of code to AST
Recast is a library that makes it easy to convert code to AST to open and close the refrigerator door.
Two more concepts must be mentioned here, estree and AST-types.
Estree is a community standard for parsing JS code into AST, that is, what values are in the resulting AST node should be basically implemented by following instructions in ESTree. Some knowledge of this standard, or how it is compiled, can improve the efficiency of modifying code later.
Ast-types are libraries used in Recast that provide syntax tree node definition, traversal, and other functions. The types defined in ast-types are estree compatible, but in practice there are sometimes perceived to be some gaps. For example, in some cases the decorators field does not exist. The type definition in ast-types can be extended through the D.ts file.
If you don’t know exactly how compilation works, recast.parse has some code to see how it should be written, and then you can write the code accordingly.
Here is an example of code:
// Operate on some nodes in the AST
import {
builders as b,
} from 'ast-types';
const clazz = b.classDeclaration(
b.identifier(camelCaseWithFirstLetter('MyComponent')),
b.classBody([]),
b.identifier('Vue')); clazz.decorators = [ b.decorator( b.callExpression( b.identifier('Component'),
[
b.objectExpression([
b.property('init', b.identifier('name'), b.literal('MyComponent'(), [,], ()];Copy the code
// The code corresponding to the last snippet
@Component({
name: 'MyComponent',})class MyComponent extends Vue {}
Copy the code
Choose the parser
When recast.parse code, esprima will be used by default for parsing. Esprima (currently version 4.0.1) has a lot of support for the new JS syntax, but for the current project, there are still some syntax cannot be parsed. To solve this problem, Recast can also customize the syntax parser used.
I also found two other syntactic parsing libraries, @typescript-eslint/typescript-estree and @babel/parser, @typescript-esLint /typescript-estree does not support the modifier syntax currently used in ue-property-decorator, so @babel/parser is chosen.
// Use custom parsing libraries
const ast = recast.parse(jsScript, {
parser: {
parse(source: string, options: any) {
return parser.parse(source, Object.assign(options, {
plugins: [
'estree'.// Support estree format
'decorators-legacy'.// Support modifier syntax
// 'typescript', which supports parsing typescript
],
tokens: true.// Necessary parameters. The default value is false. There is no tokens in the parse result. When there is no tokens, Recast will re-use Esprima for the parse operation
}))
},
},
tabWidth: 4});Copy the code
Make detailed adjustments to the generated code
Currently, when building projects using @vue/ CLI, you are prompted to use tsLint or ESLint to help keep your code clean. If you are not building projects using @vue/ CLI, it is still recommended to add tsLint or ESLint to your projects.
These libraries provide code specification rules such as “all quotes should use single quotes”, whereas code generated using recast.print uses double quotes by default. The final choice depends on the actual situation of the project, for which Recast also provides configuration options that give it more flexibility in generating code.
// Use recast to convert AST to JS code
const code = recast.print(ast, {
tabWidth: 4.// The number of Spaces used
quote: 'single'.// Use single or double quotation marks
trailingComma: true.// 使用启用trailingComma
}).code;
Copy the code
Traverse the document
Use FS in Node to complete the file traversal
import fs from 'fs';
const dir = '/Volumes/Repo2/repo/vue/project/src';
const dist = '/Volumes/Repo2/repo/vue/project_ast/src';
const pageDir = dir + '/pages';
const queue = [ pageDir ];
while (queue.length > 0) {
const filePath = queue.shift();
if (filePath) {
const stats = fs.statSync(filePath);
const isDirectory = stats.isDirectory();
if (isDirectory) {
// Add all subpaths to queue if it is a folder
constchildren = fs.readdirSync(filePath); queue.unshift(... children.map(child= > filePath + '/' + child));
} else {
// If yes, check whether it is a. Vue file
if (filePath.indexOf('.vue') > =0) {
const output = dist + filePath.substr(dir.length);
fs.mkdirSync(path.dirname(output), {
recursive: true.mode: 0o755}); handleVue(filePath, output);// Process vue files}}}}Copy the code
Subsequent content
I am currently testing on my own project. Although I have automated a lot of work, it is still a lot of work.
Another NPM component library I created, large-list, was previously written as a class, presumably because of the introduction of vue property-decorator logic, and ended up with a rollup package that was 27K in size without uglify. Using this library to convert class code to VueOptions, and then rollup to pack it again without uglify, only 4K size, allows me to write code in class form and keep the final release code small enough.
In addition, now that the migration to TS syntax is complete, it may be possible to consider converting the old code to composition-API format after the official release of Vue@3.
Master son (see my look in the eyes, don’t point praise again walk ~
Modify the less
In addition to changes to js content, there are also changes to style files during project migration. At present, less is used in the project. Although the syntax of less is relatively simple, and it can even be directly modified by using regular replacement for several times, I like to make changes. (´ ᴗ · ‘)
Although the CSS code is very different from the JS code, this time the modification is done by manipulating the AST.
It relies on PostCSS to convert CSS to AST (I think it’s important to know postCSS), but unlike Recast, PostCSS does not return the AST directly. You need to use the PostCSS plugin (plguin) to do this.
A simple plug-in is written in the following example:
import postcss from 'postcss';
const code = ` .rule { width: 20px; } `;
const myPlugin = postcss.plugin('postcss-my-plugin', (root, result) => {
root.walkRule((rule) = > {
rule.walkDecl((decl) = > {
console.log(decl.prop, decl.value); // Will print width and 20px
decl.value = '40px'; // Simply change 20px to 40px
});
});
});
postcss([ myPlugin ]).process(code).then((result) = > {
Rule {* width: 40px; *} * /
console.log(result.css);
})
Copy the code
More about the use of postCSS to modify less method is not written, there is a need for children’s shoes can be their own research.