Skip to content

Commit bfd434f

Browse files
authored
test(rsc): add SSR thenable workaround in examples (#591)
1 parent 4d6c72f commit bfd434f

File tree

5 files changed

+93
-4
lines changed

5 files changed

+93
-4
lines changed

packages/plugin-rsc/e2e/fixture.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export async function setupInlineFixture(options: {
217217
let filepath = path.join(options.dest, filename)
218218
fs.mkdirSync(path.dirname(filepath), { recursive: true })
219219
// strip indent
220-
contents = contents.replace(/^\n/, '')
220+
contents = contents.replace(/^\n*/, '').replace(/\s*$/, '\n')
221221
const indent = contents.match(/^\s*/)?.[0] ?? ''
222222
const strippedContents = contents
223223
.split('\n')

packages/plugin-rsc/e2e/helper.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,15 @@ export async function expectNoReload(page: Page) {
4242
},
4343
}
4444
}
45+
46+
export function expectNoPageError(page: Page) {
47+
const errors: Error[] = []
48+
page.on('pageerror', (error) => {
49+
errors.push(error)
50+
})
51+
return {
52+
[Symbol.dispose]: () => {
53+
expect(errors).toEqual([])
54+
},
55+
}
56+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { test } from '@playwright/test'
2+
import { setupInlineFixture, type Fixture, useFixture } from './fixture'
3+
import {
4+
expectNoPageError,
5+
waitForHydration as waitForHydration_,
6+
} from './helper'
7+
8+
test.describe(() => {
9+
const root = 'examples/e2e/temp/ssr-thenable'
10+
11+
test.beforeAll(async () => {
12+
await setupInlineFixture({
13+
src: 'examples/starter',
14+
dest: root,
15+
files: {
16+
'src/root.tsx': /* tsx */ `
17+
import { TestClientUse } from './client.tsx'
18+
19+
export function Root() {
20+
return (
21+
<html lang="en">
22+
<head>
23+
<meta charSet="UTF-8" />
24+
</head>
25+
<body>
26+
<TestClientUse />
27+
</body>
28+
</html>
29+
)
30+
}
31+
`,
32+
'src/client.tsx': /* tsx */ `
33+
"use client";
34+
import React from 'react'
35+
36+
const promise = Promise.resolve('ok')
37+
38+
export function TestClientUse() {
39+
const value = React.use(promise)
40+
return <span data-testid="client-use">{value}</span>
41+
}
42+
`,
43+
},
44+
})
45+
})
46+
47+
function defineSsrThenableTest(f: Fixture) {
48+
test('ssr-thenable', async ({ page }) => {
49+
using _ = expectNoPageError(page)
50+
await page.goto(f.url())
51+
await waitForHydration_(page)
52+
})
53+
}
54+
55+
test.describe('dev', () => {
56+
const f = useFixture({ root, mode: 'dev' })
57+
defineSsrThenableTest(f)
58+
})
59+
60+
test.describe('build', () => {
61+
const f = useFixture({ root, mode: 'build' })
62+
defineSsrThenableTest(f)
63+
})
64+
})

packages/plugin-rsc/examples/basic/src/framework/entry.ssr.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ export async function renderHTML(
2424
// deserialization needs to be kicked off inside ReactDOMServer context
2525
// for ReactDomServer preinit/preloading to work
2626
payload ??= ReactClient.createFromReadableStream<RscPayload>(rscStream1)
27-
return React.use(payload).root
27+
return <FixSsrThenable>{React.use(payload).root}</FixSsrThenable>
28+
}
29+
30+
function FixSsrThenable(props: React.PropsWithChildren) {
31+
return props.children
2832
}
2933

3034
// render html (traditional SSR)

packages/plugin-rsc/examples/starter/src/framework/entry.ssr.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,21 @@ export async function renderHTML(
1919
const [rscStream1, rscStream2] = rscStream.tee()
2020

2121
// deserialize RSC stream back to React VDOM
22-
let payload: Promise<RscPayload>
22+
let payload: Promise<RscPayload> | undefined
2323
function SsrRoot() {
2424
// deserialization needs to be kicked off inside ReactDOMServer context
2525
// for ReactDomServer preinit/preloading to work
2626
payload ??= ReactClient.createFromReadableStream<RscPayload>(rscStream1)
27-
return React.use(payload).root
27+
return <FixSsrThenable>{React.use(payload).root}</FixSsrThenable>
28+
}
29+
30+
// Add an empty component in between `SsrRoot` and user `root` to avoid React SSR bugs.
31+
// SsrRoot (use)
32+
// => FixSsrThenable
33+
// => root (which potentially has `lazy` + `use`)
34+
// https://github.com/facebook/react/issues/33937#issuecomment-3091349011
35+
function FixSsrThenable(props: React.PropsWithChildren) {
36+
return props.children
2837
}
2938

3039
// render html (traditional SSR)

0 commit comments

Comments
 (0)