「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」
大家好,我是速冻鱼🐟,一条水系前端💦,喜欢花里胡哨💐,持续沙雕🌲,是隔壁寒草🌿的好兄弟,刚开始写文章。 如果喜欢我的文章,可以关注➕点赞,为我注入能量,与我一同成长吧~
阅读本文🦀
1.如何自动生成基于TypeScript的API模块
2.如何释放业务同学的双手,让其更专注于业务实现
3.您将了解到什么是Swagger、OpenAPI、StopLight
4.您将了解到遇到一个完全没接触过的需求,如何快速圆满完成任务
5.如何编写CLI工具,完成自动化操作
前言🌵
您是否在编写前端API请求模块的时候,一边看文档一边编写前端请求接口呢?编写各种TS泛型,类型定义,导致开发效率低下,遇到后端接口改变时,又不得不手动去修改请求接口🕶
如何解决这类工程化的问题呢?我们可以利用定义接口的标准API文档,自动生成前端基于TypeScript的API调用模块,这样我们甚至可以不用去阅读API接口文档就能直接上手开发业务,下面一起一探究竟吧~
前置知识🐳
-
人话就是一个定义API的规范
OpenAPI规范是定义REST api结构和语法的标准格式。OpenAPI文档是机器和人类可读的,这使得任何人都可以轻松地确定每个API的工作方式。构建api的工程师可以使用api来规划和设计服务器、生成代码和实现合同测试。
-
人话按照OpenAPI规范生成接口文档的工具
Swagger是与实现OpenAPI规范的一些最知名、最广泛使用的工具相关的名称。Swagger工具集包括开源、免费和商业工具的组合,可以在API生命周期的不同阶段使用
-
人话就是StopLight是和Swagger一样的按照OpenAPI规范生成接口文档的工具,它需要付费,总之就是更强更好用~~
stopLight首先是一个API设计管理工具,可以使用OpenAPI等行业标准来设计、开发、测试和记录HTTP API,并使用Markdown来记录书面形式的文档。

明确需求🐿
- 编写一个CLI工具让前端只需要执行一行命令就能做到开箱即用的API请求模块
- 拉取StopLight的规范文件YML或者JSON格式
- 根据文范文件在node_modules文件夹生成API请求模块,并将生成的文件进行整合,统一导出
- 可以根据配置文件选择生成不同版本的API请求模块
- 需要兼容Swagger2.0和OpenAPI3.0规范
- 编写项目README文档
实现需求🐰
对核心功能进行调研
核心功能就在于如何生成基于TypeScript的API模块?
可以自动动手写模版引擎去生成代码,但是这个工作量太大了,而且我的水平不够,所以只有依赖于第三方库。
通过大量查阅Github和资料,总结出以下的方案
1.使用官方的Swagger-codegen
要么生成工具是一个JAR包,要么就是用JS写的库文件很久没人维护了,所以放弃
2.free-swagger
国人写的,但是start数太少,生成的代码类型丢失为any,放弃
3.ts-gear
京东莫大佬写的,但是在我的使用中发现对OpenAPI3支持的不好,放弃
4.swagger-typescript-api
外国友人写的,这个项目有600多start,代码都是使用模版生成的,但是文档写的很烂,但是实现效果还不错,最后还是采用的这个库,不过踩了很多坑
编写CLI入口文件 🍓
指定入口
package.json
{
//...
"bin": {
"heimdall": "bin/heimdall.js"
}
//...
}
拉取规范文件 🍎
StopLight会将API接口文档托管在Github,因此我们需要在CLI实现拉取规范文件
以下省略了部分代码
//...
//初始化命令行帮助信息,并获取命令行参数
const options = getCommandOptions()
//生成API入口
if (options.generate) {
const projectName = getProjectName()
//1.执行下载文件命令
await gitCloneProject(projectName)
//2.生成api文件
//2.1删除之前下载过的API文件
await removeDir(path.resolve(cwd(), "node_modules/@imf/heimdall-ts/api"))
await createApi()
//3.生成入口文件
await generateMain()
//4.删除下载的yml所在文件夹
await removeDir(path.resolve(cwd(), getProjectName()))
} else if (options.log) {
const projectName = getProjectName()
//1.执行下载文件命令
await gitCloneProject(projectName, true)
//2.执行打印日志的命令
await showLog()
//3.删除下载的文件夹
await removeDir(path.resolve(cwd(), getProjectName()))
}
//...
/**
* 克隆项目
*/
function gitCloneProject(projectName, isLog = false) {
return new Promise<void>((resolve, reject) => {
shell.exec(`git clone https://sudongyu:YUANCxzeJwiVhzQio18v@git.stoplight.io/floozy/${projectName}.git`, {
cwd: `${cwd()}`
}, () => {
const versionCode = getPkgMaifest()?.heimdall?.versionCode
//如果有versionCode,需要回退版本
if (!isLog && versionCode) {
shell.exec(`git checkout ${versionCode}`, {
cwd: `${path.resolve(cwd(), getProjectName())}`
}, () => {
resolve()
})
} else {
resolve()
}
})
})
}
生成基于TS的API调用模块 🍌
使用 swagger-typescript-api这个库来生成代码,并将其放置在node_modules文件夹下,这样不会影响使用方的代码目录结构
/**
* 创建api文件
*/
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,//是否包裹response
generateUnionEnums: true,
enumNamesAsValues: true,
moduleNameFirstTag: false,
moduleNameIndex:-1 //模块名分割
})
}
if(!openApi3Array.length&&!openApi2Array.length){
await removeDir(path.resolve(cwd(), getProjectName()))
reject('no openApi3 or openApi2 resources to generate !!!!!')
}
resolve()
})
}
整合并导出 🥝
由于 swagger-typescript-api这个库生成的文件是单个单个的,我们需要将生成的文件整合起来,利用正则表达式和模版字符串生成库入口文件
/**
* 生成入口文件index.ts
*/
function generateMain() {
return new Promise<void>((resolve, reject)=>{
//获取文件名
const fileNames = getFileName(path.resolve(cwd(), 'node_modules/@imf/heimdall-ts/api'))
//转换文件名 eg: main.ts -> MainGameApi
const transformedFileNames = fileNames.map(item => {
return transformToCamel(item)
})
//编写要写如的内容content
const content = `
${transformedFileNames.map((item, index) => {
return `import \{Api as ${item}\} from \'\.\/${fileNames[index]}\'\n`
}).join('')
}
export {
${transformedFileNames.map(item => {
return `${item}\n`
})}
}
`
//创建index文件
generateFile(path.resolve(cwd(),'node_modules/@imf/heimdall-ts/api','index.ts'))
//写入文件
writeFile(path.resolve(cwd(),'node_modules/@imf/heimdall-ts/api','index.ts'),content).then(()=>{
resolve()
})
})
}
支持版本回退 🍉
本来基本功能都实现了,但是老大又提了新需求,需要支持版本回退,这样调用方就可以方便得管理自己的API模块了
-
实现查看API文档版本号,这里我的解决方案就是利用git版本管理工具的命令可以查看日志来实现
/** * 打印版本stoplight版本信息 */ function showLog(){ return new Promise<void>((resolve, reject)=>{ shell.exec('git log --pretty=" %h %ci %s "', { cwd: `${path.resolve(cwd(), getProjectName())}` },()=>{ resolve() } ) }) } -
执行git checkout命令切换到对应版本的仓库,然后根据这个文档再生成API模块,这样就实现了版本回退
function gitCloneProject(projectName, isLog = false) { return new Promise<void>((resolve, reject) => { shell.exec(`git clone https://sudongyu:YUANCxzeJwiVhzQio18v@git.stoplight.io/floozy/${projectName}.git`, { cwd: `${cwd()}` }, () => { const versionCode = getPkgMaifest()?.heimdall?.versionCode //如果有versionCode,需要回退版本 if (!isLog && versionCode) { shell.exec(`git checkout ${versionCode}`, { cwd: `${path.resolve(cwd(), getProjectName())}` }, () => { resolve() }) } else { resolve() } }) })
编写文档 📃
一个库代码逻辑写好后,最重要的还有文档编写啦😁,写上好看易懂的文档,这个库就算大功告成了
项目效果展示 🪖
-
gif 展示
-
自动生成的代码
-
从自动生成的API模块库中导入要使用的API(智能的提示)
-
使用API模块发送请求
由于生成的TS,所以有很好的代码提示,能直接点出来想调用的接口
所有数据都拥有对应的TS类型
收获🍁
编写这个库最大的收获其实是遇到一个完全不懂的需求,如果去解决问题呢?当使用的第三方库不能满足你的需求怎么办呢。我学到的就是先从结果出发,理清思路,一步步去实现每个步骤,当遇到阻碍的时候,多去看看第三方库的Github上的issue或者阅读源码,或许就能很好的解决问题。当然还学习到了如何优雅地调用后端接口,帮助业务同学提高开发效率😂~
结束语🌞
那么我的释放双手,优雅地调用后端接口💗这篇文章就结束了,文章的目的其实很简单,就是对日常工作的总结和输出,输出一些觉得对大家有用的东西,菜不菜不重要,但是热爱🔥,希望通过文章认识更多志同道合的朋友,如果你也喜欢折腾,欢迎加我好友,一起沙雕,一起进步。
github🤖:sudongyu
个人博客👨💻:速冻鱼blog
vx👦:sudongyuer
写在最后
伙伴们,如果喜欢我的口水话给🐟🐟点一个赞👍或者关注➕都是对我最大的支持。
加我微信:sudongyuer,邀你进群,一起学习前端,成为更优秀的工程师~(群二维码在这里->前端要早睡, 二维码过期了的话看链接沸点中的评论,我会把最新的二维码放在评论区,当然也可以加我微信我拉你进群,毕竟我也是有趣的前端,认识我也不赖🌟~