Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename Node SSR Callbacks to onShellReady/onAllReady and Other Fixes #24030

Merged
merged 5 commits into from
Mar 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions fixtures/ssr/server/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ export default function render(url, res) {
let didError = false;
const {pipe, abort} = renderToPipeableStream(<App assets={assets} />, {
bootstrapScripts: [assets['main.js']],
onCompleteShell() {
onShellReady() {
// If something errored before we started streaming, we set the error code appropriately.
res.statusCode = didError ? 500 : 200;
res.setHeader('Content-type', 'text/html');
pipe(res);
},
onErrorShell(x) {
onShellError(x) {
// Something errored before we could complete the shell so we emit an alternative shell.
res.statusCode = 500;
res.send('<!doctype><p>Error</p>');
Expand Down
2 changes: 1 addition & 1 deletion fixtures/ssr2/server/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ module.exports = function render(url, res) {
res.setHeader('Content-type', 'text/html');
pipe(res);
},
onErrorShell(x) {
onShellError(x) {
// Something errored before we could complete the shell so we emit an alternative shell.
res.statusCode = 500;
res.send('<!doctype><p>Error</p>');
Expand Down
6 changes: 3 additions & 3 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,7 @@ describe('ReactDOMFizzServer', () => {
</Suspense>,
{
identifierPrefix: 'A_',
onCompleteShell() {
onShellReady() {
writableA.write('<div id="container-A">');
pipe(writableA);
writableA.write('</div>');
Expand All @@ -933,7 +933,7 @@ describe('ReactDOMFizzServer', () => {
</Suspense>,
{
identifierPrefix: 'B_',
onCompleteShell() {
onShellReady() {
writableB.write('<div id="container-B">');
pipe(writableB);
writableB.write('</div>');
Expand Down Expand Up @@ -1168,7 +1168,7 @@ describe('ReactDOMFizzServer', () => {

{
namespaceURI: 'http://www.w3.org/2000/svg',
onCompleteShell() {
onShellReady() {
writable.write('<svg>');
pipe(writable);
writable.write('</svg>');
Expand Down
39 changes: 39 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,43 @@ describe('ReactDOMFizzServer', () => {
const result = await readResult(stream);
expect(result).toContain('Loading');
});

// @gate experimental
it('should not continue rendering after the reader cancels', async () => {
let hasLoaded = false;
let resolve;
let isComplete = false;
let rendered = false;
const promise = new Promise(r => (resolve = r));
function Wait() {
if (!hasLoaded) {
throw promise;
}
rendered = true;
return 'Done';
}
const stream = await ReactDOMFizzServer.renderToReadableStream(
<div>
<Suspense fallback={<div>Loading</div>}>
<Wait /> />
</Suspense>
</div>,
);

stream.allReady.then(() => (isComplete = true));

expect(rendered).toBe(false);
expect(isComplete).toBe(false);

const reader = stream.getReader();
reader.cancel();

hasLoaded = true;
resolve();

await jest.runAllTimers();

expect(rendered).toBe(false);
expect(isComplete).toBe(true);
});
});
57 changes: 51 additions & 6 deletions packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ describe('ReactDOMFizzServer', () => {
</div>,

{
onCompleteAll() {
onAllReady() {
isCompleteCalls++;
},
},
Expand Down Expand Up @@ -179,7 +179,7 @@ describe('ReactDOMFizzServer', () => {
onError(x) {
reportedErrors.push(x);
},
onErrorShell(x) {
onShellError(x) {
reportedShellErrors.push(x);
},
},
Expand Down Expand Up @@ -213,7 +213,7 @@ describe('ReactDOMFizzServer', () => {
onError(x) {
reportedErrors.push(x);
},
onErrorShell(x) {
onShellError(x) {
reportedShellErrors.push(x);
},
},
Expand Down Expand Up @@ -244,7 +244,7 @@ describe('ReactDOMFizzServer', () => {
onError(x) {
reportedErrors.push(x);
},
onErrorShell(x) {
onShellError(x) {
reportedShellErrors.push(x);
},
},
Expand Down Expand Up @@ -298,7 +298,7 @@ describe('ReactDOMFizzServer', () => {
</div>,

{
onCompleteAll() {
onAllReady() {
isCompleteCalls++;
},
},
Expand Down Expand Up @@ -333,7 +333,7 @@ describe('ReactDOMFizzServer', () => {
</div>,

{
onCompleteAll() {
onAllReady() {
isCompleteCalls++;
},
},
Expand Down Expand Up @@ -537,4 +537,49 @@ describe('ReactDOMFizzServer', () => {
expect(output.result).not.toContain('context never found');
expect(output.result).toContain('OK');
});

// @gate experimental
it('should not continue rendering after the writable ends unexpectedly', async () => {
let hasLoaded = false;
let resolve;
let isComplete = false;
let rendered = false;
const promise = new Promise(r => (resolve = r));
function Wait() {
if (!hasLoaded) {
throw promise;
}
rendered = true;
return 'Done';
}
const {writable, completed} = getTestWritable();
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<div>
<Suspense fallback={<div>Loading</div>}>
<Wait />
</Suspense>
</div>,
{
onAllReady() {
isComplete = true;
},
},
);
pipe(writable);

expect(rendered).toBe(false);
expect(isComplete).toBe(false);

writable.end();

await jest.runAllTimers();

hasLoaded = true;
resolve();

await completed;

expect(rendered).toBe(false);
expect(isComplete).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ module.exports = function(initModules) {
new Promise((resolve, reject) => {
const writable = new DrainWritable();
const s = ReactDOMServer.renderToPipeableStream(reactElement, {
onErrorShell(e) {
onShellError(e) {
reject(e);
},
});
Expand Down
18 changes: 10 additions & 8 deletions packages/react-dom/src/server/ReactDOMFizzServerBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,27 @@ function renderToReadableStream(
): Promise<ReactDOMServerReadableStream> {
return new Promise((resolve, reject) => {
let onFatalError;
let onCompleteAll;
let onAllReady;
const allReady = new Promise((res, rej) => {
onCompleteAll = res;
onAllReady = res;
onFatalError = rej;
});

function onCompleteShell() {
function onShellReady() {
const stream: ReactDOMServerReadableStream = (new ReadableStream({
type: 'bytes',
pull(controller) {
startFlowing(request, controller);
},
cancel(reason) {},
cancel(reason) {
abort(request);
},
}): any);
// TODO: Move to sub-classing ReadableStream.
stream.allReady = allReady;
resolve(stream);
}
function onErrorShell(error: mixed) {
function onShellError(error: mixed) {
reject(error);
}
const request = createRequest(
Expand All @@ -79,9 +81,9 @@ function renderToReadableStream(
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined,
onCompleteAll,
onCompleteShell,
onErrorShell,
onAllReady,
onShellReady,
onShellError,
onFatalError,
);
if (options && options.signal) {
Expand Down
17 changes: 11 additions & 6 deletions packages/react-dom/src/server/ReactDOMFizzServerNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ function createDrainHandler(destination, request) {
return () => startFlowing(request, destination);
}

function createAbortHandler(request) {
return () => abort(request);
}

type Options = {|
identifierPrefix?: string,
namespaceURI?: string,
Expand All @@ -36,9 +40,9 @@ type Options = {|
bootstrapScripts?: Array<string>,
bootstrapModules?: Array<string>,
progressiveChunkSize?: number,
onCompleteShell?: () => void,
onErrorShell?: () => void,
onCompleteAll?: () => void,
onShellReady?: () => void,
onShellError?: () => void,
onAllReady?: () => void,
onError?: (error: mixed) => void,
|};

Expand All @@ -62,9 +66,9 @@ function createRequestImpl(children: ReactNodeList, options: void | Options) {
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined,
options ? options.onCompleteAll : undefined,
options ? options.onCompleteShell : undefined,
options ? options.onErrorShell : undefined,
options ? options.onAllReady : undefined,
options ? options.onShellReady : undefined,
options ? options.onShellError : undefined,
undefined,
);
}
Expand All @@ -86,6 +90,7 @@ function renderToPipeableStream(
hasStartedFlowing = true;
startFlowing(request, destination);
destination.on('drain', createDrainHandler(destination, request));
destination.on('close', createAbortHandler(request));
return destination;
},
abort() {
Expand Down
4 changes: 2 additions & 2 deletions packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function renderToStringImpl(
};

let readyToStream = false;
function onCompleteShell() {
function onShellReady() {
readyToStream = true;
}
const request = createRequest(
Expand All @@ -66,7 +66,7 @@ function renderToStringImpl(
Infinity,
onError,
undefined,
onCompleteShell,
onShellReady,
undefined,
undefined,
);
Expand Down
4 changes: 2 additions & 2 deletions packages/react-dom/src/server/ReactDOMLegacyServerNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function renderToNodeStreamImpl(
options: void | ServerOptions,
generateStaticMarkup: boolean,
): Readable {
function onCompleteAll() {
function onAllReady() {
// We wait until everything has loaded before starting to write.
// That way we only end up with fully resolved HTML even if we suspend.
destination.startedFlowing = true;
Expand All @@ -81,7 +81,7 @@ function renderToNodeStreamImpl(
createRootFormatContext(),
Infinity,
onError,
onCompleteAll,
onAllReady,
undefined,
undefined,
);
Expand Down
8 changes: 4 additions & 4 deletions packages/react-noop-renderer/src/ReactNoopServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,8 @@ const ReactNoopServer = ReactFizzServer({

type Options = {
progressiveChunkSize?: number,
onCompleteShell?: () => void,
onCompleteAll?: () => void,
onShellReady?: () => void,
onAllReady?: () => void,
onError?: (error: mixed) => void,
};

Expand All @@ -272,8 +272,8 @@ function render(children: React$Element<any>, options?: Options): Destination {
null,
options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined,
options ? options.onCompleteAll : undefined,
options ? options.onCompleteShell : undefined,
options ? options.onAllReady : undefined,
options ? options.onShellReady : undefined,
);
ReactNoopServer.startWork(request);
ReactNoopServer.startFlowing(request, destination);
Expand Down
Loading