Skip to content

Commit

Permalink
[Fizz] Start initial work immediately (#31079)
Browse files Browse the repository at this point in the history
In a recent update we make Flight start working immediately rather than
waitin for a new task. This commit updates fizz to have similar
mechanics. We start the render in the currently running task but we do
so in a microtask to avoid reentrancy. This aligns Fizz with Flight.

ref: #30961
  • Loading branch information
gnoff authored Sep 26, 2024
1 parent 76aee6f commit 67fee58
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 153 deletions.
4 changes: 2 additions & 2 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8119,7 +8119,7 @@ describe('ReactDOMFizzServer', () => {

prerendering = false;

const resumed = await ReactDOMFizzServer.resumeToPipeableStream(
const resumed = ReactDOMFizzServer.resumeToPipeableStream(
<App />,
JSON.parse(JSON.stringify(prerendered.postponed)),
{
Expand Down Expand Up @@ -8187,7 +8187,7 @@ describe('ReactDOMFizzServer', () => {
function onPostpone(reason) {
postpones.push(reason);
}
const result = await renderToPipeableStream(<App />, {
const result = renderToPipeableStream(<App />, {
onError,
onShellError,
onPostpone,
Expand Down
237 changes: 124 additions & 113 deletions packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ let Stream;
let React;
let ReactDOMFizzServer;
let Suspense;
let act;

describe('ReactDOMFizzServerNode', () => {
beforeEach(() => {
Expand All @@ -22,6 +23,7 @@ describe('ReactDOMFizzServerNode', () => {
ReactDOMFizzServer = require('react-dom/server');
Stream = require('stream');
Suspense = React.Suspense;
act = require('internal-test-utils').act;
});

function getTestWritable() {
Expand Down Expand Up @@ -54,54 +56,59 @@ describe('ReactDOMFizzServerNode', () => {
throw theInfinitePromise;
}

it('should call renderToPipeableStream', () => {
it('should call renderToPipeableStream', async () => {
const {writable, output} = getTestWritable();
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<div>hello world</div>,
);
pipe(writable);
jest.runAllTimers();
await act(() => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<div>hello world</div>,
);
pipe(writable);
});
expect(output.result).toMatchInlineSnapshot(`"<div>hello world</div>"`);
});

it('should emit DOCTYPE at the root of the document', () => {
it('should emit DOCTYPE at the root of the document', async () => {
const {writable, output} = getTestWritable();
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<html>
<body>hello world</body>
</html>,
);
pipe(writable);
jest.runAllTimers();
await act(() => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<html>
<body>hello world</body>
</html>,
);
pipe(writable);
});
// with Float, we emit empty heads if they are elided when rendering <html>
expect(output.result).toMatchInlineSnapshot(
`"<!DOCTYPE html><html><head></head><body>hello world</body></html>"`,
);
});

it('should emit bootstrap script src at the end', () => {
it('should emit bootstrap script src at the end', async () => {
const {writable, output} = getTestWritable();
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<div>hello world</div>,
{
bootstrapScriptContent: 'INIT();',
bootstrapScripts: ['init.js'],
bootstrapModules: ['init.mjs'],
},
);
pipe(writable);
jest.runAllTimers();
await act(() => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<div>hello world</div>,
{
bootstrapScriptContent: 'INIT();',
bootstrapScripts: ['init.js'],
bootstrapModules: ['init.mjs'],
},
);
pipe(writable);
});
expect(output.result).toMatchInlineSnapshot(
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
);
});

it('should start writing after pipe', () => {
it('should start writing after pipe', async () => {
const {writable, output} = getTestWritable();
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<div>hello world</div>,
);
jest.runAllTimers();
let pipe;
await act(() => {
pipe = ReactDOMFizzServer.renderToPipeableStream(
<div>hello world</div>,
).pipe;
});
// First we write our header.
output.result +=
'<!doctype html><html><head><title>test</title><head><body>';
Expand Down Expand Up @@ -281,24 +288,26 @@ describe('ReactDOMFizzServerNode', () => {
let isCompleteCalls = 0;
const errors = [];
const {writable, output, completed} = getTestWritable();
const {pipe, abort} = ReactDOMFizzServer.renderToPipeableStream(
<div>
<Suspense fallback={<div>Loading</div>}>
<InfiniteSuspend />
</Suspense>
</div>,
{
onError(x) {
errors.push(x.message);
},
onAllReady() {
isCompleteCalls++;
let abort;
await act(() => {
const pipeable = ReactDOMFizzServer.renderToPipeableStream(
<div>
<Suspense fallback={<div>Loading</div>}>
<InfiniteSuspend />
</Suspense>
</div>,
{
onError(x) {
errors.push(x.message);
},
onAllReady() {
isCompleteCalls++;
},
},
},
);
pipe(writable);

jest.runAllTimers();
);
pipeable.pipe(writable);
abort = pipeable.abort;
});

expect(output.result).toContain('Loading');
expect(isCompleteCalls).toBe(0);
Expand Down Expand Up @@ -360,26 +369,28 @@ describe('ReactDOMFizzServerNode', () => {
let isCompleteCalls = 0;
const errors = [];
const {writable, output, completed} = getTestWritable();
const {pipe, abort} = ReactDOMFizzServer.renderToPipeableStream(
<div>
<Suspense fallback="Loading">
<Suspense fallback={<InfiniteSuspend />}>
<InfiniteSuspend />
let abort;
await act(() => {
const pipeable = ReactDOMFizzServer.renderToPipeableStream(
<div>
<Suspense fallback="Loading">
<Suspense fallback={<InfiniteSuspend />}>
<InfiniteSuspend />
</Suspense>
</Suspense>
</Suspense>
</div>,
{
onError(x) {
errors.push(x.message);
},
onAllReady() {
isCompleteCalls++;
</div>,
{
onError(x) {
errors.push(x.message);
},
onAllReady() {
isCompleteCalls++;
},
},
},
);
pipe(writable);

jest.runAllTimers();
);
pipeable.pipe(writable);
abort = pipeable.abort;
});

expect(output.result).toContain('Loading');
expect(isCompleteCalls).toBe(0);
Expand Down Expand Up @@ -428,15 +439,15 @@ describe('ReactDOMFizzServerNode', () => {

const client = new DelayClient();
const {writable, output, completed} = getTestWritable();
ReactDOMFizzServer.renderToPipeableStream(
<DelayContext.Provider value={client}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>,
).pipe(writable);

jest.runAllTimers();
await act(() => {
ReactDOMFizzServer.renderToPipeableStream(
<DelayContext.Provider value={client}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>,
).pipe(writable);
});

expect(output.error).toBe(undefined);
expect(output.result).toContain('loading');
Expand Down Expand Up @@ -481,29 +492,28 @@ describe('ReactDOMFizzServerNode', () => {
output: output0,
completed: completed0,
} = getTestWritable();
ReactDOMFizzServer.renderToPipeableStream(
<DelayContext.Provider value={client0}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>,
).pipe(writable0);

const client1 = new DelayClient();
const {
writable: writable1,
output: output1,
completed: completed1,
} = getTestWritable();
ReactDOMFizzServer.renderToPipeableStream(
<DelayContext.Provider value={client1}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>,
).pipe(writable1);

jest.runAllTimers();
await act(() => {
ReactDOMFizzServer.renderToPipeableStream(
<DelayContext.Provider value={client0}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>,
).pipe(writable0);
ReactDOMFizzServer.renderToPipeableStream(
<DelayContext.Provider value={client1}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>,
).pipe(writable1);
});

expect(output0.error).toBe(undefined);
expect(output0.result).toContain('loading');
Expand Down Expand Up @@ -552,22 +562,22 @@ describe('ReactDOMFizzServerNode', () => {

const client = new DelayClient();
const {writable, output, completed} = getTestWritable();
ReactDOMFizzServer.renderToPipeableStream(
<>
<DelayContext.Provider value={client}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>
<DelayContext.Provider value={client}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>
</>,
).pipe(writable);

jest.runAllTimers();
await act(() => {
ReactDOMFizzServer.renderToPipeableStream(
<>
<DelayContext.Provider value={client}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>
<DelayContext.Provider value={client}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>
</>,
).pipe(writable);
});

expect(output.error).toBe(undefined);
expect(output.result).toContain('loading');
Expand Down Expand Up @@ -630,13 +640,14 @@ describe('ReactDOMFizzServerNode', () => {
expect(isComplete).toBe(true);
});

it('should encode multibyte characters correctly without nulls (#24985)', () => {
it('should encode multibyte characters correctly without nulls (#24985)', async () => {
const {writable, output} = getTestWritable();
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<div>{Array(700).fill('ののの')}</div>,
);
pipe(writable);
jest.runAllTimers();
await act(() => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<div>{Array(700).fill('ののの')}</div>,
);
pipe(writable);
});
expect(output.result.indexOf('\u0000')).toBe(-1);
expect(output.result).toEqual(
'<div>' + Array(700).fill('ののの').join('<!-- -->') + '</div>',
Expand Down
Loading

0 comments on commit 67fee58

Please sign in to comment.