5

As a front-end developer, when developing a ts project, the most tedious work should be the data type and mock data of the handwritten interface, because if this part of the work is not done, it will be difficult to write the business logic later, and it is all copy-paste similar repetitions. Work is time-consuming. The following will introduce a method to automatically generate ts type and mock data to help students free themselves from tedious work.

Below we will use an example to let everyone understand the basic process of code generation.

TS code generation basic process

Let's take the following ts code as an example to walk through the basic process of generating it.

export interface TestA {
  age: number;
  name: string;
  other?: boolean;
  friends: {
    sex?: 1 | 2;
  },
  cats: number[];
}

Step 1: Select a data source

Let's think about a question first, what information is needed to generate the interface written by the above code?

Through analysis, we first need to know how many attributes it has, and then we need to know which attributes are required. In addition, we need to know the type, enumeration and other information of each attribute. There is a data format that can perfectly provide us with the data we need, it is JSON Schema .

Students who have been exposed to the backend should all know JSON Schema , which is a description of JSON data. For example, we define the following JSON structure:

{
  "age": 1,
  "name": "测试",
  "friends": {
          "sex": 1
  },
  "cats": [
    1,
    2,
    3
  ],
  "other": true
}

Let's verbally describe this json: it has five attributes: age, name, friends, cats, and other. The type of the age attribute is number, the type of the name attribute is string, the type of the cats attribute is an arry composed of numbers, and the friends attribute is an object. , which has a sex attribute of type number and the other attribute of type boolean.

The description in JSON Schema is as follows:

{
  "type": "object",
  "properties": {
    "age": {
      "type": "number"
    },
    "name": {
      "type": "string"
    },
    "cats": {
      "type": "array",
      "items": {
        "type": "number"
      }
    },
    "friends": {
      "type": "object",
      "properties": {
        "sex": {
          "type": "number"
        },
        "required": [
          "e"
        ]
      }
    },
    "other": {
    "type": "boolean",
    },
    "required": [
      "a",
      "b"
    ]
  }
}

It can be seen that JSON Schema can perfectly programmatically realize our oral description. This example is relatively simple. The description ability of JSON Schema is far more than that. For example, enumeration, the maximum length of the array, the maximum and minimum values , whether it is necessary and other commonly used attributes can be accurately described, so it is also often used in user input verification scenarios.

Step 2: Select a code generation tool

Seeing this title, I believe most students already know the answer, yes, it is TS AST and TS Compiler API , the latter can generate or modify TS AST compiled files. Let's take a look at how to use the TS Compiler API to generate an abstract syntax tree and compile it into the code mentioned above.

The corresponding TS Compiler code is as follows:

factory.createInterfaceDeclaration(
    undefined,
    [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
    factory.createIdentifier("TestA"),
    undefined,
    undefined,
    [
      factory.createPropertySignature(
        undefined,
        factory.createIdentifier("age"),
        undefined,
        factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
      ),
      factory.createPropertySignature(
        undefined,
        factory.createIdentifier("name"),
        undefined,
        factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
      ),
      factory.createPropertySignature(
        undefined,
        factory.createIdentifier("other"),
        factory.createToken(ts.SyntaxKind.QuestionToken),
        factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword)
      ),
      factory.createPropertySignature(
        undefined,
        factory.createIdentifier("friends"),
        undefined,
        factory.createTypeLiteralNode([factory.createPropertySignature(
          undefined,
          factory.createIdentifier("sex"),
          factory.createToken(ts.SyntaxKind.QuestionToken),
          factory.createUnionTypeNode([
            factory.createLiteralTypeNode(factory.createNumericLiteral("1")),
            factory.createLiteralTypeNode(factory.createNumericLiteral("2"))
          ])
        )])
      ),
      factory.createPropertySignature(
        undefined,
        factory.createIdentifier("cats"),
        undefined,
        factory.createArrayTypeNode(factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword))
      )
    ]
 )

At first glance, it is very complicated to generate this simple type of code, but if you look closely, if these methods are encapsulated, the code will be much simpler, and there are already some mature third-party libraries, such as ts-morph and so on.

Ts Compiler Api only has English documents, and it is complicated to use, and we are not sure which function needs to be called to generate different types of code, but we can go to TS AST View to query, it can generate the corresponding abstract syntax according to the TS code you input Tree and Compiler code, the above code is provided by TS AST View.

factory.createInterfaceDeclaration method will generate an interface node. After generation, we also need to call a method to print the generated interface and output it as a string file. The reference code is as follows:

// ast转代码
// 需要将上文factory.createInterfaceDeclaration生成的节点传入
export const genCode = (node: ts.Node, fileName: string) => {
    const printer = ts.createPrinter();
    const resultFile = ts.createSourceFile(fileName, '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
    const result = printer.printNode(
        ts.EmitHint.Unspecified,
        node,
        resultFile
    );
    return result;
};

Step 3: Beautify the output code

We should be familiar with this step of beautifying the code. I believe that our compiler is equipped with Prettier , a must-have tool for every front-end project. It can not only directly format the files we are writing, but also can also format our manual The incoming string code , not much to say, the code above:

import * as prettier from 'prettier';

// 默认的prettier配置
const defaultPrettierOptions = {
    singleQuote: true,
    trailingComma: 'all',
    printWidth: 120,
    tabWidth: 2,
    proseWrap: 'always',
    endOfLine: 'lf',
    bracketSpacing: false,
    arrowFunctionParentheses: 'avoid',
    overrides: [
        {
            files: '.prettierrc',
            options: {
                parser: 'json',
            },
        },
        {
            files: 'document.ejs',
            options: {
                parser: 'html',
            },
        },
    ],
};

// 格式化美化文件
type prettierFileType = (content:string) => [string, boolean];
export const prettierFile: prettierFileType = (content:string) => {
    let result = content;
    let hasError = false;
    try {
        result = prettier.format(content, {
            parser: 'typescript',
            ...defaultPrettierOptions
        });
    }
    catch (error) {
        hasError = true;
    }
    return [result, hasError];
};

Step 4: Write the generated code to our file

This step is relatively simple. It is only necessary to generate the file with the fs Api provided by node. The code is as follows:

// 创建目录
export const mkdir = (dir:string) => {
    if (!fs.existsSync(dir)) {
        mkdir(path.dirname(dir));
        fs.mkdirSync(dir);
    }
};
// 写文件
export const writeFile = (folderPath:string, fileName:string, content:string) => {
    const filePath = path.join(folderPath, fileName);
    mkdir(path.dirname(filePath));
    const [prettierContent, hasError] = prettierFile(content);
    fs.writeFileSync(filePath, prettierContent, {
        encoding: 'utf8',
    });
    return hasError;
};

Front-end and back-end collaboration

The above process is still missing an important step: Who provides the data source JSON Schema?

This requires front-end and back-end collaboration. At present, the back-end has mature tools for generating JSON Schema, such as Swagger , YAPI and so on.

Back-end projects that access Swagger can provide the front-end with the swagger.json file. The content of the file includes the detailed data of all interfaces, including JSON Schema data.

YAPI is different from Swagger. It is a centralized management platform for APIs. For the APIs managed on it, we can obtain the detailed data of all APIs through the interfaces provided by it, which are similar to the content provided by swagger.json, and the YAPI platform supports import or Generate swagger.json.

If there is an interface management platform and relevant specifications are formulated, the efficiency of the front and back ends will be greatly improved, and the communication cost will be reduced, and the front end can also do some engineering efficiency-related work based on the management .

Difficulties to overcome

The above steps are just a brief introduction to an idea of generating ts type codes. There are still some difficulties to be solved under this idea, such as:

  • In actual development, we need comments, but the TS Compiler API cannot generate comments: This problem can be solved by manually inserting comments in the corresponding places after the code string is generated .
  • The type of actual business can be very complex and deeply nested: This problem we can solve by recursive function.
  • What should I do if the API of the generated type code is changed, or the newly added API should be placed in the same file as the original one, how to deal with this situation? TS ComPiler API can read source files, even existing files can be read, we can read source files and then use Compiler API to modify its abstract syntax tree to achieve the function of modifying or appending types .
  • Front-end and back-end coordination problems: This needs to be solved by a leader.

Summarize

After the four steps mentioned above, we understand the basic process of generating code, and the implementation scheme of each step is not fixed, you can choose by yourself:

  • On the issue of data source selection, in addition to JSON Schema, we can also choose the original json data as the data source, but the generated type is not so accurate. Here is a very useful website: JSON2TS .
  • We can also use some commonly used template engines to generate code generation tools, such as Nunjucks , EJS , etc. They can not only generate HTML, but also generate files and can return generated strings.
  • It is recommended to use prettier for this step of code beautification.
  • For the front end, currently the best way to output files is Node.
This article only provides an idea of engineering to generate simple and reproducible codes such as TS types and Mock data. After implementation, it can reduce part of - work content and let us focus more on business logic development.

references


TNTWEB
3.8k 声望8.5k 粉丝

腾讯新闻前端团队