Skip to content

Commit

Permalink
refactor(projects): split @sa/request to @sa/axios, @sa/fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
honghuangdc committed Dec 27, 2023
1 parent e935f77 commit 9ecb516
Show file tree
Hide file tree
Showing 20 changed files with 311 additions and 41 deletions.
3 changes: 2 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export default defineConfig(
{
ignores: ['index', 'App', '[id]']
}
]
],
'no-empty-function': 'off'
}
}
);
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@
"dependencies": {
"@better-scroll/core": "2.5.1",
"@iconify/vue": "4.1.1",
"@sa/axios": "workspace:^",
"@sa/color-palette": "workspace:*",
"@sa/fetch": "workspace:*",
"@sa/hooks": "workspace:*",
"@sa/materials": "workspace:*",
"@sa/request": "workspace:*",
"@sa/utils": "workspace:*",
"@vueuse/core": "10.7.0",
"ant-design-vue": "4.0.7",
Expand Down
18 changes: 18 additions & 0 deletions packages/axios/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@sa/axios",
"version": "1.0.0",
"exports": {
".": "./src/index.ts"
},
"typesVersions": {
"*": {
"*": ["./src/*"]
}
},
"dependencies": {
"@sa/utils": "workspace:*",
"axios": "1.6.2",
"axios-retry": "^4.0.0",
"form-data": "^4.0.0"
}
}
2 changes: 2 additions & 0 deletions packages/axios/src/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Request id key */
export const REQUEST_ID_KEY = 'X-Request-Id';
77 changes: 77 additions & 0 deletions packages/axios/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import axios from 'axios';
import type { CancelTokenSource, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
import axiosRetry from 'axios-retry';
import { nanoid } from '@sa/utils';
import { createDefaultOptions, createRetryOptions } from './options';
import { getContentType, transformRequestData } from './shared';
import { REQUEST_ID_KEY } from './constant';
import type { RequestInstance, RequestOption } from './type';

export function createRequest(axiosConfig?: CreateAxiosDefaults, options?: Partial<RequestOption>) {
const opts = createDefaultOptions(options);

const instance = axios.create(axiosConfig);

const cancelTokenSourceMap = new Map<string, CancelTokenSource>();

// config axios retry
const retryOptions = createRetryOptions(axiosConfig);
axiosRetry(instance, retryOptions);

instance.interceptors.request.use(conf => {
const config: InternalAxiosRequestConfig = { ...conf };

// set request id
const requestId = nanoid();
config.headers.set(REQUEST_ID_KEY, requestId);

// config cancel token
const cancelTokenSource = axios.CancelToken.source();
config.cancelToken = cancelTokenSource.token;
cancelTokenSourceMap.set(requestId, cancelTokenSource);

// transform data
const contentType = getContentType(config);
config.data = transformRequestData(config.data, contentType);

// handle config by hook
const handledConfig = opts.onRequest?.(config) || config;

return handledConfig;
});

instance.interceptors.response.use(
async response => {
console.log('response: ', response);
return response;
},
error => {
console.log('response error: ', error);
return Promise.reject(error);
}
);

function cancelRequest(requestId: string) {
const cancelTokenSource = cancelTokenSourceMap.get(requestId);
if (cancelTokenSource) {
cancelTokenSource.cancel();
cancelTokenSourceMap.delete(requestId);
}
}

function cancelAllRequest() {
cancelTokenSourceMap.forEach(cancelTokenSource => {
cancelTokenSource.cancel();
});
cancelTokenSourceMap.clear();
}

const requestInstance: RequestInstance = instance as RequestInstance;

requestInstance.cancelRequest = cancelRequest;
requestInstance.cancelAllRequest = cancelAllRequest;

return requestInstance;
}

export default createRequest;
26 changes: 26 additions & 0 deletions packages/axios/src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { CreateAxiosDefaults } from 'axios';
import type { IAxiosRetryConfig } from 'axios-retry';
import type { RequestOption } from './type';

export function createDefaultOptions(options?: Partial<RequestOption>) {
const opts: RequestOption = {
onRequest: async config => config,
onBackendSuccess: _response => true,
onBackendFail: async () => {},
onError: async () => {}
};

Object.assign(opts, options);

return opts;
}

export function createRetryOptions(config?: Partial<CreateAxiosDefaults>) {
const retryConfig: IAxiosRetryConfig = {
retries: 3
};

Object.assign(retryConfig, config);

return retryConfig;
}
53 changes: 53 additions & 0 deletions packages/axios/src/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import FormData from 'form-data';
import type { AxiosHeaderValue, InternalAxiosRequestConfig } from 'axios';
import type { ContentType } from './type';

export function transformRequestData(data: any, contentType: AxiosHeaderValue) {
if (typeof contentType !== 'string') return data;

const $contentType = contentType as ContentType;

// 1. "application/json": do not transform

// 2 "application/x-www-form-urlencoded": transform to query string
if ($contentType === 'application/x-www-form-urlencoded' && typeof data === 'object') {
const params = new URLSearchParams();
Object.keys(data).forEach(key => {
params.append(key, data[key]);
});

return params;
}

// 3. "multipart/form-data": transform to FormData
if ($contentType === 'multipart/form-data' && typeof data === 'object') {
const formData = new FormData();
const entries = Object.entries(data);

entries.forEach(([key, value]) => {
if (isFiles(value)) {
value.forEach(file => {
formData.append(key, file);
});
} else {
formData.append(key, value);
}
});
}

return data;
}

function isFiles(data: File[] | unknown): data is File[] {
const isArray = Array.isArray(data);
const hasData = isArray && data.length > 0;
const isFile = hasData && Object.prototype.toString.call(data[0]) === '[object File]';

return isFile;
}

export function getContentType(config: InternalAxiosRequestConfig) {
const contentType: AxiosHeaderValue = config.headers?.['Content-Type'] || 'application/json';

return contentType;
}
50 changes: 50 additions & 0 deletions packages/axios/src/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';

export type ContentType =
| 'text/html'
| 'text/plain'
| 'multipart/form-data'
| 'application/json'
| 'application/x-www-form-urlencoded'
| 'application/octet-stream';

export interface RequestOption {
/**
* The hook before request
*
* For example: You can add header token in this hook
*
* @param config Axios config
*/
onRequest: (config: InternalAxiosRequestConfig) => Promise<InternalAxiosRequestConfig>;
/**
* The hook to check backend response is success or not
*
* @param response Axios response
*/
onBackendSuccess: (response: AxiosResponse) => boolean;
/**
* The hook after backend request fail
*
* For example: You can handle the expired token in this hook
*
* @param response Axios response
* @param instance Axios instance
* @returns
*/
onBackendFail: (response: AxiosResponse, instance: AxiosInstance) => Promise<AxiosResponse> | Promise<void>;
/**
* The hook to handle error
*
* For example: You can show error message in this hook
*
* @param error
*/
onError: (error: AxiosError) => Promise<void>;
}

/** The axios instance with cancel request function */
export type RequestInstance = AxiosInstance & {
cancelRequest: (requestId: string) => void;
cancelAllRequest: () => void;
};
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@sa/request",
"name": "@sa/fetch",
"version": "1.0.0",
"exports": {
".": "./src/index.ts"
Expand All @@ -10,7 +10,6 @@
}
},
"dependencies": {
"axios": "1.6.2",
"ofetch": "1.3.3"
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ofetch } from 'ofetch';
import type { FetchOptions } from 'ofetch';

export function createFetch(options: FetchOptions) {
export function createRequest(options: FetchOptions) {
const request = ofetch.create(options);

return request;
}

export default createFetch;
export default createRequest;
20 changes: 20 additions & 0 deletions packages/ofetch/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ESNext",
"jsx": "preserve",
"lib": ["DOM", "ESNext"],
"baseUrl": ".",
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"types": ["node"],
"strict": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
25 changes: 0 additions & 25 deletions packages/request/src/axios/index.ts

This file was deleted.

4 changes: 0 additions & 4 deletions packages/request/src/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/request/src/types/index.ts

This file was deleted.

3 changes: 2 additions & 1 deletion packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"dependencies": {
"colord": "2.9.3",
"crypto-js": "4.2.0",
"localforage": "1.10.0"
"localforage": "1.10.0",
"nanoid": "5.0.4"
},
"devDependencies": {
"@types/crypto-js": "4.2.1"
Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './color';
export * from './crypto';
export * from './storage';
export * from './nanoid';
3 changes: 3 additions & 0 deletions packages/utils/src/nanoid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { nanoid } from 'nanoid';

export { nanoid };
Loading

0 comments on commit 9ecb516

Please sign in to comment.