From 6b33714c75052e9f37d7eccf543461a4e81c4ee6 Mon Sep 17 00:00:00 2001 From: Condor Hero Date: Fri, 27 Dec 2024 22:48:10 +0800 Subject: [PATCH] feat: support sync requests of XHR --- .../react-sample/fake/index.fake.ts | 4 + .../react-sample/fake/sync-request.fake.ts | 26 +++ .../src/components/the-card/index.tsx | 108 +++++++---- .../src/components/the-nav/index.tsx | 48 ++++- .../src/components/the-nav/type.ts | 31 +++ .../playground/react-sample/src/views/App.tsx | 2 +- .../src/createFakeMiddleware.ts | 2 +- .../src/createHookTemplate.ts | 183 ++++++++++++++++++ .../vite-plugin-fake-server/src/plugin.ts | 128 +----------- .../src/shared/simulateServerResponse.mjs | 182 +++++++++-------- .../src/xhook/index.mjs | 28 ++- 11 files changed, 492 insertions(+), 250 deletions(-) create mode 100644 packages/playground/react-sample/fake/sync-request.fake.ts create mode 100644 packages/playground/react-sample/src/components/the-nav/type.ts create mode 100644 packages/vite-plugin-fake-server/src/createHookTemplate.ts diff --git a/packages/playground/react-sample/fake/index.fake.ts b/packages/playground/react-sample/fake/index.fake.ts index 6148906..ca94628 100644 --- a/packages/playground/react-sample/fake/index.fake.ts +++ b/packages/playground/react-sample/fake/index.fake.ts @@ -165,4 +165,8 @@ export default defineFakeRoute([ res.end(body); }, }, + { + url: "/response-is-any", + response: "The response can not be a function." as any, + }, ]); diff --git a/packages/playground/react-sample/fake/sync-request.fake.ts b/packages/playground/react-sample/fake/sync-request.fake.ts new file mode 100644 index 0000000..e985553 --- /dev/null +++ b/packages/playground/react-sample/fake/sync-request.fake.ts @@ -0,0 +1,26 @@ +import { defineFakeRoute } from "vite-plugin-fake-server/client"; + +import { resultSuccess } from "./nest/utils"; + +export default defineFakeRoute({ + url: "/sync-request", + /** + * The sleep function for synchronous requests will block the browser's rendering. + * When making a synchronous request, it is recommended to set the delay to 0. + */ + timeout: 0, + response: () => { + return resultSuccess({ + timestamp: Date.now(), + status: "success", + code: 200, + message: "ok", + data: { + description: ` + The sleep function for synchronous requests will block the browser's rendering. + When making a synchronous request, it is recommended to set the delay to 0. + `, + }, + }); + }, +}); diff --git a/packages/playground/react-sample/src/components/the-card/index.tsx b/packages/playground/react-sample/src/components/the-card/index.tsx index a0379d4..4b383ca 100644 --- a/packages/playground/react-sample/src/components/the-card/index.tsx +++ b/packages/playground/react-sample/src/components/the-card/index.tsx @@ -1,9 +1,12 @@ -import type { BUTTON_LIST } from "#src/components"; +import type { Option } from "#src/components/the-nav/type"; import { REQUEST_TYPE, TheLoading } from "#src/components"; import { useEffect, useState } from "react"; function getType(value: unknown) { + if (value === undefined) { + return ""; + } return Object.prototype.toString.call(value).slice(8, -1); } @@ -15,7 +18,8 @@ export function TheCard({ responseType, body, headers, -}: (typeof BUTTON_LIST)[number]) { + sync, +}: Option) { const [responseOrigin, setResponseOrigin] = useState<{ [key: string]: any }>({}); const [responseData, setResponseData] = useState(); const [loading, setLoading] = useState(false); @@ -32,33 +36,77 @@ export function TheCard({ : `${baseURL}${value}`.replace(":id", Math.random().toString()); if (type === REQUEST_TYPE[0]) { - setLoading(true); - const xhr = new XMLHttpRequest(); + if (!sync) { + setLoading(true); + const xhr = new XMLHttpRequest(); - if (responseType === "xml") { - xhr.responseType = "document"; - // Force the response to be parsed as XML - xhr.overrideMimeType("application/xml"); - } - else { - xhr.responseType = "json"; - } + if (responseType === "xml") { + xhr.responseType = "document"; + // Force the response to be parsed as XML + xhr.overrideMimeType("application/xml"); + } + else { + xhr.responseType = "json"; + } - if (method.toUpperCase() === "GET" || method.toUpperCase() === "HEAD") { - xhr.open(method, `${requestURL}?${queryParams}`, true); - xhr.setRequestHeader("Content-Type", "application/json"); - for (const key in headers) { - xhr.setRequestHeader(key.toString(), headers[key as keyof typeof headers]); + if (method.toUpperCase() === "GET" || method.toUpperCase() === "HEAD") { + xhr.open(method, `${requestURL}?${queryParams}`, true); + xhr.setRequestHeader("Content-Type", "application/json"); + for (const key in headers) { + xhr.setRequestHeader(key.toString(), headers[key as keyof typeof headers]); + } + xhr.send(); + } + else { + xhr.open(method, requestURL, true); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.send(JSON.stringify(body)); } - xhr.send(); + + xhr.addEventListener("load", () => { + const showHeader = xhr.getResponseHeader("show-header"); + const accessControlAllowOrigin = xhr.getResponseHeader("access-control-allow-origin"); + setResponseOrigin({ + status: xhr.status, + statusText: xhr.statusText, + url: xhr.responseURL, + headers: new Headers({ + "show-header": showHeader || "", + "access-control-allow-origin": accessControlAllowOrigin || "", + }), + }); + setResponseData(xhr.response); + + if (responseType === "xml") { + const xmlContainer = document.getElementById("xmlContainer"); + const xmlDoc = xhr.responseXML!; + const xmlRoot = xmlDoc.querySelector("#xml-root"); + xmlContainer!.appendChild(xmlRoot!); + } + }); + + xhr.addEventListener("loadend", () => { + setLoading(false); + }); } else { - xhr.open(method, requestURL, true); - xhr.setRequestHeader("Content-Type", "application/json"); - xhr.send(JSON.stringify(body)); - } - - xhr.addEventListener("load", () => { + setLoading(true); + const xhr = new XMLHttpRequest(); + // Note: It's a sync request + const isAsync = false; + if (method.toUpperCase() === "GET" || method.toUpperCase() === "HEAD") { + xhr.open(method, `${requestURL}?${queryParams}`, isAsync); + xhr.setRequestHeader("Content-Type", "application/json"); + for (const key in headers) { + xhr.setRequestHeader(key.toString(), headers[key as keyof typeof headers]); + } + xhr.send(); + } + else { + xhr.open(method, requestURL, isAsync); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.send(JSON.stringify(body)); + } const showHeader = xhr.getResponseHeader("show-header"); const accessControlAllowOrigin = xhr.getResponseHeader("access-control-allow-origin"); setResponseOrigin({ @@ -71,18 +119,8 @@ export function TheCard({ }), }); setResponseData(xhr.response); - - if (responseType === "xml") { - const xmlContainer = document.getElementById("xmlContainer"); - const xmlDoc = xhr.responseXML!; - const xmlRoot = xmlDoc.querySelector("#xml-root"); - xmlContainer!.appendChild(xmlRoot!); - } - }); - - xhr.addEventListener("loadend", () => { setLoading(false); - }); + } } else if (type === REQUEST_TYPE[1]) { setLoading(true); diff --git a/packages/playground/react-sample/src/components/the-nav/index.tsx b/packages/playground/react-sample/src/components/the-nav/index.tsx index fd168ed..7c85d95 100644 --- a/packages/playground/react-sample/src/components/the-nav/index.tsx +++ b/packages/playground/react-sample/src/components/the-nav/index.tsx @@ -1,7 +1,9 @@ +import type { Option } from "./type"; + import { useLocation, useNavigate } from "react-router"; export const REQUEST_TYPE = ["XHR", "Fetch"]; -export const OPTIONS = [ +export const OPTIONS: Option[] = [ { label: "Response In Mock", value: "response-in-mock", @@ -121,6 +123,28 @@ export const OPTIONS = [ method: "POST", body: { name: "CondorHero", age: 18 }, }, + + /* Only For XHR */ + { + label: "Sync Request", + value: "sync-request", + method: "GET", + onlyXHR: true, + sync: true, + }, + { + label: "Sync External URL", + value: "https://my-json-server.typicode.com/typicode/demo/comments", + method: "GET", + onlyXHR: true, + sync: true, + }, + + { + label: "Response Is Any", + value: "response-is-any", + method: "GET", + }, { label: "Custom Response Header", value: "custom-response-header", @@ -139,7 +163,15 @@ export const OPTIONS = [ }, ]; -export const BUTTON_LIST = REQUEST_TYPE.flatMap(typeItem => OPTIONS.map(item => ({ ...item, type: typeItem }))); +export const BUTTON_LIST = REQUEST_TYPE.flatMap( + typeItem => OPTIONS.map( + (item) => { + return (typeItem === "Fetch" && item.onlyXHR) + ? undefined + : { ...item, type: typeItem }; + }, + ), +).filter(Boolean) as Option[]; export function TheNav() { const navigate = useNavigate(); @@ -175,17 +207,23 @@ export function TheNav() { } return ( -