Skip to content

Commit

Permalink
Use ReadableStream in client hydration for Firefox compatibility (#35796
Browse files Browse the repository at this point in the history
)

Firefox doesn't support `TransformStream` but `ReadableStream` at the moment, change the implmentation of hydration to make firefox work with RSC

## Bug

Fixes #35763
x-ref: vercel/next-react-server-components#38

- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Errors have helpful link attached, see `contributing.md`
  • Loading branch information
huozhi committed Mar 31, 2022
1 parent 69a166b commit d165836
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/build_test_deploy.yml
Expand Up @@ -535,6 +535,9 @@ jobs:
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
- run: node run-tests.js test/integration/production/test/index.test.js
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
# test rsc hydration on firefox due to limited support of TransformStream api
- run: xvfb-run yarn jest test/integration/react-streaming-and-server-components/test/index.test.js -t "should handle streaming server components correctly"
if: ${{needs.build.outputs.docsChange != 'docs only change'}}

testSafari:
name: Test Safari (production)
Expand Down
33 changes: 18 additions & 15 deletions packages/next/client/index.tsx
Expand Up @@ -680,7 +680,7 @@ if (process.env.__NEXT_RSC) {
const encoder = new TextEncoder()

let initialServerDataBuffer: string[] | undefined = undefined
let initialServerDataWriter: WritableStreamDefaultWriter | undefined =
let initialServerDataWriter: ReadableStreamDefaultController | undefined =
undefined
let initialServerDataLoaded = false
let initialServerDataFlushed = false
Expand All @@ -693,7 +693,7 @@ if (process.env.__NEXT_RSC) {
throw new Error('Unexpected server data: missing bootstrap script.')

if (initialServerDataWriter) {
initialServerDataWriter.write(encoder.encode(seg[2]))
initialServerDataWriter.enqueue(encoder.encode(seg[2]))
} else {
initialServerDataBuffer.push(seg[2])
}
Expand All @@ -708,19 +708,19 @@ if (process.env.__NEXT_RSC) {
// Hence, we use two variables `initialServerDataLoaded` and
// `initialServerDataFlushed` to make sure the writer will be closed and
// `initialServerDataBuffer` will be cleared in the right time.
function nextServerDataRegisterWriter(writer: WritableStreamDefaultWriter) {
function nextServerDataRegisterWriter(ctr: ReadableStreamDefaultController) {
if (initialServerDataBuffer) {
initialServerDataBuffer.forEach((val) => {
writer.write(encoder.encode(val))
ctr.enqueue(encoder.encode(val))
})
if (initialServerDataLoaded && !initialServerDataFlushed) {
writer.close()
ctr.close()
initialServerDataFlushed = true
initialServerDataBuffer = undefined
}
}

initialServerDataWriter = writer
initialServerDataWriter = ctr
}

// When `DOMContentLoaded`, we can close all pending writers to finish hydration.
Expand Down Expand Up @@ -764,19 +764,22 @@ if (process.env.__NEXT_RSC) {
if (response) return response

if (initialServerDataBuffer) {
const t = new TransformStream()
const writer = t.writable.getWriter()
response = createFromFetch(Promise.resolve({ body: t.readable }))
nextServerDataRegisterWriter(writer)
const readable = new ReadableStream({
start(controller) {
nextServerDataRegisterWriter(controller)
},
})
response = createFromFetch(Promise.resolve({ body: readable }))
} else {
const fetchPromise = serialized
? (() => {
const t = new TransformStream()
const writer = t.writable.getWriter()
writer.ready.then(() => {
writer.write(new TextEncoder().encode(serialized))
const readable = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(serialized))
controller.close()
},
})
return Promise.resolve({ body: t.readable })
return Promise.resolve({ body: readable })
})()
: fetchFlight(getCacheKey())
response = createFromFetch(fetchPromise)
Expand Down

0 comments on commit d165836

Please sign in to comment.