diff --git a/docs/reference/plugin/plugins/request.md b/docs/reference/plugin/plugins/request.md index 1d688db1..b6fe9883 100644 --- a/docs/reference/plugin/plugins/request.md +++ b/docs/reference/plugin/plugins/request.md @@ -1,6 +1,6 @@ # @fesjs/plugin-request -基于 axios 封装的 request,内置防止重复请求、请求缓存、错误处理等功能。 +基于 fetch 封装的 request,内置防止重复请求、请求缓存、错误处理等功能。 ## 启用方式 @@ -10,35 +10,39 @@ { "dependencies": { "@fesjs/fes": "^3.0.0", - "@fesjs/plugin-request": "^3.0.0" + "@fesjs/plugin-request": "^4.0.0-rc.0" } } ``` ## 运行时配置 -入口文件的全局配置,具体请求的配置参数会覆盖全局配置,支持 [axios](https://axios-http.com/zh/docs/req_config) 所有的参数。 +入口文件的全局配置,具体请求的配置参数会覆盖全局配置,支持 [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#sending_a_request_with_credentials_included) 所有的参数。 ```js import { defineRuntimeConfig } from '@fesjs/fes'; export default defineRuntimeConfig({ request: { - // API 前缀 baseURL: '', - dataHandler(data, response) { + timeout: 10000, // 默认 10s + method: 'POST', // 默认 post + mergeRequest: false, // 是否合并请求 + responseType: null, // 可选 'json' | 'text' | 'blob' | 'arrayBuffer' | 'formData',默认根据 content-type 处理 + credentials: 'include', // 默认 include, 'include' | 'same-origin' | 'omit' + headers: {}, // 传给服务器的 header + cacheData: false, // 是否缓存 + requestInterceptor: (config: Config) => Config, + responseInterceptor: (response: RequestResponse) => RequestResponse, + transformData(data, response) { // 处理响应内容异常 - if (data.code !== '0') { + if (isPlainObject(data)) { if (data.code === '10000') { - FMesseage.error('hello world'); + return Promise.reject(data); } - if (data.code === '20000') { - FMesseage.error('hello world'); - } - throw new Error(response); + return data?.result ? data.result : data; } - // 响应数据格式化 - return data?.result ? data.result : data; + return data; }, // http 异常,和插件异常 errorHandler(error) { @@ -47,13 +51,7 @@ export default defineRuntimeConfig({ console.log(error.response.data); console.log(error.response.status); console.log(error.response.headers); - } else if (error.request) { - // 请求已经成功发起,但没有收到响应 - // `error.request` 在浏览器中是 XMLHttpRequest 的实例, - // 而在node.js中是 http.ClientRequest 的实例 - console.log(error.request); - } else if (error.type) { - // 插件异常 + } else if (error.msg) { console.log(error.msg); } else { // 发送请求时出了点问题 @@ -61,11 +59,7 @@ export default defineRuntimeConfig({ } console.log(error.config); }, - // 请求拦截器 - requestInterceptors: [], - // 响应拦截器 - responseInterceptors: [], - // 支持其他 axios 配置 + // 支持其他 fetch 配置 ...otherConfigs, }, }); @@ -82,7 +76,7 @@ export default defineRuntimeConfig({ - url: 后端接口 url - data: 参数 - - options: 配置支持 [axios](https://axios-http.com/zh/docs/req_config) 所有的参数,和插件扩展参数。 + - options: 配置支持 [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#sending_a_request_with_credentials_included) 所有的参数,和插件扩展参数。 - **返回值**: Promise @@ -148,7 +142,7 @@ request( password: '123456', }, { - cache: { + cacheData: { cacheType: 'ram', // ram: 内存,session: sessionStorage,local:localStorage cacheTime: 1000 * 60 * 3, // 缓存时间:默认3min }, @@ -162,7 +156,37 @@ request( }); ``` -若 `cache` 传 `true`,则默认使用 `ram` 缓存类型,缓存时间 3min。 +若 `cacheData` 传 `true`,则默认使用 `ram` 缓存类型,缓存时间 3min。 + +### 请求 abort + +```javascript +import { request } from '@fesjs/fes'; + +const controller = new AbortController(); +request('/url/abort', null, { + signal: controller.signal, +}).then((response) => { + console.log('process response: ' + response); +}); +// cancel the request +controller.abort(); +``` + +### 获取 response headers + +```javascript +import { rawRequest } from '@fesjs/fes'; + +const controller = new AbortController(); +rawRequest('/url/abort', null, { + signal: controller.signal, +}).then((response) => { + console.log('process headers: ' + response.headers); +}); +// cancel the request +controller.abort(); +``` ### 结合 use 使用 @@ -184,3 +208,11 @@ export default { }, }; ``` + +## 3.x 升级到 4.x + +1. 缓存参数 cache 改成 cacheData(避免与 fetch 原本的 cache 冲突) +2. dataHandler 改成 transformData +3. requestInterceptors 改为 requestInterceptor,不在支持数组,只支持函数 +4. responseInterceptors 改为 responseInterceptor,不在支持数组,只支持函数 +5. 其他 axios 特有的配置不在支持 diff --git a/docs/reference/plugin/plugins/windicss.md b/docs/reference/plugin/plugins/windicss.md index be28d80b..9dd4dced 100644 --- a/docs/reference/plugin/plugins/windicss.md +++ b/docs/reference/plugin/plugins/windicss.md @@ -1,17 +1,22 @@ # @fesjs/plugin-windicss +::: warning 即将废弃 +由于 windicss 不怎么维护了,本插件即将废弃,推荐使用 [tailwindcss](https://tailwindcss.com/)。 +::: ## 介绍 `windicss` 支持 ## 启用方式 + 在 `package.json` 中引入依赖: + ```json { "devDependencies": { "@fesjs/plugin-windicss": "^2.0.0" - }, + } } ``` @@ -23,6 +28,6 @@ export default { windicss: { root: './', - } -} + }, +}; ``` diff --git a/packages/create-fes-app/templates/app/h5/package.json b/packages/create-fes-app/templates/app/h5/package.json index 1e5272fb..7a972c0f 100644 --- a/packages/create-fes-app/templates/app/h5/package.json +++ b/packages/create-fes-app/templates/app/h5/package.json @@ -46,10 +46,10 @@ "dependencies": { "@fesjs/fes": "^3.0.0-rc.1", "@fesjs/plugin-icon": "^3.0.0-rc.0", - "@fesjs/plugin-request": "^3.0.0-rc.3", + "@fesjs/plugin-request": "^4.0.0-rc.0", "@fesjs/builder-webpack": "^3.0.0-rc.1", "vue": "^3.2.37", "core-js": "^3.27.0" }, "private": true -} +} \ No newline at end of file diff --git a/packages/fes-builder-webpack/package.json b/packages/fes-builder-webpack/package.json index 5cfa3775..24b67570 100644 --- a/packages/fes-builder-webpack/package.json +++ b/packages/fes-builder-webpack/package.json @@ -59,7 +59,7 @@ "style-loader": "^2.0.0", "terser-webpack-plugin": "^5.3.6", "vue-loader": "^16.1.2", - "webpack": "^5.69.0", + "webpack": "^5.76.2", "webpack-bundle-analyzer": "^4.4.0", "webpack-chain": "^6.5.1", "webpack-dev-server": "^4.8.1", @@ -69,4 +69,4 @@ "@fesjs/fes": "3.0.0-rc.8", "core-js": "^3.27.0" } -} +} \ No newline at end of file diff --git a/packages/fes-builder-webpack/src/plugins/commands/webpackConfig/css.js b/packages/fes-builder-webpack/src/plugins/commands/webpackConfig/css.js index 5fad5ec9..9f30e5cc 100644 --- a/packages/fes-builder-webpack/src/plugins/commands/webpackConfig/css.js +++ b/packages/fes-builder-webpack/src/plugins/commands/webpackConfig/css.js @@ -93,8 +93,8 @@ export default function createCssWebpackConfig({ isDev, config, webpackConfig, b if (!isDev) { webpackConfig.plugin('extra-css').use(require.resolve('mini-css-extract-plugin'), [ { - filename: '[name].[contenthash:8].css', - chunkFilename: '[id].[contenthash:8].css', + filename: 'css/[name].[contenthash:8].css', + chunkFilename: 'css/[id].[contenthash:8].css', }, ]); webpackConfig.optimization.minimizer('css').use(require.resolve('css-minimizer-webpack-plugin'), [{}]); diff --git a/packages/fes-builder-webpack/src/plugins/commands/webpackConfig/index.js b/packages/fes-builder-webpack/src/plugins/commands/webpackConfig/index.js index 6aa22f4d..a3fd41cc 100644 --- a/packages/fes-builder-webpack/src/plugins/commands/webpackConfig/index.js +++ b/packages/fes-builder-webpack/src/plugins/commands/webpackConfig/index.js @@ -77,8 +77,8 @@ export default async function getConfig({ api, cwd, config, env, entry = {}, mod webpackConfig.output .path(absoluteOutput) .publicPath(publicPath || '/') - .filename('[name].[contenthash:8].js') - .chunkFilename('[name].[contenthash:8].chunk.js'); + .filename('js/[name].[contenthash:8].js') + .chunkFilename('js/[name].[contenthash:8].chunk.js'); // --------------- resolve ----------- webpackConfig.resolve.extensions.merge(['.mjs', '.js', '.jsx', '.vue', '.ts', '.tsx', '.json', '.wasm']); diff --git a/packages/fes-plugin-login/package.json b/packages/fes-plugin-login/package.json index c342d781..e6fa3723 100644 --- a/packages/fes-plugin-login/package.json +++ b/packages/fes-plugin-login/package.json @@ -29,8 +29,8 @@ }, "peerDependencies": { "@fesjs/fes": "3.0.0-rc.4", - "@fesjs/plugin-request": "3.0.0-rc.6", + "@fesjs/plugin-request": "^4.0.0-rc.0", "vue": "^3.2.37" }, "typings": "./types.d.ts" -} +} \ No newline at end of file diff --git a/packages/fes-plugin-request/package.json b/packages/fes-plugin-request/package.json index b1c2e189..b930cd26 100644 --- a/packages/fes-plugin-request/package.json +++ b/packages/fes-plugin-request/package.json @@ -1,6 +1,6 @@ { "name": "@fesjs/plugin-request", - "version": "3.0.0-rc.5", + "version": "4.0.0-rc.0", "description": "@fesjs/plugin-request", "main": "lib/index.js", "files": [ @@ -33,7 +33,7 @@ }, "dependencies": { "@fesjs/utils": "3.0.0-rc.2", - "axios": "^1.0.0-alpha.1" + "@qlin/request": "^0.1.1" }, "typings": "./types.d.ts" -} +} \ No newline at end of file diff --git a/packages/fes-plugin-request/src/template/cacheControl.js b/packages/fes-plugin-request/src/template/cacheControl.js deleted file mode 100644 index fafb4439..00000000 --- a/packages/fes-plugin-request/src/template/cacheControl.js +++ /dev/null @@ -1,201 +0,0 @@ -import { isObject, isString, isURLSearchParams, checkHttpRequestHasBody } from './helpers'; -/** - * 缓存实现的功能 - * 1. 唯一定位一个请求(url, data | params, method) - * 其中请求参数根据请求方法使用其中一个就够了 - * 一个请求同时包含 data | params 参数的设计本身不合理 - * 不对这种情况进行兼容 - * 2. 控制缓存内容的大小,localStorage 只有5M - * 3. 控制缓存时间 - * session(存在内存中) - * expireTime 存在localStoreage 中 - * 4. 成功的、且响应内容为json的请求进行缓存 - */ - -/** - * 配置数据 - * type: 'ram' | 'sessionStorage' | 'localStorage' - * cacheTime: '' - */ - -/** - * 缓存数据结构 - * cache: { - * url: 'url', // 缓存 url - * data: data, // 数据 - * expire: '' // 缓存时间 - * } - */ - -/** - * 请求参数可以为如下类型 - * - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams - * - Browser only: FormData, File, Blob - * 只缓存参数类型为: string、plain object、URLSearchParams 或者无参数的 请求 - */ - -const CACHE_KEY_PREFIX = '__FES_REQUEST_CACHE:'; -const CACHE_TYPE = { - ram: 'ram', - session: 'sessionStorage', - local: 'localStorage', -}; - -const CACHE_DATA_MAP = new Map(); - -function genInnerKey(key, cacheType = 'ram') { - if (cacheType !== CACHE_TYPE.ram) { - return `${CACHE_KEY_PREFIX}${key}`; - } - return key; -} - -function canCache(data) { - return !data || isObject(data) || isString(data) || Array.isArray(data) || isURLSearchParams(data); -} - -function setCacheData({ key, cacheType = 'ram', data, cacheTime = 1000 * 60 * 3 }) { - const _key = genInnerKey(key, cacheType); - - const currentCacheData = { - cacheType, - data, - cacheTime, - expire: Date.now() + cacheTime, - }; - if (cacheType !== CACHE_TYPE.ram) { - const cacheInstance = window[CACHE_TYPE[cacheType]]; - try { - cacheInstance.setItem(_key, JSON.stringify(currentCacheData)); - } catch (e) { - // setItem 出现异常,清理缓存 - for (const item in cacheInstance) { - if (item.startsWith(CACHE_KEY_PREFIX) && Object.prototype.hasOwnProperty.call(cacheInstance, item)) { - cacheInstance.removeItem(item); - } - } - } - } else { - CACHE_DATA_MAP.set(_key, currentCacheData); - } -} - -function isExpire({ expire, cacheTime }) { - if (!cacheTime || expire >= Date.now()) { - return false; - } - return true; -} - -function getCacheData({ key, cacheType = 'ram' }) { - const _key = genInnerKey(key, cacheType); - if (cacheType !== CACHE_TYPE.ram) { - const cacheInstance = window[CACHE_TYPE[cacheType]]; - const text = cacheInstance.getItem(_key) || null; - try { - const currentCacheData = JSON.parse(text); - if (currentCacheData && !isExpire(currentCacheData)) { - return currentCacheData.data; - } - cacheInstance.removeItem(_key); - return null; - } catch (e) { - cacheInstance.removeItem(_key); - return null; - } - } else { - const currentCacheData = CACHE_DATA_MAP.get(_key); - if (currentCacheData && !isExpire(currentCacheData)) { - return currentCacheData.data; - } - CACHE_DATA_MAP.delete(_key); - return null; - } -} - -// 存储缓存队列 -const cacheStartFlag = new Map(); -const cachingQueue = new Map(); - -/** - * 等上一次请求结果 - * 1. 如果上一次请求成功,直接使用上一次的请求结果 - * 2. 如果上一次请求失败,重启本次请求 - */ -function handleCachingStart(ctx, config) { - const _key = genInnerKey(ctx.key, config.cache.cacheType); - const caching = cacheStartFlag.get(_key); - if (caching) { - return new Promise((resolve) => { - const queue = cachingQueue.get(_key) || []; - cachingQueue.set(_key, queue.concat(resolve)); - }); - } - cacheStartFlag.set(_key, true); -} - -// 有请求成功的 -function handleCachingQueueSuccess(ctx, config) { - // 移除首次缓存 flag - const _key = genInnerKey(ctx.key, config.cache.cacheType); - const queue = cachingQueue.get(_key); - if (queue && queue.length > 0) { - queue.forEach((resolve) => { - resolve({ - response: ctx.response, - }); - }); - } - cachingQueue.delete(_key); - cacheStartFlag.delete(_key); -} - -// 处理请求失败 -function handleCachingQueueError(ctx, config) { - const _key = genInnerKey(ctx.key, config.cache.cacheType); - const queue = cachingQueue.get(_key); - if (queue && queue.length > 0) { - const firstResolve = queue.shift(); - firstResolve(); - cachingQueue.set(_key, queue); - } else { - cachingQueue.delete(_key); - cacheStartFlag.delete(_key); - } -} - -export default async (ctx, next) => { - const { config } = ctx; - if (config.cache) { - const cacheData = getCacheData({ key: ctx.key, cacheType: config.cache.cacheType }); - if (cacheData) { - ctx.response = { - data: cacheData, - }; - return; - } - const result = await handleCachingStart(ctx, config); - if (result) { - Object.keys(result).forEach((key) => { - ctx[key] = result[key]; - }); - return; - } - } - await next(); - - if (config.cache) { - const requestdata = checkHttpRequestHasBody(config.method) ? config.data : config.params; - if (!ctx.error && ctx.response && canCache(requestdata) && canCache(ctx.response.data)) { - handleCachingQueueSuccess(ctx, config); - - setCacheData({ - key: ctx.key, - data: ctx.response.data, - ...config.cache, - }); - } else { - handleCachingQueueError(ctx, config); - } - } -}; diff --git a/packages/fes-plugin-request/src/template/genRequestKey.js b/packages/fes-plugin-request/src/template/genRequestKey.js deleted file mode 100644 index 27bbbd95..00000000 --- a/packages/fes-plugin-request/src/template/genRequestKey.js +++ /dev/null @@ -1,22 +0,0 @@ -import { isURLSearchParams } from './helpers'; -/** - * 唯一定位一个请求(url, data | params, method) - * 其中请求参数(data, params)根据请求方法,只使用其中一个 - * 一个请求同时包含 data | params 参数的设计本身不合理 - * 不对这种情况进行兼容 - */ - -const getQueryString = (data) => { - if (isURLSearchParams(data)) { - return data.toString(); - } - return data ? JSON.stringify(data) : ''; -}; - -export default async function genRequestKey(ctx, next) { - const { url, data, params, method } = ctx.config; - - ctx.key = `${url}${getQueryString(data)}${getQueryString(params)}${method}`; - - await next(); -} diff --git a/packages/fes-plugin-request/src/template/helpers.js b/packages/fes-plugin-request/src/template/helpers.js deleted file mode 100644 index 97cdef0f..00000000 --- a/packages/fes-plugin-request/src/template/helpers.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - *判断类型 - * @param {*} obj 需要判断的对象 - */ -export function typeOf(obj) { - const map = { - '[object Boolean]': 'boolean', - '[object Number]': 'number', - '[object String]': 'string', - '[object Function]': 'function', - '[object Array]': 'array', - '[object Date]': 'date', - '[object RegExp]': 'regExp', - '[object Undefined]': 'undefined', - '[object Null]': 'null', - '[object Object]': 'object', - '[object URLSearchParams]': 'URLSearchParams', - }; - return map[Object.prototype.toString.call(obj)]; -} - -export function isFunction(obj) { - return typeOf(obj) === 'function'; -} - -export function isDate(obj) { - return typeOf(obj) === 'date'; -} - -export function isString(obj) { - return typeOf(obj) === 'string'; -} - -export function isArray(obj) { - return typeOf(obj) === 'array'; -} - -export function isObject(obj) { - return typeOf(obj) === 'object'; -} - -export function isURLSearchParams(obj) { - return typeOf(obj) === 'URLSearchParams'; -} - -export function checkHttpRequestHasBody(method) { - method = method.toUpperCase(); - const HTTP_METHOD = { - GET: { - request_body: false, - }, - POST: { - request_body: true, - }, - PUT: { - request_body: true, - }, - DELETE: { - request_body: true, - }, - HEAD: { - request_body: false, - }, - OPTIONS: { - request_body: false, - }, - PATCH: { - request_body: true, - }, - }; - return HTTP_METHOD[method].request_body; -} - -export function trimObj(obj) { - if (isObject(obj)) { - Object.entries(obj).forEach(([key, value]) => { - if (isString(value)) { - obj[key] = value.trim(); - } else if (isObject(value)) { - trimObj(value); - } - }); - } -} diff --git a/packages/fes-plugin-request/src/template/paramsProcess.js b/packages/fes-plugin-request/src/template/paramsProcess.js deleted file mode 100644 index 06864044..00000000 --- a/packages/fes-plugin-request/src/template/paramsProcess.js +++ /dev/null @@ -1,11 +0,0 @@ -import { checkHttpRequestHasBody, trimObj } from './helpers'; - -export default async (ctx, next) => { - const config = ctx.config; - if (checkHttpRequestHasBody(config.method)) { - trimObj(config.data); - } else { - trimObj(config.params); - } - await next(); -}; diff --git a/packages/fes-plugin-request/src/template/preventRepeatReq.js b/packages/fes-plugin-request/src/template/preventRepeatReq.js deleted file mode 100644 index 0b1505af..00000000 --- a/packages/fes-plugin-request/src/template/preventRepeatReq.js +++ /dev/null @@ -1,64 +0,0 @@ -const requestMap = new Map(); - -const mergeRequestMap = new Map(); -const requestQueue = new Map(); - -function handleCachingStart(ctx) { - const isRequesting = mergeRequestMap.get(ctx.key); - if (isRequesting) { - return new Promise((resolve) => { - const queue = requestQueue.get(ctx.key) || []; - requestQueue.set(ctx.key, queue.concat(resolve)); - }); - } - mergeRequestMap.set(ctx.key, true); -} - -function handleRepeatRequest(ctx) { - const queue = requestQueue.get(ctx.key); - if (queue && queue.length > 0) { - queue.forEach((resolve) => { - if (ctx.error) { - resolve({ - error: ctx.error, - }); - } else { - resolve({ - response: ctx.response, - }); - } - }); - } - requestQueue.delete(ctx.key); - mergeRequestMap.delete(ctx.key); -} - -export default async (ctx, next) => { - if (ctx.config.mergeRequest) { - const result = await handleCachingStart(ctx); - if (result) { - Object.keys(result).forEach((key) => { - ctx[key] = result[key]; - }); - return; - } - } else { - if (requestMap.get(ctx.key) && !ctx.config.mergeRequest) { - ctx.error = { - type: 'REPEAT', - msg: '重复请求', - config: ctx.config, - }; - return; - } - requestMap.set(ctx.key, true); - } - - await next(); - - if (ctx.config.mergeRequest) { - handleRepeatRequest(ctx); - } else { - requestMap.delete(ctx.key); - } -}; diff --git a/packages/fes-plugin-request/src/template/request.js b/packages/fes-plugin-request/src/template/request.js index a0a69193..f083b494 100644 --- a/packages/fes-plugin-request/src/template/request.js +++ b/packages/fes-plugin-request/src/template/request.js @@ -1,131 +1,34 @@ -import axios from 'axios'; import { ApplyPluginsType, plugin } from '@fesjs/fes'; import { ref } from 'vue'; -import scheduler from './scheduler'; -import { checkHttpRequestHasBody, isFunction } from './helpers'; -import paramsProcess from './paramsProcess'; -import genRequestKey from './genRequestKey'; -import preventRepeatReq from './preventRepeatReq'; -import cacheControl from './cacheControl'; - -function addInterceptors(instance, interceptors, type = 'request') { - interceptors.forEach((fn) => { - if (Array.isArray(fn)) { - instance.interceptors[type].use(...fn); - } else if (isFunction(fn)) { - instance.interceptors[type].use(fn); - } - }); -} - -function addRequestInterceptors(instance, interceptors) { - addInterceptors(instance, interceptors, 'request'); -} - -function addResponseInterceptors(instance, interceptors) { - addInterceptors(instance, interceptors, 'response'); -} - -async function axiosMiddleware(context, next) { - try { - context.response = await context.instance.request(context.config); - } catch (error) { - context.error = error; - } - await next(); -} +import { createRequest } from '@qlin/request'; function getRequestInstance() { - const { - dataHandler, - errorHandler, - requestInterceptors = [], - responseInterceptors = [], - ...otherConfigs - } = plugin.applyPlugins({ + const defaultConfig = plugin.applyPlugins({ key: 'request', type: ApplyPluginsType.modify, - initialValue: {}, + initialValue: { + timeout: 10000, + }, }); - const defaultConfig = Object.assign( - { - timeout: 10000, - withCredentials: true, - }, - otherConfigs, - ); - const instance = axios.create(defaultConfig); - - addRequestInterceptors(instance, requestInterceptors); - addResponseInterceptors(instance, responseInterceptors); - - // 洋葱模型内部应该这是对数据的处理,避免有副作用调用 - scheduler.use(paramsProcess).use(genRequestKey).use(cacheControl).use(preventRepeatReq).use(axiosMiddleware); - - return { - context: { - errorHandler, - dataHandler: dataHandler || ((data) => data), - instance, - defaultConfig, - }, - request: scheduler.compose(), - }; + return createRequest(defaultConfig); } -function userConfigHandler(url, data, options = {}) { - options.url = url; - options.method = (options.method || 'post').toUpperCase(); - if (checkHttpRequestHasBody(options.method)) { - options.data = data; - } else { - options.params = data; - } - return options; -} +const currentRequest = getRequestInstance(); -let currentRequestInstance = null; - -function createContext(userConfig) { - return { - ...currentRequestInstance.context, - config: { - ...currentRequestInstance.context.defaultConfig, - ...userConfig, - }, - }; -} - -function getCustomerHandler(ctx, options = {}) { - const { dataHandler, errorHandler } = ctx; - return { - dataHandler: options.dataHandler || dataHandler, - errorHandler: options.errorHandler || errorHandler, - }; -} - -export const request = (url, data, options = {}) => { +export const rawRequest = (url, data, options = {}) => { if (typeof options === 'string') { options = { method: options, }; } - if (!currentRequestInstance) { - currentRequestInstance = getRequestInstance(); - } - const userConfig = userConfigHandler(url, data, options); - const context = createContext(userConfig); - const { dataHandler, errorHandler } = getCustomerHandler(context, options); + return currentRequest(url, data, options); +}; - return currentRequestInstance.request(context).then(async () => { - if (!context.error) { - return dataHandler(context.response.data, context.response); - } - errorHandler && errorHandler(context.error); - return Promise.reject(context.error); - }); +export const request = async (url, data, options = {}) => { + const response = await rawRequest(url, data, options); + return response.data; }; function isPromiseLike(obj) { diff --git a/packages/fes-plugin-request/src/template/scheduler.js b/packages/fes-plugin-request/src/template/scheduler.js deleted file mode 100644 index bcecd266..00000000 --- a/packages/fes-plugin-request/src/template/scheduler.js +++ /dev/null @@ -1,32 +0,0 @@ -class Scheduler { - constructor() { - this.middlewares = []; - } - - use(fn) { - if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); - this.middlewares.push(fn); - return this; - } - - compose() { - return (context, next) => { - let index = -1; - const dispatch = (i) => { - if (i <= index) return Promise.reject(new Error('next() called multiple times')); - index = i; - let fn = this.middlewares[i]; - if (index === this.middlewares.length) fn = next; - if (!fn) return Promise.resolve(); - try { - return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); - } catch (e) { - return Promise.reject(e); - } - }; - return dispatch(0); - }; - } -} - -export default new Scheduler(); diff --git a/packages/fes-plugin-request/types.d.ts b/packages/fes-plugin-request/types.d.ts index 225c92cf..fb8c9604 100644 --- a/packages/fes-plugin-request/types.d.ts +++ b/packages/fes-plugin-request/types.d.ts @@ -1,39 +1,18 @@ -import { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'; +import type { Config, ParamsType } from '@qlin/request/dist/interface'; import { Ref } from 'vue'; -type RequestInterceptor = (value: AxiosRequestConfig) => AxiosRequestConfig | [(value: AxiosRequestConfig) => AxiosRequestConfig, (error: any) => any]; -type ResponseInterceptor = (value: AxiosResponse) => AxiosResponse | [(value: AxiosResponse) => AxiosResponse, (error: any) => any]; +export * from '@qlin/request'; -export type RequestResponse = AxiosResponse; -export type RequestError = AxiosError & { type: string; msg: string; [key: string]: any }; -interface RequestPluginOption { - mergeRequest?: boolean; - dataHandler?(data: any, response: AxiosResponse): any; - errorHandler?(error: RequestError): void; - cache?: - | boolean - | { - type?: 'ram' | 'sessionStorage' | 'localStorage'; - cacheTime?: number; - }; -} - -export type RequestOptions = AxiosRequestConfig & RequestPluginOption; - -export function request(url: string, data?: null | Record, options?: AxiosRequestConfig & RequestPluginOption): Promise; +export function request(url: string, data?: ParamsType | null, options?: Partial): Promise; +export function rawRequest(url: string, data?: ParamsType | null, options?: Partial): Promise; export function useRequest( url: string, - data?: null | Record, - options?: AxiosRequestConfig & RequestPluginOption, + data?: null | ParamsType, + options?: Partial, ): { loadingRef: Ref; errorRef: Ref; dataRef: Ref }; declare module '@fesjs/fes' { interface PluginRuntimeConfig { - request?: { - dataHandler?(data: any, response: AxiosResponse): any; - errorHandler?(error: RequestError): void; - requestInterceptors?: RequestInterceptor[]; - responseInterceptors?: ResponseInterceptor[]; - } & AxiosRequestConfig; + request?: Partial; } } diff --git a/packages/fes-preset-built-in/src/plugins/features/publicPath.js b/packages/fes-preset-built-in/src/plugins/features/publicPath.js index c8e17255..72a5ba32 100644 --- a/packages/fes-preset-built-in/src/plugins/features/publicPath.js +++ b/packages/fes-preset-built-in/src/plugins/features/publicPath.js @@ -4,7 +4,7 @@ export default (api) => { config: { default: '/', schema(joi) { - return joi.string().regex(/\/$/).error(new Error('config.publicPath must end with /.')); + return joi.string(); }, }, }); diff --git a/packages/fes-template-h5/package.json b/packages/fes-template-h5/package.json index fa1f66ba..1b635353 100644 --- a/packages/fes-template-h5/package.json +++ b/packages/fes-template-h5/package.json @@ -43,7 +43,7 @@ "@ttou/postcss-px-to-viewport": "1.1.4", "@fesjs/fes": "^3.0.0-rc.0", "@fesjs/plugin-icon": "^3.0.0-rc.0", - "@fesjs/plugin-request": "^3.0.0-rc.0", + "@fesjs/plugin-request": "^4.0.0-rc.0", "@fesjs/builder-vite": "^3.0.0-rc.1", "core-js": "^3.27.0", "vue": "^3.2.37" diff --git a/packages/fes-template-vite/package.json b/packages/fes-template-vite/package.json index 6371b500..1b021e7b 100644 --- a/packages/fes-template-vite/package.json +++ b/packages/fes-template-vite/package.json @@ -56,7 +56,7 @@ "@fesjs/plugin-model": "^3.0.0-rc.0", "@fesjs/plugin-monaco-editor": "^3.0.0-rc.0", "@fesjs/plugin-pinia": "^3.0.0-rc.0", - "@fesjs/plugin-request": "^3.0.0-rc.0", + "@fesjs/plugin-request": "^4.0.0-rc.0", "@fesjs/plugin-sass": "^3.0.0-rc.0", "@fesjs/plugin-windicss": "^3.0.0-rc.0", "core-js": "^3.27.0", diff --git a/packages/fes-template/package.json b/packages/fes-template/package.json index dd1fb088..b65c8048 100644 --- a/packages/fes-template/package.json +++ b/packages/fes-template/package.json @@ -56,7 +56,7 @@ "@fesjs/plugin-monaco-editor": "^3.0.0-rc.0", "@fesjs/plugin-pinia": "^3.0.0-rc.0", "@fesjs/plugin-qiankun": "^3.0.0-rc.0", - "@fesjs/plugin-request": "^3.0.0-rc.0", + "@fesjs/plugin-request": "^4.0.0-rc.0", "@fesjs/plugin-sass": "^3.0.0-rc.0", "@fesjs/plugin-watermark": "^3.0.0-rc.0", "@fesjs/plugin-windicss": "^3.0.0-rc.0", @@ -67,4 +67,4 @@ "vuex": "^4.0.0" }, "private": true -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f9eeec0c..352c520a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2103,6 +2103,14 @@ resolved "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== +"@qlin/request@^0.1.1": + version "0.1.1" + resolved "https://registry.npmmirror.com/@qlin/request/-/request-0.1.1.tgz#265026e2741208640522161eb9621077aae05487" + integrity sha512-3bQ3XaD4I4/EYZC15CR1TIHFCWQtZfYz0oleXlf4RS1xIW+7zna3OT5c0VKPiDn/aK1wZAH0mYODChIaRKSciA== + dependencies: + "@types/lodash-es" "^4.17.6" + lodash-es "^4.17.21" + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.npmmirror.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -2571,6 +2579,13 @@ dependencies: "@types/lodash" "*" +"@types/lodash-es@^4.17.6": + version "4.17.7" + resolved "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.7.tgz#22edcae9f44aff08546e71db8925f05b33c7cc40" + integrity sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ== + dependencies: + "@types/lodash" "*" + "@types/lodash@*": version "4.14.191" resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" @@ -3696,15 +3711,6 @@ aws4@^1.8.0: resolved "https://registry.npmmirror.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== -axios@^1.0.0-alpha.1: - version "1.3.4" - resolved "https://registry.npmmirror.com/axios/-/axios-1.3.4.tgz#f5760cefd9cfb51fd2481acf88c05f67c4523024" - integrity sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ== - dependencies: - follow-redirects "^1.15.0" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - babel-jest@^27.0.6, babel-jest@^27.5.1: version "27.5.1" resolved "https://registry.npmmirror.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" @@ -6156,7 +6162,7 @@ flatted@^3.1.0: resolved "https://registry.npmmirror.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -follow-redirects@^1.0.0, follow-redirects@^1.15.0: +follow-redirects@^1.0.0: version "1.15.2" resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== @@ -6182,15 +6188,6 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - form-data@~2.3.2: version "2.3.3" resolved "https://registry.npmmirror.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -9588,11 +9585,6 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - prr@~1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -11609,10 +11601,10 @@ webpack-virtual-modules@^0.5.0: resolved "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz#362f14738a56dae107937ab98ea7062e8bdd3b6c" integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw== -webpack@^5.69.0: - version "5.75.0" - resolved "https://registry.npmmirror.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152" - integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ== +webpack@^5.76.2: + version "5.76.2" + resolved "https://registry.npmmirror.com/webpack/-/webpack-5.76.2.tgz#6f80d1c1d1e3bf704db571b2504a0461fac80230" + integrity sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51"