Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
11 changes: 9 additions & 2 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
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
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,9 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
}
return moduleExports[metadata.name];
}

export function getModuleDebugInfo<T>(metadata: ClientReference<T>): null {
// We don't emit any debug info on the server since we assume the loading
// of the bundle is insignificant on the server.
return null;
}
Loading
Loading