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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ module.exports = {
files: ['packages/react-server-dom-webpack/**/*.js'],
globals: {
__webpack_chunk_load__: 'readonly',
__webpack_get_script_filename__: 'readonly',
__webpack_require__: 'readonly',
},
},
Expand Down
23 changes: 19 additions & 4 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
resolveServerReference,
preloadModule,
requireModule,
getModuleDebugInfo,
dispatchHint,
readPartialStringChunk,
readFinalStringChunk,
Expand Down Expand Up @@ -790,8 +791,14 @@ function resolveModuleChunk<T>(
resolvedChunk.status = RESOLVED_MODULE;
resolvedChunk.value = value;
if (__DEV__) {
// We don't expect to have any debug info for this row.
resolvedChunk._debugInfo = null;
const debugInfo = getModuleDebugInfo(value);
if (debugInfo !== null && resolvedChunk._debugInfo != null) {
// Add to the live set if it was already initialized.
// $FlowFixMe[method-unbinding]
resolvedChunk._debugInfo.push.apply(resolvedChunk._debugInfo, debugInfo);
} else {
resolvedChunk._debugInfo = debugInfo;
}
}
if (resolveListeners !== null) {
initializeModuleChunk(resolvedChunk);
Expand Down Expand Up @@ -3977,7 +3984,11 @@ function flushComponentPerformance(
// Track the root most component of the result for deduping logging.
result.component = componentInfo;
isLastComponent = false;
} else if (candidateInfo.awaited) {
} else if (
candidateInfo.awaited &&
// Skip awaits on client resources since they didn't block the server component.
candidateInfo.awaited.env != null
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added these checks to only log awaits on something with an environment in the Server Components track.

That's because we now have debug info that originates on the client so they don't block the Server Component from completing on the server. They may still block the (Client) Component track but shouldn't be included in the Server Components track.

Currently these aren't included anyway since there's no start time on the lazy nodes but with some other changes this could change.

These are already not included in the Server Requests track since that's just logged when IO rows are resolved.

) {
if (endTime > childrenEndTime) {
childrenEndTime = endTime;
}
Expand Down Expand Up @@ -4059,7 +4070,11 @@ function flushComponentPerformance(
// Track the root most component of the result for deduping logging.
result.component = componentInfo;
isLastComponent = false;
} else if (candidateInfo.awaited) {
} else if (
candidateInfo.awaited &&
// Skip awaits on client resources since they didn't block the server component.
candidateInfo.awaited.env != null
) {
// If we don't have an end time for an await, that means we aborted.
const asyncInfo: ReactAsyncInfo = candidateInfo;
const env = response._rootEnvironmentName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const resolveClientReference = $$$config.resolveClientReference;
export const resolveServerReference = $$$config.resolveServerReference;
export const preloadModule = $$$config.preloadModule;
export const requireModule = $$$config.requireModule;
export const getModuleDebugInfo = $$$config.getModuleDebugInfo;
export const dispatchHint = $$$config.dispatchHint;
export const prepareDestinationForModule =
$$$config.prepareDestinationForModule;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ export const resolveClientReference: any = null;
export const resolveServerReference: any = null;
export const preloadModule: any = null;
export const requireModule: any = null;
export const getModuleDebugInfo: any = null;
export const prepareDestinationForModule: any = null;
export const usedWithSSR = true;
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const resolveClientReference: any = null;
export const resolveServerReference: any = null;
export const preloadModule: any = null;
export const requireModule: any = null;
export const getModuleDebugInfo: any = null;
export const dispatchHint: any = null;
export const prepareDestinationForModule: any = null;
export const usedWithSSR = true;
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
);
}

export function getModuleDebugInfo<T>(metadata: ClientReference<T>): null {
throw new Error(
'renderToHTML should not have emitted Client References. This is a bug in React.',
);
}

export const usedWithSSR = true;

type HintCode = string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import type {
Thenable,
FulfilledThenable,
RejectedThenable,
ReactDebugInfo,
ReactIOInfo,
ReactAsyncInfo,
} from 'shared/ReactTypes';

import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';

export type ServerConsumerModuleMap = string; // Module root path
Expand Down Expand Up @@ -118,3 +122,93 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
}
return moduleExports[metadata.name];
}

// We cache ReactIOInfo across requests so that inner refreshes can dedupe with outer.
const moduleIOInfoCache: Map<string, ReactIOInfo> = __DEV__
? new Map()
: (null: any);

export function getModuleDebugInfo<T>(
metadata: ClientReference<T>,
): null | ReactDebugInfo {
if (!__DEV__) {
return null;
}
const filename = metadata.specifier;
let ioInfo = moduleIOInfoCache.get(filename);
if (ioInfo === undefined) {
let href;
try {
// $FlowFixMe
href = new URL(filename, document.baseURI).href;
} catch (_) {
href = filename;
}
let start = -1;
let end = -1;
let byteSize = 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 === href) {
start = resourceEntry.startTime;
end = start + resourceEntry.duration;
// $FlowFixMe[prop-missing]
byteSize = (resourceEntry.transferSize: any) || 0;
}
}
}
const value = Promise.resolve(href);
// $FlowFixMe
value.status = 'fulfilled';
// Is there some more useful representation for the chunk?
// $FlowFixMe
value.value = href;
// Create a fake stack frame that points to the beginning of the chunk. This is
// probably not source mapped so will link to the compiled source rather than
// any individual file that goes into the chunks.
const fakeStack = new Error('react-stack-top-frame');
if (fakeStack.stack.startsWith('Error: react-stack-top-frame')) {
// Looks like V8
fakeStack.stack =
'Error: react-stack-top-frame\n' +
// Add two frames since we always trim one off the top.
' at Client Component Bundle (' +
href +
':1:1)\n' +
' at Client Component Bundle (' +
href +
':1:1)';
} else {
// Looks like Firefox or Safari.
// Add two frames since we always trim one off the top.
fakeStack.stack =
'Client Component Bundle@' +
href +
':1:1\n' +
'Client Component Bundle@' +
href +
':1:1';
}
ioInfo = ({
name: 'script',
start: start,
end: end,
value: value,
debugStack: fakeStack,
}: ReactIOInfo);
if (byteSize > 0) {
// $FlowFixMe[cannot-write]
ioInfo.byteSize = byteSize;
}
moduleIOInfoCache.set(filename, ioInfo);
}
// We could dedupe the async info too but conceptually each request is its own await.
const asyncInfo: ReactAsyncInfo = {
awaited: ioInfo,
};
return [asyncInfo];
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @flow
*/

import type {Thenable} from 'shared/ReactTypes';
import type {Thenable, ReactDebugInfo} from 'shared/ReactTypes';

import type {ImportMetadata} from '../shared/ReactFlightImportMetadata';

Expand Down Expand Up @@ -80,3 +80,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
const moduleExports = parcelRequire(metadata[ID]);
return moduleExports[metadata[NAME]];
}

export function getModuleDebugInfo<T>(
metadata: ClientReference<T>,
): null | ReactDebugInfo {
// TODO
return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
Thenable,
FulfilledThenable,
RejectedThenable,
ReactDebugInfo,
} from 'shared/ReactTypes';

import type {
Expand All @@ -28,7 +29,10 @@ import {

import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';

import {loadChunk} from 'react-client/src/ReactFlightClientConfig';
import {
loadChunk,
addChunkDebugInfo,
} from 'react-client/src/ReactFlightClientConfig';

export type ServerConsumerModuleMap = null | {
[clientId: string]: {
Expand Down Expand Up @@ -231,3 +235,19 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
}
return moduleExports[metadata[NAME]];
}

export function getModuleDebugInfo<T>(
metadata: ClientReference<T>,
): null | ReactDebugInfo {
if (!__DEV__) {
return null;
}
const chunks = metadata[CHUNKS];
const debugInfo: ReactDebugInfo = [];
let i = 0;
while (i < chunks.length) {
const chunkFilename = chunks[i++];
addChunkDebugInfo(debugInfo, chunkFilename);
}
return debugInfo;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,102 @@
* @flow
*/

import type {
ReactDebugInfo,
ReactIOInfo,
ReactAsyncInfo,
} from 'shared/ReactTypes';

export function loadChunk(filename: string): Promise<mixed> {
return __turbopack_load_by_url__(filename);
}

// We cache ReactIOInfo across requests so that inner refreshes can dedupe with outer.
const chunkIOInfoCache: Map<string, ReactIOInfo> = __DEV__
? new Map()
: (null: any);

export function addChunkDebugInfo(
target: ReactDebugInfo,
filename: string,
): void {
if (!__DEV__) {
return;
}
let ioInfo = chunkIOInfoCache.get(filename);
if (ioInfo === undefined) {
let href;
try {
// $FlowFixMe
href = new URL(filename, document.baseURI).href;
} catch (_) {
href = filename;
}
let start = -1;
let end = -1;
let byteSize = 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 === href) {
start = resourceEntry.startTime;
end = start + resourceEntry.duration;
// $FlowFixMe[prop-missing]
byteSize = (resourceEntry.transferSize: any) || 0;
}
}
}
const value = Promise.resolve(href);
// $FlowFixMe
value.status = 'fulfilled';
// Is there some more useful representation for the chunk?
// $FlowFixMe
value.value = href;
// Create a fake stack frame that points to the beginning of the chunk. This is
// probably not source mapped so will link to the compiled source rather than
// any individual file that goes into the chunks.
const fakeStack = new Error('react-stack-top-frame');
if (fakeStack.stack.startsWith('Error: react-stack-top-frame')) {
// Looks like V8
fakeStack.stack =
'Error: react-stack-top-frame\n' +
// Add two frames since we always trim one off the top.
' at Client Component Bundle (' +
href +
':1:1)\n' +
' at Client Component Bundle (' +
href +
':1:1)';
} else {
// Looks like Firefox or Safari.
// Add two frames since we always trim one off the top.
fakeStack.stack =
'Client Component Bundle@' +
href +
':1:1\n' +
'Client Component Bundle@' +
href +
':1:1';
}
ioInfo = ({
name: 'script',
start: start,
end: end,
value: value,
debugStack: fakeStack,
}: ReactIOInfo);
if (byteSize > 0) {
// $FlowFixMe[cannot-write]
ioInfo.byteSize = byteSize;
}
chunkIOInfoCache.set(filename, ioInfo);
}
// We could dedupe the async info too but conceptually each request is its own await.
const asyncInfo: ReactAsyncInfo = {
awaited: ioInfo,
};
target.push(asyncInfo);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
* @flow
*/

import type {ReactDebugInfo} from 'shared/ReactTypes';

export function loadChunk(filename: string): Promise<mixed> {
return __turbopack_load_by_url__(filename);
}

export function addChunkDebugInfo(
target: ReactDebugInfo,
filename: string,
): void {
// We don't emit any debug info on the server since we assume the loading
// of the bundle is insignificant on the server.
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ global.__webpack_require__ = function (id) {
}
return webpackClientModules[id] || webpackServerModules[id];
};
global.__webpack_get_script_filename__ = function (id) {
return id;
};

const previousCompile = Module.prototype._compile;

Expand Down
Loading
Loading