前端工程化:使用 shelljs 生成 yapi 接口文件
之前的文章介绍了使用 yapi-to-typescript (下文简称 ytt)生成接口类型定义文件,方便我们直接使用接口的请求和响应类型,能减少很多写接口类型的时间。
使用 yapi-to-typescript 生成接口响应数据的 TS 类型声明
既然能生成接口类型定义文件,那也就可以生成接口请求代码文件咯,可是网上找了下没找到有相关的工具库,想必是每个公司的项目接口请求代码风格差异较大,要开发一个可以灵活配置生成对应代码的库也略显麻烦,比如这篇文章就是教大家如何写出生成请求代码的文件,而我也没有写一个这样的库。
前置条件
首先需要了解两个工具库,一个是 ytt,一个是 shelljs。因为 ytt 已经帮我们请求 yapi 拿到了接口数据,我们就没有必要再去获取一次了。
shelljs 是一个具有 shell 功能的工具库,很强大,这里主要用来操作文件系统。
开搞
第一步:存储接口数据
ytt 中提供了钩子函数,在类型文件生成完成后会调用,但是这个函数中我们拿不到 yapi 上的接口,所以需要在 ytt 的 outputFilePath 中手动保存下来。
首先,我们定义两个变量和一个方法:
// 所有接口信息
let interfaceInfos: MyInterface[] = [];
// 所有生成的文件名。方便每次生成的时候先删除
let filesName = new Set<string>();
// 存储所有接口信息。在类型定义文件生成成功后的 success 钩子函数中遍历生成接口调用文件。
const saveInterface = (interfaceInfo: Interface, name: string) => {
filesName.add(name);
interfaceInfo.fileName = name; // 接口数据中增加 fileName 字段,告知这个接口属于哪个文件
interfaceInfos.push(interfaceInfo as MyInterface);
};
在 outputFilePath 将所有接口保存下来:
outputFilePath: (interfaceInfo: Interface) => {
// 接口文档中分类有中文,使用拼音库转成英文拼音
let nameArr: string[] = pinyin(interfaceInfo._category.name, {
toneType: 'none',
nonZh: 'consecutive',
type: 'array',
});
let name: string = camelCase(nameArr.join('-'));
// 这个函数就是保存所有接口信息的函数
saveInterface(interfaceInfo, name);
return `src/types/api/${name}.ts`;
},
在 ytt 的 success 钩子函数中我们就可以编写生成请求代码的代码了:
// 这里是 ytt 完成的钩子函数
{
success() {
shelljs.echo('--------------------接口请求代码生成中...');
// 先删除文件,每次重新生成
filesName.forEach((fileName) => {
shelljs.rm('-f', getFilePath(fileName));
});
// 遍历接口信息生成接口代码
interfaceInfos.forEach((item) => {
generatorRequest(item);
});
shelljs.echo('--------------------接口请求代码生成完成');
},
}
生成接口代码
生成接口代码有几点要注意:
- 接口文件只能生成一次,在第一次生成的时候往里面写入公共代码,如 import 语句。
- 在遍历每个接口数据的时候,可以通过上面往接口数据中添加的 fileName 字段知道这个接口属于哪个文件。
- 对 rest 风格地址的处理。
入口函数:
// 生成接口请求代码
const generatorRequest = (data: MyInterface): void => {
const filePath = getFilePath(data.fileName);
writeCommon(data.fileName, filePath);
const fnName = getFnName(data);
const [reqType, resType] = getReqResType(fnName);
const str = `
export const ${fnName} = (${getRest(data)}data?: Types.${reqType}, options?: Options) => {
return ${getMethod(data)}<Types.${resType}>(
\`${getReqPath(data)}\`,
data,
options
);
};
`;
shelljs.ShellString(str).toEnd(filePath);
};
封装 getFilePath 获取操作的文件路径,我把所有文件写在 src/services 中。
type MyInterface = Interface & { fileName: string };
// 生成文件目录路径
const basePath = './src/services';
// 获取编辑的文件的相对路径
export const getFilePath = (fileName: string): string => {
return `${basePath}/${fileName}.ts`;
};
写入公共内容:
// 写入公共内容,如 import 的依赖,只写一次
const writeCommon = (fileName: string, filePath: string): void => {
// 使用 shelljs.find 方法判断文件是否存在,从而一些公共内容在首次创建时写入
const res = shelljs.find(filePath);
// 0 是找到了,1 是没找到
if (res.code === 1) {
// 写入 import 代码
shelljs
.ShellString(
`
import * as Types from '@/types/api/${fileName}';
import request, { Options } from '@/utils/request';
`
)
.to(filePath);
}
};
写入公共代码后,generatorRequest 中以下的代码就是生成每条请求语句,追加到对应的文件中:
const fnName = getFnName(data);
const [reqType, resType] = getReqResType(fnName);
const str = `
export const ${fnName} = (${getRest(data)}data?: Types.${reqType}, options?: Options) => {
return ${getMethod(data)}<Types.${resType}>(
\`${getReqPath(data)}\`,
data,
options
);
};
`;
shelljs.ShellString(str).toEnd(filePath);
通过请求地址生成请求的函数名:
// 生成函数名
const getFnName = (data: Interface): string => {
const { path } = data;
return camelCase(path);
};
由于 ytt 生成的类型名称也是通过路径生成的,所以我们将路径生成的函数名再稍加处理就可以生成请求和响应的类型名称了
// 生成请求和响应类型的字符串。路径生成的函数名首字符大写,然后加 Request 或 Response
const getReqResType = (fnName: string): string[] => {
const name = fnName.charAt(0).toUpperCase() + fnName.slice(1);
return [`${name}Request`, `${name}Response`];
};
最后一点要注意的是,由于项目中一些请求路径是 rest 风格的,需要对其进行处理。如 service/getUserInfo/:uid 这样的请求地址。
这里我通过正则匹配替换,将 :uid 替换成 ${rest.uid} 这样的字符串。然后函数中添加一个参数 rest。当然,也可以直接生成 ${data.uid},只是这样又得去给请求参数的类型添加联合类型了。
// 获取请求地址
const getReqPath = (data: Interface): string => {
let path = data.path;
if (path.startsWith('/')) {
path = path.substring(1);
}
path = convertReqPath(path);
return path;
};
// 转换请求地址中的 :xxx 为 ${rest.xxx}
const convertReqPath = (path: string): string => {
return path.replace(/:\w+/, (str) => {
return `\${rest.${str.slice(1)}}`;
});
};
最后在写一个函数生成请求函数中的 rest 参数:
// 请求 url 中有 :xxx 的时候,添加第一个参数 rest
const getRest = (data: Interface): string => {
if (/:\w+/.test(data.path)) {
return 'rest: Record<string, string>, ';
} else {
return '';
}
};
以上这些函数就用在了这个模板字符串中:
const str = `
export const ${fnName} = (${getRest(data)}data?: Types.${reqType}, options?: Options) => {
return ${getMethod(data)}<Types.${resType}>(
\`${getReqPath(data)}\`,
data,
options
);
};
`;
大功告成,运行 ytt 生成接口类型文件的同时,也生成了这样的请求文件:
前端工程化:使用 shelljs 生成 yapi 接口文件的更多相关文章
- 基于webpack的前端工程化开发解决方案探索(一):动态生成HTML(转)
1.什么是工程化开发 软件工程的工程化开发概念由来已久,但对于前端开发来说,我们没有像VS或者eclipse这样量身打造的IDE,因为在大多数人眼中,前端代码无需编译,因此只要一个浏览器来运行调试就行 ...
- 基于gulp编写的一个简单实用的前端开发环境好了,安装完Gulp后,接下来是你大展身手的时候了,在你自己的电脑上面随便哪个地方建一个目录,打开命令行,然后进入创建好的目录里面,开始撸代码,关于生成的json文件请点击这里https://docs.npmjs.com/files/package.json,打开的速度看你的网速了注意:以下是为了演示 ,我建的一个目录结构,你自己可以根据项目需求自己建目
自从Node.js出现以来,基于其的前端开发的工具框架也越来越多了,从Grunt到Gulp再到现在很火的WebPack,所有的这些新的东西的出现都极大的解放了我们在前端领域的开发,作为一个在前端领域里 ...
- vuex前端工程化之动态导入文件--require.context( )
随着项目的复杂,文件结构越来越多,Store中modules中的文件也越来越多,如果一个一个加载是不是很麻烦呢? 先看一个项目中store项目结构: 过去都是通过import分别引入文件: 1 imp ...
- 前端工程化(二)---webpack配置
导航 前端工程化(一)---工程基础目录搭建 前端工程化(二)---webpack配置 前端工程化(三)---Vue的开发模式 前端工程化(四)---helloWord 继续上一遍的配置,本节主要记录 ...
- 页面仔初窥"前端工程化"
今天看了几篇前端界的一位大牛--张云龙的文章,其中一篇在自己的理解范围内看得懂一些,有所收获,说的是前端工程化的事,看完算是对前端工程形成了一个模糊的概念. 现在我所接触到的前端开发,还是张云龙大神所 ...
- 公司内部技术分享之Vue.js和前端工程化
今天主要的核心话题是Vue.js和前端工程化.我将结合我这两年多的工作学习经历来谈谈这个,主要侧重点是前端工程化,Vue.js侧重点相对前端工程化,比重不是特别大. Vue.js Vue.js和Rea ...
- 10分钟学会前端工程化(webpack4.0)
一.概要 1.1.前端工程化 随着前端的不断发展与壮大,前端变得越来越复杂,组件化.模块化.工程化.自动化成了前端发展中不可或缺的一部分,具体到前端工程化,面临的问题是如何提高编码->测试-&g ...
- web前端工程化/构建自动化
前端工程化 前端工程化的概念在近些年来逐渐成为主流构建大型web应用不可或缺的一部分,在此我通过以下这三方面总结一下自己的理解. 为什么需要前端工程化. 前端工程化的演化. 怎么实现前端工程化. 为什 ...
- chrome插件: yapi 接口TypeScript代码生成器
前言 2020-09-12 天气晴,蓝天白云,微风,甚好. 前端Jser一枚,在公司的电脑前,浏览器打开着yapi的接口文档,那密密麻麻的接口数据,要一个一个的去敲打成为TypeScript的inte ...
随机推荐
- RSA公钥加密-私钥解密/私钥加密-公钥解密
package com.tebon.ams.util;import org.apache.commons.codec.binary.Base64;import org.apache.log4j.Log ...
- JAVA多线程学习六-守护线程
java中的守护程序线程是一个服务提供程序线程,它为用户线程提供服务. 它的生命依赖于用户线程,即当所有用户线程都死掉时,JVM会自动终止该线程. 有许多java守护程序线程自动运行,例如 gc,fi ...
- File 类的 getPath()、getAbsolutePath()、getCanonicalPath() 的区别【转】
File 类的 getPath().getAbsolutePath().getCanonicalPath() 的区别 感谢大佬:https://blog.csdn.net/zsensei/articl ...
- 一键部署mysql 无修改直接cp 执行 100% 有效
一键部署mysql 无修改直接cp 执行 100% 有效 将安装包拖至/opt目录下,编一个脚本文件,然后source执行脚本,等脚本执行完成, 即可使用mysql -u root -p点击 ...
- 实现反向代理客户端IP透传
默认情况下,使用反向代理时,后端服务器只能看到访问是从反向代理服务器的IP,无法真正识别到客户端IP.通过配置IP透传实现后端服务器识别到客户端真实IP. 一.Apache后端服务器部署 1.1 安装 ...
- 带你十天轻松搞定 Go 微服务系列(九、链路追踪)
序言 我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分 用户服务 产品服务 订单服务 支付服务 RPC 服务 Auth 验证 ...
- Ubuntu18修改/迁移mysql5.7数据存放路径
1.停止mysql服务 sudo service mysql stop 2.修改mysql配置文件,一般是 /etc/mysql/my.cnf,或者/etc/mysql/mysql.conf.d/my ...
- Solution -「UVA 1104」Chips Challenge
\(\mathcal{Description}\) Link. 在一个 \(n\times n\) 的方格图中,有一些格子已经放了零件,有一些格子可以放零件,其余格子不能放零件.求至多放多少个 ...
- Solution -「LOCAL」ZB 平衡树
\(\mathcal{Description}\) OurOJ. 维护一列二元组 \((a,b)\),给定初始 \(n\) 个元素,接下来 \(m\) 次操作: 在某个位置插入一个二元组: 翻 ...
- Spring Boot run()方法剖析
Spring Boot自动配置部分重点介绍了相关注解,关于main中调用的run方法并没有阐述过.run方法的作用是什么呢?只有注解没有main里的run方法Spring Boot工程就好比身体个方面 ...