diff --git a/.eslintrc.js b/.eslintrc.js
index 79589e0..bed9867 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,9 +1,13 @@
module.exports = {
extends: 'standard',
- parser: 'babel-eslint',
+ parserOptions: {
+ ecmaVersion: 10,
+ parser: 'babel-eslint',
+ },
rules: {
- 'indent': [2, 4],
+ indent: [2, 4],
'promise/param-names': 0,
+ 'template-curly-spacing': 'off',
'comma-dangle': [2, 'always-multiline'],
},
globals: {
diff --git a/docs/guide/middleware.md b/docs/guide/middleware.md
index 664656d..8933a8e 100644
--- a/docs/guide/middleware.md
+++ b/docs/guide/middleware.md
@@ -28,7 +28,8 @@ function (ctx, next) {
return next().then(() => {
// 注意这里才有响应!
ctx.res // 响应对象
- ctx.res.data // 响应的数据
+ ctx.res.data // 响应格式化后的数据
+ ctx.res.rawData // 响应的原始数据
ctx.reqTime // 请求花费的时间
ctx.endTime // 收到响应的时间
})
@@ -68,7 +69,8 @@ async function (ctx, next) {
| req.reqFnParams | 发起请求时的参数对象(上面那些参数都会被放进来作为属性) |
| --- | --- |
| res | 响应 |
-| res.data | 响应的数据 |
+| res.data | 响应格式化后的数据 |
+| res.rawData | 响应的原始数据 |
| res.error | 错误对象(可以取 stack 和 message) |
| res.* | [透传 axios 的配置](https://github.com/axios/axios#response-schema) |
| --- | --- |
diff --git a/examples/apis-web/fake-post.js b/examples/apis-web/fake-post.js
index b3dd0e1..54668b9 100644
--- a/examples/apis-web/fake-post.js
+++ b/examples/apis-web/fake-post.js
@@ -78,7 +78,7 @@ export default {
name: 'ct',
path: 'custom-transformRequest',
axiosOptions: {
- transformRequest: () => `ct`,
+ transformRequest: () => 'ct',
},
},
/**
@@ -88,5 +88,13 @@ export default {
name: 'pj',
path: 'post-json',
},
+ /**
+ * raw-data
+ */
+ {
+ name: 'rd',
+ path: 'raw-data',
+ afterFn: ([, ctx]) => ctx.res.rawData,
+ },
],
}
diff --git a/package.json b/package.json
index 62d9352..2912d84 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "tua-api",
- "version": "1.5.0",
+ "version": "1.6.0",
"description": "🏗 A common tool helps converting configs to api functions",
"main": "dist/TuaApi.cjs.js",
"module": "dist/TuaApi.esm.js",
@@ -50,42 +50,42 @@
"koa-compose": "^4.1.0"
},
"devDependencies": {
- "@babel/core": "^7.8.4",
- "@babel/plugin-external-helpers": "^7.8.3",
- "@babel/plugin-proposal-decorators": "^7.8.3",
- "@babel/plugin-proposal-object-rest-spread": "^7.8.3",
- "@babel/preset-env": "^7.8.4",
- "@commitlint/cli": "^8.3.5",
- "@commitlint/config-conventional": "^8.3.4",
- "@types/jest": "^25.1.2",
- "all-contributors-cli": "^6.13.0",
- "axios-mock-adapter": "^1.17.0",
+ "@babel/core": "^7.10.5",
+ "@babel/plugin-external-helpers": "^7.10.4",
+ "@babel/plugin-proposal-decorators": "^7.10.5",
+ "@babel/plugin-proposal-object-rest-spread": "^7.10.4",
+ "@babel/preset-env": "^7.10.4",
+ "@commitlint/cli": "^9.1.1",
+ "@commitlint/config-conventional": "^9.1.1",
+ "@rollup/plugin-babel": "^5.1.0",
+ "@rollup/plugin-commonjs": "^14.0.0",
+ "@rollup/plugin-json": "^4.1.0",
+ "@rollup/plugin-node-resolve": "^8.4.0",
+ "@rollup/plugin-replace": "^2.3.3",
+ "@types/jest": "^26.0.5",
+ "all-contributors-cli": "^6.16.1",
+ "axios-mock-adapter": "^1.18.2",
"babel-core": "^7.0.0-bridge.0",
- "babel-eslint": "^10.0.3",
- "babel-jest": "^25.1.0",
- "codecov": "^3.6.5",
- "cross-env": "^7.0.0",
- "eslint": "^6.8.0",
- "eslint-config-standard": "^14.1.0",
- "eslint-plugin-import": "^2.20.1",
- "eslint-plugin-node": "^11.0.0",
+ "babel-eslint": "^10.1.0",
+ "babel-jest": "^26.1.0",
+ "codecov": "^3.7.1",
+ "cross-env": "^7.0.2",
+ "eslint": "^7.5.0",
+ "eslint-config-standard": "^14.1.1",
+ "eslint-plugin-import": "^2.22.0",
+ "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
- "gh-pages": "^2.2.0",
- "husky": "^4.2.3",
- "jest": "^25.1.0",
- "lint-staged": "^10.0.7",
+ "gh-pages": "^3.1.0",
+ "husky": "^4.2.5",
+ "jest": "^26.1.0",
+ "lint-staged": "^10.2.11",
"rimraf": "^3.0.2",
- "rollup": "^1.31.1",
- "rollup-plugin-babel": "^4.3.3",
- "rollup-plugin-commonjs": "^10.1.0",
+ "rollup": "^2.22.1",
"rollup-plugin-eslint": "^7.0.0",
- "rollup-plugin-json": "^4.0.0",
- "rollup-plugin-node-resolve": "^5.2.0",
- "rollup-plugin-replace": "^2.2.0",
- "rollup-plugin-uglify": "^6.0.4",
- "typescript": "^3.7.5",
- "vuepress": "^1.3.0"
+ "rollup-plugin-terser": "^6.1.0",
+ "typescript": "^3.9.7",
+ "vuepress": "^1.5.2"
},
"repository": {
"type": "git",
diff --git a/rollup.config.js b/rollup.config.js
index 1602043..c92aabd 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -1,10 +1,10 @@
-import json from 'rollup-plugin-json'
-import babel from 'rollup-plugin-babel'
-import replace from 'rollup-plugin-replace'
-import commonjs from 'rollup-plugin-commonjs'
+import json from '@rollup/plugin-json'
+import babel from '@rollup/plugin-babel'
+import replace from '@rollup/plugin-replace'
+import commonjs from '@rollup/plugin-commonjs'
import { eslint } from 'rollup-plugin-eslint'
-import { uglify } from 'rollup-plugin-uglify'
-import nodeResolve from 'rollup-plugin-node-resolve'
+import { terser } from 'rollup-plugin-terser'
+import nodeResolve from '@rollup/plugin-node-resolve'
import pkg from './package.json'
@@ -40,7 +40,7 @@ const plugins = [
json(),
nodeResolve(),
commonjs(),
- babel(),
+ babel({ babelHelpers: 'bundled' }),
]
const env = 'process.env.NODE_ENV'
const external = ['axios', 'fetch-jsonp']
@@ -68,6 +68,11 @@ export default [{
plugins: [
...plugins,
replace({ [env]: '"production"' }),
- uglify(),
+ terser({
+ output: {
+ /* eslint-disable */
+ ascii_only: true,
+ },
+ }),
],
}]
diff --git a/src/constants.js b/src/constants.js
index e690976..5f7605b 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -1,5 +1,5 @@
// 支持的请求类型
-const VALID_REQ_TYPES = ['wx', 'axios', 'jsonp']
+const VALID_REQ_TYPES = ['wx', 'axios', 'jsonp', 'custom']
// 小程序中合法的请求方法
const WX_VALID_METHODS = ['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT']
@@ -12,6 +12,7 @@ const ERROR_STRINGS = {
noData: 'no data!',
argsType: 'the first parameter must be an object!',
middleware: 'middleware must be a function!',
+ reqTypeAndCustomFetch: 'reqType or customFetch only!',
reqTypeFn: (reqType) => `invalid reqType: "${reqType}", ` +
`support these reqTypes: ["${VALID_REQ_TYPES.join('", "')}"].`,
diff --git a/src/index.d.ts b/src/index.d.ts
index 099af9e..587359b 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -22,6 +22,7 @@ export type ReqType = (
| 'wx' | 'WX'
| 'axios' | 'AXIOS'
| 'jsonp' | 'JSONP'
+ | 'custom' | 'CUSTOM'
)
export type Method = (
@@ -66,6 +67,7 @@ export interface CtxReq {
}
export interface CtxRes extends AxiosResponse {
data: any
+ rawData: any
error?: Error
[k: string]: any
}
@@ -93,6 +95,7 @@ export interface BaseApiConfig {
afterFn?: (args: [U?, Ctx?]) => Promise
beforeFn?: () => Promise
middleware?: Middleware[]
+ customFetch?: AnyPromiseFunction
commonParams?: object
axiosOptions?: AxiosOptions
jsonpOptions?: JsonpOptions
@@ -121,14 +124,14 @@ export interface RuntimeOptionsOnly {
fullPath?: string
callbackName?: string
}
-export interface WxRuntimeOptions extends WxApiConfig, RuntimeOptionsOnly {}
-export interface WebRuntimeOptions extends WebApiConfig, RuntimeOptionsOnly {}
+export interface WxRuntimeOptions extends WxApiConfig, RuntimeOptionsOnly { }
+export interface WebRuntimeOptions extends WebApiConfig, RuntimeOptionsOnly { }
export interface Api {
key: string
mock: Mock
params: ParamsConfig
- (
+ (
params?: U,
runtimeOptions?: RuntimeOptions
): Promise
@@ -137,13 +140,14 @@ export interface Apis { [k: string]: SyncFnMap }
export interface SyncFnMap { [k: string]: Api }
export interface TuaApiClass {
- new (args?: {
+ new(args?: {
// deprecated
host?: string
baseUrl?: string
reqType?: string
middleware?: Middleware[]
+ customFetch?: AnyPromiseFunction
axiosOptions?: AxiosOptions
jsonpOptions?: JsonpOptions
defaultErrorData?: any
@@ -157,8 +161,8 @@ export interface TuaApiInstance {
/* -- export utils -- */
-export function getSyncFnMapByApis(apis: Apis): SyncFnMap
-export function getPreFetchFnKeysBySyncFnMap(syncFnMap: SyncFnMap): Api[]
+export function getSyncFnMapByApis (apis: Apis): SyncFnMap
+export function getPreFetchFnKeysBySyncFnMap (syncFnMap: SyncFnMap): Api[]
/* -- export default -- */
diff --git a/src/index.js b/src/index.js
index f40e772..396c0ed 100644
--- a/src/index.js
+++ b/src/index.js
@@ -36,6 +36,7 @@ class TuaApi {
* @param {string} [options.baseUrl] 服务器基础地址,例如 https://example.com/
* @param {string} [options.reqType] 使用什么工具发(axios/jsonp/wx)
* @param {function[]} [options.middleware] 中间件函数数组
+ * @param {function} [options.customFetch] 自定义请求函数
* @param {object} [options.axiosOptions] 透传 axios 配置参数
* @param {object} [options.jsonpOptions] 透传 fetch-jsonp 配置参数
* @param {object} [options.defaultErrorData] 出错时的默认数据
@@ -43,15 +44,19 @@ class TuaApi {
constructor ({
host,
baseUrl = host,
- reqType = isWx() ? 'wx' : 'axios',
+ reqType,
middleware = [],
+ customFetch,
axiosOptions = {},
jsonpOptions = {},
defaultErrorData = { code: 999, msg: '出错啦!' },
} = {}) {
this.baseUrl = baseUrl
- this.reqType = reqType
+ this.reqType = reqType !== undefined
+ ? reqType.toLowerCase()
+ : (isWx() ? 'wx' : 'axios')
this.middleware = middleware
+ this.customFetch = customFetch
this.axiosOptions = axiosOptions
this.jsonpOptions = jsonpOptions
this.defaultErrorData = defaultErrorData
@@ -64,6 +69,9 @@ class TuaApi {
'[host] 属性将被废弃, 请用 [baseUrl] 替代!',
)
}
+ if (reqType && reqType !== 'custom' && customFetch) {
+ throw TypeError(ERROR_STRINGS.reqTypeAndCustomFetch)
+ }
return this
}
@@ -109,28 +117,31 @@ class TuaApi {
* @param {string} options.reqType 使用什么工具发(axios/jsonp/wx)
* @param {object} options.reqParams 请求参数
* @param {object} options.header 请求的 header
+ * @param {function} [options.customFetch] 自定义请求函数
* @param {string} options.callback 使用 jsonp 时标识回调函数的名称
* @param {string} options.callbackName 使用 jsonp 时的回调函数名
* @param {object} options.axiosOptions 透传 axios 配置参数
* @param {object} options.jsonpOptions 透传 fetch-jsonp 配置参数
* @return {Promise}
*/
- _reqFn ({
- url,
- mock,
- header,
- method,
- fullUrl,
- reqType,
- reqParams: data,
- callback,
- callbackName,
- axiosOptions,
- jsonpOptions,
- ...rest
- }) {
+ _reqFn (options) {
+ const {
+ url,
+ mock,
+ header,
+ method: _method,
+ fullUrl,
+ reqType: _reqType,
+ reqParams: data,
+ callback,
+ callbackName,
+ axiosOptions,
+ jsonpOptions,
+ ...rest
+ } = options
+
// check type
- this._checkReqType(reqType)
+ this._checkReqType(_reqType)
// mock data
if (mock) {
@@ -141,7 +152,12 @@ class TuaApi {
return Promise.resolve({ data: resData })
}
- method = method.toLowerCase()
+ const method = _method.toLowerCase()
+ const reqType = _reqType.toLowerCase()
+
+ if (reqType === 'custom') {
+ return rest.customFetch({ url, data, method, header, ...rest })
+ }
if (reqType === 'wx') {
return getWxPromise({ url, fullUrl, data, method, header, ...rest })
@@ -161,7 +177,6 @@ class TuaApi {
// 防止接口返回非英文时报错
jsonpOptions.charset = jsonpOptions.charset || 'UTF-8'
-
jsonpOptions.jsonpCallback = callback || jsonpOptions.jsonpCallback
jsonpOptions.jsonpCallbackFunction = callbackName || jsonpOptions.jsonpCallbackFunction
@@ -226,6 +241,7 @@ class TuaApi {
* @param {function} options.afterFn 在请求完成后执行的钩子函数(将被废弃)
* @param {function} options.beforeFn 在请求发起前执行的钩子函数(将被废弃)
* @param {function[]} options.middleware 中间件函数数组
+ * @param {function} [options.customFetch] 自定义请求函数
* @param {Boolean} options.useGlobalMiddleware 是否使用全局中间件
* @param {string} options.baseUrl 服务器地址
* @param {string} options.reqType 使用什么工具发
@@ -261,9 +277,21 @@ class TuaApi {
// 向前兼容
type = method
- // 合并全局默认值
+ /* 合并全局默认值 */
+ if (rest.reqType && rest.customFetch) {
+ if (rest.reqType.toLowerCase() !== 'custom') {
+ logger.warn(ERROR_STRINGS.reqTypeAndCustomFetch)
+ }
+ rest.reqType = 'custom'
+ } else if (rest.customFetch || this.customFetch) {
+ // 没有配置 reqType,但配了公共配置或默认配置的 customFetch
+ rest.reqType = 'custom'
+ } else {
+ // 没有配置 customFetch
+ rest.reqType = rest.reqType || this.reqType
+ }
rest.baseUrl = rest.baseUrl || this.baseUrl
- rest.reqType = rest.reqType || this.reqType
+ rest.customFetch = rest.customFetch || this.customFetch
rest.axiosOptions = rest.axiosOptions
? { ...this.axiosOptions, ...rest.axiosOptions }
: this.axiosOptions
@@ -294,9 +322,8 @@ class TuaApi {
// 向前兼容
runtimeParams.host = runtimeParams.host || runtimeParams.baseUrl
- runtimeParams.baseUrl = runtimeParams.baseUrl || runtimeParams.host
-
runtimeParams.method = runtimeParams.method || runtimeParams.type
+ runtimeParams.baseUrl = runtimeParams.baseUrl || runtimeParams.host
// 请求的上下文信息
const ctx = {
diff --git a/src/middlewareFns.js b/src/middlewareFns.js
index 70da86b..31f7095 100644
--- a/src/middlewareFns.js
+++ b/src/middlewareFns.js
@@ -38,6 +38,7 @@ const recordReqTimeMiddleware = (ctx, next) => {
*/
const formatResDataMiddleware = (ctx, next) => next().then(() => {
const jsonData = ctx.res.data
+ ctx.res.rawData = ctx.res.data
if (!jsonData) return Promise.reject(Error(ERROR_STRINGS.noData))
@@ -65,7 +66,7 @@ const formatReqParamsMiddleware = (ctx, next) => {
throw TypeError(ERROR_STRINGS.argsType)
}
- if (isFormData(args)) {
+ if (isFormData(args) || Array.isArray(args)) {
ctx.req.reqParams = args
return next()
@@ -97,6 +98,7 @@ const setReqFnParamsMiddleware = (ctx, next) => {
ctx.req.reqFnParams = {
url,
+ baseUrl,
fullUrl,
reqParams,
...rest,
diff --git a/src/utils/combineUrls.js b/src/utils/combineUrls.js
index 46ca644..9411bb1 100644
--- a/src/utils/combineUrls.js
+++ b/src/utils/combineUrls.js
@@ -5,12 +5,15 @@
* @returns {string} The combined URL
*/
function combineUrls (baseUrl = '', relativeUrl = '') {
- if (!relativeUrl) return baseUrl
+ const strBaseUrl = baseUrl === null ? '' : String(baseUrl)
+ const strRelativeUrl = relativeUrl === null ? '' : String(relativeUrl)
+
+ if (!strRelativeUrl) return strBaseUrl
return (
- baseUrl.replace(/\/+$/, '') +
+ strBaseUrl.replace(/\/+$/, '') +
'/' +
- relativeUrl.replace(/^\/+/, '')
+ strRelativeUrl.replace(/^\/+/, '')
)
}
diff --git a/test/__tests__/axios.test.js b/test/__tests__/axios.test.js
index e8fc981..0b4e909 100644
--- a/test/__tests__/axios.test.js
+++ b/test/__tests__/axios.test.js
@@ -7,10 +7,7 @@ import { fakeGetApi, fakePostApi } from '@examples/apis-web/'
const mock = new MockAdapter(axios)
-const params = {
- param1: 'steve',
- param2: 'young',
-}
+const params = { param1: 'steve', param2: 'young' }
const reqAPUrl = 'http://example-base.com/fake-post/array-params'
const reqOPUrl = 'http://example-base.com/fake-post/object-params'
@@ -22,6 +19,7 @@ const reqMFDUrl = 'http://example-base.com/fake-get/mock-function-data'
const reqBFCUrl = 'http://example-base.com/fake-get/beforeFn-cookie'
const reqCTUrl = 'http://example-base.com/fake-post/custom-transformRequest'
const reqPjUrl = 'http://example-base.com/fake-post/post-json'
+const reqRdUrl = 'http://example-base.com/fake-post/raw-data'
describe('middleware', () => {
test('change baseUrl before request', async () => {
@@ -128,10 +126,12 @@ describe('fake post requests', () => {
test('empty-array-params', async () => {
const data = { code: 0, data: 'object data' }
+ const arrayArgs = [1, 2]
mock.onPost(reqEAPUrl).reply(200, data)
- const resData = await fakePostApi.eap()
+ const resData = await fakePostApi.eap(arrayArgs)
expect(resData).toEqual(data)
+ expect(mock.history.post[0].data).toEqual(JSON.stringify(arrayArgs))
})
test('array-params', async () => {
@@ -190,4 +190,12 @@ describe('fake post requests', () => {
expect(data).toBe(JSON.stringify(fakePostConfig.commonParams))
expect(mock.history.post[0].headers['Content-Type']).toBe('application/json;charset=utf-8')
})
+
+ test('raw-data', async () => {
+ const data = [0, 'array data']
+ mock.onPost(reqRdUrl).reply(200, data)
+ const resData = await fakePostApi.rd()
+
+ expect(resData).toEqual(data)
+ })
})
diff --git a/test/__tests__/custom.test.js b/test/__tests__/custom.test.js
new file mode 100644
index 0000000..2426859
--- /dev/null
+++ b/test/__tests__/custom.test.js
@@ -0,0 +1,51 @@
+import TuaApi from '@/index'
+import { ERROR_STRINGS } from '@/constants'
+
+const customFetch = jest.fn(() => Promise.resolve({ data: Math.random() }))
+
+describe('customFetch', () => {
+ const tuaApi = new TuaApi()
+ const fooApi = tuaApi.getApi({
+ prefix: 'foo',
+ pathList: [
+ { path: 'bar', customFetch },
+ { path: 'axios', customFetch, reqType: 'axios' },
+ { path: 'customAxios', customFetch, reqType: 'custom' },
+ ],
+ })
+
+ test('both customFetch and reqType', () => {
+ expect(() => new TuaApi({ reqType: 'axios', customFetch })).toThrow(TypeError(ERROR_STRINGS.reqTypeAndCustomFetch))
+ })
+
+ test('global customFetch should be called', async () => {
+ const tuaApi = new TuaApi({ customFetch })
+ const fooApi = tuaApi.getApi({
+ prefix: 'foo',
+ pathList: [
+ { path: 'globalCustomFetch' },
+ ],
+ })
+ await fooApi.globalCustomFetch()
+
+ expect(customFetch).toBeCalled()
+ })
+
+ test('local customFetch should be called', async () => {
+ await fooApi.bar()
+
+ expect(customFetch).toBeCalled()
+ })
+
+ test('local customFetch should be called with reqType', async () => {
+ await fooApi.axios()
+
+ expect(customFetch).toBeCalled()
+ })
+
+ test('local customFetch should be called with `custom` reqType', async () => {
+ await fooApi.customAxios()
+
+ expect(customFetch).toBeCalled()
+ })
+})
diff --git a/test/__tests__/utils.test.js b/test/__tests__/utils.test.js
index a885bd4..2abc9d9 100644
--- a/test/__tests__/utils.test.js
+++ b/test/__tests__/utils.test.js
@@ -9,6 +9,10 @@ import {
} from '@/utils'
test('combineUrls', () => {
+ expect(combineUrls(0, 0)).toBe('0/0')
+ expect(combineUrls(1, 1)).toBe('1/1')
+ expect(combineUrls(1, null)).toBe('1')
+ expect(combineUrls(null, 1)).toBe('/1')
expect(combineUrls(undefined, undefined)).toBe('')
expect(combineUrls(undefined, 'users')).toBe('/users')
expect(combineUrls('https://api.github.com', undefined)).toBe('https://api.github.com')
diff --git a/test/__tests__/wx.test.js b/test/__tests__/wx.test.js
index 7cdc944..5972e07 100644
--- a/test/__tests__/wx.test.js
+++ b/test/__tests__/wx.test.js
@@ -76,6 +76,7 @@ describe('middleware', () => {
expect(ctx.endTime).toBeDefined()
expect(ctx.res.data).toBeDefined()
+ expect(ctx.res.rawData).toBeDefined()
})
tuaApi.use(globalMiddlewareFn)