Skip to content

Commit

Permalink
[Flight] Transfer Debug Info in Server-to-Server Flight Requests (fac…
Browse files Browse the repository at this point in the history
…ebook#28275)

A Flight Server can be a consumer of a stream from another Server. In
this case the meta data is attached to debugInfo properties on lazy,
Promises, Arrays or Elements that might in turn get forwarded to the
next stream. In this case we want to forward this debug information to
the client in the stream.

I also added a DEV only `environmentName` option to the Flight Server.
This lets you name the server that is producing the debug info so that
you can trace the origin of where that component is executing. This
defaults to `"server"`. DevTools could use this for badges or different
colors.
  • Loading branch information
sebmarkbage authored and AndyPengc12 committed Apr 15, 2024
1 parent 3fe8278 commit d24d649
Show file tree
Hide file tree
Showing 15 changed files with 186 additions and 13 deletions.
2 changes: 1 addition & 1 deletion packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const INITIALIZED = 'fulfilled';
const ERRORED = 'rejected';

// Dev-only
type ReactDebugInfo = Array<{+name?: string}>;
type ReactDebugInfo = Array<{+name?: string, +env?: string}>;

type PendingChunk<T> = {
status: 'pending',
Expand Down
68 changes: 66 additions & 2 deletions packages/react-client/src/__tests__/ReactFlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ describe('ReactFlight', () => {
const rootModel = await ReactNoopFlightClient.read(transport);
const greeting = rootModel.greeting;
expect(greeting._debugInfo).toEqual(
__DEV__ ? [{name: 'Greeting'}] : undefined,
__DEV__ ? [{name: 'Greeting', env: 'server'}] : undefined,
);
ReactNoop.render(greeting);
});
Expand All @@ -214,7 +214,7 @@ describe('ReactFlight', () => {
await act(async () => {
const promise = ReactNoopFlightClient.read(transport);
expect(promise._debugInfo).toEqual(
__DEV__ ? [{name: 'Greeting'}] : undefined,
__DEV__ ? [{name: 'Greeting', env: 'server'}] : undefined,
);
ReactNoop.render(await promise);
});
Expand Down Expand Up @@ -1806,4 +1806,68 @@ describe('ReactFlight', () => {

expect(ReactNoop).toMatchRenderedOutput(<div>Ba</div>);
});

it('preserves debug info for server-to-server pass through', async () => {
function ThirdPartyLazyComponent() {
return <span>!</span>;
}

const lazy = React.lazy(async () => ({
default: <ThirdPartyLazyComponent />,
}));

function ThirdPartyComponent() {
return <span>stranger</span>;
}

function ServerComponent({transport}) {
// This is a Server Component that receives other Server Components from a third party.
const children = ReactNoopFlightClient.read(transport);
return <div>Hello, {children}</div>;
}

const promiseComponent = Promise.resolve(<ThirdPartyComponent />);

const thirdPartyTransport = ReactNoopFlightServer.render(
[promiseComponent, lazy],
{
environmentName: 'third-party',
},
);

// Wait for the lazy component to initialize
await 0;

const transport = ReactNoopFlightServer.render(
<ServerComponent transport={thirdPartyTransport} />,
);

await act(async () => {
const promise = ReactNoopFlightClient.read(transport);
expect(promise._debugInfo).toEqual(
__DEV__ ? [{name: 'ServerComponent', env: 'server'}] : undefined,
);
const result = await promise;
const thirdPartyChildren = await result.props.children[1];
// We expect the debug info to be transferred from the inner stream to the outer.
expect(thirdPartyChildren[0]._debugInfo).toEqual(
__DEV__
? [{name: 'ThirdPartyComponent', env: 'third-party'}]
: undefined,
);
expect(thirdPartyChildren[1]._debugInfo).toEqual(
__DEV__
? [{name: 'ThirdPartyLazyComponent', env: 'third-party'}]
: undefined,
);
ReactNoop.render(result);
});

expect(ReactNoop).toMatchRenderedOutput(
<div>
Hello, <span>stranger</span>
<span>!</span>
</div>,
);
});
});
6 changes: 5 additions & 1 deletion packages/react-noop-renderer/src/ReactNoopFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ const ReactNoopFlightServer = ReactFlightServer({
});

type Options = {
onError?: (error: mixed) => void,
environmentName?: string,
identifierPrefix?: string,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
};

function render(model: ReactClientValue, options?: Options): Destination {
Expand All @@ -80,6 +82,8 @@ function render(model: ReactClientValue, options?: Options): Destination {
bundlerConfig,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.environmentName : undefined,
);
ReactNoopFlightServer.startWork(request);
ReactNoopFlightServer.startFlowing(request, destination);
Expand Down
2 changes: 2 additions & 0 deletions packages/react-server-dom-esm/src/ReactFlightDOMServerNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function createDrainHandler(destination: Destination, request: Request) {
}

type Options = {
environmentName?: string,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
identifierPrefix?: string,
Expand All @@ -73,6 +74,7 @@ function renderToPipeableStream(
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.environmentName : undefined,
);
let hasStartedFlowing = false;
startWork(request);
Expand Down
2 changes: 2 additions & 0 deletions packages/react-server-dom-fb/src/ReactFlightDOMServerFB.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ function renderToDestination(
model,
null,
options ? options.onError : undefined,
undefined,
undefined,
);
startWork(request);
startFlowing(request, destination);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export {
} from './ReactFlightTurbopackReferences';

type Options = {
environmentName?: string,
identifierPrefix?: string,
signal?: AbortSignal,
onError?: (error: mixed) => void,
Expand All @@ -51,6 +52,7 @@ function renderToReadableStream(
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.environmentName : undefined,
);
if (options && options.signal) {
const signal = options.signal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export {
} from './ReactFlightTurbopackReferences';

type Options = {
environmentName?: string,
identifierPrefix?: string,
signal?: AbortSignal,
onError?: (error: mixed) => void,
Expand All @@ -51,6 +52,7 @@ function renderToReadableStream(
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.environmentName : undefined,
);
if (options && options.signal) {
const signal = options.signal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function createDrainHandler(destination: Destination, request: Request) {
}

type Options = {
environmentName?: string,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
identifierPrefix?: string,
Expand All @@ -70,6 +71,7 @@ function renderToPipeableStream(
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.environmentName : undefined,
);
let hasStartedFlowing = false;
startWork(request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export {
} from './ReactFlightWebpackReferences';

type Options = {
environmentName?: string,
identifierPrefix?: string,
signal?: AbortSignal,
onError?: (error: mixed) => void,
Expand All @@ -55,6 +56,7 @@ function renderToReadableStream(
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.environmentName : undefined,
);
if (options && options.signal) {
const signal = options.signal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export {
} from './ReactFlightWebpackReferences';

type Options = {
environmentName?: string,
identifierPrefix?: string,
signal?: AbortSignal,
onError?: (error: mixed) => void,
Expand All @@ -55,6 +56,7 @@ function renderToReadableStream(
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.environmentName : undefined,
);
if (options && options.signal) {
const signal = options.signal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ function createCancelHandler(request: Request, reason: string) {
}

type Options = {
environmentName?: string,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
identifierPrefix?: string,
Expand All @@ -82,6 +83,7 @@ function renderToPipeableStream(
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.environmentName : undefined,
);
let hasStartedFlowing = false;
startWork(request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ describe('ReactFlightDOMEdge', () => {
<ServerComponent recurse={20} />,
);
const serializedContent = await readResult(stream);
const expectedDebugInfoSize = __DEV__ ? 30 * 20 : 0;
const expectedDebugInfoSize = __DEV__ ? 42 * 20 : 0;
expect(serializedContent.length).toBeLessThan(150 + expectedDebugInfoSize);
});

Expand Down
Loading

0 comments on commit d24d649

Please sign in to comment.