# Rapper 是什么？

> 注：可以使用@antmjs/api 代替

Rapper 是 TypeScript 的最佳拍档，它可以帮你生成具有类型定义的请求方案。

- 无需自行书写请求代码，把 HTTP 接口当做函数调用
- 请求参数/返回数据类型化，静态校验、自动补全快到飞起

### @antmjs/rapper 是什么？

基于 Rapper 开发，使配置更灵活，同时增加本地类型同步远程文档重要功能

- ++++
- 本地接口类型上传到 rapper 远程文档，本地编码驱动远程文档
- 自定义请求函数模板，满足不同编程规范

### 快速开始

1. package.json scripts 中 添加
   { "rap" : "npx rapper"}

2. 配置 antm.config.js

```js
{
  rapper: {
    // 拉取接口地址
    apiUrl?: string;
    /** rap 前端地址，默认是 http://rap2.taobao.org */
    rapUrl?: string;
    // 生成的文件目录地址
    rapperPath?: string;
    // rap登录cookie
    tokenCookie?: string;
    // rap项目id
    repositoryId?: number;
  }
}
```

3. 开始写你的 ts 接口类型, 然后执行 npm run rap

### rapper 名称对应 接口 ts 类型介绍

> ts 接口类型需要配合 rapper 使用

- 暂时不支持 url 带 path 参数
  > rapper 接口字段名称对应

```ts
interface XY {
  x: number
  y: number
  z: number
}
export type IUserInfo = {
  request: {
    // (1)
    age?: string // (2)
  }
  response: {
    // (3)
    /**
     *
     * @value true
     */
    success: boolean
    data: {
      /**
       * 数组演示 // (4)
       * @rule 123 // (6)
       */
      array: {
        /**
         * 名称
         * @value #cname // (5)
         */
        name: string
        /**
         * 支持泛型以及接口引用
         */
        other: XY
      }[]
    }
  }
}
```

以下是 rapper 中含义

- 1 [request] 请求参数定义;
- 2 [age?: string] 入参定义字段名称，必选，类型;
- 3 [response] 返回数据定义;
- 4 [jsDoc 描述： 数组演示] 字段简介
- 5 [jsDoc 关键字：@value #cname] 字段初始值
- 6 [jsDoc 关键字：@rule 123] 字段生成规则

> 注意 jsDoc 关键字的值中【@】符号由于转义问题需要替换成【#】或者【\\@】或者【/@】

### 命令函入参会和 config 合并（命令行优先级更高）

- --u 上传
- --d 下载
- --m xx 指定 moduleId，不传默认提交更改的模块

### rapper 配置 config 有三种方案

##### 方案一（推荐）

通过 antm.config.js 配置 config

```js
// <!-- antm.config.js文件 -->
const antmRapper = require('@antmjs/rapper')
// <!--  使用antm 提供 defineConfig 会有类型提示 -->
export.default = antmRapper.defineConfig({
  upload: { xx: xx }, // 本地上传 配置
})
```

##### 方案二

通过 命令行参数执行 config 路径

```bash
  $ npx rapper --config  ./config/index.js
```

```js
// <!-- ./config/index.js文件 -->
const antmRapper = require('@antmjs/rapper')
// <!--  antmRapper 提供 defineConfig 会有类型提示 -->
export.default = antmRapper.defineConfig({
  upload: { xx: xx }, // 本地上传 配置
  download: { xx: xx } // 远程下载 配置
})
```

##### 方案三

通过 package.json 配置 antm.rapper

```js
<!--package.json  文件  -->
{
  'antm': {
  'rapper': {
    'upload': { xx: xx }, // 本地上传 配置
  }
}
}
```

### 本地代码类型同步到远程 raper 文档

- 解析本地文件
- fetch 方法追加注释 （接口 id 接口模块 id）
- 格式化 类型
- 调用 rapper 接口

### 增量更新实现

- 每次更新会给文件头部 加一个 MD5 值
- 初始化会检查合法的文件（符合 formatFunc 结构的文件） MD5 值 对不住
- 去解析当前文件以及 依赖当前文件的文件
- 提交变更的模块接口（文件级检查）,做不到方法级检查

### config 接口类型

```ts
interface IConfig {
  // 下载配置
  download: {
    /**
     *
     * @param params   rap上填入接口的信息
     * @returns
     * reqTypeName: request类型名称;
     * resTypeName: response类型名称;
     * funcMain: 请求函数体;
     */
    requestFunc?: (params: {
      funcDescription: string
      repositoryId: number
      moduleId: number
      interfaceId: number
      requestUrl: string
      requestMethod: string
      rapUrl: string
    }) => {
      reqTypeName: string
      resTypeName: string
      funcMain: string
    }
    /**
     *
     * @param params   rap 上填入的module信息
     * @returns
     * fileName: 模块的文件名称;
     * moduleHeader: 模块头部的banner;
     */
    requestModule?: (params: {
      repositoryId: number
      moduleId: number
      moduleRapUrl: string
      moduleDescription: string
    }) => {
      fileName: string
      moduleHeader: string
    }
    // 自定下载的module
    moduleId?: number
  }
  rapper: {
    // 拉取接口地址
    apiUrl?: string
    /** rap 前端地址，默认是 http://rap2.taobao.org */
    rapUrl?: string
    // 生成的文件目录地址
    rapperPath?: string
    // rap登录cookie
    tokenCookie?: string
    // rap项目id
    repositoryId?: number
  }
  upload: {
    //  模式 type 文件扫描入口是type（需要编译生成fetch)
    //  fetch 文件扫描入口是fetch请求函数（不需要编译）
    mode?: 'type' | 'fetch'
    // 需要解析的文件名称正则
    fileRegex?: string
    /**
     *
     * @param params  函数信息
     * @returns
     *  resTypeName: request 类型名称;
     * reqTypeName: response  类型名称;
     * reqUrl: 请求 url;
     * reqMethod: 请求method;
     * interfaceId: 接口id;
     */
    formatFunc?: (params: {
      funcName: string
      body: string
      comment: string
      // 三种函数 定义 会被选中到导出
      funcType: 'CallExpression' | 'FunctionDeclaration' | 'ArrowFunction'
    }) => {
      resTypeName: string
      reqTypeName: string
      reqUrl: string
      reqMethod: string
      interfaceId: number
    } | null
    // 指定下载的 模块id
    moduleId?: number
    // webpack 别名
    alias?: Record<string, string>
  }
  // 内部标识使用 不用管
  __completion?: boolean
  // 是不是上传
  isUpload: boolean
}

export type IOptions = Partial<IConfig>
```

### defaultConfig 会和传进来的 config 合并补全

````typescript
   const defaultOptions = {
    download: {
      //请求 function 模板
      requestFunc(params) {
        function getFnName(url: string): null | string {
          const fnName = url.match(/\/([.a-z0-9_-]+)\/([a-z0-9_-]+$)/i);
          if (fnName && fnName.length === 3) {
            if (/^\d+\.\d+$/.test(fnName[1])) {
              return fnName[2];
            }
            return fnName[1] + fnName[2].charAt(0).toUpperCase() + fnName[2].slice(1);
          }
          return null;
        }
        const fnName = getFnName(params.requestUrl);
        if (!fnName) {
          throw new TypeError('接口路径不对,请修改合规');
        }
        const camelCaseName = `${fnName.charAt(0).toUpperCase()}${fnName.slice(1)}`;
        const reqTypeName = `IReq${camelCaseName}`;
        const resTypeName = `IRes${camelCaseName}`;
        return {
          reqTypeName,
          resTypeName,
          funcMain: `
              /**
               * 接口名：${params.funcDescription}
               * Rap 地址: ${params.rapUrl}?id=${params.repositoryId}&mod=${params.moduleId}&itf=${params.interfaceId}
               */
              export const ${fnName} = createFetch<${reqTypeName}, ${resTypeName}>('${params.requestUrl}', '${params.requestMethod}')
              `,
        };
      },
      //请求 函数共工头（用于引入函数
      requestModule(params) {
        return {
          fileName: params.moduleDescription,
          moduleHeader: `
/* eslint-disable */
/* tslint:disable */
// @ts-nocheck

import instance from '@/utils/request'

function createFetch<REQ extends Record<string, unknown>, RES extends {data: any}> (url: string, method: string) {
  return  <T extends boolean = false>(
    data: REQ,
    options?: {
      proxy?: T
      pageError?: boolean
    }
  ): Promise<T extends true ? RES['data'] : RES> => {
    return instance(
      {
        url,
        method,
        data,
      },
      options
    )
  }
}
`,
        };
      },
    },
    rapper: {
      // 拉取接口地址
      apiUrl:
        'http://rap2api.taobao.org/repository/get?id=284428&token=TTDNJ7gvXgy9R-9axC-7_mbi4ZxEPlp6',
      /** rap 前端地址，默认是 http://rap2.taobao.org */
      rapUrl: 'http://rap2.taobao.org',

      rapperPath: './src/actions',
      tokenCookie:
        'aliyungf_tc=f3a5915db08fc3b6de3ec5df0d0b3a5dc07c0b701e44cf4bf98a855799570bfe; koa.sid=2I353u8TTwtrHSdPXdJ9t8Mx5lTOeQFV; koa.sid.sig=D4vYLNcryQ8vcU4GkJJknTi_Fm8',
      repositoryId: 284428,
    },
    upload: {
      mode: 'type' as const,
      // fileRegex 将尝试使用绝对文件路径检测测试文件
      // (/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$
      fileRegex: './src/actions/types/.*(js|jsx|ts|tsx)',

      formatFunc(params) {
        // createFetch<IReqGoodsQbf, IResGoodsQbf>('/c/api/1.0/approve/goods/qbf', 'GET')
        // export const goodsQbf = createFetch<IGoodsQbf['request'], IGoodsQbf['response']>("/c/api/1.0/approve/goods/qbf", "GET");
        const [_, reqTypeName, resTypeName, reqUrl, reqMethod] =
          params.body.match(
            /createFetch<([\w\[\]'"]+),\s+([\w\[\]'"]+)>\(['"]([\s\S]+)['"], ['"]([a-zA-Z]+)['"]\)/,
          ) || [];
        if (!reqTypeName || !resTypeName) {
          return null;
        }
        const matchInterfaceId = params.comment.match(/http:\/\/rap2\.tao[\s\S]+&itf=(\d+)/);
        return {
          resTypeName,
          reqTypeName,
          // 如果返回 null '' undefined 0 等 就会被认为是新的接口，会触发上rap操作
          interfaceId: matchInterfaceId ? Number(matchInterfaceId[1]) : null,
          reqUrl: reqUrl,
          reqMethod: reqMethod,
        };
      },
      // webpack 别名 alias 绝对路径
      alias: {
        '@': './src',
      },
    },
    isUpload: true,
  };
    ```
````
