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
4 changes: 2 additions & 2 deletions fixtures/flight/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@
"scripts": {
"predev": "cp -r ../../build/oss-experimental/* ./node_modules/ && rm -rf node_modules/.cache;",
"prebuild": "cp -r ../../build/oss-experimental/* ./node_modules/ && rm -rf node_modules/.cache;",
"dev": "concurrently \"npm run dev:region\" \"npm run dev:global\"",
"dev": "concurrently \"yarn run dev:region\" \"yarn run dev:global\"",
"dev:global": "NODE_ENV=development BUILD_PATH=dist node --experimental-loader ./loader/global.js --inspect=127.0.0.1:9230 server/global",
"dev:region": "NODE_ENV=development BUILD_PATH=dist nodemon --watch src --watch dist -- --enable-source-maps --experimental-loader ./loader/region.js --conditions=react-server --inspect=127.0.0.1:9229 server/region",
"start": "node scripts/build.js && concurrently \"npm run start:region\" \"npm run start:global\"",
"start": "node scripts/build.js && concurrently \"yarn run start:region\" \"yarn run start:global\"",
"start:global": "NODE_ENV=production node --experimental-loader ./loader/global.js server/global",
"start:region": "NODE_ENV=production node --experimental-loader ./loader/region.js --conditions=react-server server/region",
"build": "node scripts/build.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-client/src/ReactFlightClientDevToolsHook.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function injectInternals(internals: Object): boolean {
} catch (err) {
// Catch all errors because it is unsafe to throw during initialization.
if (__DEV__) {
console.error('React instrumentation encountered an error: %s.', err);
console.error('React instrumentation encountered an error: %o.', err);
}
}
if (hook.checkDCE) {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-devtools-shared/src/__tests__/setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ function patchConsoleForTestingBeforeHookInstallation() {
// if they use this code path.
firstArg = firstArg.slice(9);
}
if (firstArg === 'React instrumentation encountered an error: %s') {
if (firstArg === 'React instrumentation encountered an error: %o') {
// Rethrow errors from React.
throw args[1];
} else if (
Expand Down
136 changes: 126 additions & 10 deletions packages/react-devtools-shared/src/backend/fiber/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ export function getInternalReactConstants(version: string): {
ReactPriorityLevels: ReactPriorityLevelsType,
ReactTypeOfWork: WorkTagMap,
StrictModeBits: number,
SuspenseyImagesMode: number,
} {
// **********************************************************
// The section below is copied from files in React repo.
Expand Down Expand Up @@ -407,6 +408,8 @@ export function getInternalReactConstants(version: string): {
StrictModeBits = 0b10;
}

const SuspenseyImagesMode = 0b0100000;

let ReactTypeOfWork: WorkTagMap = ((null: any): WorkTagMap);

// **********************************************************
Expand Down Expand Up @@ -820,6 +823,7 @@ export function getInternalReactConstants(version: string): {
ReactPriorityLevels,
ReactTypeOfWork,
StrictModeBits,
SuspenseyImagesMode,
};
}

Expand Down Expand Up @@ -988,6 +992,7 @@ export function attach(
ReactPriorityLevels,
ReactTypeOfWork,
StrictModeBits,
SuspenseyImagesMode,
} = getInternalReactConstants(version);
const {
ActivityComponent,
Expand Down Expand Up @@ -2930,7 +2935,7 @@ export function attach(
}
if (suspenseNode.parent !== parentNode) {
throw new Error(
'Cannot remove a node from a different parent than is being reconciled.',
'Cannot remove a Suspense node from a different parent than is being reconciled.',
);
}
let previousSuspenseSibling = remainingReconcilingChildrenSuspenseNodes;
Expand Down Expand Up @@ -3345,6 +3350,114 @@ export function attach(
insertSuspendedBy(asyncInfo);
}

function trackDebugInfoFromHostComponent(
devtoolsInstance: DevToolsInstance,
fiber: Fiber,
): void {
if (fiber.tag !== HostComponent) {
return;
}
if ((fiber.mode & SuspenseyImagesMode) === 0) {
// In any released version, Suspensey Images are only enabled inside a ViewTransition
// subtree, which is enabled by the SuspenseyImagesMode.
// TODO: If we ever enable the enableSuspenseyImages flag then it would be enabled for
// all images and we'd need some other check for if the version of React has that enabled.
return;
}

const type = fiber.type;
const props: {
src?: string,
onLoad?: (event: any) => void,
loading?: 'eager' | 'lazy',
...
} = fiber.memoizedProps;

const maySuspendCommit =
type === 'img' &&
props.src != null &&
props.src !== '' &&
props.onLoad == null &&
props.loading !== 'lazy';

// Note: We don't track "maySuspendCommitOnUpdate" separately because it doesn't matter if
// it didn't suspend this particular update if it would've suspended if it mounted in this
// state, since we're tracking the dependencies inside the current state.

if (!maySuspendCommit) {
return;
}

const instance = fiber.stateNode;
if (instance == null) {
// Should never happen.
return;
}

// Unlike props.src, currentSrc will be fully qualified which we need for comparison below.
// Unlike instance.src it will be resolved into the media queries currently matching which is
// the state we're inspecting.
const src = instance.currentSrc;
if (typeof src !== 'string' || src === '') {
return;
}
let start = -1;
let end = -1;
let fileSize = 0;
// $FlowFixMe[method-unbinding]
if (typeof performance.getEntriesByType === 'function') {
// We may be able to collect the start and end time of this resource from Performance Observer.
const resourceEntries = performance.getEntriesByType('resource');
for (let i = 0; i < resourceEntries.length; i++) {
const resourceEntry = resourceEntries[i];
if (resourceEntry.name === src) {
start = resourceEntry.startTime;
end = start + resourceEntry.duration;
// $FlowFixMe[prop-missing]
fileSize = (resourceEntry.encodedBodySize: any) || 0;
}
}
}
// A representation of the image data itself.
// TODO: We could render a little preview in the front end from the resource API.
const value: {
currentSrc: string,
naturalWidth?: number,
naturalHeight?: number,
fileSize?: number,
} = {
currentSrc: src,
};
if (instance.naturalWidth > 0 && instance.naturalHeight > 0) {
// The intrinsic size of the file value itself, if it's loaded
value.naturalWidth = instance.naturalWidth;
value.naturalHeight = instance.naturalHeight;
}
if (fileSize > 0) {
// Cross-origin images won't have a file size that we can access.
value.fileSize = fileSize;
}
const promise = Promise.resolve(value);
(promise: any).status = 'fulfilled';
(promise: any).value = value;
const ioInfo: ReactIOInfo = {
name: 'img',
start,
end,
value: promise,
// $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file.
owner: fiber, // Allow linking to the <link> if it's not filtered.
};
const asyncInfo: ReactAsyncInfo = {
awaited: ioInfo,
// $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file.
owner: fiber._debugOwner == null ? null : fiber._debugOwner,
debugStack: fiber._debugStack == null ? null : fiber._debugStack,
debugTask: fiber._debugTask == null ? null : fiber._debugTask,
};
insertSuspendedBy(asyncInfo);
}

function mountVirtualChildrenRecursively(
firstChild: Fiber,
lastChild: null | Fiber, // non-inclusive
Expand Down Expand Up @@ -3619,6 +3732,7 @@ export function attach(
throw new Error('Did not expect a host hoistable to be the root');
}
aquireHostInstance(nearestInstance, fiber.stateNode);
trackDebugInfoFromHostComponent(nearestInstance, fiber);
}

if (fiber.tag === OffscreenComponent && fiber.memoizedState !== null) {
Expand Down Expand Up @@ -4447,20 +4561,22 @@ export function attach(
aquireHostResource(nearestInstance, nextFiber.memoizedState);
trackDebugInfoFromHostResource(nearestInstance, nextFiber);
} else if (
(nextFiber.tag === HostComponent ||
nextFiber.tag === HostText ||
nextFiber.tag === HostSingleton) &&
prevFiber.stateNode !== nextFiber.stateNode
nextFiber.tag === HostComponent ||
nextFiber.tag === HostText ||
nextFiber.tag === HostSingleton
) {
// In persistent mode, it's possible for the stateNode to update with
// a new clone. In that case we need to release the old one and aquire
// new one instead.
const nearestInstance = reconcilingParent;
if (nearestInstance === null) {
throw new Error('Did not expect a host hoistable to be the root');
}
releaseHostInstance(nearestInstance, prevFiber.stateNode);
aquireHostInstance(nearestInstance, nextFiber.stateNode);
if (prevFiber.stateNode !== nextFiber.stateNode) {
// In persistent mode, it's possible for the stateNode to update with
// a new clone. In that case we need to release the old one and aquire
// new one instead.
releaseHostInstance(nearestInstance, prevFiber.stateNode);
aquireHostInstance(nearestInstance, nextFiber.stateNode);
}
trackDebugInfoFromHostComponent(nearestInstance, nextFiber);
}

let updateFlags = NoUpdate;
Expand Down
12 changes: 6 additions & 6 deletions packages/react-reconciler/src/ReactFiberDevToolsHook.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export function injectInternals(internals: Object): boolean {
} catch (err) {
// Catch all errors because it is unsafe to throw during initialization.
if (__DEV__) {
console.error('React instrumentation encountered an error: %s.', err);
console.error('React instrumentation encountered an error: %o.', err);
}
}
if (hook.checkDCE) {
Expand All @@ -101,7 +101,7 @@ export function onScheduleRoot(root: FiberRoot, children: ReactNodeList) {
} catch (err) {
if (__DEV__ && !hasLoggedError) {
hasLoggedError = true;
console.error('React instrumentation encountered an error: %s', err);
console.error('React instrumentation encountered an error: %o', err);
}
}
}
Expand Down Expand Up @@ -144,7 +144,7 @@ export function onCommitRoot(root: FiberRoot, eventPriority: EventPriority) {
if (__DEV__) {
if (!hasLoggedError) {
hasLoggedError = true;
console.error('React instrumentation encountered an error: %s', err);
console.error('React instrumentation encountered an error: %o', err);
}
}
}
Expand All @@ -162,7 +162,7 @@ export function onPostCommitRoot(root: FiberRoot) {
if (__DEV__) {
if (!hasLoggedError) {
hasLoggedError = true;
console.error('React instrumentation encountered an error: %s', err);
console.error('React instrumentation encountered an error: %o', err);
}
}
}
Expand All @@ -177,7 +177,7 @@ export function onCommitUnmount(fiber: Fiber) {
if (__DEV__) {
if (!hasLoggedError) {
hasLoggedError = true;
console.error('React instrumentation encountered an error: %s', err);
console.error('React instrumentation encountered an error: %o', err);
}
}
}
Expand All @@ -199,7 +199,7 @@ export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
if (__DEV__) {
if (!hasLoggedError) {
hasLoggedError = true;
console.error('React instrumentation encountered an error: %s', err);
console.error('React instrumentation encountered an error: %o', err);
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/ReactIODescription.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export function getIODescription(value: any): string {
return value.url;
} else if (typeof value.href === 'string') {
return value.href;
} else if (typeof value.src === 'string') {
return value.src;
} else if (typeof value.currentSrc === 'string') {
return value.currentSrc;
} else if (typeof value.command === 'string') {
return value.command;
} else if (
Expand Down
Loading