“This is the second day of my participation in the Gwen Challenge in November. See details: The Last Gwen Challenge in 2021”

Hello everyone, I am quick-frozen fish 🐟, a front end of water system 💦, like colorful whistle 💐, continuous sand sculpture 🌲, I am a good brother of the next door Cold grass Whale, I just started to write an article. If you like my article, you can follow ➕ to like it, inject energy into me, and grow with me

Read this article 🦀

1. How to automatically generate typescript-based API modules

2. How to release the hands of business students and make them focus more on business realization

3. What will you learn about Swagger, OpenAPI, StopLight

4. You will learn how to complete a task quickly and successfully with a completely new requirement

5. How to write CLI tools to automate operations

Preface 🌵

Are you writing a front-end API request module while reading the documentation and writing the front-end request interface? Write a variety of TS generics, type definitions, resulting in inefficient development, encounter backend interface changes, and have to manually modify the request interface 🕶

How do you solve this kind of engineering problem? We can use the standard API documentation that defines interfaces to automatically generate the typescript-based API call modules on the front end, so we can get started without even reading the API documentation

Pre-knowledge 🐳

  • OpenAPI

    Human language is a specification that defines an API

    The OpenAPI specification is the standard format for defining the structure and syntax of REST apis. OpenAPI documentation is machine-readable and human-readable, making it easy for anyone to determine how each API works. Engineers building the API can use the API to plan and design the server, generate code, and implement contract tests.

  • Swagger

    A tool that generates interface documentation according to the OpenAPI specification

    Swagger is the name associated with some of the best known and most widely used tools that implement the OpenAPI specification. The Swagger toolset includes a combination of open source, free, and commercial tools that can be used at different stages of the API life cycle

  • StopLight

    StopLight is a tool that generates interface documents according to the OpenAPI specification, for a fee, and is simply stronger and more useful

    StopLight is first and foremost an API design management tool that can design, develop, test, and document HTTP apis using industry standards such as OpenAPI, and document documents in written form using Markdown.

  • API specification file

Clear requirements 🐿

  • Write a CLI tool that allows the front end to do an API request module out of the box with only one line of command
  • Pull StopLight’s specification file in YML or JSON format
  • Generate API request module in node_modules folder according to the sample article, and integrate the generated files and export them uniformly
  • You can choose to generate different versions of the API request module based on the configuration file
  • Compatibility with Swagger2.0 and OpenAPI3.0 specifications is required
  • Write the project README document

Implementing requirements 🐰

Conduct research on core functions

The core function is how do you generate typescript-based API modules?

I could write the template engine automatically to generate the code, but it was too much work and I wasn’t good enough, so I had to rely on third-party libraries.

Through extensive consulting of Github and materials, the following schemes are summarized

1. Use the official Swagger-CodeGen

Github.com/swagger-api…

Either the generation tool is a JAR package, or the library file written in JS is not maintained for a long time, so it is abandoned

2.free-swagger

Github.com/yeyan1996/f…

The generated code type is lost to any, abandoned

3.ts-gear

Github.com/superwf/ts-…

It was written by Mo Dadao of JINGdong, but I found the support for OpenAPI3 is not good in my use, so give it up

4.swagger-typescript-api

Github.com/acacode/swa…

Written by foreign friends, this project has more than 600 start, the code is generated by using the template, but the documentation is very bad, but the implementation effect is good, and finally adopted this library, but stepped on a lot of pits

Write CLI entry file 🍓

Specify the entrance

package.json

{
  / /...
  "bin": {
    "heimdall": "bin/heimdall.js"
  }
 / /...
}

Copy the code

Pull the specification file 🍎

StopLight will host the API documentation on Github, so we’ll need to implement a pull specification file on the CLI

Some of the code is omitted below

/ /...

// Initialize the command line help information and get the command line arguments

const options = getCommandOptions()

// Generate API entry
if (options.generate) {
    const projectName = getProjectName()
    //1. Run the download file command
    await gitCloneProject(projectName)
    //2. Generate the API file
    //2.1 Delete previously downloaded API files
    await removeDir(path.resolve(cwd(), "node_modules/@imf/heimdall-ts/api"))

    await createApi()
    //3. Generate an entry file
    await generateMain()
    //4. Delete the downloaded yML folder
    await removeDir(path.resolve(cwd(), getProjectName()))

} else if (options.log) {
    const projectName = getProjectName()
    //1. Run the download file command
    await gitCloneProject(projectName, true)
    //2. Run the command to print logs
    await showLog()
    //3. Delete the downloaded folder
    await removeDir(path.resolve(cwd(), getProjectName()))
}

/ /...


/** * Clone project */
function gitCloneProject(projectName, isLog = false) {
    return new Promise<void> ((resolve, reject) = > {
        shell.exec(`git clone https://sudongyu:[email protected]/floozy/${projectName}.git`, {
            cwd: `${cwd()}`
        }, () = > {
            constversionCode = getPkgMaifest()? .heimdall? .versionCode// If you have versionCode, roll back the version
            if(! isLog && versionCode) { shell.exec(`git checkout ${versionCode}`, {
                    cwd: `${path.resolve(cwd(), getProjectName())}`
                }, () = > {
                    resolve()
                })
            } else {
                resolve()
            }
        })
    })
}


Copy the code

Generate the TS-based API call module 🍌

Use the swagger-typescript-API library to generate code and place it in the node_modules folder without affecting the user’s code directory structure

/** * create API file */
 function createApi() {
     return new Promise<void> (async (resolve, reject) => {
         //V3
         / *NOTE: all fields are optional expect one of `output`, `url`, `spec` */
         const openApi3Array = getOenAPI3YmlFileName(path.resolve(cwd(), `${getProjectName()}`))
         for (let item of openApi3Array) {
             await generateApi({
                 name: `${item.replace('.oas3.yml'.' ')}Api.ts`.url: null.output: path.resolve(process.cwd(), "node_modules/@imf/heimdall-ts/api"),
                 input: path.resolve(process.cwd(), `${getProjectName()}`.`${item}`),
                 httpClientType: "axios".// or "fetch",
                 unwrapResponseData: true.generateUnionEnums: true.enumNamesAsValues: true.moduleNameFirstTag: false.moduleNameIndex: -1})}//V2
         / *NOTE: all fields are optional expect one of `output`, `url`, `spec` */
         const openApi2Array = getOenAPI2YmlFileName(path.resolve(cwd(), `${getProjectName()}`))
         for (let item of openApi2Array) {
             await generateApi({
                 name: `${item.replace('.oas2.yml'.' ')}Api.ts`.url: null.output: path.resolve(process.cwd(), "node_modules/@imf/heimdall-ts/api"),
                 input: path.resolve(process.cwd(), `${getProjectName()}`.`${item}`),
                 httpClientType: "axios".// or "fetch",
                 unwrapResponseData: true.// Whether to wrap response
                 generateUnionEnums: true.enumNamesAsValues: true.moduleNameFirstTag: false.moduleNameIndex: -1 // Module name split})}if(! openApi3Array.length&&! openApi2Array.length){await removeDir(path.resolve(cwd(), getProjectName()))
             reject('no openApi3 or openApi2 resources to generate !!!!! ')
         }
            resolve()
     })

}
Copy the code

Consolidate and export 🥝

Since the swagger-typescript-API library generates individual files, we need to consolidate the generated files to generate library entry files using regular expressions and template strings

/** * generates the entry file index.ts */
function generateMain() {
    return new Promise<void> ((resolve, reject) = >{
        // Get the file name
        const fileNames = getFileName(path.resolve(cwd(), 'node_modules/@imf/heimdall-ts/api'))
        // Convert filename eg: main.ts -> MainGameApi
        const transformedFileNames = fileNames.map(item= > {
            return transformToCamel(item)
        })
        // Write the content to be written
        const content = `
        ${transformedFileNames.map((item, index) => {
                return `import \{Api as ${item}\} from \'\.\/${fileNames[index]}\'\n`
            }).join(' ')}
   
   export {
        ${transformedFileNames.map(item => {
            return `${item}\n`
        })}
    }
   `
        // Create index file
        generateFile(path.resolve(cwd(),'node_modules/@imf/heimdall-ts/api'.'index.ts'))
        // Write to the file
        writeFile(path.resolve(cwd(),'node_modules/@imf/heimdall-ts/api'.'index.ts'),content).then(() = >{
            resolve()
        })
    })
}

Copy the code

Supported version rollback 🍉

The basic functions have been implemented, but the boss raised a new requirement to support version rollback, so that callers can easily manage their OWN API modules

  • Implementation view API document version number, here my solution is to use git version management tool command can view the log to achieve

    /** * Prints version stoplight version information */
    function showLog(){
        return new Promise<void> ((resolve, reject) = >{
            shell.exec('git log --pretty=" %h %ci %s "', {
                    cwd: `${path.resolve(cwd(), getProjectName())}`
                },() = >{
                resolve()
                }
            )
        })
    }
    Copy the code
  • The version rollback is achieved by executing the Git checkout command to switch to the repository for the corresponding version, and then regenerating API modules based on this document

    function gitCloneProject(projectName, isLog = false) {
        return new Promise<void> ((resolve, reject) = > {
            shell.exec(`git clone https://sudongyu:[email protected]/floozy/${projectName}.git`, {
                cwd: `${cwd()}`
            }, () = > {
                constversionCode = getPkgMaifest()? .heimdall? .versionCode// If you have versionCode, roll back the version
                if(! isLog && versionCode) { shell.exec(`git checkout ${versionCode}`, {
                        cwd: `${path.resolve(cwd(), getProjectName())}`
                    }, () = > {
                        resolve()
                    })
                } else {
                    resolve()
                }
            })
        })
    Copy the code

Document writing 📃

After a good library code logic, the most important and document writing 😁, write good-looking easy to understand the document, the library even if done

Project effect display 🪖

  • GIF show

  • Automatically generated code

  • Import the API to use from the automatically generated API module library (smart hints)

  • Send the request using the API module

    Because of the generated TS, there is a good code prompt, you can directly click on the interface you want to call

All data has a corresponding TS type

Harvest 🍁

The biggest gain from writing this library is actually encountered a completely unknown requirement, how to solve the problem? What happens when you use a third-party library that doesn’t meet your needs? What I have learned is to start from the results first, clear my mind, and realize each step step by step. When encountering obstacles, I can solve the problem well by checking the issue on Github of the third-party library or reading the source code. Of course, I also learned how to gracefully call the back-end interface to help business students improve the development efficiency 😂 ~

Conclusion 🌞

So the release of my hands, calling the backend interface 💗 gracefully over this article, the purpose of the article is very simple, is the author’s daily work and output, output some feel useful things for people, food is not food is not important, but love 🔥, hope that through articles meet more like-minded friends, if you like, welcome to add my good friend, Sand sculpture together, progress together.

Making 🤖 : sudongyu

Personal blog 👨💻: Frozen fish blog

Vx 👦 : sudongyuer

Write in the last

Guys, if you like my words, give 🐟🐟 a thumbs up 👍 or follow ➕ to support me the most.

Add me on wechat: Sudongyuer, invite you into the group and learning the front together, become a better engineer ~ (group of qr code here – > front to go to bed early, qr code has expired, see the links to the boiling point in the comments, I will put the latest qr code in the comments section, of course, can also add me WeChat I pull you into the group, after all, I also am interesting front end, I also not bad 🌟 ~