feat(react-renderer): use React v19 and specify `react-dom/server.edge` for `renderToReadableStream` (#1119)

* feat(react-renderer): use React v19 and specify `react-dom/server.edge` for `renderToReadableStream`

* changeset
pull/1121/head
Yusuke Wada 2025-04-11 07:43:03 +09:00 committed by GitHub
parent ca3cada076
commit 684ae9a21d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 65 additions and 41 deletions

View File

@ -0,0 +1,5 @@
---
'@hono/react-renderer': major
---
feat: use React v19 and specify `react-dom/server.edge` for `renderToReadableStream`

View File

@ -40,17 +40,17 @@
"homepage": "https://github.com/honojs/middleware", "homepage": "https://github.com/honojs/middleware",
"peerDependencies": { "peerDependencies": {
"hono": "*", "hono": "*",
"react": "*", "react": "^19.0.0",
"react-dom": "*" "react-dom": "^19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@arethetypeswrong/cli": "^0.17.4", "@arethetypeswrong/cli": "^0.17.4",
"@cloudflare/vitest-pool-workers": "^0.7.8", "@cloudflare/vitest-pool-workers": "^0.7.8",
"@types/react": "^18", "@types/react": "^19.1.0",
"@types/react-dom": "^18.2.17", "@types/react-dom": "^19.1.2",
"publint": "^0.3.9", "publint": "^0.3.9",
"react": "^18.2.0", "react": "^19.1.0",
"react-dom": "^18.2.0", "react-dom": "^19.1.0",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.0.8"

View File

@ -128,6 +128,7 @@ describe('Basic', () => {
({ children }) => { ({ children }) => {
return ( return (
<html> <html>
<head></head>
<body>{children}</body> <body>{children}</body>
</html> </html>
) )
@ -139,7 +140,9 @@ describe('Basic', () => {
const res = await app.request('/') const res = await app.request('/')
expect(res).not.toBeNull() expect(res).not.toBeNull()
expect(res.status).toBe(200) expect(res.status).toBe(200)
expect(await res.text()).toBe('<!DOCTYPE html><html><body><h1>Hello</h1></body></html>') expect(await res.text()).toBe(
'<!DOCTYPE html><html><head></head><body><h1>Hello</h1></body></html>'
)
}) })
it('Should return a content without a doctype', async () => { it('Should return a content without a doctype', async () => {
@ -161,7 +164,7 @@ describe('Basic', () => {
const res = await app.request('/') const res = await app.request('/')
expect(res).not.toBeNull() expect(res).not.toBeNull()
expect(res.status).toBe(200) expect(res.status).toBe(200)
expect(await res.text()).toBe('<html><body><h1>Hello</h1></body></html>') expect(await res.text()).toBe('<html><head></head><body><h1>Hello</h1></body></html>')
}) })
it('Should return a custom doctype', async () => { it('Should return a custom doctype', async () => {
@ -187,7 +190,7 @@ describe('Basic', () => {
expect(res).not.toBeNull() expect(res).not.toBeNull()
expect(res.status).toBe(200) expect(res.status).toBe(200)
expect(await res.text()).toBe( expect(await res.text()).toBe(
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html><body><h1>Hello</h1></body></html>' '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html><head></head><body><h1>Hello</h1></body></html>'
) )
}) })
@ -231,6 +234,8 @@ describe('Streaming', () => {
expect(res.status).toBe(200) expect(res.status).toBe(200)
expect(res.headers.get('Transfer-Encoding')).toBe('chunked') expect(res.headers.get('Transfer-Encoding')).toBe('chunked')
expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8') expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')
expect(await res.text()).toBe('<!DOCTYPE html><html><body><h1>Hello</h1></body></html>') expect(await res.text()).toBe(
'<!DOCTYPE html><html><head></head><body><h1>Hello</h1></body></html>'
)
}) })
}) })

View File

@ -30,10 +30,11 @@ const createRenderer =
options?: RendererOptions options?: RendererOptions
) => ) =>
async (children: React.ReactElement, props?: Props) => { async (children: React.ReactElement, props?: Props) => {
const node = component ? component({ children, Layout, c, ...props }) : children const node = component ? await component({ children, Layout, c, ...props }) : children
if (options?.stream) { if (options?.stream) {
const { renderToReadableStream } = await import('react-dom/server') // @ts-expect-error `react-dom/server.edge` is not typed well
const { renderToReadableStream } = await import('react-dom/server.edge')
const stream = await renderToReadableStream( const stream = await renderToReadableStream(
React.createElement(RequestContext.Provider, { value: c }, node), React.createElement(RequestContext.Provider, { value: c }, node),
options.readableStreamOptions options.readableStreamOptions
@ -52,8 +53,8 @@ const createRenderer =
typeof options?.docType === 'string' typeof options?.docType === 'string'
? options.docType ? options.docType
: options?.docType === false : options?.docType === false
? '' ? ''
: '<!DOCTYPE html>' : '<!DOCTYPE html>'
const body = const body =
docType + renderToString(React.createElement(RequestContext.Provider, { value: c }, node)) docType + renderToString(React.createElement(RequestContext.Provider, { value: c }, node))
return c.html(body) return c.html(body)
@ -71,7 +72,7 @@ export const reactRenderer = (
if (component) { if (component) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
c.setLayout((props: any) => { c.setLayout((props: any) => {
return component({ ...props, Layout, c }, c) return component({ ...props, Layout, c })
}) })
} }
c.setRenderer(createRenderer(c, Layout, component, options)) c.setRenderer(createRenderer(c, Layout, component, options))

View File

@ -2197,18 +2197,18 @@ __metadata:
dependencies: dependencies:
"@arethetypeswrong/cli": "npm:^0.17.4" "@arethetypeswrong/cli": "npm:^0.17.4"
"@cloudflare/vitest-pool-workers": "npm:^0.7.8" "@cloudflare/vitest-pool-workers": "npm:^0.7.8"
"@types/react": "npm:^18" "@types/react": "npm:^19.1.0"
"@types/react-dom": "npm:^18.2.17" "@types/react-dom": "npm:^19.1.2"
publint: "npm:^0.3.9" publint: "npm:^0.3.9"
react: "npm:^18.2.0" react: "npm:^19.1.0"
react-dom: "npm:^18.2.0" react-dom: "npm:^19.1.0"
tsup: "npm:^8.4.0" tsup: "npm:^8.4.0"
typescript: "npm:^5.8.2" typescript: "npm:^5.8.2"
vitest: "npm:^3.0.8" vitest: "npm:^3.0.8"
peerDependencies: peerDependencies:
hono: "*" hono: "*"
react: "*" react: ^19.0.0
react-dom: "*" react-dom: ^19.0.0
languageName: unknown languageName: unknown
linkType: soft linkType: soft
@ -3924,12 +3924,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react-dom@npm:^18.2.17": "@types/react-dom@npm:^19.1.2":
version: 18.3.5 version: 19.1.2
resolution: "@types/react-dom@npm:18.3.5" resolution: "@types/react-dom@npm:19.1.2"
peerDependencies: peerDependencies:
"@types/react": ^18.0.0 "@types/react": ^19.0.0
checksum: b163d35a6b32a79f5782574a7aeb12a31a647e248792bf437e6d596e2676961c394c5e3c6e91d1ce44ae90441dbaf93158efb4f051c0d61e2612f1cb04ce4faa checksum: 100c341cacba9ec8ae1d47ee051072a3450e9573bf8eeb7262490e341cb246ea0f95a07a1f2077e61cf92648f812a0324c602fcd811bd87b7ce41db2811510cd
languageName: node languageName: node
linkType: hard linkType: hard
@ -3943,6 +3943,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react@npm:^19.1.0":
version: 19.1.0
resolution: "@types/react@npm:19.1.0"
dependencies:
csstype: "npm:^3.0.2"
checksum: 632fd20ee176e55801a61c5f854141b043571a3e363ef106b047b766a813a12735cbb37abb3d61d126346979f530f2ed269a60c8ef3cdee54e5e9fe4174e5dad
languageName: node
linkType: hard
"@types/request@npm:^2.48.8": "@types/request@npm:^2.48.8":
version: 2.48.12 version: 2.48.12
resolution: "@types/request@npm:2.48.12" resolution: "@types/request@npm:2.48.12"
@ -11882,15 +11891,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-dom@npm:^18.2.0": "react-dom@npm:^19.1.0":
version: 18.3.1 version: 19.1.0
resolution: "react-dom@npm:18.3.1" resolution: "react-dom@npm:19.1.0"
dependencies: dependencies:
loose-envify: "npm:^1.1.0" scheduler: "npm:^0.26.0"
scheduler: "npm:^0.23.2"
peerDependencies: peerDependencies:
react: ^18.3.1 react: ^19.1.0
checksum: a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 checksum: 3e26e89bb6c67c9a6aa86cb888c7a7f8258f2e347a6d2a15299c17eb16e04c19194e3452bc3255bd34000a61e45e2cb51e46292392340432f133e5a5d2dfb5fc
languageName: node languageName: node
linkType: hard linkType: hard
@ -11903,6 +11911,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react@npm:^19.1.0":
version: 19.1.0
resolution: "react@npm:19.1.0"
checksum: 530fb9a62237d54137a13d2cfb67a7db6a2156faed43eecc423f4713d9b20c6f2728b026b45e28fcd72e8eadb9e9ed4b089e99f5e295d2f0ad3134251bdd3698
languageName: node
linkType: hard
"read-yaml-file@npm:^1.1.0": "read-yaml-file@npm:^1.1.0":
version: 1.1.0 version: 1.1.0
resolution: "read-yaml-file@npm:1.1.0" resolution: "read-yaml-file@npm:1.1.0"
@ -12610,12 +12625,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"scheduler@npm:^0.23.2": "scheduler@npm:^0.26.0":
version: 0.23.2 version: 0.26.0
resolution: "scheduler@npm:0.23.2" resolution: "scheduler@npm:0.26.0"
dependencies: checksum: 5b8d5bfddaae3513410eda54f2268e98a376a429931921a81b5c3a2873aab7ca4d775a8caac5498f8cbc7d0daeab947cf923dbd8e215d61671f9f4e392d34356
loose-envify: "npm:^1.1.0"
checksum: 26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78
languageName: node languageName: node
linkType: hard linkType: hard