首页 前端知识 Pont 自动生成TypeScript接口代码,自动生成类型与后端对齐

Pont 自动生成TypeScript接口代码,自动生成类型与后端对齐

2024-05-13 10:05:17 前端知识 前端哥 543 96 我要收藏

Pont

gitHub:https://github.com/alibaba/pont

为什么使用Pont等前后端接口同步工具?

日常开发中前后端同学协作的一个非常典型的场景:接口数据请求的调用、处理。以往都是以手动编写的方式来进行协作,但是这种方式所带来的关联性十分脆弱,服务端的代码或者文档出现变动,前端关联的接口代码就会面临出错、失效的问题。

另外在 TypeScript 火热的现在,为了能充分利用 TS 的类型能力来规范接口的字段类型,通常我们也会根据接口文档来编写接口的类型定义。实际开发中,每个接口都可能存在大量的字段,这个时候去照着接口文档搬运类型定义,不得不说是非常枯燥无聊的,更别说在搬运的过程中也有出错的可能。

对比主流方案

  • Pont: 阿里的接口 swagger 对接方案,支持 vscode 插件,十分强大

  • swagger-api: Swagger 生态的代码生成工具,不过需要 java 运行环境 (使用起来依旧是需要依赖其他的js库)

  • YAPI: 支持接口管理、MOCK等功能,代码生成需要 java 运行环境 (不支持一键生成Ts接口代码)

  • JsonToAny ( Gitee / GitHub )一款开源工具,把导入的JSON文件直接复制过去自定义类型(个人认为改造项目可以使用这个)

Pont概念:

pont 在法语中是“桥”的意思,寓意着前后端之间的桥梁。

Pont 把 swagger、rap、dip 等多种接口文档平台,转换成 Pont 元数据。Pont 利用接口元数据,可以高度定制化生成前端接口层代码,接口 mock 平台和接口测试平台。

其中 swagger 数据源,Pont 已经完美支持。并在一些大型项目中使用了近两年,各种高度定制化需求都可以满足。

工作流程:

img

快速开始

全局安装 pont-engine

# 选择一个你喜欢的包管理器

# NPM
$ npm i -g pont-engine

# Yarn
$ yarn global add pont-engine

# pnpm
$ pnpm add -g pont-engine

1. 安装

全局安装 pont-engine

# 选择一个你喜欢的包管理器

# NPM
$ npm i -g pont-engine

# Yarn
$ yarn global add pont-engine

# pnpm
$ pnpm add -g pont-engine

2. 初始化

使用 pont start 命令,快速创建初始模板
在这里插入图片描述

3. 安装 VSCode 插件

打开 VSCode 插件商店,输入 vscode-pont 搜索安装

在这里插入图片描述

功能演示

在这里插入图片描述

使用帮助
初始化引导
  • 当前工作空间下不存在pont-config.json文件
  • 当前工作空间下没有安装pont-enging

在这里插入图片描述

切换数据源(多数据源展示)

通过点击右侧切换数据源图标按钮,弹出数据菜单,搜索切换数据源
在这里插入图片描述

更新本地模块和基类

通过点击右侧更新模块或基类图标按钮,快速增量更新模块和基类
在这里插入图片描述

接口代码片段
  • 点击后打开接口搜索面板,快捷搜索需要调用的接口,选择接口后生成调用的代码片段。
  • 通过重写 CodeGenerator.codeSnippet,自定义需要生成代码片段。具体使用,请参考 pont-config.json 配置项
    在这里插入图片描述
拉取远程数据源

拉取同步最新的远程数据源数据,并和本地api-lock.json进行diff对比,计算出可以增量更新的模块和基类
在这里插入图片描述

更新全部数据源

使用远程数据源数据,更新本地 api-lock.json 文件,不生成接口代码

在这里插入图片描述

生成接口代码

通过读取解析 api-lock.json 文件,生成接口代码

在这里插入图片描述

4. 命令行方式

为了避免一部分用户和技术团队不使用 vscode-pont,pont 可以以命令行命令的方式来提供服务。

命令行提供的命令目前还比较基础,提供命令如下:

pont start

一键接入 pont,若本地存在 pont-config.json 配置文件,将覆盖重复的配置项。

pont check

校验本地的 pont-lock.json 文件是否缺失、损坏。建议用户在项目中,在 pre-commit 里加上 pont check 命令,以防止在团队协作过程中,pont-lock.json 被误删、解决该文件冲突时被损坏等情况。

pont ls

查看所有数据源

pont select [dsName]

切换当前数据源

pont diff

查看远程数据和本地数据在模块、基类上的差异,以作针对性、选择性同步。

pont updateBo [boName]

选择性更新本地的基类

pont updateMod [modName]

选择性更新本地的模块

5、pont-config.json 配置项

对于 pont-config.json 的配置,在 vscode-pont 插件中已经做了自动提示、自动补全、配置项描述提醒等功能。具体配置项介绍如下:

originUrl

值类型:字符串

描述: 接口平台提供数据源的 open api url(需要免登),目前只支持 Swagger。如 “https://petstore.swagger.io/v2/swagger.json”

outDir

值类型:字符串

描述: 生成代码的存放路径,使用相对路径即可。如:“./src/api”

templatePath

值类型:字符串

描述:指定自定义代码生成器的路径(使用相对路径指定)。一旦指定,pont 将即刻生成一份默认的自定义代码生成器。自定义代码生成器是一份 ts 文件,通过覆盖默认的代码生成器,来自定义生成代码。默认的代码生成器包含两个类,一个负责管理目录结构,一个负责管理目录结构每个文件如何生成代码。自定义代码生成器通过继承这两个类(类型完美,可以查看提示和含义),覆盖对应的代码来达到自定义的目的。具体使用方法请参看自定义代码生成器文档。

示例:可以参看示例 demo 中的 template。

prettierConfig

值类型:object

描述:生成的代码会用 prettier 来美化。此处配置 prettier 的配置项即可,具体可以参考 prettier 文档。

usingMultipleOrigins

值类型:boolean

描述:pont 支持一个项目中配置多个 Swagger 来源。此处配置是否启用多数据源

origins

值类型:array

描述:配置每个数据来源

配置项:

{
  "originType": "SwaggerV2 | SwaggerV3", // 注:暂不支持 SwaggerV1
  "originUrl": string,
  "name": string,
  "usingOperationId": boolean,
  "transformPath"?: string,
  "fetchMethodPath"?: string
}

示例:

"origins": [{
  "name": "pet",
  "originUrl": "",
}, {
  "name": "fruit",
  "originUrl": ""
}]
transformPath

值类型:string

描述:可选项。指定数据源预处理路径(使用相对路径指定)。一旦指定,Pont 将生成一份默认的数据预处理器。Pont 将 Swagger.json 数据转换为内部标准数据源之后会尝试调用由transformPath指定的转换程序,这样用户就有机会对数据进行一些处理。

数据预处理器示例:

// transfrom.ts 根据 Mod.name进行过滤
import { StandardDataSource } from 'pont-engine';

export default function transform(data: StandardDataSource) {
  if (data.name === 'fooapi') {
    const filterMods = ['modName1', 'modName2', 'modName3'];
    let { mods, baseClasses } = filterModsAndBaseClass(filterMods, data);
    data.mods = mods;
    data.baseClasses = baseClasses;
  }
  return data;
}

/**
 * 过滤mod及所依赖的baseClass
 * @param filterMods Mod.name数组
 * @param data StandardDataSource
 */
function filterModsAndBaseClass(filterMods: string[], data: StandardDataSource) {
  let mods = data.mods.filter(mod => {
    return filterMods.includes(mod.name);
  });
  // 获取所有typeName
  let typeNames = JSON.stringify(mods).match(/"typeName":".+?"/g);

  typeNames = Array.from(new Set(typeNames)) // 去重
    // 取typeName的值
    .map(item => item.split(':')[1].replace(/\"/g, ''));

  // 过滤baseClasses
  let baseClasses = data.baseClasses.filter(cls => typeNames.includes(cls.name));

  return { mods, baseClasses };
}
fetchMethodPath

值类型:string

描述: 可选项, 相对项目根目录路径。用于 Swagger 数据源需要登录才能请求成功的场景,可指定获取 Swagger 源数据的方法。默认为 node-fetch 的 fetch 方法,可通过自定义 fetch 方法获取带鉴权的接口的文档

示例:

注意:此文件目前只能使用 .ts 后缀

// ./myFetchMethod.ts
import axios from 'axios';

export default async function(url: string): Promise<string> {
  const { data } = await axios.post('/api/login', {
    username: 'my_name',
    password: '123456'
  });

  return axios
    .get(url, {
      headers: {
        Authorization: data.token
      }
    })
    .then(res => JSON.stringify(res.data));
}

配置项示例:

注意:路径字段不需要加 .ts 后缀

{
  // ...
  "fetchMethodPath": "./myFetchMethod", 
}
mocks

值类型:object

子字段:

  • 字段名:“enable” 类型:boolean 默认值: true 含义:是否生效
  • 字段名:“basePath” 类型:string 默认值:“” 含义:接口的 basePath
  • 字段名: “port” 类型:string 默认值:8080 含义:mocks 服务的端口号
  • 字段名 “wrapper” 类型:string 默认值:“{“code”: 0, “data”: {response}, “message”: “”}” 含义:接口返回结构,pont 可以计算返回数据类型(比如此处会替换到 {response}),此处可以指定接口返回结构。

如:

{
  "mocks": {
    "enable": true,
    "basePath": "",
    "port": 8080,
    "wrapper": "{\"code\": 0, \"data\": {response}, \"message\": \"\"}"
  }
}
templateType

值类型:字符串

可选值:‘fetch’ | ‘hooks’

描述:可选项。用于生成 pont 内置模板。配置该项时,一旦检测到本地模板文件不存在将自动使用配置的模板类型生成模板文件。

内置模板功能强大,使用方法请参看内置模板使用方法及贡献流程。

6.pontTemplate.ts(自动代码模板生成)

示例:

import { Interface, BaseClass, Property, CodeGenerator } from "pont-engine";

export default class MyGenerator extends CodeGenerator {
  getInterfaceContentInDeclaration(inter: Interface) {
    const method = inter.method.toUpperCase();

    const paramsCode = inter
      .getParamsCode("Params")
      .replace("lock: number", "lock?: number")
      .replace(": file", ": FormData");

    return `
      export ${paramsCode}

      export type HooksParams = () => Params | Params;

      export type Response = ${inter.responseType}

      export function mutate(params?: HooksParams, newValue?: any, shouldRevalidate = true);
  
      export function trigger(params?: HooksParams, shouldRevalidate = true);

      ${
        method === "GET"
          ? `
        export function useRequest(params?: HooksParams, options?: ConfigInterface): { isLoading: boolean; data: Response, error: Error };`
          : `
        export function useRequest(params?: HooksParams, options?: ConfigInterface): { isLoading: boolean; data: Response, error: Error };
        `
      }

      export const method: string;

      export function request(params?: Params, option = {}): Promise<Response>;
    `;
  }

  getBaseClassInDeclaration(base: BaseClass) {
    const originProps = base.properties;

    base.properties = base.properties.map(prop => {
      return new Property({
        ...prop,
        required: false
      });
    });

    const result = super.getBaseClassInDeclaration(base);
    base.properties = originProps;

    return result;
  }

  getCommonDeclaration() {
    return `
    declare type ConfigInterface = import("swr").ConfigInterface;
    `;
  }

  getInterfaceContent(inter: Interface) {
    const method = inter.method.toUpperCase();

    return `
    /**
     * @desc ${inter.description}
     */

    import * as defs from '../../baseClass';
    import * as Hooks from '../../hooks';

    import * as SWR from 'swr';

    import { PontCore } from '../../pontCore'

    export ${inter.getParamsCode("Params", this.surrounding)}

    export const method = "${method}";

    export function mutate(params = {}, newValue = undefined, shouldRevalidate = true) {
      return SWR.mutate(Hooks.getUrlKey("${
        inter.path
      }", params, "${method}"), newValue, shouldRevalidate);
    }

    export function trigger(params = {}, shouldRevalidate = true) {
      return SWR.trigger(Hooks.getUrlKey("${
        inter.path
      }", params, "${method}"), shouldRevalidate);
    }

    ${
      method === "GET"
        ? `
      export function useRequest(params = {}, swrOptions = {}) {
        return Hooks.useRequest("${inter.path}", params, swrOptions);
      };`
        : `
      export function useDeprecatedRequest(params = {}, swrOptions = {}) {
        return Hooks.useRequest("${inter.path}", params, swrOptions, { method: ${method} });
      }
      `
    }

    export function request(params = {}, option  = {}) {
      return PontCore.fetch(PontCore.getUrl("${
        inter.path
      }", params, "${method}"), {
        ...option,
        method: "${method}",
      });
    }`;
  }
}

inter变量定义:

在这里插入图片描述

7.定制化 Pont

可参考https://github.com/alibaba/pont/blob/master/docs/customizedPont.md

8.总结

​ Pont是可以可以作为后端和前端一个良好定义接口规范的工具。相对于市面上其他的工具,他优势在于使用方便,可以配置模板自动化生成接口代码。目前我自己实践的是,在项目搭建初期,假如后端可以通过Swagger配合,直接就可以将整个生成的代码目录通过config.json配置到src的目录下,后端假如更新了接口的内容,一键生成同步即可。

​ 优点有很多,也讲讲缺点:

​ 1、自定义pontTemplate.ts格式是通过返回字符串的格式生成的,改动起来不太友好,需要用字符串匹配替换的形式生成。

​ 2、对二次开发的项目或者旧项目不友好,代码风格差异太大,不好修改。

​ 3、和后端对接紧密相连,假如后端Swagger格式不对,使用起来并不好,可能手动配置的东西更多。

​ 4、对于JS项目,没必要使用,因为使用该工具最大的作用是定义类型,假如是纯JS项目,引入更加复杂,也起不到什么校验类型的作用。反而增加了开发的复杂度。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/8484.html
标签
评论
会员中心 联系我 留言建议 回顶部
复制成功!