Skip to content

Commit

Permalink
[Flight] Let environmentName vary over time by making it a function o…
Browse files Browse the repository at this point in the history
…f string (#29867)

This lets the environment name vary within a request by the context a
component, log or error being executed in.

A potentially different API would be something like
`setEnvironmentName()` but we'd have to extend the `ReadableStream` or
something to do that like we do for `.allReady`. As a function though it
has some expansion possibilities, e.g. we could potentially also pass
some information to it for context about what is being asked for.

If it changes before completing a task, we also emit the change so that
we have the debug info for what the environment was before entering a
component and what it was after completing it.
  • Loading branch information
sebmarkbage committed Jun 12, 2024
1 parent fb57fc5 commit 55c9d45
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 18 deletions.
44 changes: 44 additions & 0 deletions packages/react-client/src/__tests__/ReactFlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2565,6 +2565,50 @@ describe('ReactFlight', () => {
);
});

it('can change the environment name inside a component', async () => {
let env = 'A';
function Component(props) {
env = 'B';
return <div>hi</div>;
}

const transport = ReactNoopFlightServer.render(
{
greeting: <Component />,
},
{
environmentName() {
return env;
},
},
);

await act(async () => {
const rootModel = await ReactNoopFlightClient.read(transport);
const greeting = rootModel.greeting;
expect(getDebugInfo(greeting)).toEqual(
__DEV__
? [
{
name: 'Component',
env: 'A',
owner: null,
stack: gate(flag => flag.enableOwnerStacks)
? ' in Object.<anonymous> (at **)'
: undefined,
},
{
env: 'B',
},
]
: undefined,
);
ReactNoop.render(greeting);
});

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

// @gate enableServerComponentLogs && __DEV__
it('replays logs, but not onError logs', async () => {
function foo() {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-noop-renderer/src/ReactNoopFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const ReactNoopFlightServer = ReactFlightServer({
});

type Options = {
environmentName?: string,
environmentName?: string | (() => string),
identifierPrefix?: string,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function createCancelHandler(request: Request, reason: string) {
}

type Options = {
environmentName?: string,
environmentName?: string | (() => string),
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
identifierPrefix?: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTem
export type {TemporaryReferenceSet};

type Options = {
environmentName?: string,
environmentName?: string | (() => string),
identifierPrefix?: string,
signal?: AbortSignal,
temporaryReferences?: TemporaryReferenceSet,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTem
export type {TemporaryReferenceSet};

type Options = {
environmentName?: string,
environmentName?: string | (() => string),
identifierPrefix?: string,
signal?: AbortSignal,
temporaryReferences?: TemporaryReferenceSet,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function createCancelHandler(request: Request, reason: string) {
}

type Options = {
environmentName?: string,
environmentName?: string | (() => string),
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
identifierPrefix?: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTem
export type {TemporaryReferenceSet};

type Options = {
environmentName?: string,
environmentName?: string | (() => string),
identifierPrefix?: string,
signal?: AbortSignal,
temporaryReferences?: TemporaryReferenceSet,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTem
export type {TemporaryReferenceSet};

type Options = {
environmentName?: string,
environmentName?: string | (() => string),
identifierPrefix?: string,
signal?: AbortSignal,
temporaryReferences?: TemporaryReferenceSet,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function createCancelHandler(request: Request, reason: string) {
}

type Options = {
environmentName?: string,
environmentName?: string | (() => string),
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
identifierPrefix?: string,
Expand Down
50 changes: 40 additions & 10 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ type Task = {
keyPath: null | string, // parent server component keys
implicitSlot: boolean, // true if the root server component of this sequence had a null key
thenableState: ThenableState | null,
environmentName: string, // DEV-only. Used to track if the environment for this task changed.
};

interface Reference {}
Expand Down Expand Up @@ -425,7 +426,7 @@ export type Request = {
onError: (error: mixed) => ?string,
onPostpone: (reason: string) => void,
// DEV-only
environmentName: string,
environmentName: () => string,
didWarnForKey: null | WeakSet<ReactComponentInfo>,
};

Expand Down Expand Up @@ -481,7 +482,7 @@ function RequestInstance(
onError: void | ((error: mixed) => ?string),
identifierPrefix?: string,
onPostpone: void | ((reason: string) => void),
environmentName: void | string,
environmentName: void | string | (() => string),
temporaryReferences: void | TemporaryReferenceSet,
) {
if (
Expand Down Expand Up @@ -531,7 +532,11 @@ function RequestInstance(

if (__DEV__) {
this.environmentName =
environmentName === undefined ? 'Server' : environmentName;
environmentName === undefined
? () => 'Server'
: typeof environmentName !== 'function'
? () => environmentName
: environmentName;
this.didWarnForKey = null;
}
const rootTask = createTask(this, model, null, false, abortSet);
Expand All @@ -544,7 +549,7 @@ export function createRequest(
onError: void | ((error: mixed) => ?string),
identifierPrefix?: string,
onPostpone: void | ((reason: string) => void),
environmentName: void | string,
environmentName: void | string | (() => string),
temporaryReferences: void | TemporaryReferenceSet,
): Request {
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
Expand Down Expand Up @@ -1049,14 +1054,14 @@ function renderFunctionComponent<Props>(
componentDebugInfo = (prevThenableState: any)._componentDebugInfo;
} else {
// This is a new component in the same task so we can emit more debug info.
const componentDebugID = debugID;
const componentName =
(Component: any).displayName || Component.name || '';
const componentEnv = request.environmentName();
request.pendingChunks++;

const componentDebugID = debugID;
componentDebugInfo = ({
name: componentName,
env: request.environmentName,
env: componentEnv,
owner: owner,
}: ReactComponentInfo);
if (enableOwnerStacks) {
Expand All @@ -1069,6 +1074,9 @@ function renderFunctionComponent<Props>(
outlineModel(request, componentDebugInfo);
emitDebugChunk(request, componentDebugID, componentDebugInfo);

// We've emitted the latest environment for this task so we track that.
task.environmentName = componentEnv;

if (enableOwnerStacks) {
warnForMissingKey(request, key, validated, componentDebugInfo);
}
Expand Down Expand Up @@ -1644,7 +1652,7 @@ function createTask(
request.writtenObjects.set(model, serializeByValueID(id));
}
}
const task: Task = {
const task: Task = (({
id,
status: PENDING,
model,
Expand Down Expand Up @@ -1697,7 +1705,10 @@ function createTask(
return renderModel(request, task, parent, parentPropertyName, value);
},
thenableState: null,
};
}: Omit<Task, 'environmentName'>): any);
if (__DEV__) {
task.environmentName = request.environmentName();
}
abortSet.add(task);
return task;
}
Expand Down Expand Up @@ -3252,7 +3263,7 @@ function emitConsoleChunk(
}

// TODO: Don't double badge if this log came from another Flight Client.
const env = request.environmentName;
const env = request.environmentName();
const payload = [methodName, stackTrace, owner, env];
// $FlowFixMe[method-unbinding]
payload.push.apply(payload, args);
Expand Down Expand Up @@ -3420,6 +3431,15 @@ function retryTask(request: Request, task: Task): void {
// any future references.
request.writtenObjects.set(resolvedModel, serializeByValueID(task.id));

if (__DEV__) {
const currentEnv = request.environmentName();
if (currentEnv !== task.environmentName) {
// The environment changed since we last emitted any debug information for this
// task. We emit an entry that just includes the environment name change.
emitDebugChunk(request, task.id, {env: currentEnv});
}
}

// Object might contain unresolved values like additional elements.
// This is simulating what the JSON loop would do if this was part of it.
emitChunk(request, task, resolvedModel);
Expand All @@ -3428,6 +3448,16 @@ function retryTask(request: Request, task: Task): void {
// We don't need to escape it again so it's not passed the toJSON replacer.
// $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
const json: string = stringify(resolvedModel);

if (__DEV__) {
const currentEnv = request.environmentName();
if (currentEnv !== task.environmentName) {
// The environment changed since we last emitted any debug information for this
// task. We emit an entry that just includes the environment name change.
emitDebugChunk(request, task.id, {env: currentEnv});
}
}

emitModelChunk(request, task.id, json);
}

Expand Down

0 comments on commit 55c9d45

Please sign in to comment.