Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ let webpackModuleLoading;
let React;
let ReactServer;
let ReactDOMServer;
let ReactDOMFizzStatic;
let ReactServerDOMServer;
let ReactServerDOMStaticServer;
let ReactServerDOMClient;
Expand Down Expand Up @@ -102,6 +103,7 @@ describe('ReactFlightDOMEdge', () => {
);
React = require('react');
ReactDOMServer = require('react-dom/server.edge');
ReactDOMFizzStatic = require('react-dom/static.edge');
ReactServerDOMClient = require('react-server-dom-webpack/client');
use = React.use;
});
Expand Down Expand Up @@ -228,6 +230,30 @@ describe('ReactFlightDOMEdge', () => {
}
}

async function createBufferedUnclosingStream(
prelude: ReadableStream<Uint8Array>,
): ReadableStream<Uint8Array> {
const chunks: Array<Uint8Array> = [];
const reader = prelude.getReader();
while (true) {
const {done, value} = await reader.read();
if (done) {
break;
} else {
chunks.push(value);
}
}

let i = 0;
return new ReadableStream({
async pull(controller) {
if (i < chunks.length) {
controller.enqueue(chunks[i++]);
}
},
});
}

it('should allow an alternative module mapping to be used for SSR', async () => {
function ClientComponent() {
return <span>Client Component</span>;
Expand Down Expand Up @@ -1777,4 +1803,114 @@ describe('ReactFlightDOMEdge', () => {
expect(error).not.toBe(null);
expect(error.message).toBe(expectedMessage);
});

// @gate enableHalt
it('does not include source locations in component stacks for halted components', async () => {
// We only support adding source locations for halted components in the Node.js builds.

async function Component() {
await new Promise(() => {});
return null;
}

function App() {
return ReactServer.createElement(
'html',
null,
ReactServer.createElement(
'body',
null,
ReactServer.createElement(
ReactServer.Suspense,
{fallback: 'Loading...'},
ReactServer.createElement(Component, null),
),
),
);
}

const serverAbortController = new AbortController();
const errors = [];
const prerenderResult = ReactServerDOMStaticServer.unstable_prerender(
ReactServer.createElement(App, null),
webpackMap,
{
signal: serverAbortController.signal,
onError(err) {
errors.push(err);
},
},
);

await new Promise(resolve => {
setImmediate(() => {
serverAbortController.abort();
resolve();
});
});

const {prelude} = await prerenderResult;

expect(errors).toEqual([]);

function ClientRoot({response}) {
return use(response);
}

const prerenderResponse = ReactServerDOMClient.createFromReadableStream(
await createBufferedUnclosingStream(prelude),
{
serverConsumerManifest: {
moduleMap: null,
moduleLoading: null,
},
},
);

let componentStack;
let ownerStack;

const clientAbortController = new AbortController();

const fizzPrerenderStreamResult = ReactDOMFizzStatic.prerender(
React.createElement(ClientRoot, {response: prerenderResponse}),
{
signal: clientAbortController.signal,
onError(error, errorInfo) {
componentStack = errorInfo.componentStack;
ownerStack = React.captureOwnerStack
? React.captureOwnerStack()
: null;
},
},
);

await new Promise(resolve => {
setImmediate(() => {
clientAbortController.abort();
resolve();
});
});

const fizzPrerenderStream = await fizzPrerenderStreamResult;
const prerenderHTML = await readResult(fizzPrerenderStream.prelude);

expect(prerenderHTML).toContain('Loading...');

if (__DEV__) {
expect(normalizeCodeLocInfo(componentStack)).toBe(
'\n in Component\n in Suspense\n in body\n in html\n in ClientRoot (at **)',
);
} else {
expect(normalizeCodeLocInfo(componentStack)).toBe(
'\n in Suspense\n in body\n in html\n in ClientRoot (at **)',
);
}

if (__DEV__) {
expect(normalizeCodeLocInfo(ownerStack)).toBe('\n in App (at **)');
} else {
expect(ownerStack).toBeNull();
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ let webpackModules;
let webpackModuleLoading;
let React;
let ReactDOMServer;
let ReactDOMFizzStatic;
let ReactServer;
let ReactServerDOMServer;
let ReactServerDOMStaticServer;
Expand Down Expand Up @@ -70,11 +71,21 @@ describe('ReactFlightDOMNode', () => {

React = require('react');
ReactDOMServer = require('react-dom/server.node');
ReactDOMFizzStatic = require('react-dom/static');
ReactServerDOMClient = require('react-server-dom-webpack/client');
Stream = require('stream');
use = React.use;
});

function normalizeCodeLocInfo(str) {
return (
str &&
str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) {
return ' in ' + name + (/\d/.test(m) ? ' (at **)' : '');
})
);
}

function readResult(stream) {
return new Promise((resolve, reject) => {
let buffer = '';
Expand All @@ -93,6 +104,42 @@ describe('ReactFlightDOMNode', () => {
});
}

async function readWebResult(webStream: ReadableStream<Uint8Array>) {
const reader = webStream.getReader();
let result = '';
while (true) {
const {done, value} = await reader.read();
if (done) {
return result;
}
result += Buffer.from(value).toString('utf8');
}
}

async function createBufferedUnclosingStream(
prelude: ReadableStream<Uint8Array>,
): ReadableStream<Uint8Array> {
const chunks: Array<Uint8Array> = [];
const reader = prelude.getReader();
while (true) {
const {done, value} = await reader.read();
if (done) {
break;
} else {
chunks.push(value);
}
}

let i = 0;
return new ReadableStream({
async pull(controller) {
if (i < chunks.length) {
controller.enqueue(chunks[i++]);
}
},
});
}

it('should support web streams in node', async () => {
function Text({children}) {
return <span>{children}</span>;
Expand Down Expand Up @@ -543,4 +590,125 @@ describe('ReactFlightDOMNode', () => {
const result = await readResult(ssrStream);
expect(result).toContain('loading...');
});

// @gate enableHalt && enableAsyncDebugInfo
it('includes source locations in component and owner stacks for halted components', async () => {
async function Component() {
await new Promise(() => {});
return null;
}

function App() {
return ReactServer.createElement(
'html',
null,
ReactServer.createElement(
'body',
null,
ReactServer.createElement(
ReactServer.Suspense,
{fallback: 'Loading...'},
ReactServer.createElement(Component, null),
),
),
);
}

const errors = [];
const serverAbortController = new AbortController();
const {pendingResult} = await serverAct(async () => {
// destructure trick to avoid the act scope from awaiting the returned value
return {
pendingResult: ReactServerDOMStaticServer.unstable_prerender(
ReactServer.createElement(App, null),
webpackMap,
{
signal: serverAbortController.signal,
onError(error) {
errors.push(error);
},
},
),
};
});

await await serverAct(
async () =>
new Promise(resolve => {
setImmediate(() => {
serverAbortController.abort();
resolve();
});
}),
);

const {prelude} = await pendingResult;

expect(errors).toEqual([]);

function ClientRoot({response}) {
return use(response);
}

const prerenderResponse = ReactServerDOMClient.createFromReadableStream(
await createBufferedUnclosingStream(prelude),
{
serverConsumerManifest: {
moduleMap: null,
moduleLoading: null,
},
},
);

let componentStack;
let ownerStack;

const clientAbortController = new AbortController();

const fizzPrerenderStreamResult = ReactDOMFizzStatic.prerender(
React.createElement(ClientRoot, {response: prerenderResponse}),
{
signal: clientAbortController.signal,
onError(error, errorInfo) {
componentStack = errorInfo.componentStack;
ownerStack = React.captureOwnerStack
? React.captureOwnerStack()
: null;
},
},
);

await await serverAct(
async () =>
new Promise(resolve => {
setImmediate(() => {
clientAbortController.abort();
resolve();
});
}),
);

const fizzPrerenderStream = await fizzPrerenderStreamResult;
const prerenderHTML = await readWebResult(fizzPrerenderStream.prelude);

expect(prerenderHTML).toContain('Loading...');

if (__DEV__) {
expect(normalizeCodeLocInfo(componentStack)).toBe(
'\n in Component (at **)\n in Suspense\n in body\n in html\n in ClientRoot (at **)',
);
} else {
expect(normalizeCodeLocInfo(componentStack)).toBe(
'\n in Suspense\n in body\n in html\n in ClientRoot (at **)',
);
}

if (__DEV__) {
expect(normalizeCodeLocInfo(ownerStack)).toBe(
'\n in Component (at **)\n in App (at **)',
);
} else {
expect(ownerStack).toBeNull();
}
});
});
Loading