From a9a1af4d5187117422559813328231ba53746a40 Mon Sep 17 00:00:00 2001 From: Igor Katsuba Date: Mon, 24 Feb 2025 08:13:45 +0200 Subject: [PATCH] feat: add support for `Response` in component routing (#266) --- mocks/app-function/routes/api-response.tsx | 5 +++ mocks/app-function/routes/async-jsx.tsx | 4 +++ mocks/app-function/routes/async-response.tsx | 9 +++++ mocks/app-function/routes/jsx-response.tsx | 3 ++ src/server/server.ts | 10 ++++-- test-integration/apps.test.ts | 36 ++++++++++++++++++++ 6 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 mocks/app-function/routes/api-response.tsx create mode 100644 mocks/app-function/routes/async-jsx.tsx create mode 100644 mocks/app-function/routes/async-response.tsx create mode 100644 mocks/app-function/routes/jsx-response.tsx diff --git a/mocks/app-function/routes/api-response.tsx b/mocks/app-function/routes/api-response.tsx new file mode 100644 index 0000000..4f370bd --- /dev/null +++ b/mocks/app-function/routes/api-response.tsx @@ -0,0 +1,5 @@ +export default function ApiResponse() { + return new Response(JSON.stringify({ message: 'API Response' }), { + headers: { 'Content-Type': 'application/json' }, + }) +} diff --git a/mocks/app-function/routes/async-jsx.tsx b/mocks/app-function/routes/async-jsx.tsx new file mode 100644 index 0000000..8e1a109 --- /dev/null +++ b/mocks/app-function/routes/async-jsx.tsx @@ -0,0 +1,4 @@ +export default async function AsyncJsx() { + await new Promise((resolve) => setTimeout(resolve, 10)) // simulate async work + return
Async JSX Response
+} diff --git a/mocks/app-function/routes/async-response.tsx b/mocks/app-function/routes/async-response.tsx new file mode 100644 index 0000000..a5b1fab --- /dev/null +++ b/mocks/app-function/routes/async-response.tsx @@ -0,0 +1,9 @@ +export default async function AsyncResponse() { + return new Response(JSON.stringify({ message: 'Async Response' }), { + status: 201, + headers: { + 'Content-Type': 'application/json', + 'x-custom': 'async', + }, + }) +} diff --git a/mocks/app-function/routes/jsx-response.tsx b/mocks/app-function/routes/jsx-response.tsx new file mode 100644 index 0000000..3051372 --- /dev/null +++ b/mocks/app-function/routes/jsx-response.tsx @@ -0,0 +1,3 @@ +export default function JsxResponse() { + return
JSX Response
+} diff --git a/src/server/server.ts b/src/server/server.ts index 465592e..5636a22 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -182,8 +182,14 @@ export const createApp = (options: BaseServerOptions): Hono // export default function Helle() {} if (typeof routeDefault === 'function') { subApp.get(path, setInnerMeta) - subApp.get(path, (c) => { - return c.render(routeDefault(c), route as any) + subApp.get(path, async (c) => { + const result = await routeDefault(c) + + if (result instanceof Response) { + return result + } + + return c.render(result, route as any) }) } } diff --git a/test-integration/apps.test.ts b/test-integration/apps.test.ts index 570ce83..306f988 100644 --- a/test-integration/apps.test.ts +++ b/test-integration/apps.test.ts @@ -877,3 +877,39 @@ describe('Nested Dynamic Routes', () => { expect(await res.text()).toBe('Resource2 Id abcdef / 12345') }) }) + +describe('Function Component Response', () => { + const ROUTES = import.meta.glob('../mocks/app-function/routes/**/[a-z[-][a-z[_-]*.(tsx|ts)', { + eager: true, + }) + + const app = createApp({ + root: '../mocks/app-function/routes', + ROUTES: ROUTES as any, + }) + + it('Should handle direct Response return from function component', async () => { + const res = await app.request('/api-response') + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ message: 'API Response' }) + }) + + it('Should handle JSX return from function component', async () => { + const res = await app.request('/jsx-response') + expect(res.status).toBe(200) + expect(await res.text()).toBe('
JSX Response
') + }) + + it('Should handle async function component with Response', async () => { + const res = await app.request('/async-response') + expect(res.status).toBe(201) + expect(res.headers.get('x-custom')).toBe('async') + expect(await res.json()).toEqual({ message: 'Async Response' }) + }) + + it('Should handle async function component with JSX', async () => { + const res = await app.request('/async-jsx') + expect(res.status).toBe(200) + expect(await res.text()).toBe('
Async JSX Response
') + }) +})