From 38c29bcd95354b0bb10e3582afdd30805cf6f0fb Mon Sep 17 00:00:00 2001 From: Steve Young <2747745470@qq.com> Date: Tue, 19 Feb 2019 18:13:40 +0800 Subject: [PATCH] feat: close #20 #21, upgrade deps and support runtimeOptions (#22) * feat: close #20 #21, upgrade deps and support runtimeOptions --- README.md | 26 +++-- babel.config.js | 2 +- commitlint.config.js | 2 +- docs/.vuepress/config.js | 41 ++++---- docs/README.md | 25 +++-- docs/config/README.md | 11 ++- docs/config/{detail.md => common.md} | 6 +- docs/config/runtime.md | 27 ++++++ docs/config/{own.md => self.md} | 6 +- docs/{quick-start => guide}/README.md | 19 ++-- docs/{quick-start => guide}/export-utils.md | 0 docs/{quick-start => guide}/installation.md | 0 docs/{quick-start => guide}/middleware.md | 0 docs/{quick-start => guide}/mock.md | 0 package.json | 101 +++++++++++--------- rollup.config.js | 29 ++++-- src/TuaApi.js | 59 ++++++------ src/adapters/axios.js | 8 +- src/adapters/jsonp.js | 4 +- src/adapters/wx.js | 4 +- src/constants.js | 14 +++ src/middlewareFns.js | 24 ++--- src/utils/params.js | 3 +- test/__tests__/axios.test.js | 20 +++- test/__tests__/core.test.js | 7 +- test/__tests__/exportUtils.test.js | 2 +- test/__tests__/fp.test.js | 2 +- test/__tests__/jsonp.test.js | 5 +- test/__tests__/utils.test.js | 7 +- test/__tests__/wx.test.js | 12 ++- tsconfig.json | 12 +++ 31 files changed, 298 insertions(+), 180 deletions(-) rename docs/config/{detail.md => common.md} (96%) create mode 100644 docs/config/runtime.md rename docs/config/{own.md => self.md} (87%) rename docs/{quick-start => guide}/README.md (85%) rename docs/{quick-start => guide}/export-utils.md (100%) rename docs/{quick-start => guide}/installation.md (100%) rename docs/{quick-start => guide}/middleware.md (100%) rename docs/{quick-start => guide}/mock.md (100%) create mode 100644 tsconfig.json diff --git a/README.md b/README.md index f087677..a11c368 100644 --- a/README.md +++ b/README.md @@ -86,8 +86,14 @@ fooApi ```js // 甚至可以更进一步和 tua-storage 配合使用 import TuaStorage from 'tua-storage' +import { getSyncFnMapByApis } from 'tua-api' -const storage = new TuaStorage({ ... }) +// 本地写好的各种接口配置 +import * as apis from '@/apis' + +const tuaStorage = new TuaStorage({ + syncFnMap: getSyncFnMapByApis(apis), +}) const fetchParam = { key: fooApi.bar.key, @@ -103,7 +109,7 @@ const fetchParam = { // ... } -storage +tuaStorage .load(fetchParam) .then(console.log) .catch(console.error) @@ -155,7 +161,7 @@ export default { } ``` -[更多配置请点击这里查看](https://tuateam.github.io/tua-api/config/detail.html) +[更多配置请点击这里查看](https://tuateam.github.io/tua-api/config/common.html) ### 配置导出 最后来看一下 `apis/index.js` 该怎么写: @@ -180,7 +186,8 @@ tuaApi // 链式调用 .use(...) -export const somethingApi = tuaApi.getApi(require('./something').default) +export const fakeGet = tuaApi.getApi(require('./fake-get').default) +export const fakePost = tuaApi.getApi(require('./fake-post').default) ``` 小程序端建议使用 [@tua-mp/cli](https://tuateam.github.io/tua-mp/tua-mp-cli/) 一键生成 api。 @@ -190,15 +197,16 @@ $ tuamp add api ``` ### 配置的构成 -在 `tua-api` 中配置分为三种: +在 `tua-api` 中配置分为四种: -* 默认配置(调用 `new TuaApi({ ... })` 时传递的) -* 公共配置(和 `pathList` 同级的配置) -* 自身配置(`pathList` 数组中的对象上的配置) +* [默认配置(调用 `new TuaApi({ ... })` 时传递的)](https://tuateam.github.io/tua-api/config/default.html) +* [公共配置(和 `pathList` 同级的配置)](https://tuateam.github.io/tua-api/config/common.html) +* [自身配置(`pathList` 数组中的对象上的配置)](https://tuateam.github.io/tua-api/config/self.html) +* [运行配置(在实际调用接口时传递的配置)](https://tuateam.github.io/tua-api/config/runtime.html) 其中优先级自然是: -`自身配置 > 公共配置 > 默认配置` +`默认配置 < 公共配置 < 自身配置 < 运行配置`

👉更多配置点击这里👈 diff --git a/babel.config.js b/babel.config.js index 6c4999f..86b92a1 100644 --- a/babel.config.js +++ b/babel.config.js @@ -14,7 +14,7 @@ module.exports = { ], ], }, - prod: { + production: { presets: [ [ '@babel/preset-env', diff --git a/commitlint.config.js b/commitlint.config.js index 12c1ce8..4962982 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,6 +1,6 @@ module.exports = { // https://www.npmjs.com/package/@commitlint/config-conventional extends: ['@commitlint/config-conventional'], - "rules": { + rules: { }, } diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 1549492..4e502a1 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,43 +1,45 @@ +const { name } = require('../../package.json') + +const description = '🏗 一款可配置的通用 api 请求函数生成工具' + module.exports = { - base: '/tua-api/', + base: '/' + name + '/', locales: { - '/': { - title: 'tua-api', - description: '🏗 一款可配置的通用 api 请求函数生成工具' - } + '/': { title: name, description }, }, head: [ ['link', { rel: 'icon', href: `/logo.png` }], ], + evergreen: true, serviceWorker: true, themeConfig: { repo: 'tuateam/tua-api', docsDir: 'docs', editLinks: true, lastUpdated: '上次更新', + sidebarDepth: 2, editLinkText: '在 GitHub 上编辑此页', - lastUpdated: '上次更新', nav: [ { - text: '快速上手', - link: '/quick-start/', + text: '🌱指南', + link: '/guide/', }, { - text: '配置', + text: '⚙️配置', link: '/config/', }, { - text: '生态系统', + text: '🔥生态系统', items: [ - { text: '本地存储', link: 'https://tuateam.github.io/tua-storage/' }, - { text: '小程序框架', link: 'https://tuateam.github.io/tua-mp/' }, + { text: '📦本地存储', link: 'https://tuateam.github.io/tua-storage/' }, + { text: '🖖小程序框架', link: 'https://tuateam.github.io/tua-mp/' }, ], }, ], sidebar: { - '/quick-start/': [ + '/guide/': [ { - title: '快速上手', + title: '🌱指南', collapsable: false, children: [ 'installation', @@ -51,21 +53,22 @@ module.exports = { ], '/config/': [ { - title: '配置', + title: '⚙️配置', collapsable: false, children: [ '', 'default', - 'detail', - 'own', + 'common', + 'self', + 'runtime', ], }, ], }, serviceWorker: { updatePopup: { - message: 'New content is available.', - buttonText: 'Refresh', + message: 'New content is available.', + buttonText: 'Refresh', }, }, }, diff --git a/docs/README.md b/docs/README.md index ac42578..8d9a9b7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,7 @@ --- home: true actionText: 快速上手 → -actionLink: /quick-start/ +actionLink: /guide/ features: - title: 支持多端 details: 支持 web 端、Node 端和小程序端 @@ -16,17 +16,24 @@ features: footer: MIT Licensed | Copyright © 2018-present StEve Young --- -

- 让我们优雅地调用 api~ -

+

让我们优雅地调用 api~

- Standard - JavaScript Style + + Standard - JavaScript Style +

- Build Status - Coverage Status - Version - License + + Build Status + + + Coverage Status + + + Downloads per month + Version + License +

diff --git a/docs/config/README.md b/docs/config/README.md index a01dc03..44f4a37 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -1,10 +1,11 @@ # 配置说明 -在 `tua-api` 中配置分为三种: +在 `tua-api` 中配置分为四种: -* 默认配置(调用 `new TuaApi({ ... })` 时传递的) -* 公共配置(和 `pathList` 同级的配置) -* 自身配置(`pathList` 数组中的对象上的配置) +* [默认配置(调用 `new TuaApi({ ... })` 时传递的)](./default.md) +* [公共配置(和 `pathList` 同级的配置)](./common.md) +* [自身配置(`pathList` 数组中的对象上的配置)](./self.md) +* [运行配置(在实际调用接口时传递的配置)](./runtime.md) 其中优先级自然是: -`默认配置 < 公共配置 < 自身配置` +`默认配置 < 公共配置 < 自身配置 < 运行配置` diff --git a/docs/config/detail.md b/docs/config/common.md similarity index 96% rename from docs/config/detail.md rename to docs/config/common.md index cb5f3a3..8626f42 100644 --- a/docs/config/detail.md +++ b/docs/config/common.md @@ -1,4 +1,4 @@ -# 详细配置 +# 公共配置 详细地址指的是填写在 `src/apis/foobar.js` 中的一级配置。这部分的配置优先级比默认配置高,但低于各个接口的自身配置。 ## type 请求类型 @@ -37,7 +37,7 @@ export default { } ``` -详情参阅 [快速上手 - mock 章节](../quick-start/mock.md) +详情参阅 [mock 章节](../guide/mock.md) ## prefix 接口中间地址 建议与文件同名,方便维护。 @@ -93,7 +93,7 @@ export default { } ``` -详情参阅:[中间件进阶](../quick-start/middleware.md) +详情参阅:[中间件进阶](../guide/middleware.md) ## beforeFn 发起请求前钩子函数 在请求发起前执行的函数(例如小程序可以通过返回 `header` 传递 `cookie`),因为是通过 `beforeFn().then(...)` 调用,所以注意要返回 Promise。 diff --git a/docs/config/runtime.md b/docs/config/runtime.md new file mode 100644 index 0000000..9843dc3 --- /dev/null +++ b/docs/config/runtime.md @@ -0,0 +1,27 @@ +# 运行配置 +运行配置指的是在接口实际调用时通过第二个参数传递的配置。这部分的配置优先级最高。 + +以下接口以导出为 `exampleApi` 为例。 + +```js +exampleApi.foo( + { ... }, // 第一个参数传接口参数 + { ... } // 第二个参数传接口配置 +) +``` + +## callbackName 回调函数名称 +在通过 jsonp 发起请求时,为了使用缓存一般需要添加 callbackName,但是注意重复请求时会报错。 + +```js +exampleApi.foo( + { ... }, + { callbackName: `foo` } +) +``` + +## 其他参数 +公共配置一节中的所有参数(除了 `pathList` 外),以及自身配置一节中的所有参数均有效,且优先级最高。 + +* 详情参阅[公共配置](./common.md) +* 详情参阅[自身配置](./self.md) diff --git a/docs/config/own.md b/docs/config/self.md similarity index 87% rename from docs/config/own.md rename to docs/config/self.md index 74adc90..a2ee314 100644 --- a/docs/config/own.md +++ b/docs/config/self.md @@ -1,5 +1,5 @@ # 自身配置 -自身配置指的是填写在 `pathList` 中的配置。这部分的配置优先级最高。 +自身配置指的是填写在 `pathList` 中的配置。这部分的配置优先级比公共配置高,但低于各个接口的运行配置。 以下接口以导出为 `exampleApi` 为例。 @@ -67,6 +67,6 @@ export default { 有时某个接口正好不需要上一级中 `commonParams` 的参数。那么可以传递 `null` 覆盖上一级中的 `commonParams`。 ## 其他参数 -上一节中的所有参数(除了 `pathList` 外)均有效,且优先级最高。 +上一节中的所有参数(除了 `pathList` 外)均有效。 -详情参阅上一节 [详细配置](./detail.md) +详情参阅上一节 [公共配置](./common.md) diff --git a/docs/quick-start/README.md b/docs/guide/README.md similarity index 85% rename from docs/quick-start/README.md rename to docs/guide/README.md index fd864ac..6c1ec3e 100644 --- a/docs/quick-start/README.md +++ b/docs/guide/README.md @@ -53,7 +53,7 @@ const fetchParam = { // ... } -storage +tuaStorage .load(fetchParam) .then(console.log) .catch(console.error) @@ -62,15 +62,15 @@ storage ## 怎么写 `api` 配置? 拿以下 api 地址举例: -* https://example-base.com/foo/bar/something/create -* https://example-base.com/foo/bar/something/modify -* https://example-base.com/foo/bar/something/delete +* `https://example-base.com/foo/bar/something/create` +* `https://example-base.com/foo/bar/something/modify` +* `https://example-base.com/foo/bar/something/delete` ### 地址结构划分 以上地址,一般将其分为`3`部分: -* host: `https://example-base.com/` -* prefix: `foo/bar/something` +* host: `'https://example-base.com/'` +* prefix: `'foo/bar/something'` * pathList: `[ 'create', 'modify', 'delete' ]` ### 文件结构 @@ -91,7 +91,7 @@ storage export default { // 请求的公用服务器地址 - host: 'https://example-base.com//', + host: 'https://example-base.com/', // 请求的中间路径 prefix: 'foo/bar/something', @@ -105,7 +105,7 @@ export default { } ``` -[更多配置请点击这里查看](../config/detail.md) +[更多配置请点击这里查看](../config/common.md) ### 配置导出 最后来看一下 `apis/index.js` 该怎么写: @@ -130,7 +130,8 @@ tuaApi // 链式调用 .use(...) -export const somethingApi = tuaApi.getApi(require('./something').default) +export const fakeGet = tuaApi.getApi(require('./fake-get').default) +export const fakePost = tuaApi.getApi(require('./fake-post').default) ``` ::: tip diff --git a/docs/quick-start/export-utils.md b/docs/guide/export-utils.md similarity index 100% rename from docs/quick-start/export-utils.md rename to docs/guide/export-utils.md diff --git a/docs/quick-start/installation.md b/docs/guide/installation.md similarity index 100% rename from docs/quick-start/installation.md rename to docs/guide/installation.md diff --git a/docs/quick-start/middleware.md b/docs/guide/middleware.md similarity index 100% rename from docs/quick-start/middleware.md rename to docs/guide/middleware.md diff --git a/docs/quick-start/mock.md b/docs/guide/mock.md similarity index 100% rename from docs/quick-start/mock.md rename to docs/guide/mock.md diff --git a/package.json b/package.json index d233d66..66a1c29 100644 --- a/package.json +++ b/package.json @@ -1,30 +1,34 @@ { "name": "tua-api", - "version": "0.4.0", - "main": "dist/TuaApi.umd.js", - "module": "dist/TuaApi.es.js", - "jsnext:main": "dist/TuaApi.es.js", - "description": "A common tool helps converting configs to api functions", + "version": "1.0.0", + "description": "🏗 A common tool helps converting configs to api functions", + "main": "dist/TuaApi.cjs.js", + "module": "dist/TuaApi.esm.js", + "unpkg": "dist/TuaApi.umd.js", + "jsdelivr": "dist/TuaApi.umd.js", "files": [ - "src/", - "dist/", - "examples/" + "src", + "dist", + "examples" ], "scripts": { - "docs": "vuepress dev docs -h localhost", - "docs:build": "vuepress build docs", "cov": "open coverage/lcov-report/index.html", - "tdd": "cross-env NODE_ENV=test jest --watch", - "test": "cross-env NODE_ENV=test jest && codecov", - "lint": "eslint --fix src/ test/", - "deploy": "yarn docs:build && gh-pages -m \"[ci skip]\" -d docs/.vuepress/dist", + "docs": "vuepress dev docs", + "docs:build": "vuepress build docs", + "lint": "eslint --fix . docs/.vuepress/ --ignore-path .gitignore", + "test": "cross-env NODE_ENV=test jest", + "test:tdd": "cross-env NODE_ENV=test jest --watch", "prebuild": "rimraf dist/* & npm run test", - "build": "cross-env NODE_ENV=prod rollup -c", - "pub": "npm run build && npm publish" + "build": "cross-env NODE_ENV=production rollup -c", + "deploy": "npm run docs:build && gh-pages -m \"[ci skip]\" -d docs/.vuepress/dist", + "next:pm": "npm --no-git-tag-version version preminor", + "next:pr": "npm --no-git-tag-version version prerelease", + "pub": "npm run build && npm publish", + "pub:n": "npm run build && npm publish --tag next" }, "husky": { "hooks": { - "pre-push": "lint-staged", + "pre-push": "npm test", "pre-commit": "lint-staged", "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } @@ -41,6 +45,10 @@ "transform": { "^.+\\.js$": "babel-jest" }, + "moduleNameMapper": { + "@/(.*)$": "/src/$1", + "@examples/(.*)$": "/examples/$1" + }, "collectCoverage": true, "collectCoverageFrom": [ "src/**" @@ -50,46 +58,51 @@ "package.json", "dist/*" ], - "author": "StEve Young", - "license": "MIT", + "dependencies": { + "axios": "^0.18.0", + "fetch-jsonp": "^1.1.3", + "koa-compose": "^4.1.0" + }, "devDependencies": { - "@babel/core": "^7.2.0", + "@babel/core": "^7.3.3", "@babel/plugin-external-helpers": "^7.2.0", - "@babel/plugin-proposal-decorators": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.2.0", - "@babel/preset-env": "^7.2.0", - "@commitlint/cli": "^7.2.1", - "@commitlint/config-conventional": "^7.1.2", - "axios-mock-adapter": "^1.15.0", + "@babel/plugin-proposal-decorators": "^7.3.0", + "@babel/plugin-proposal-object-rest-spread": "^7.3.2", + "@babel/preset-env": "^7.3.1", + "@commitlint/cli": "^7.5.2", + "@commitlint/config-conventional": "^7.5.0", + "axios-mock-adapter": "^1.16.0", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^10.0.1", - "babel-jest": "^23.6.0", - "codecov": "^3.1.0", + "babel-jest": "^24.1.0", + "codecov": "^3.2.0", "cross-env": "^5.2.0", - "eslint": "^5.10.0", + "eslint": "^5.14.0", "eslint-config-standard": "^12.0.0", - "eslint-plugin-import": "^2.14.0", - "eslint-plugin-node": "^8.0.0", + "eslint-plugin-import": "^2.16.0", + "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^4.0.1", "eslint-plugin-standard": "^4.0.0", "gh-pages": "^2.0.1", - "husky": "^1.2.0", - "jest": "^23.6.0", - "lint-staged": "^8.1.0", - "rimraf": "^2.6.2", - "rollup": "^0.67.4", - "rollup-plugin-babel": "^4.0.3", + "husky": "^1.3.1", + "jest": "^24.1.0", + "lint-staged": "^8.1.4", + "rimraf": "^2.6.3", + "rollup": "^1.2.2", + "rollup-plugin-babel": "^4.3.2", "rollup-plugin-commonjs": "^9.2.0", "rollup-plugin-eslint": "^5.0.0", "rollup-plugin-json": "^3.1.0", "rollup-plugin-node-resolve": "^4.0.0", "rollup-plugin-replace": "^2.1.0", - "rollup-plugin-uglify": "^6.0.0", - "vuepress": "^0.14.8" + "rollup-plugin-uglify": "^6.0.2", + "vuepress": "^1.0.0-alpha.39" }, - "dependencies": { - "axios": "^0.18.0", - "fetch-jsonp": "^1.1.3", - "koa-compose": "^4.1.0" - } + "repository": { + "type": "git", + "url": "git+https://github.com/tuateam/tua-api.git" + }, + "homepage": "https://tuateam.github.io/tua-api/", + "author": "StEve Young", + "license": "MIT" } diff --git a/rollup.config.js b/rollup.config.js index 3d1c500..7172795 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -6,14 +6,27 @@ import { eslint } from 'rollup-plugin-eslint' import { uglify } from 'rollup-plugin-uglify' import nodeResolve from 'rollup-plugin-node-resolve' +import pkg from './package.json' + +const input = `src/TuaApi.js` +const banner = `/* ${pkg.name} version ${pkg.version} */` + const output = { - es: { - file: 'dist/TuaApi.es.js', - format: 'es', + cjs: { + file: pkg.main, + banner, + format: 'cjs', + exports: 'named', + }, + esm: { + file: pkg.module, + banner, + format: 'esm', }, umd: { file: 'dist/TuaApi.umd.js', name: 'TuaApi', + banner, format: 'umd', exports: 'named', globals: { @@ -26,21 +39,21 @@ const plugins = [ eslint(), json(), nodeResolve(), - babel(), commonjs(), + babel(), replace({ - 'process.env.NODE_ENV': JSON.stringify('prod'), + 'process.env.NODE_ENV': JSON.stringify('production'), }), ] const external = ['axios', 'fetch-jsonp'] export default [{ - input: 'src/TuaApi.js', - output: [ output.es, output.umd ], + input, + output: [ output.cjs, output.esm, output.umd ], plugins, external, }, { - input: 'src/TuaApi.js', + input, output: { ...output.umd, file: 'dist/TuaApi.umd.min.js', diff --git a/src/TuaApi.js b/src/TuaApi.js index 81e6e6e..4754784 100644 --- a/src/TuaApi.js +++ b/src/TuaApi.js @@ -10,6 +10,7 @@ import { apiConfigToReqFnParams, } from './utils' import { + ERROR_STRINGS, VALID_REQ_TYPES, } from './constants' import { @@ -18,9 +19,9 @@ import { getFetchJsonpPromise, } from './adapters/' import { + setFullUrlMiddleware, formatResDataMiddleware, recordReqTimeMiddleware, - updateFullUrlMiddleware, recordStartTimeMiddleware, formatReqParamsMiddleware, } from './middlewareFns' @@ -52,7 +53,7 @@ class TuaApi { this.jsonpOptions = jsonpOptions this.defaultErrorData = defaultErrorData - this._checkReqType() + this._checkReqType(this.reqType) return this } @@ -66,7 +67,7 @@ class TuaApi { */ use (fn) { if (typeof fn !== 'function') { - throw TypeError('middleware must be a function!') + throw TypeError(ERROR_STRINGS.middleware) } this.middleware.push(fn) @@ -93,7 +94,7 @@ class TuaApi { * @param {Object} options * @param {String} options.url 接口地址 * @param {String} options.type 接口请求类型 get/post... - * @param {String} options.fullUrl 完整接口地址() + * @param {String} options.fullUrl 完整接口地址 * @param {String} options.path 接口路径名称 * @return {Promise} */ @@ -109,10 +110,7 @@ class TuaApi { ...rest }) { // check type - if (VALID_REQ_TYPES.indexOf(reqType) === -1) { - logger.error(`reqType 的有效值为: ${VALID_REQ_TYPES.join(', ')}!`) - throw Error('invalid reqType') - } + this._checkReqType(reqType) // mock data if (rest.mock) { @@ -153,11 +151,10 @@ class TuaApi { /** * 检查 reqType 是否合法 */ - _checkReqType () { - if (VALID_REQ_TYPES.indexOf(this.reqType) === -1) { - logger.error(`invalid reqType: ${this.reqType}, support these reqType: ${VALID_REQ_TYPES}`) - throw TypeError(`invalid reqType`) - } + _checkReqType (reqType) { + if (VALID_REQ_TYPES.indexOf(reqType) !== -1) return + + throw TypeError(ERROR_STRINGS.reqTypeFn(reqType)) } /** @@ -177,8 +174,8 @@ class TuaApi { formatReqParamsMiddleware, // 业务侧中间件函数数组 ...middlewareFns, - // 更新请求参数 - updateFullUrlMiddleware, + // 生成 fullUrl 参数 + setFullUrlMiddleware, // 统一转换响应数据为对象 formatResDataMiddleware, // 记录结束时间 @@ -192,7 +189,7 @@ class TuaApi { data: { ...this.defaultErrorData }, error, })) - .then((res) => { ctx.res = res }), + .then(res => { ctx.res = res }), ]) } @@ -245,25 +242,27 @@ class TuaApi { /** * 被业务侧调用的函数 * @param {Object} args 接口参数(覆盖默认值) - * @param {Object} options - * @param {String} options.callbackName 自定义回调函数名称(用于 jsonp) + * @param {Object} runtimeOptions 运行时配置 * @return {Promise} */ const apiFn = ( args = {}, - { callbackName = `${path}Callback` } = {} + runtimeOptions = {} ) => { // args 可能为 null args = args === null ? {} : args + // 最终的运行时配置,runtimeOptions 有最高优先级 + const runtimeParams = { type, path, params, prefix, apiName, fullPath, ...rest, ...runtimeOptions } + + // 自定义回调函数名称(用于 jsonp) + runtimeParams.callbackName = runtimeParams.callbackName || `${runtimeParams.path}Callback` + // 请求的上下文信息 const ctx = { - req: { args, type, path, params, prefix, apiName, fullPath, callbackName, reqFnParams: {}, mock: apiFn.mock, ...rest }, + req: { args, mock: apiFn.mock, reqFnParams: {}, ...runtimeParams }, } - // 中间件函数 - const middlewareFn = this._getMiddlewareFn(middleware, useGlobalMiddleware) - // 执行完 beforeFn 后执行的函数 const beforeFnCb = (rArgs = {}) => { // 兼容小程序传递请求头(建议还是放在中间件中) @@ -276,21 +275,17 @@ class TuaApi { // 合并 beforeFn 中传入的 params ctx.req.params = Array.isArray(params) ? rArgs.params - : { - ...params, - // 可以通过给 beforeFn - // 添加 params 返回值添加通用参数 - ...rArgs.params, - } + // 可以通过给 beforeFn 添加 params 返回值来添加通用参数 + : { ...params, ...rArgs.params } } + // 中间件函数 + const middlewareFn = this._getMiddlewareFn(middleware, useGlobalMiddleware) + return beforeFn() .then(beforeFnCb) - // 执行请求中间件函数 .then(() => middlewareFn(ctx)) - // 请求执行完成后的钩子 .then(() => afterFn([ctx.res.data, ctx])) - // 抛出错误或响应数据 .then((data) => ctx.res.error ? Promise.reject(ctx.res.error) : data || ctx.res.data diff --git a/src/adapters/axios.js b/src/adapters/axios.js index ae7791c..f6810d9 100644 --- a/src/adapters/axios.js +++ b/src/adapters/axios.js @@ -1,7 +1,7 @@ +import axios from 'axios' + +import { DEFAULT_HEADER } from '../constants' import { logger, getParamStrFromObj } from '../utils' -import { - DEFAULT_HEADER, -} from '../constants' // 获取使用 axios 发起请求后的 promise 对象 export const getAxiosPromise = ({ @@ -19,7 +19,7 @@ export const getAxiosPromise = ({ logger.log(`Req Data:`, data) } - return require('axios')({ + return axios({ url, data, method, diff --git a/src/adapters/jsonp.js b/src/adapters/jsonp.js index 4678427..82d1a0d 100644 --- a/src/adapters/jsonp.js +++ b/src/adapters/jsonp.js @@ -1,10 +1,12 @@ +import fetchJsonp from 'fetch-jsonp' + import { logger } from '../utils' // 获取发起 jsonp 请求后的 promise 对象 export const getFetchJsonpPromise = ({ url, jsonpOptions }) => { logger.log(`Jsonp Url: ${url}`) - return require('fetch-jsonp')(url, jsonpOptions) + return fetchJsonp(url, jsonpOptions) .then(res => res.json()) .then(data => ({ data })) } diff --git a/src/adapters/wx.js b/src/adapters/wx.js index d9411a3..6319513 100644 --- a/src/adapters/wx.js +++ b/src/adapters/wx.js @@ -1,5 +1,5 @@ import { logger, promisifyWxApi } from '../utils' -import { WX_VALID_METHODS } from '../constants' +import { ERROR_STRINGS, WX_VALID_METHODS } from '../constants' export const getWxPromise = ({ url, @@ -27,7 +27,7 @@ export const getWxPromise = ({ isShowLoading && showLoadingFn() if (WX_VALID_METHODS.indexOf(method) === -1) { - return Promise.reject(Error(`Unknown Method: ${method}!!!`)) + return Promise.reject(Error(ERROR_STRINGS.unknownMethodFn(method))) } return promisifyWxApi(wx.request)({ diff --git a/src/constants.js b/src/constants.js index 7527052..1d9a889 100644 --- a/src/constants.js +++ b/src/constants.js @@ -4,9 +4,23 @@ const VALID_REQ_TYPES = ['wx', 'axios', 'jsonp'] // 小程序中合法的请求方法 const WX_VALID_METHODS = ['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT'] +// 默认请求头 const DEFAULT_HEADER = { 'Content-Type': 'application/x-www-form-urlencoded' } +// 错误信息 +const ERROR_STRINGS = { + noData: `no data!`, + argsType: `the first parameter must be an object!`, + middleware: `middleware must be a function!`, + + reqTypeFn: (reqType) => `invalid reqType: "${reqType}", ` + + `support these reqTypes: ["${VALID_REQ_TYPES.join('", "')}"].`, + unknownMethodFn: method => `unknown method: "${method}"!`, + requiredParamFn: (apiName, param) => `${apiName} must pass required param: "${param}"!`, +} + export { + ERROR_STRINGS, DEFAULT_HEADER, VALID_REQ_TYPES, WX_VALID_METHODS, diff --git a/src/middlewareFns.js b/src/middlewareFns.js index b843362..de95dc5 100644 --- a/src/middlewareFns.js +++ b/src/middlewareFns.js @@ -1,3 +1,4 @@ +import { ERROR_STRINGS } from './constants' import { checkArrayParams, getParamStrFromObj, @@ -37,7 +38,7 @@ const recordReqTimeMiddleware = (ctx, next) => { const formatResDataMiddleware = (ctx, next) => next().then(() => { const jsonData = ctx.res.data - if (!jsonData) return Promise.reject(Error('没有数据')) + if (!jsonData) return Promise.reject(Error(ERROR_STRINGS.noData)) if (Array.isArray(jsonData)) { const [ code, data, msg ] = jsonData @@ -63,7 +64,7 @@ const formatReqParamsMiddleware = (ctx, next) => { } = ctx.req if (typeof args !== 'object') { - throw Error(`请检查参数是否为对象!`) + throw TypeError(ERROR_STRINGS.argsType) } checkArrayParams(ctx.req) @@ -73,17 +74,13 @@ const formatReqParamsMiddleware = (ctx, next) => { ? { ...commonParams, ...args } : { ...getDefaultParamObj(ctx.req), ...args } - // 请求地址,用于 post 请求 + // 请求地址 const url = host + fullPath - // 完整请求地址,将参数拼在 url 上,用于 get 请求 - const fullUrl = url + '?' + getParamStrFromObj(reqParams) - ctx.req.reqFnParams = { ...ctx.req.reqFnParams, ...rest, url, - fullUrl, reqParams, } @@ -91,22 +88,27 @@ const formatReqParamsMiddleware = (ctx, next) => { } /** - * 更新请求 fullUrl 参数,因为可能请求参数被别的中间件改了 + * 设置请求 fullUrl 参数 * @param {Object} ctx 上下文对象 * @param {Function} next 转移控制权给下一个中间件的函数 */ -const updateFullUrlMiddleware = (ctx, next) => { +const setFullUrlMiddleware = (ctx, next) => { const { url, reqParams } = ctx.req.reqFnParams - ctx.req.reqFnParams.fullUrl = url + '?' + getParamStrFromObj(reqParams) + const paramsStr = getParamStrFromObj(reqParams) + + // 完整请求地址,将参数拼在 url 上,用于 get 请求 + ctx.req.reqFnParams.fullUrl = paramsStr + ? url + '?' + paramsStr + : url return next() } export { + setFullUrlMiddleware, recordReqTimeMiddleware, formatResDataMiddleware, - updateFullUrlMiddleware, recordStartTimeMiddleware, formatReqParamsMiddleware, } diff --git a/src/utils/params.js b/src/utils/params.js index 0576fa3..af179e8 100644 --- a/src/utils/params.js +++ b/src/utils/params.js @@ -6,6 +6,7 @@ import { reduce, } from './fp' import { logger } from './logger' +import { ERROR_STRINGS } from '../constants' /** * 将对象序列化为 queryString 的形式 @@ -59,7 +60,7 @@ const getDefaultParamObj = ({ args[key] == null if (isRequiredValUndefined) { - throw Error(`${apiName}:必须传递参数 ${key}!请检查!`) + throw Error(ERROR_STRINGS.requiredParamFn(apiName, key)) } return { [key]: val } diff --git a/test/__tests__/axios.test.js b/test/__tests__/axios.test.js index c876a24..a80b84d 100644 --- a/test/__tests__/axios.test.js +++ b/test/__tests__/axios.test.js @@ -1,7 +1,8 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' -import { fakeGetApi, fakePostApi } from '../../examples/apis-web/' +import { ERROR_STRINGS } from '@/constants' +import { fakeGetApi, fakePostApi } from '@examples/apis-web/' const mock = new MockAdapter(axios) @@ -28,7 +29,7 @@ describe('mock data', () => { describe('error handling', () => { test('non-object params', () => { - return expect(fakePostApi.ap('a')).rejects.toEqual(Error('请检查参数是否为对象!')) + return expect(fakePostApi.ap('a')).rejects.toEqual(TypeError(ERROR_STRINGS.argsType)) }) test('error', () => { @@ -38,7 +39,8 @@ describe('error handling', () => { }) test('must pass required params', () => { - return expect(fakePostApi.op()).rejects.toEqual(Error(`op:必须传递参数 param3!请检查!`)) + return expect(fakePostApi.op()) + .rejects.toEqual(Error(ERROR_STRINGS.requiredParamFn('op', 'param3'))) }) }) @@ -50,6 +52,18 @@ describe('fake get requests', () => { expect(resData).toEqual(data) }) + + test('runtime get', async () => { + const data = { code: 0, data: 'runtime get' } + mock.onGet(reqAPUrl).reply(200, data) + const resData = await fakePostApi.ap(null, { + type: 'get', + reqType: 'axios', + commonParams: null, + }) + + expect(resData).toEqual(data) + }) }) describe('fake post requests', () => { diff --git a/test/__tests__/core.test.js b/test/__tests__/core.test.js index 01d65bb..9911f06 100644 --- a/test/__tests__/core.test.js +++ b/test/__tests__/core.test.js @@ -1,13 +1,14 @@ -import TuaApi from '../../src/TuaApi' +import TuaApi from '@/TuaApi' +import { ERROR_STRINGS } from '@/constants' describe('error handling', () => { const tuaApi = new TuaApi() test('non-function middleware', () => { - expect(() => tuaApi.use('')).toThrow(TypeError('middleware must be a function!')) + expect(() => tuaApi.use('')).toThrow(TypeError(ERROR_STRINGS.middleware)) }) test('unknown reqType', () => { - expect(() => new TuaApi({ reqType: '' })).toThrow(TypeError(`invalid reqType`)) + expect(() => new TuaApi({ reqType: '' })).toThrow(TypeError(ERROR_STRINGS.reqTypeFn(''))) }) }) diff --git a/test/__tests__/exportUtils.test.js b/test/__tests__/exportUtils.test.js index 558b1fb..14a6108 100644 --- a/test/__tests__/exportUtils.test.js +++ b/test/__tests__/exportUtils.test.js @@ -1,7 +1,7 @@ import { getSyncFnMapByApis, getPreFetchFnKeysBySyncFnMap, -} from '../../src/exportUtils' +} from '@/exportUtils' const noop1 = () => {} noop1.key = 'noop1' diff --git a/test/__tests__/fp.test.js b/test/__tests__/fp.test.js index a6805e8..5ff8f1e 100644 --- a/test/__tests__/fp.test.js +++ b/test/__tests__/fp.test.js @@ -9,7 +9,7 @@ import { compose, flatten, mergeAll, -} from '../../src/utils/' +} from '@/utils/' describe('functional programming functions', () => { test('map', () => { diff --git a/test/__tests__/jsonp.test.js b/test/__tests__/jsonp.test.js index 1ada724..2bb2994 100644 --- a/test/__tests__/jsonp.test.js +++ b/test/__tests__/jsonp.test.js @@ -1,6 +1,7 @@ import fetchJsonp from 'fetch-jsonp' -import { fakeGetApi } from '../../examples/apis-web/' +import { fakeGetApi } from '@examples/apis-web/' +import { ERROR_STRINGS } from '@/constants' jest.mock('fetch-jsonp') @@ -40,7 +41,7 @@ describe('fake jsonp requests', () => { test('invalid-req-type', () => { return expect(fakeGetApi.irt({ param3: 'steve' })) - .rejects.toEqual(Error('invalid reqType')) + .rejects.toEqual(TypeError(ERROR_STRINGS.reqTypeFn('foobar'))) }) test('data should be passed through afterFn', async () => { diff --git a/test/__tests__/utils.test.js b/test/__tests__/utils.test.js index 45ba134..1324078 100644 --- a/test/__tests__/utils.test.js +++ b/test/__tests__/utils.test.js @@ -1,10 +1,11 @@ +import { ERROR_STRINGS } from '@/constants' import { promisifyWxApi, checkArrayParams, getDefaultParamObj, getParamStrFromObj, apiConfigToReqFnParams, -} from '../../src/utils' +} from '@/utils' test('promisifyWxApi', () => { const fn = ({ success }) => setTimeout(() => success('test'), 0) @@ -40,12 +41,12 @@ test('getDefaultParamObj', () => { expect(() => getDefaultParamObj({ params: { b: { required: true } }, apiName: 'steve', - })).toThrow('steve:必须传递参数 b!请检查!') + })).toThrow(Error(ERROR_STRINGS.requiredParamFn('steve', 'b'))) expect(() => getDefaultParamObj({ params: { c: { isRequired: true } }, apiName: 'steve', - })).toThrow('steve:必须传递参数 c!请检查!') + })).toThrow(Error(ERROR_STRINGS.requiredParamFn('steve', 'c'))) }) test('getParamStrFromObj', () => { diff --git a/test/__tests__/wx.test.js b/test/__tests__/wx.test.js index 3f4c473..64f572a 100644 --- a/test/__tests__/wx.test.js +++ b/test/__tests__/wx.test.js @@ -1,7 +1,9 @@ import '../__mocks__/wxMock' -import TuaApi from '../../src/TuaApi' -import fakeWx from '../../examples/apis-mp/fake-wx' -import { mockApi, fakeWxApi } from '../../examples/apis-mp/' +import TuaApi from '@/TuaApi' +import { ERROR_STRINGS } from '@/constants' + +import fakeWx from '@examples/apis-mp/fake-wx' +import { mockApi, fakeWxApi } from '@examples/apis-mp/' const testObjData = { code: 0, data: 'object data' } const testArrData = [ 0, 'array data' ] @@ -101,7 +103,7 @@ describe('fake wx requests', () => { test('no-beforeFn', () => { return expect(fakeWxApi.noBeforeFn()) - .rejects.toEqual(Error('没有数据')) + .rejects.toEqual(Error(ERROR_STRINGS.noData)) }) test('hide-loading', async () => { @@ -125,7 +127,7 @@ describe('fake wx requests', () => { test('unknown-type', () => { return expect(fakeWxApi.unknownType()) - .rejects.toEqual(Error(`Unknown Method: FOO!!!`)) + .rejects.toEqual(Error(ERROR_STRINGS.unknownMethodFn('FOO'))) }) test('nav-loading', async () => { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ba4c70 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "allowJs": true, + "baseUrl": ".", + "paths":{ + "@/*": ["src/*"], + "@examples/*": ["examples/*"] + } + }, + "exclude": ["node_modules", "dist"] +}