feat(react-renderer): Nested layout support for react-renderer (#449)
* feat: nested layout support for react-renderer * refactor * feat: describe changesetpull/457/head
parent
ffd67e5412
commit
284904357f
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hono/react-renderer': minor
|
||||
---
|
||||
|
||||
nested layout support for react-renderer
|
|
@ -39,7 +39,7 @@
|
|||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"esbuild": "^0.20.2",
|
||||
"hono": "^3.11.7",
|
||||
"hono": "^4.2.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tsup": "^8.0.1",
|
||||
|
|
|
@ -15,14 +15,21 @@ type BaseProps = {
|
|||
children: React.ReactElement
|
||||
}
|
||||
|
||||
type ComponentProps = Props &
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
BaseProps & { Layout: React.FC<Record<string, any> & { children: React.ReactElement }> }
|
||||
|
||||
const RequestContext = React.createContext<Context | null>(null)
|
||||
|
||||
const createRenderer =
|
||||
(c: Context, component?: React.FC<Props & BaseProps>, options?: RendererOptions) =>
|
||||
(
|
||||
c: Context,
|
||||
Layout: React.FC<{ children: React.ReactElement }>,
|
||||
component?: React.FC<ComponentProps>,
|
||||
options?: RendererOptions
|
||||
) =>
|
||||
async (children: React.ReactElement, props?: Props) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const node = component ? component({ children, c, ...props }) : children
|
||||
const node = component ? component({ children, Layout, c, ...props }) : children
|
||||
|
||||
if (options?.stream) {
|
||||
const { renderToReadableStream } = await import('react-dom/server')
|
||||
|
@ -53,11 +60,20 @@ const createRenderer =
|
|||
}
|
||||
|
||||
export const reactRenderer = (
|
||||
component?: React.FC<Props & BaseProps>,
|
||||
component?: React.FC<ComponentProps>,
|
||||
options?: RendererOptions
|
||||
): MiddlewareHandler =>
|
||||
function reactRenderer(c, next) {
|
||||
c.setRenderer(createRenderer(c, component, options))
|
||||
const Layout = (c.getLayout() ?? React.Fragment) as React.FC<{
|
||||
children: React.ReactElement
|
||||
}>
|
||||
if (component) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
c.setLayout((props: any) => {
|
||||
return component({ ...props, Layout, c }, c)
|
||||
})
|
||||
}
|
||||
c.setRenderer(createRenderer(c, Layout, component, options))
|
||||
return next()
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,69 @@ describe('Basic', () => {
|
|||
expect(await res.text()).toBe('<h1>http://localhost/</h1>')
|
||||
})
|
||||
|
||||
it('nested layout with Layout', async () => {
|
||||
const app = new Hono()
|
||||
app.use(
|
||||
'*',
|
||||
// @ts-expect-error - `title` is not defined
|
||||
reactRenderer(({ children, title, Layout }) => {
|
||||
return (
|
||||
<Layout>
|
||||
<html>
|
||||
<head>{title}</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
</Layout>
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
const app2 = new Hono()
|
||||
app2.use(
|
||||
'*',
|
||||
// @ts-expect-error - `title` is not defined
|
||||
reactRenderer(({ children, Layout, title }) => {
|
||||
return (
|
||||
<Layout title={title}>
|
||||
<div className='nested'>{children}</div>
|
||||
</Layout>
|
||||
)
|
||||
})
|
||||
)
|
||||
app2.get('/', (c) => c.render(<h1>http://localhost/nested</h1>, { title: 'Nested' }))
|
||||
|
||||
const app3 = new Hono()
|
||||
app3.use(
|
||||
'*',
|
||||
// @ts-expect-error - `title` is not defined
|
||||
reactRenderer(({ children, Layout, title }) => {
|
||||
return (
|
||||
<Layout title={title}>
|
||||
<div className='nested2'>{children}</div>
|
||||
</Layout>
|
||||
)
|
||||
})
|
||||
)
|
||||
app3.get('/', (c) => c.render(<h1>http://localhost/nested</h1>, { title: 'Nested2' }))
|
||||
app2.route('/nested2', app3)
|
||||
|
||||
app.route('/nested', app2)
|
||||
|
||||
let res = await app.request('http://localhost/nested')
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe(
|
||||
'<html><head>Nested</head><body><div class="nested"><h1>http://localhost/nested</h1></div></body></html>'
|
||||
)
|
||||
|
||||
res = await app.request('http://localhost/nested/nested2')
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe(
|
||||
'<html><head>Nested2</head><body><div class="nested"><div class="nested2"><h1>http://localhost/nested</h1></div></div></body></html>'
|
||||
)
|
||||
})
|
||||
|
||||
it('Should return a default doctype', async () => {
|
||||
const app = new Hono()
|
||||
app.use(
|
||||
|
|
|
@ -2013,7 +2013,7 @@ __metadata:
|
|||
"@types/react": "npm:^18"
|
||||
"@types/react-dom": "npm:^18.2.17"
|
||||
esbuild: "npm:^0.20.2"
|
||||
hono: "npm:^3.11.7"
|
||||
hono: "npm:^4.2.3"
|
||||
react: "npm:^18.2.0"
|
||||
react-dom: "npm:^18.2.0"
|
||||
tsup: "npm:^8.0.1"
|
||||
|
@ -9661,6 +9661,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hono@npm:^4.2.3":
|
||||
version: 4.2.3
|
||||
resolution: "hono@npm:4.2.3"
|
||||
checksum: eced562fcfc97cd4f2fd4fb081e99775d23c58c8fb1a6c57311ddfba71c23e1da4d33294b0ab1fdd0c4587351dc278f4f1f7d02fcd0ded156c32c74e66a01f45
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hosted-git-info@npm:^2.1.4":
|
||||
version: 2.8.9
|
||||
resolution: "hosted-git-info@npm:2.8.9"
|
||||
|
|
Loading…
Reference in New Issue