mirror of
https://github.com/facebook/react.git
synced 2026-02-27 03:07:57 +00:00
This represents a virtual renderer that connects to the Flight Client. It's virtual in the sense that the actual rendering has already happened on the server. The Flight Client parses the result. Most of the result then end up in objects that render into another renderer and that's how we see most Server Components in DevTools. As part of the client's tree. However, some things are side-effects that don't really connect to any particular client renderer. For example preloads() and logs. For those we need to treat the Flight Client as if it was its own renderer just like a Fiber renderer or even legacy renderer. We really could support Fizz and Flight Server as DevTools targets too for example to connect it to the backend but there's just not much demand for that. This will initially only be used to track the owners of replayed console logs but could be expanded to more. For example to send controls to start profiling on the server. It could also be expanded to build an RSC payload inspector that is automatically connected.
147 lines
4.2 KiB
JavaScript
147 lines
4.2 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import Agent from './agent';
|
|
|
|
import {attach as attachFiber} from './fiber/renderer';
|
|
import {attach as attachFlight} from './flight/renderer';
|
|
import {attach as attachLegacy} from './legacy/renderer';
|
|
|
|
import {hasAssignedBackend} from './utils';
|
|
|
|
import type {DevToolsHook, ReactRenderer, RendererInterface} from './types';
|
|
|
|
// this is the backend that is compatible with all older React versions
|
|
function isMatchingRender(version: string): boolean {
|
|
return !hasAssignedBackend(version);
|
|
}
|
|
|
|
export type InitBackend = typeof initBackend;
|
|
|
|
export function initBackend(
|
|
hook: DevToolsHook,
|
|
agent: Agent,
|
|
global: Object,
|
|
): () => void {
|
|
if (hook == null) {
|
|
// DevTools didn't get injected into this page (maybe b'c of the contentType).
|
|
return () => {};
|
|
}
|
|
|
|
const subs = [
|
|
hook.sub(
|
|
'renderer-attached',
|
|
({
|
|
id,
|
|
renderer,
|
|
rendererInterface,
|
|
}: {
|
|
id: number,
|
|
renderer: ReactRenderer,
|
|
rendererInterface: RendererInterface,
|
|
...
|
|
}) => {
|
|
agent.setRendererInterface(id, rendererInterface);
|
|
|
|
// Now that the Store and the renderer interface are connected,
|
|
// it's time to flush the pending operation codes to the frontend.
|
|
rendererInterface.flushInitialOperations();
|
|
},
|
|
),
|
|
|
|
hook.sub('unsupported-renderer-version', (id: number) => {
|
|
agent.onUnsupportedRenderer(id);
|
|
}),
|
|
|
|
hook.sub('fastRefreshScheduled', agent.onFastRefreshScheduled),
|
|
hook.sub('operations', agent.onHookOperations),
|
|
hook.sub('traceUpdates', agent.onTraceUpdates),
|
|
|
|
// TODO Add additional subscriptions required for profiling mode
|
|
];
|
|
|
|
const attachRenderer = (id: number, renderer: ReactRenderer) => {
|
|
// only attach if the renderer is compatible with the current version of the backend
|
|
if (!isMatchingRender(renderer.reconcilerVersion || renderer.version)) {
|
|
return;
|
|
}
|
|
let rendererInterface = hook.rendererInterfaces.get(id);
|
|
|
|
// Inject any not-yet-injected renderers (if we didn't reload-and-profile)
|
|
if (rendererInterface == null) {
|
|
if (
|
|
// v16-19
|
|
typeof renderer.findFiberByHostInstance === 'function' ||
|
|
// v16.8+
|
|
renderer.currentDispatcherRef != null
|
|
) {
|
|
// react-reconciler v16+
|
|
rendererInterface = attachFiber(hook, id, renderer, global);
|
|
} else if (typeof renderer.getCurrentComponentInfo === 'function') {
|
|
// react-flight/client
|
|
rendererInterface = attachFlight(hook, id, renderer, global);
|
|
} else if (renderer.ComponentTree) {
|
|
// react-dom v15
|
|
rendererInterface = attachLegacy(hook, id, renderer, global);
|
|
} else {
|
|
// Older react-dom or other unsupported renderer version
|
|
}
|
|
|
|
if (rendererInterface != null) {
|
|
hook.rendererInterfaces.set(id, rendererInterface);
|
|
}
|
|
}
|
|
|
|
// Notify the DevTools frontend about new renderers.
|
|
// This includes any that were attached early (via __REACT_DEVTOOLS_ATTACH__).
|
|
if (rendererInterface != null) {
|
|
hook.emit('renderer-attached', {
|
|
id,
|
|
renderer,
|
|
rendererInterface,
|
|
});
|
|
} else {
|
|
hook.emit('unsupported-renderer-version', id);
|
|
}
|
|
};
|
|
|
|
// Connect renderers that have already injected themselves.
|
|
hook.renderers.forEach((renderer, id) => {
|
|
attachRenderer(id, renderer);
|
|
});
|
|
|
|
// Connect any new renderers that injected themselves.
|
|
subs.push(
|
|
hook.sub(
|
|
'renderer',
|
|
({id, renderer}: {id: number, renderer: ReactRenderer, ...}) => {
|
|
attachRenderer(id, renderer);
|
|
},
|
|
),
|
|
);
|
|
|
|
hook.emit('react-devtools', agent);
|
|
hook.reactDevtoolsAgent = agent;
|
|
const onAgentShutdown = () => {
|
|
subs.forEach(fn => fn());
|
|
hook.rendererInterfaces.forEach(rendererInterface => {
|
|
rendererInterface.cleanup();
|
|
});
|
|
hook.reactDevtoolsAgent = null;
|
|
};
|
|
agent.addListener('shutdown', onAgentShutdown);
|
|
subs.push(() => {
|
|
agent.removeListener('shutdown', onAgentShutdown);
|
|
});
|
|
|
|
return () => {
|
|
subs.forEach(fn => fn());
|
|
};
|
|
}
|