likes
comments
collection
share

基于 Pipeline 的 TS/JS API 代码自动生成(apipgen)

作者站长头像
站长
· 阅读数 9

大家写项目的时候联调对接后端,写相应的 API 函数的时候会不会觉得很麻烦,我是这么觉得的,如果使用的 Typescript ,确保和后端的类型保持一致,还要手写类型,接口请求和返回参数定义类型成了繁琐的一件事情。

如果后端有提供接口描述的数据源(swagger、yapi 或其他源)等等,我们就可以利用 aippgen 自动的生成接口与类型。

/**
 * @summary uploads an image
 * @method post
 */
export function postPetPetIdUploadImage(data: FormData, paths: OpenAPITypes.PostPetPetIdUploadImagePath, config?: AxiosRequestConfig) {
  const url = `/pet/${paths?.petId}/uploadImage`
  http.request<Response<OpenAPITypes.ApiResponse>>({ url, data, ...config })
}
/**
 * @summary Add a new pet to the store
 * @method post
 */
export function postPet(data: OpenAPITypes.Pet, config?: AxiosRequestConfig) {
  const url = '/pet'
  http.request<Response<void>>({ url, data, ...config })
}
export type Response<T> = T;

export interface ApiResponse {
  code?: number;
  type?: string;
  message?: string;
}
export interface Category {
  id?: number;
  name?: string;
}
export interface Pet {
  id?: number;
  category?: Category;
  name: string;
  photoUrls: string[];
  tags?: Tag[];
  /** @description pet status in the store */
  status?: string;
}

aippgen(API Pipeline Generator)是 API 生成工具,其中 Pipeline 是管道的意思,apipgen 通过不同的管道支持不同的源和输出格式,目前 apipgen 官方默认支持 swag-ts-axiosswag-js-axios 两种管道,支持自定义管道,不同管道间可以复用和重组。

要使用它之前,我们先在本地项目文件夹中安装:

pnpm add apipgen -D
# Or Yarn
yarn add apipgen --dev

📖 使用

使用 apipgen 要先编写配置文件,由配置文件决定输入/输出的内容,支持多个格式的配置文件 .ts|.js|.cjs|.json

import { defineConfig } from 'apipgen'

export default defineConfig({
  /**
   * 使用的编译处理管道,支持 npm 包(添加前缀apipgen-)或本地路径
   *
   * 默认支持 swag-ts-axios|swag-js-axios
   * @default 'swag-ts-axios'
   */
  pipeline: 'swag-ts-axios',
  // 输入源(swagger url 或 swagger json)以及输出源
  // 如果有多个源,可以使用 server 字段
  input: 'https://petstore.swagger.io/v2/swagger.json',
  output: {
    main: 'src/api/index.ts',
    type: 'src/api/index.type.ts',
  },

  // API baseUrl,此配置将传递给 axios
  baseURL: 'import.meta.env.VITE_APP_BASE_API',
  // 自定义 responseType,默认 T
  responseType: 'T extends { data?: infer V } ? V : void',
})

配置后,使用 apipgen 脚本生成代码:

# run apipgen
pnpm apipgen

提供源(input)

input 目前支持两个输入源 url|json

export default defineConfig({
  // 直接输入服务 url
  input: 'http://...api-docs',
  // 或者选择其他源
  input: { /* url|json */ }
})

多服务(Server)

如果有多个服务,可以使用 server 设置多个服务。顶层的其他配置会被用作附加配置

export default defineConfig({
  baseUrl: 'https://...',
  // 所有 server 都继承上层配置
  server: [
    { uri: '...', import: '...', output: {/* ... */} },
    { uri: '...', import: '...', output: {/* ... */} },
    { uri: '...', import: '...', output: {/* ... */} },
  ]
})

导入(Import)

当然我们项目中可能会想自定义 axios 拦截器,或使用 axios.create 的新实例,这个时候可以使用 import 字段自定义导入请求的路径。

不介意过渡封装 axios,这会破坏 axios 原本的功能

我们先在相同目录下定义一个 http.instance.ts 用于配置 axios 请求

// 使用 axios.create 创建新的实例
const request = axios.create({
  baseURL: 'https://...'
})
// 使用拦截器
request.interceptors.request.use(/* ... */)
request.interceptors.response.use(/* ... */)

接着我们配置 apipgen.config 文件,添加 import 字段。

export default defineConfig({
  pipeline: 'swag-ts-axios',
  input: {
    uri: 'https://petstore.swagger.io/v2/swagger.json'
  },
  output: {
    main: 'src/apis/index.ts'
  },
  import: {
    // 将实际的 http 请求导入路径更改为 './http.instance'
    http: './http.instance'
  }
})

接着运行 apipgen,我们就能得到具有拦截器的 axios 实例:

import http from "./http.instance";
import { AxiosRequestConfig } from "axios";
import * as OpenAPITypes from "./index.type";
import { Response } from "./index.type";

/**
 * @summary uploads an image
 * @method post
 */
export function postPetPetIdUploadImage(data: FormData, paths: OpenAPITypes.PostPetPetIdUploadImagePath, config?: AxiosRequestConfig) {
  const url = `/pet/${paths?.petId}/uploadImage`;
  http.request<Response<OpenAPITypes.ApiResponse>>({ url, data, ...config });
}

Swagger 转 Javascript Axios

如果你的项目是 Javascript, 使用 apipgen 的 swag-js-axios 管道还可以生成具有 TS 类型提示的 JS 文件

export default defineConfig({
  pipeline: 'swag-js-axios',
  input: {
    uri: 'https://petstore.swagger.io/v2/swagger.json',
  },
})
export default defineConfig({
  pipeline: 'swag-js-axios',
  input: {
    uri: 'https://petstore.swagger.io/v2/swagger.json',
  },
})
/**
 * @summary uploads an image
 * @method post
 * @param {FormData} data
 * @param {import("./index.type").PostPetPetIdUploadImagePath} paths
 * @param {import("axios").AxiosRequestConfig=} config
 * @return {import("./index.type").Response<import("./index.type").ApiResponse>}
 */
export function postPetPetIdUploadImage(data, paths, config) {
  const url = `/pet/${paths?.petId}/uploadImage`
  http.request({ url, data, ...config })
}

管道(Pipeline)

apipgen 由特殊的处理管道运作,从输入 config 到最终 dest 输出文件作为一个完整管道。

apipgen 在定义配置时传入 pipeline 参数支持 npm 包(前缀 apipgen-) 和本地路径。

我们可以根据我们不同的需求(源、调用的 http 请求库)自定义不同的处理管道:

export default defineConfig({
  pipeline: './custom-pipe',
})

而管道中由 apipgen 提供的 pipeline 函数定义。

// custom-pipe.ts

// 使用 apipgen 提供的 pipeline 创建 API 管道生成器
import { pipeline } from 'apipgen'

// 每个管道都暴露了对应方法,可以进行复用并重组
import { dest, generate, original } from 'apipgen-swag-ts-axios'

function myCustomPipe(config) {
  const process = pipeline(
    // 读取配置,转换为内部配置,并提供默认值
    config => readConfig(config),
    // 获取数据源
    configRead => original(configRead),
    // 解析数据源为数据图表(graphs)
    configRead => parser(configRead),
    // 编译数据,转换为抽象语法树(AST)
    configRead => compiler(configRead),
    // 生成代码(code)
    configRead => generate(configRead),
    // 利用 outputs 输出文件
    configRead => dest(configRead),
  )
  return process(config)
}

function readConfig(config) {
  // ...
}

function parser(configRead) {
  // ...
}

function compiler(configRead) {
  // ...
}

如果大家感兴趣,会考虑出一期怎么自定义 aippgen 管道的具体文章。