作用
按顺序标记所有曲目
export function markAllTracksInOrder() {
if (supportsUserTiming) {
// Ensure we create the Server Component track groups earlier than the Client Scheduler
// and Client Components. We can always add the 0 time slot even if it's in the past.
// That's still considered for ordering.
// 确保我们比客户端调度器和客户端组件更早创建服务器组件的轨道组。即使时间槽为 0 ,即
// 使已经是过去的时间,我们也可以添加它。这仍然会被考虑用于排序。
console.timeStamp(
'Server Requests Track',
0.001,
0.001,
IO_TRACK,
undefined,
'primary-light',
);
console.timeStamp(
'Server Components Track',
0.001,
0.001,
'Primary',
COMPONENTS_TRACK,
'primary-light',
);
}
}
记录组件渲染
export function logComponentRender(
componentInfo: ReactComponentInfo,
trackIdx: number,
startTime: number,
endTime: number,
childrenEndTime: number,
rootEnv: string,
): void {
if (supportsUserTiming && childrenEndTime >= 0 && trackIdx < 10) {
const env = componentInfo.env;
const name = componentInfo.name;
const isPrimaryEnv = env === rootEnv;
const selfTime = endTime - startTime;
const color =
selfTime < 0.5
? isPrimaryEnv
? 'primary-light'
: 'secondary-light'
: selfTime < 50
? isPrimaryEnv
? 'primary'
: 'secondary'
: selfTime < 500
? isPrimaryEnv
? 'primary-dark'
: 'secondary-dark'
: 'error';
const entryName =
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
const debugTask = componentInfo.debugTask;
const measureName = '\u200b' + entryName;
if (__DEV__ && debugTask) {
const properties: Array<[string, string]> = [];
if (componentInfo.key != null) {
addValueToProperties('key', componentInfo.key, properties, 0, '');
}
if (componentInfo.props != null) {
addObjectToProperties(componentInfo.props, properties, 0, '');
}
debugTask.run(
performance.measure.bind(performance, measureName, {
start: startTime < 0 ? 0 : startTime,
end: childrenEndTime,
detail: {
devtools: {
color: color,
track: trackNames[trackIdx],
trackGroup: COMPONENTS_TRACK,
properties,
},
},
}),
);
performance.clearMeasures(measureName);
} else {
console.timeStamp(
measureName,
startTime < 0 ? 0 : startTime,
childrenEndTime,
trackNames[trackIdx],
COMPONENTS_TRACK,
color,
);
}
}
}
日志组件已中止
export function logComponentAborted(
componentInfo: ReactComponentInfo,
trackIdx: number,
startTime: number,
endTime: number,
childrenEndTime: number,
rootEnv: string,
): void {
if (supportsUserTiming) {
const env = componentInfo.env;
const name = componentInfo.name;
const isPrimaryEnv = env === rootEnv;
const entryName =
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
const measureName = '\u200b' + entryName;
if (__DEV__) {
const properties: Array<[string, string]> = [
[
'Aborted',
'The stream was aborted before this Component finished rendering.',
],
];
if (componentInfo.key != null) {
addValueToProperties('key', componentInfo.key, properties, 0, '');
}
if (componentInfo.props != null) {
addObjectToProperties(componentInfo.props, properties, 0, '');
}
performance.measure(measureName, {
start: startTime < 0 ? 0 : startTime,
end: childrenEndTime,
detail: {
devtools: {
color: 'warning',
track: trackNames[trackIdx],
trackGroup: COMPONENTS_TRACK,
tooltipText: entryName + ' Aborted',
properties,
},
},
});
performance.clearMeasures(measureName);
} else {
console.timeStamp(
measureName,
startTime < 0 ? 0 : startTime,
childrenEndTime,
trackNames[trackIdx],
COMPONENTS_TRACK,
'warning',
);
}
}
}
记录组件出错
export function logComponentErrored(
componentInfo: ReactComponentInfo,
trackIdx: number,
startTime: number,
endTime: number,
childrenEndTime: number,
rootEnv: string,
error: mixed,
): void {
if (supportsUserTiming) {
const env = componentInfo.env;
const name = componentInfo.name;
const isPrimaryEnv = env === rootEnv;
const entryName =
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
const measureName = '\u200b' + entryName;
if (__DEV__) {
const message =
typeof error === 'object' &&
error !== null &&
typeof error.message === 'string'
? String(error.message)
: String(error);
const properties: Array<[string, string]> = [['Error', message]];
if (componentInfo.key != null) {
addValueToProperties('key', componentInfo.key, properties, 0, '');
}
if (componentInfo.props != null) {
addObjectToProperties(componentInfo.props, properties, 0, '');
}
performance.measure(measureName, {
start: startTime < 0 ? 0 : startTime,
end: childrenEndTime,
detail: {
devtools: {
color: 'error',
track: trackNames[trackIdx],
trackGroup: COMPONENTS_TRACK,
tooltipText: entryName + ' Errored',
properties,
},
},
});
performance.clearMeasures(measureName);
} else {
console.timeStamp(
measureName,
startTime < 0 ? 0 : startTime,
childrenEndTime,
trackNames[trackIdx],
COMPONENTS_TRACK,
'error',
);
}
}
}
日志去重组件渲染
export function logDedupedComponentRender(
componentInfo: ReactComponentInfo,
trackIdx: number,
startTime: number,
endTime: number,
rootEnv: string,
): void {
if (supportsUserTiming && endTime >= 0 && trackIdx < 10) {
const env = componentInfo.env;
const name = componentInfo.name;
const isPrimaryEnv = env === rootEnv;
const color = isPrimaryEnv ? 'primary-light' : 'secondary-light';
const entryName = name + ' [deduped]';
const debugTask = componentInfo.debugTask;
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
entryName,
startTime < 0 ? 0 : startTime,
endTime,
trackNames[trackIdx],
COMPONENTS_TRACK,
color,
),
);
} else {
console.timeStamp(
entryName,
startTime < 0 ? 0 : startTime,
endTime,
trackNames[trackIdx],
COMPONENTS_TRACK,
color,
);
}
}
}
日志组件等待已中止
备注
export function logComponentAwaitAborted(
asyncInfo: ReactAsyncInfo,
trackIdx: number,
startTime: number,
endTime: number,
rootEnv: string,
): void {
if (supportsUserTiming && endTime > 0) {
const entryName =
'await ' + getIOShortName(asyncInfo.awaited, '', asyncInfo.env, rootEnv);
const debugTask = asyncInfo.debugTask || asyncInfo.awaited.debugTask;
if (__DEV__ && debugTask) {
const properties = [
['Aborted', 'The stream was aborted before this Promise resolved.'],
];
const tooltipText =
getIOLongName(asyncInfo.awaited, '', asyncInfo.env, rootEnv) +
' Aborted';
debugTask.run(
performance.measure.bind(performance, entryName, {
start: startTime < 0 ? 0 : startTime,
end: endTime,
detail: {
devtools: {
color: 'warning',
track: trackNames[trackIdx],
trackGroup: COMPONENTS_TRACK,
properties,
tooltipText,
},
},
}),
);
performance.clearMeasures(entryName);
} else {
console.timeStamp(
entryName,
startTime < 0 ? 0 : startTime,
endTime,
trackNames[trackIdx],
COMPONENTS_TRACK,
'warning',
);
}
}
}
记录组件等待出错
export function logComponentAwaitErrored(
asyncInfo: ReactAsyncInfo,
trackIdx: number,
startTime: number,
endTime: number,
rootEnv: string,
error: mixed,
): void {
if (supportsUserTiming && endTime > 0) {
const description = getIODescription(error);
const entryName =
'await ' +
getIOShortName(asyncInfo.awaited, description, asyncInfo.env, rootEnv);
const debugTask = asyncInfo.debugTask || asyncInfo.awaited.debugTask;
if (__DEV__ && debugTask) {
const message =
typeof error === 'object' &&
error !== null &&
typeof error.message === 'string'
? String(error.message)
: String(error);
const properties = [['Rejected', message]];
const tooltipText =
getIOLongName(asyncInfo.awaited, description, asyncInfo.env, rootEnv) +
' Rejected';
debugTask.run(
performance.measure.bind(performance, entryName, {
start: startTime < 0 ? 0 : startTime,
end: endTime,
detail: {
devtools: {
color: 'error',
track: trackNames[trackIdx],
trackGroup: COMPONENTS_TRACK,
properties,
tooltipText,
},
},
}),
);
performance.clearMeasures(entryName);
} else {
console.timeStamp(
entryName,
startTime < 0 ? 0 : startTime,
endTime,
trackNames[trackIdx],
COMPONENTS_TRACK,
'error',
);
}
}
}
日志组件等待
export function logComponentAwait(
asyncInfo: ReactAsyncInfo,
trackIdx: number,
startTime: number,
endTime: number,
rootEnv: string,
value: mixed,
): void {
if (supportsUserTiming && endTime > 0) {
const description = getIODescription(value);
const name = getIOShortName(
asyncInfo.awaited,
description,
asyncInfo.env,
rootEnv,
);
const entryName = 'await ' + name;
const color = getIOColor(name);
const debugTask = asyncInfo.debugTask || asyncInfo.awaited.debugTask;
if (__DEV__ && debugTask) {
const properties: Array<[string, string]> = [];
if (typeof value === 'object' && value !== null) {
addObjectToProperties(value, properties, 0, '');
} else if (value !== undefined) {
addValueToProperties('awaited value', value, properties, 0, '');
}
const tooltipText = getIOLongName(
asyncInfo.awaited,
description,
asyncInfo.env,
rootEnv,
);
debugTask.run(
performance.measure.bind(performance, entryName, {
start: startTime < 0 ? 0 : startTime,
end: endTime,
detail: {
devtools: {
color: color,
track: trackNames[trackIdx],
trackGroup: COMPONENTS_TRACK,
properties,
tooltipText,
},
},
}),
);
performance.clearMeasures(entryName);
} else {
console.timeStamp(
entryName,
startTime < 0 ? 0 : startTime,
endTime,
trackNames[trackIdx],
COMPONENTS_TRACK,
color,
);
}
}
}
记录输入输出信息错误
export function logIOInfoErrored(
ioInfo: ReactIOInfo,
rootEnv: string,
error: mixed,
): void {
const startTime = ioInfo.start;
const endTime = ioInfo.end;
if (supportsUserTiming && endTime >= 0) {
const description = getIODescription(error);
const entryName = getIOShortName(ioInfo, description, ioInfo.env, rootEnv);
const debugTask = ioInfo.debugTask;
const measureName = '\u200b' + entryName;
if (__DEV__ && debugTask) {
const message =
typeof error === 'object' &&
error !== null &&
typeof error.message === 'string'
? String(error.message)
: String(error);
const properties = [['rejected with', message]];
const tooltipText =
getIOLongName(ioInfo, description, ioInfo.env, rootEnv) + ' Rejected';
debugTask.run(
performance.measure.bind(performance, measureName, {
start: startTime < 0 ? 0 : startTime,
end: endTime,
detail: {
devtools: {
color: 'error',
track: IO_TRACK,
properties,
tooltipText,
},
},
}),
);
performance.clearMeasures(measureName);
} else {
console.timeStamp(
measureName,
startTime < 0 ? 0 : startTime,
endTime,
IO_TRACK,
undefined,
'error',
);
}
}
}
记录 IO 信息
export function logIOInfo(
ioInfo: ReactIOInfo,
rootEnv: string,
value: mixed,
): void {
const startTime = ioInfo.start;
const endTime = ioInfo.end;
if (supportsUserTiming && endTime >= 0) {
const description = getIODescription(value);
const entryName = getIOShortName(ioInfo, description, ioInfo.env, rootEnv);
const color = getIOColor(entryName);
const debugTask = ioInfo.debugTask;
const measureName = '\u200b' + entryName;
if (__DEV__ && debugTask) {
const properties: Array<[string, string]> = [];
if (typeof value === 'object' && value !== null) {
addObjectToProperties(value, properties, 0, '');
} else if (value !== undefined) {
addValueToProperties('Resolved', value, properties, 0, '');
}
const tooltipText = getIOLongName(
ioInfo,
description,
ioInfo.env,
rootEnv,
);
debugTask.run(
performance.measure.bind(performance, measureName, {
start: startTime < 0 ? 0 : startTime,
end: endTime,
detail: {
devtools: {
color: color,
track: IO_TRACK,
properties,
tooltipText,
},
},
}),
);
performance.clearMeasures(measureName);
} else {
console.timeStamp(
measureName,
startTime < 0 ? 0 : startTime,
endTime,
IO_TRACK,
undefined,
color,
);
}
}
}
常量
支持用户时序
备注
源码中 27 - 36 行
// 支持用户时序
const supportsUserTiming =
enableProfilerTimer &&
typeof console !== 'undefined' &&
typeof console.timeStamp === 'function' &&
typeof performance !== 'undefined' &&
typeof performance.measure === 'function';
// IO 跟踪
const IO_TRACK = 'Server Requests ⚛';
// 组件追踪
const COMPONENTS_TRACK = 'Server Components ⚛';
追踪名称
备注
- 源码中 62 - 73 行
const trackNames = [
'Primary',
'Parallel',
// 使用零宽空格填充,以为每个轨道提供一个唯一名称。
'Parallel\u200b', // Padded with zero-width space to give each track a unique name.
'Parallel\u200b\u200b',
'Parallel\u200b\u200b\u200b',
'Parallel\u200b\u200b\u200b\u200b',
'Parallel\u200b\u200b\u200b\u200b\u200b',
'Parallel\u200b\u200b\u200b\u200b\u200b\u200b',
'Parallel\u200b\u200b\u200b\u200b\u200b\u200b\u200b',
'Parallel\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b',
];
工具
获取 I/O 颜色
function getIOColor(
functionName: string,
): 'tertiary-light' | 'tertiary' | 'tertiary-dark' {
// Add some color variation to be able to distinguish various sources.
// 添加一些颜色变化,以便能够区分各种来源。
switch (functionName.charCodeAt(0) % 3) {
case 0:
return 'tertiary-light';
case 1:
return 'tertiary';
default:
return 'tertiary-dark';
}
}
获取 I/O 长名称
function getIOLongName(
ioInfo: ReactIOInfo,
description: string,
env: void | string,
rootEnv: string,
): string {
const name = ioInfo.name;
const longName = description === '' ? name : name + ' (' + description + ')';
const isPrimaryEnv = env === rootEnv;
return isPrimaryEnv || env === undefined
? longName
: longName + ' [' + env + ']';
}
获取 I/O 短名称
function getIOShortName(
ioInfo: ReactIOInfo,
description: string,
env: void | string,
rootEnv: string,
): string {
const name = ioInfo.name;
const isPrimaryEnv = env === rootEnv;
const envSuffix = isPrimaryEnv || env === undefined ? '' : ' [' + env + ']';
let desc = '';
const descMaxLength = 30 - name.length - envSuffix.length;
if (descMaxLength > 1) {
const l = description.length;
if (l > 0 && l <= descMaxLength) {
// We can fit the full description
// 我们可以填写完整的描述
desc = ' (' + description + ')';
} else if (
description.startsWith('http://') ||
description.startsWith('https://') ||
description.startsWith('/')
) {
// Looks like a URL. Let's see if we can extract something shorter.
// 看起来像一个网址。我们看看是否能提取出更短的东西。
// We don't have to do a full parse so let's try something cheaper.
// 我们不需要进行完整解析,所以试着用更简单的方法。
let queryIdx = description.indexOf('?');
if (queryIdx === -1) {
queryIdx = description.length;
}
if (description.charCodeAt(queryIdx - 1) === 47 /* "/" */) {
// Ends with slash. Look before that.
// 以斜杠结尾。查看斜杠前的内容。
queryIdx--;
}
const slashIdx = description.lastIndexOf('/', queryIdx - 1);
if (queryIdx - slashIdx < descMaxLength) {
// This may now be either the file name or the host.
// 这现在可能是文件名或主机名。
// Include the slash to make it more obvious what we trimmed.
// 包含斜杠以更明显地显示我们删掉了什么。
desc = ' (…' + description.slice(slashIdx, queryIdx) + ')';
} else {
// cut out the middle to not exceed the max length
// 剪掉中间部分以不超过最大长度
const start = description.slice(slashIdx, slashIdx + descMaxLength / 2);
const end = description.slice(queryIdx - descMaxLength / 2, queryIdx);
desc = ' (' + (slashIdx > 0 ? '…' : '') + start + '…' + end + ')';
}
}
}
return name + desc + envSuffix;
}