未完待续...

pull/1/head
954270063@qq.com 2020-04-14 23:45:21 +08:00
parent 2d475c89e5
commit fdab3afb48
27 changed files with 5810 additions and 0 deletions

20
.babelrc Normal file
View File

@ -0,0 +1,20 @@
{
"presets": [
"@babel/preset-typescript",
[
"@babel/preset-env",
{
"targets": {
"browsers": [
"ie >= 11"
]
},
"modules": false,
"loose": true
}
]
],
"plugins": [
"@babel/plugin-proposal-optional-chaining"
]
}

5
.eslintignore Normal file
View File

@ -0,0 +1,5 @@
/node_modules
/package
/types
/coverage
/rollup.config.js

27
.eslintrc Normal file
View File

@ -0,0 +1,27 @@
/*
* @Author: early-autumn
* @Date: 2020-03-06 20:35:23
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-05 14:17:08
*/
{
"parser": "@typescript-eslint/parser",
"extends": [
"plugin:@typescript-eslint/recommended",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
"plugins": [
"@typescript-eslint",
"prettier"
],
"env": {
"node": true,
"es6": true
},
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-namespace": "off"
}
}

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/node_modules
/package
/types
/coverage
/yarn-error.log

10
.prettierrc Normal file
View File

@ -0,0 +1,10 @@
{
"printWidth": 120,
"tabWidth": 2,
"singleQuote": true,
"semi": true,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always",
"parser": "typescript"
}

9
.travis.yml Normal file
View File

@ -0,0 +1,9 @@
language: node_js
node_js: stable
cache:
directories:
- node_modules
install:
- npm install
script:
- npm run coverage

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 初秋
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

6
index.js Normal file
View File

@ -0,0 +1,6 @@
/*
* @Author: early-autumn
* @Date: 2020-04-05 01:56:05
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-05 01:56:06
*/

70
package.json Normal file
View File

@ -0,0 +1,70 @@
{
"name": "axios-miniprogram",
"version": "1.0.0",
"description": "微信小程序专用请求库。",
"main": "package/index.js",
"miniprogram": "package",
"types": "types/index.d.ts",
"files": [
"package",
"types"
],
"scripts": {
"build": "rollup -c",
"lint": "eslint --ext ts --fix src test",
"prettier": "prettier --write --config .prettierrc \"{src,test}/**/*.{js,ts}\"",
"test": "jest",
"test:watch": "yarn test -- --watch",
"test:cov": "yarn test -- --coverage",
"coverage": "yarn test:cov --coverageReporters=text-lcov | coveralls"
},
"husky": {
"hooks": {
"pre-commit": "yarn lint && yarn prettier && yarn test && git add ."
}
},
"jest": {
"preset": "ts-jest",
"globals": {
"ts-jest": {
"babelConfig": "test/.babelrc"
}
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/early-autumn/axios-miniprogram.git"
},
"keywords": [],
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/early-autumn/axios-miniprogram/issues"
},
"homepage": "https://github.com/early-autumn/axios-miniprogram#readme",
"devDependencies": {
"@babel/core": "^7.8.7",
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
"@babel/plugin-transform-modules-commonjs": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.8.3",
"@babel/preset-env": "^7.8.7",
"@babel/preset-typescript": "^7.8.3",
"@babel/runtime": "^7.8.7",
"@types/jest": "^25.1.3",
"@typescript-eslint/eslint-plugin": "^2.22.0",
"@typescript-eslint/parser": "^2.22.0",
"coveralls": "^3.0.9",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-prettier": "^3.1.2",
"husky": "^4.2.3",
"jest": "^25.1.0",
"miniprogram-api-typings": "^2.10.3",
"prettier": "^1.19.1",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-typescript2": "^0.26.0",
"ts-jest": "^25.2.1",
"typescript": "^3.8.3"
}
}

45
rollup.config.js Normal file
View File

@ -0,0 +1,45 @@
/*
* @Author: early-autumn
* @Date: 2020-03-06 20:40:30
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-13 15:24:07
*/
import fs from 'fs';
import path from 'path';
import nodeResolve from 'rollup-plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import typescript from 'rollup-plugin-typescript2';
function removeDir(name) {
try {
if (fs.statSync(name).isFile()) {
fs.unlinkSync(name);
} else {
fs.readdirSync(name).forEach((dir) => removeDir(path.join(name, dir)));
fs.rmdirSync(name);
}
} catch (err) {}
}
export default function() {
removeDir('package');
removeDir('types');
return {
input: 'src/index.ts',
output: {
file: 'package/index.js',
format: 'cjs',
indent: false,
},
plugins: [
nodeResolve({
extensions: ['.ts'],
}),
typescript({ useTsconfigDeclarationDir: true }),
babel({
extensions: ['.ts'],
}),
],
};
}

21
src/cancel/Cancel.ts Normal file
View File

@ -0,0 +1,21 @@
/*
* @Author: early-autumn
* @Date: 2020-04-13 21:14:53
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 13:41:45
*/
import { Cancel } from '../types';
export default class CancelStatic implements Cancel {
message?: string;
constructor(message?: string) {
this.message = message;
}
toString() {
const message = this.message ? `: ${this.message}` : '';
return `Cancel${message}`;
}
}

63
src/cancel/CancelToken.ts Normal file
View File

@ -0,0 +1,63 @@
/*
* @Author: early-autumn
* @Date: 2020-04-13 20:00:08
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 19:17:53
*/
import { CancelToken, CancelAction, CancelExecutor, CancelTokenSource } from '../types';
import Cancel from './Cancel';
export default class CancelTokenStatic implements CancelToken {
reason?: Cancel;
listener: Promise<Cancel>;
constructor(executor: CancelExecutor) {
let action!: CancelAction;
this.listener = new Promise<Cancel>((resolve) => {
action = (message) => {
// 防止重复取消
if (this.reason) {
return;
}
this.reason = new Cancel(message);
resolve(this.reason);
};
});
executor(action);
}
throwIfRequested() {
if (this.reason) {
throw this.reason;
}
}
/**
* CancelTokenSource
*
* CancelTokenSource.token CancelToken
*
* CancelTokenSource.cancel CancelAction
*
* CancelTokenSource.cancel('这里可以填写您的错误信息')
*
* CancelTokenSource.token
*/
static source(): CancelTokenSource {
let cancel!: CancelAction;
const token = new CancelTokenStatic(function executor(action) {
cancel = action;
});
return {
token,
cancel,
};
}
}

16
src/cancel/isCancel.ts Normal file
View File

@ -0,0 +1,16 @@
/*
* @Author: early-autumn
* @Date: 2020-04-14 09:23:25
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 19:18:58
*/
import Cancel from './Cancel';
/**
*
*
* @param value
*/
export default function isCancel(value: any) {
return value && value instanceof Cancel;
}

54
src/core/Axios.ts Normal file
View File

@ -0,0 +1,54 @@
/*
* @Author: early-autumn
* @Date: 2020-04-13 18:00:27
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 23:40:52
*/
import { Params, Data, AxiosRequestConfig, Axios, AxiosMethodConfig, AxiosPromise, Method } from '../types';
import dispatchRequest from './dispatchRequest';
export default class AxiosStatic implements Axios {
request(config: AxiosRequestConfig): AxiosPromise {
return dispatchRequest(config);
}
options(url: string, params?: Params, config?: AxiosMethodConfig): AxiosPromise {
return this._requestMethodWithoutParams('options', url, params, config);
}
get(url: string, params?: Params, config?: AxiosMethodConfig): AxiosPromise {
return this._requestMethodWithoutParams('get', url, params, config);
}
head(url: string, params?: Params, config?: AxiosMethodConfig): AxiosPromise {
return this._requestMethodWithoutParams('head', url, params, config);
}
post(url: string, data?: Data, config?: AxiosMethodConfig): AxiosPromise {
return this._requestMethodWithoutData('post', url, data, config);
}
put(url: string, data?: Data, config?: AxiosMethodConfig): AxiosPromise {
return this._requestMethodWithoutData('put', url, data, config);
}
delete(url: string, params?: Params, config?: AxiosMethodConfig): AxiosPromise {
return this._requestMethodWithoutParams('delete', url, params, config);
}
trace(url: string, params?: Params, config?: AxiosMethodConfig): AxiosPromise {
return this._requestMethodWithoutParams('trace', url, params, config);
}
connect(url: string, params?: Params, config?: AxiosMethodConfig): AxiosPromise {
return this._requestMethodWithoutParams('connect', url, params, config);
}
_requestMethodWithoutParams(method: Method, url: string, params?: Params, config: AxiosMethodConfig = {}) {
return this.request({ ...config, method, url, params });
}
_requestMethodWithoutData(method: Method, url: string, data?: Data, config: AxiosMethodConfig = {}) {
return this.request({ ...config, method, url, data });
}
}

27
src/core/createError.ts Normal file
View File

@ -0,0 +1,27 @@
/*
* @Author: early-autumn
* @Date: 2020-04-14 22:23:39
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 22:55:33
*/
import { AxiosRequestConfig, AxiosResponse } from '../types';
class AxiosError extends Error {
isAxiosError = true;
config: AxiosRequestConfig;
response?: AxiosResponse;
constructor(message: string, config: AxiosRequestConfig, response?: AxiosResponse) {
super(message);
this.config = config;
this.response = response;
// 修复
Object.setPrototypeOf(this, AxiosError.prototype);
}
}
export default function createError(message: string, config: AxiosRequestConfig, response?: AxiosResponse): AxiosError {
return new AxiosError(message, config, response);
}

View File

@ -0,0 +1,15 @@
/*
* @Author: early-autumn
* @Date: 2020-04-13 15:22:22
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 23:23:46
*/
import { AxiosRequestConfig, AxiosPromise } from '../types';
import request from './request';
import transformRequestConfig from './transformRequestConfig';
export default function dispatchRequest(config: AxiosRequestConfig): AxiosPromise {
transformRequestConfig(config);
return request(config);
}

55
src/core/request.ts Normal file
View File

@ -0,0 +1,55 @@
/*
* @Author: early-autumn
* @Date: 2020-04-13 18:01:16
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 22:51:17
*/
import { AxiosRequestConfig, AxiosPromise } from '../types';
import createError from './createError';
/**
*
*
* @param config
*/
export default function request(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve, reject) => {
const { cancelToken, method, ...options } = config;
// method 转为全大写
const methodType = (method?.toUpperCase() ?? 'GET') as WechatMiniprogram.RequestOption['method'];
function catchError({ errMsg }: WechatMiniprogram.GeneralCallbackResult): void {
reject(createError(errMsg, config));
}
function handleResponse(result: WechatMiniprogram.RequestSuccessCallbackResult): void {
const response = { ...result, config };
const { statusCode, errMsg } = response;
if (statusCode >= 200 && statusCode < 300) {
resolve(response);
} else {
reject(createError(!!errMsg ? errMsg : `Request failed with status code ${statusCode}`, config, response));
}
}
// 替换 config 中的 success fail complete
const request = wx.request({
...options,
method: methodType,
success: handleResponse,
fail: catchError,
complete: undefined,
});
// 如果存在取消令牌
// 则调用取消令牌里的 listener 监听用户的取消操作
if (cancelToken !== undefined) {
cancelToken.listener.then(function onCanceled(reason): void {
request.abort();
reject(reason);
});
}
});
}

View File

@ -0,0 +1,21 @@
/*
* @Author: early-autumn
* @Date: 2020-04-14 10:15:50
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 23:09:27
*/
import { AxiosRequestConfig } from '../types';
import processURL from '../helper/processURL';
import processData from '../helper/processData';
/**
* config
*
* @param config AxiosRequestConfig
*/
export default function transformRequestConfig(config: AxiosRequestConfig): void {
const { url, params, data } = config;
config.url = processURL(url, params);
config.data = processData(data);
}

21
src/helper/processData.ts Normal file
View File

@ -0,0 +1,21 @@
/*
* @Author: early-autumn
* @Date: 2020-04-13 22:50:35
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 23:09:59
*/
import { Data } from '../types';
import { isPlainObject } from './utils';
/**
*
*
* @param data
*/
export default function processData(data: Data): Data {
if (!isPlainObject(data)) {
return data;
}
return JSON.stringify(data);
}

88
src/helper/processURL.ts Normal file
View File

@ -0,0 +1,88 @@
/*
* @Author: early-autumn
* @Date: 2020-04-13 21:45:45
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 23:07:28
*/
import { Params } from '../types';
import { isPlainObject, isDate } from './utils';
/**
*
*
* @param str
*/
function encode(str: string): string {
return encodeURIComponent(str)
.replace(/%40/g, '@')
.replace(/%3A/gi, ':')
.replace(/%24/g, '$')
.replace(/%2C/gi, ',')
.replace(/%20/g, '+')
.replace(/%5B/gi, '[')
.replace(/%5D/gi, ']');
}
/**
* URL
*
* @param url URL
* @param params
*/
function joinURL(url: string, paramsStr: string): string {
// 移除 hash
const hashIndex = paramsStr.indexOf('#');
if (hashIndex !== -1) {
paramsStr = paramsStr.slice(0, hashIndex);
}
// 拼接前缀
const prefix = url.indexOf('?') === -1 ? '?' : '&';
paramsStr = `${prefix}${paramsStr}`;
return `${url}${paramsStr}`;
}
/**
* URL
*
* @param url URL
* @param params
*/
export default function processURL(url: string, params?: Params): string {
if (params === undefined) {
return url;
}
const parts: string[] = [];
Object.entries(params).forEach(([key, value]): void => {
if (value === null || value === undefined || value !== value) {
return;
}
// 如果值是一个数组, 则特殊处理 key
if (Array.isArray(value)) {
key += '[]';
}
// 转成数组统一处理
const values: any[] = [].concat(value);
values.forEach((val: any): void => {
if (isPlainObject(val)) {
val = JSON.stringify(val);
} else if (isDate(val)) {
val = val.toISOString();
}
parts.push(`${encode(key)}=${encode(val)}`);
});
});
if (parts.length !== 0) {
url = joinURL(url, parts.join('&'));
}
return url;
}

29
src/helper/utils.ts Normal file
View File

@ -0,0 +1,29 @@
/*
* @Author: early-autumn
* @Date: 2020-04-13 21:55:40
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 14:28:31
*/
const _toString = Object.prototype.toString;
/**
*
*
* @param date
*/
export function isDate(date: any): date is Date {
return _toString.call(date) === '[object Date]';
}
/**
*
*
* @param obj
*/
export function isPlainObject(obj: any): obj is object {
return _toString.call(obj) === '[object Object]';
}
// export function isObject(value: any): value is object {
// return value !== null && typeof value === 'object';
// }

6
src/index.ts Normal file
View File

@ -0,0 +1,6 @@
/*
* @Author: early-autumn
* @Date: 2020-04-14 23:22:52
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 23:22:52
*/

176
src/types.ts Normal file
View File

@ -0,0 +1,176 @@
/*
* @Author: early-autumn
* @Date: 2020-04-13 15:23:53
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 23:42:10
*/
import 'miniprogram-api-typings';
export declare type AnyObject = Record<string, any>;
export declare type Params = AnyObject;
export declare type Data = AxiosRequestConfig['data'];
/**
*
*/
export declare type Method =
| 'options'
| 'get'
| 'head'
| 'post'
| 'put'
| 'delete'
| 'trace'
| 'connect'
| WechatMiniprogram.RequestOption['method'];
/**
*
*/
export type AxiosRequestConfig = Omit<WechatMiniprogram.RequestOption, 'method' | 'success' | 'fail' | 'complete'> & {
/** HTTP
*
*
* - 'options': HTTP OPTIONS;
* - 'OPTIONS': HTTP OPTIONS;
* - 'get': HTTP GET;
* - 'GET': HTTP GET;
* - 'head': HTTP HEAD;
* - 'HEAD': HTTP HEAD;
* - 'post': HTTP POST;
* - 'POST': HTTP POST;
* - 'put': HTTP PUT;
* - 'PUT': HTTP PUT;
* - 'delete': HTTP DELETE;
* - 'DELETE': HTTP DELETE;
* - 'trace': HTTP TRACE;
* - 'TRACE': HTTP TRACE;
* - 'connect': HTTP CONNECT;
* - 'CONNECT': HTTP CONNECT;
*/
method?: Method;
/**
* URL
*/
params?: Params;
/**
* http2
*/
enableHttp2?: boolean;
/**
* quic
*/
enableQuic?: boolean;
/**
* cache
*/
enableCache?: boolean;
/**
*
*/
cancelToken?: CancelToken;
};
/**
*
*/
export interface AxiosResponse extends WechatMiniprogram.RequestSuccessCallbackResult {
config: AxiosRequestConfig;
}
export declare type AxiosPromise = Promise<AxiosResponse>;
export type AxiosMethodConfig = Omit<AxiosRequestConfig, 'url'>;
/**
* Axios
*/
export interface Axios {
/**
*
* @param config 000
*/
request(config: AxiosRequestConfig): AxiosPromise;
options(url: string, params?: Params, config?: AxiosMethodConfig): AxiosPromise;
get(url: string, params?: Params, config?: AxiosMethodConfig): AxiosPromise;
head(url: string, params?: Params, config?: AxiosMethodConfig): AxiosPromise;
post(url: string, data?: Data, config?: AxiosMethodConfig): AxiosPromise;
put(url: string, data?: Data, config?: AxiosMethodConfig): AxiosPromise;
delete(url: string, params?: Params, config?: AxiosMethodConfig): AxiosPromise;
trace(url: string, params?: Data, config?: AxiosMethodConfig): AxiosPromise;
connect(url: string, params?: Data, config?: AxiosMethodConfig): AxiosPromise;
}
export interface AxiosInstance extends Axios {
(config: AxiosRequestConfig): AxiosPromise;
}
// export interface AxiosError extends Error {
// isAxiosError: boolean;
// config: AxiosRequestConfig;
// response?: AxiosResponse;
// }
/**
*
*/
export interface Cancel {
/**
*
*/
message?: string;
/**
*
*/
toString(): string;
}
/**
*
*/
export interface CancelAction {
(message?: string): void;
}
/**
*
*/
export interface CancelExecutor {
(cancel: CancelAction): void;
}
/**
*
*/
export interface CancelToken {
/**
*
*/
reason?: Cancel;
/**
*
*/
listener: Promise<Cancel>;
/**
*
*/
throwIfRequested(): void;
}
/**
* source
*/
export interface CancelTokenSource {
token: CancelToken;
cancel: CancelAction;
}

16
test/.babelrc Normal file
View File

@ -0,0 +1,16 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
],
"plugins": [
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-transform-modules-commonjs"
]
}

13
test/index.test.ts Normal file
View File

@ -0,0 +1,13 @@
/*
* @Author: early-autumn
* @Date: 2020-04-14 23:43:45
* @LastEditors: early-autumn
* @LastEditTime: 2020-04-14 23:45:11
*/
import { isDate } from '../src/helper/utils';
describe('', () => {
it('', () => {
expect(isDate({})).toBeFalsy();
});
});

24
tsconfig.json Normal file
View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"lib": [
"dom",
"es2017"
],
"declaration": true,
"declarationDir": "./types",
"sourceMap": true,
"removeComments": false,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"moduleResolution": "node",
"baseUrl": "./",
"esModuleInterop": true,
"skipLibCheck": true,
},
"include": [
"./src/**/*"
]
}

4947
yarn.lock Normal file

File diff suppressed because it is too large Load Diff