diff --git a/packages/react-devtools-extensions/src/checkForDuplicateInstallations.js b/packages/react-devtools-extensions/src/checkForDuplicateInstallations.js index d86cac7709295..01db8edb34f74 100644 --- a/packages/react-devtools-extensions/src/checkForDuplicateInstallations.js +++ b/packages/react-devtools-extensions/src/checkForDuplicateInstallations.js @@ -9,13 +9,15 @@ declare var chrome: any; -import {__DEBUG__} from 'react-devtools-shared/src/constants'; +import { + INTERNAL_EXTENSION_ID, + LOCAL_EXTENSION_ID, + __DEBUG__, +} from 'react-devtools-shared/src/constants'; import {getBrowserName} from './utils'; import { EXTENSION_INSTALL_CHECK, EXTENSION_INSTALLATION_TYPE, - INTERNAL_EXTENSION_ID, - LOCAL_EXTENSION_ID, } from './constants'; const IS_CHROME = getBrowserName() === 'Chrome'; diff --git a/packages/react-devtools-extensions/src/constants.js b/packages/react-devtools-extensions/src/constants.js index 310bbaca11563..c17ad4d64acd8 100644 --- a/packages/react-devtools-extensions/src/constants.js +++ b/packages/react-devtools-extensions/src/constants.js @@ -7,6 +7,12 @@ * @flow strict-local */ +import { + CHROME_WEBSTORE_EXTENSION_ID, + INTERNAL_EXTENSION_ID, + LOCAL_EXTENSION_ID, +} from 'react-devtools-shared/src/constants'; + declare var chrome: any; export const CURRENT_EXTENSION_ID = chrome.runtime.id; @@ -15,10 +21,6 @@ export const EXTENSION_INSTALL_CHECK = 'extension-install-check'; export const SHOW_DUPLICATE_EXTENSION_WARNING = 'show-duplicate-extension-warning'; -export const CHROME_WEBSTORE_EXTENSION_ID = 'fmkadmapgofadopljbjfkapdkoienihi'; -export const INTERNAL_EXTENSION_ID = 'dnjnjgbfilfphmojnmhliehogmojhclc'; -export const LOCAL_EXTENSION_ID = 'ikiahnapldjmdmpkmfhjdjilojjhgcbf'; - export const EXTENSION_INSTALLATION_TYPE: | 'public' | 'internal' diff --git a/packages/react-devtools-scheduling-profiler/src/CanvasPage.js b/packages/react-devtools-scheduling-profiler/src/CanvasPage.js index 48efcf8105e7c..ab5c828280c0a 100644 --- a/packages/react-devtools-scheduling-profiler/src/CanvasPage.js +++ b/packages/react-devtools-scheduling-profiler/src/CanvasPage.js @@ -374,6 +374,7 @@ function AutoSizedCanvas({ surface, defaultFrame, data.flamechart, + data.internalModuleSourceToRanges, data.duration, ); flamechartViewRef.current = flamechartView; diff --git a/packages/react-devtools-scheduling-profiler/src/content-views/FlamechartView.js b/packages/react-devtools-scheduling-profiler/src/content-views/FlamechartView.js index 86f411a5b6e24..d0e11daeca128 100644 --- a/packages/react-devtools-scheduling-profiler/src/content-views/FlamechartView.js +++ b/packages/react-devtools-scheduling-profiler/src/content-views/FlamechartView.js @@ -11,6 +11,7 @@ import type { Flamechart, FlamechartStackFrame, FlamechartStackLayer, + InternalModuleSourceToRanges, } from '../types'; import type { Interaction, @@ -20,6 +21,11 @@ import type { ViewRefs, } from '../view-base'; +import { + CHROME_WEBSTORE_EXTENSION_ID, + INTERNAL_EXTENSION_ID, + LOCAL_EXTENSION_ID, +} from 'react-devtools-shared/src/constants'; import { BackgroundColorView, Surface, @@ -69,6 +75,56 @@ function hoverColorForStackFrame(stackFrame: FlamechartStackFrame): string { return hslaColorToString(color); } +function isInternalModule( + internalModuleSourceToRanges: InternalModuleSourceToRanges, + flamechartStackFrame: FlamechartStackFrame, +): boolean { + const {locationColumn, locationLine, scriptUrl} = flamechartStackFrame; + + if (scriptUrl == null || locationColumn == null || locationLine == null) { + return true; + } + + // Internal modules are only registered if DevTools was running when the profile was captured, + // but DevTools should also hide its own frames to avoid over-emphasizing them. + if ( + // Handle webpack-internal:// sources + scriptUrl.includes('/react-devtools') || + scriptUrl.includes('/react_devtools') || + // Filter out known extension IDs + scriptUrl.includes(CHROME_WEBSTORE_EXTENSION_ID) || + scriptUrl.includes(INTERNAL_EXTENSION_ID) || + scriptUrl.includes(LOCAL_EXTENSION_ID) + + // Unfortunately this won't get everything, like relatively loaded chunks or Web Worker files. + ) { + return true; + } + + // Filter out React internal packages. + const ranges = internalModuleSourceToRanges.get(scriptUrl); + if (ranges != null) { + for (let i = 0; i < ranges.length; i++) { + const [startStackFrame, stopStackFram] = ranges[i]; + + const isAfterStart = + locationLine > startStackFrame.lineNumber || + (locationLine === startStackFrame.lineNumber && + locationColumn >= startStackFrame.columnNumber); + const isBeforeStop = + locationLine < stopStackFram.lineNumber || + (locationLine === stopStackFram.lineNumber && + locationColumn <= stopStackFram.columnNumber); + + if (isAfterStart && isBeforeStop) { + return true; + } + } + } + + return false; +} + class FlamechartStackLayerView extends View { /** Layer to display */ _stackLayer: FlamechartStackLayer; @@ -76,6 +132,8 @@ class FlamechartStackLayerView extends View { /** A set of `stackLayer`'s frames, for efficient lookup. */ _stackFrameSet: Set; + _internalModuleSourceToRanges: InternalModuleSourceToRanges; + _intrinsicSize: Size; _hoveredStackFrame: FlamechartStackFrame | null = null; @@ -85,11 +143,13 @@ class FlamechartStackLayerView extends View { surface: Surface, frame: Rect, stackLayer: FlamechartStackLayer, + internalModuleSourceToRanges: InternalModuleSourceToRanges, duration: number, ) { super(surface, frame); this._stackLayer = stackLayer; this._stackFrameSet = new Set(stackLayer); + this._internalModuleSourceToRanges = internalModuleSourceToRanges; this._intrinsicSize = { width: duration, height: FLAMECHART_FRAME_HEIGHT, @@ -160,9 +220,19 @@ class FlamechartStackLayerView extends View { } const showHoverHighlight = _hoveredStackFrame === _stackLayer[i]; - context.fillStyle = showHoverHighlight - ? hoverColorForStackFrame(stackFrame) - : defaultColorForStackFrame(stackFrame); + + let textFillStyle; + if (isInternalModule(this._internalModuleSourceToRanges, stackFrame)) { + context.fillStyle = showHoverHighlight + ? COLORS.INTERNAL_MODULE_FRAME_HOVER + : COLORS.INTERNAL_MODULE_FRAME; + textFillStyle = COLORS.INTERNAL_MODULE_FRAME_TEXT; + } else { + context.fillStyle = showHoverHighlight + ? hoverColorForStackFrame(stackFrame) + : defaultColorForStackFrame(stackFrame); + textFillStyle = COLORS.TEXT_COLOR; + } const drawableRect = intersectionOfRects(nodeRect, visibleArea); context.fillRect( @@ -172,7 +242,9 @@ class FlamechartStackLayerView extends View { drawableRect.size.height, ); - drawText(name, context, nodeRect, drawableRect); + drawText(name, context, nodeRect, drawableRect, { + fillStyle: textFillStyle, + }); } // Render bottom border. @@ -264,13 +336,22 @@ export class FlamechartView extends View { surface: Surface, frame: Rect, flamechart: Flamechart, + internalModuleSourceToRanges: InternalModuleSourceToRanges, duration: number, ) { super(surface, frame, layeredLayout); - this.setDataAndUpdateSubviews(flamechart, duration); + this.setDataAndUpdateSubviews( + flamechart, + internalModuleSourceToRanges, + duration, + ); } - setDataAndUpdateSubviews(flamechart: Flamechart, duration: number) { + setDataAndUpdateSubviews( + flamechart: Flamechart, + internalModuleSourceToRanges: InternalModuleSourceToRanges, + duration: number, + ) { const {surface, frame, _onHover, _hoveredStackFrame} = this; // Clear existing rows on data update @@ -285,6 +366,7 @@ export class FlamechartView extends View { surface, frame, stackLayer, + internalModuleSourceToRanges, duration, ); this._verticalStackView.addSubview(rowView); diff --git a/packages/react-devtools-scheduling-profiler/src/content-views/constants.js b/packages/react-devtools-scheduling-profiler/src/content-views/constants.js index a715945f77e92..4d896d1967e96 100644 --- a/packages/react-devtools-scheduling-profiler/src/content-views/constants.js +++ b/packages/react-devtools-scheduling-profiler/src/content-views/constants.js @@ -45,6 +45,9 @@ export const MIN_INTERVAL_SIZE_PX = 70; // TODO Replace this with "export let" vars export let COLORS = { BACKGROUND: '', + INTERNAL_MODULE_FRAME: '', + INTERNAL_MODULE_FRAME_HOVER: '', + INTERNAL_MODULE_FRAME_TEXT: '', NATIVE_EVENT: '', NATIVE_EVENT_HOVER: '', NETWORK_PRIMARY: '', @@ -107,6 +110,15 @@ export function updateColorsToMatchTheme(element: Element): boolean { COLORS = { BACKGROUND: computedStyle.getPropertyValue('--color-background'), + INTERNAL_MODULE_FRAME: computedStyle.getPropertyValue( + '--color-scheduling-profiler-internal-module', + ), + INTERNAL_MODULE_FRAME_HOVER: computedStyle.getPropertyValue( + '--color-scheduling-profiler-internal-module-hover', + ), + INTERNAL_MODULE_FRAME_TEXT: computedStyle.getPropertyValue( + '--color-scheduling-profiler-internal-module-text', + ), NATIVE_EVENT: computedStyle.getPropertyValue( '--color-scheduling-profiler-native-event', ), diff --git a/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js b/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js index e458f8060f84b..f8690dc57447c 100644 --- a/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js +++ b/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js @@ -282,6 +282,7 @@ describe('preprocessData', () => { "componentMeasures": Array [], "duration": 0.005, "flamechart": Array [], + "internalModuleSourceToRanges": Map {}, "laneToLabelMap": Map { 0 => "Sync", 1 => "InputContinuousHydration", @@ -449,6 +450,7 @@ describe('preprocessData', () => { "componentMeasures": Array [], "duration": 0.011, "flamechart": Array [], + "internalModuleSourceToRanges": Map {}, "laneToLabelMap": Map { 0 => "Sync", 1 => "InputContinuousHydration", @@ -636,6 +638,7 @@ describe('preprocessData', () => { "componentMeasures": Array [], "duration": 0.013, "flamechart": Array [], + "internalModuleSourceToRanges": Map {}, "laneToLabelMap": Map { 0 => "Sync", 1 => "InputContinuousHydration", @@ -914,6 +917,7 @@ describe('preprocessData', () => { ], "duration": 0.031, "flamechart": Array [], + "internalModuleSourceToRanges": Map {}, "laneToLabelMap": Map { 0 => "Sync", 1 => "InputContinuousHydration", diff --git a/packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js b/packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js index aff78142b81a6..6c41de1e3e279 100644 --- a/packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js +++ b/packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js @@ -13,6 +13,7 @@ import { } from '@elg/speedscope'; import type {TimelineEvent} from '@elg/speedscope'; import type { + ErrorStackFrame, BatchUID, Flamechart, Milliseconds, @@ -30,6 +31,7 @@ import type { import {REACT_TOTAL_NUM_LANES, SCHEDULING_PROFILER_VERSION} from '../constants'; import InvalidProfileError from './InvalidProfileError'; import {getBatchRange} from '../utils/getBatchRange'; +import ErrorStackParser from 'error-stack-parser'; type MeasureStackElement = {| type: ReactMeasureType, @@ -43,6 +45,8 @@ type ProcessorState = {| asyncProcessingPromises: Promise[], batchUID: BatchUID, currentReactComponentMeasure: ReactComponentMeasure | null, + internalModuleCurrentStackFrame: ErrorStackFrame | null, + internalModuleStackStringSet: Set, measureStack: MeasureStackElement[], nativeEventStack: NativeEvent[], nextRenderShouldGenerateNewBatchID: boolean, @@ -793,6 +797,49 @@ function processTimelineEvent( ); } // eslint-disable-line brace-style + // Internal module ranges + else if (name.startsWith('--react-internal-module-start-')) { + const stackFrameStart = name.substr(30); + + if (!state.internalModuleStackStringSet.has(stackFrameStart)) { + state.internalModuleStackStringSet.add(stackFrameStart); + + const parsedStackFrameStart = parseStackFrame(stackFrameStart); + + state.internalModuleCurrentStackFrame = parsedStackFrameStart; + } + } else if (name.startsWith('--react-internal-module-stop-')) { + const stackFrameStop = name.substr(19); + + if (!state.internalModuleStackStringSet.has(stackFrameStop)) { + state.internalModuleStackStringSet.add(stackFrameStop); + + const parsedStackFrameStop = parseStackFrame(stackFrameStop); + + if ( + parsedStackFrameStop !== null && + state.internalModuleCurrentStackFrame !== null + ) { + const parsedStackFrameStart = state.internalModuleCurrentStackFrame; + + state.internalModuleCurrentStackFrame = null; + + const range = [parsedStackFrameStart, parsedStackFrameStop]; + const ranges = currentProfilerData.internalModuleSourceToRanges.get( + parsedStackFrameStart.fileName, + ); + if (ranges == null) { + currentProfilerData.internalModuleSourceToRanges.set( + parsedStackFrameStart.fileName, + [range], + ); + } else { + ranges.push(range); + } + } + } + } // eslint-disable-line brace-style + // Other user timing marks/measures else if (ph === 'R' || ph === 'n') { // User Timing mark @@ -855,6 +902,15 @@ function preprocessFlamechart(rawData: TimelineEvent[]): Flamechart { return flamechart; } +function parseStackFrame(stackFrame: string): ErrorStackFrame | null { + const error = new Error(); + error.stack = stackFrame; + + const frames = ErrorStackParser.parse(error); + + return frames.length === 1 ? frames[0] : null; +} + export default async function preprocessData( timeline: TimelineEvent[], ): Promise { @@ -870,6 +926,7 @@ export default async function preprocessData( componentMeasures: [], duration: 0, flamechart, + internalModuleSourceToRanges: new Map(), laneToLabelMap: new Map(), laneToReactMeasureMap, nativeEvents: [], @@ -913,6 +970,8 @@ export default async function preprocessData( asyncProcessingPromises: [], batchUID: 0, currentReactComponentMeasure: null, + internalModuleCurrentStackFrame: null, + internalModuleStackStringSet: new Set(), measureStack: [], nativeEventStack: [], nextRenderShouldGenerateNewBatchID: true, diff --git a/packages/react-devtools-scheduling-profiler/src/types.js b/packages/react-devtools-scheduling-profiler/src/types.js index 4bfafe9a2eccc..e5b14e7897ace 100644 --- a/packages/react-devtools-scheduling-profiler/src/types.js +++ b/packages/react-devtools-scheduling-profiler/src/types.js @@ -17,6 +17,12 @@ export type Return = Return_<*, T>; // Project types +export type ErrorStackFrame = { + fileName: string, + lineNumber: number, + columnNumber: number, +}; + export type Milliseconds = number; export type ReactLane = number; @@ -169,11 +175,17 @@ export type ViewState = {| viewToMutableViewStateMap: Map, |}; +export type InternalModuleSourceToRanges = Map< + string, + Array<[ErrorStackFrame, ErrorStackFrame]>, +>; + export type ReactProfilerData = {| batchUIDToMeasuresMap: Map, componentMeasures: ReactComponentMeasure[], duration: number, flamechart: Flamechart, + internalModuleSourceToRanges: InternalModuleSourceToRanges, laneToLabelMap: Map, laneToReactMeasureMap: Map, nativeEvents: NativeEvent[], diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 3f623ea656e48..4f92f6b5b012e 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -420,6 +420,11 @@ export type DevToolsHook = { didError?: boolean, ) => void, + // Scheduling Profiler internal module filtering + getInternalModuleRanges: () => Array<[Error, Error]>, + registerInternalModuleStart: (moduleStartError: Error) => void, + registerInternalModuleStop: (moduleStopError: Error) => void, + // Testing dangerous_setTargetConsoleForTesting?: (fakeConsole: Object) => void, ... diff --git a/packages/react-devtools-shared/src/constants.js b/packages/react-devtools-shared/src/constants.js index 40c28a0f6abdf..3c3aaae2fc461 100644 --- a/packages/react-devtools-shared/src/constants.js +++ b/packages/react-devtools-shared/src/constants.js @@ -7,6 +7,10 @@ * @flow */ +export const CHROME_WEBSTORE_EXTENSION_ID = 'fmkadmapgofadopljbjfkapdkoienihi'; +export const INTERNAL_EXTENSION_ID = 'dnjnjgbfilfphmojnmhliehogmojhclc'; +export const LOCAL_EXTENSION_ID = 'ikiahnapldjmdmpkmfhjdjilojjhgcbf'; + // Flip this flag to true to enable verbose console debug logging. export const __DEBUG__ = false; @@ -147,6 +151,9 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = { '--color-resize-bar-active': '#dcdcdc', '--color-resize-bar-border': '#d1d1d1', '--color-resize-bar-dot': '#333333', + '--color-scheduling-profiler-internal-module': '#d1d1d1', + '--color-scheduling-profiler-internal-module-hover': '#c9c9c9', + '--color-scheduling-profiler-internal-module-text': '#444', '--color-scheduling-profiler-native-event': '#ccc', '--color-scheduling-profiler-native-event-hover': '#aaa', '--color-scheduling-profiler-network-primary': '#fcf3dc', @@ -288,6 +295,9 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = { '--color-resize-bar-active': '#31363f', '--color-resize-bar-border': '#3d424a', '--color-resize-bar-dot': '#cfd1d5', + '--color-scheduling-profiler-internal-module': '#303542', + '--color-scheduling-profiler-internal-module-hover': '#363b4a', + '--color-scheduling-profiler-internal-module-text': '#7f8899', '--color-scheduling-profiler-native-event': '#b2b2b2', '--color-scheduling-profiler-native-event-hover': '#949494', '--color-scheduling-profiler-network-primary': '#fcf3dc', diff --git a/packages/react-devtools-shared/src/hook.js b/packages/react-devtools-shared/src/hook.js index 6f34f86132ab6..94ebe42a06046 100644 --- a/packages/react-devtools-shared/src/hook.js +++ b/packages/react-devtools-shared/src/hook.js @@ -490,6 +490,24 @@ export function installHook(target: any): DevToolsHook | null { } } + const openModuleRangesStack: Array = []; + const moduleRanges: Array<[Error, Error]> = []; + + function getInternalModuleRanges(): Array<[Error, Error]> { + return moduleRanges; + } + + function registerInternalModuleStart(moduleStartError: Error) { + openModuleRangesStack.push(moduleStartError); + } + + function registerInternalModuleStop(moduleStopError: Error) { + const moduleStartError = openModuleRangesStack.pop(); + if (moduleStartError) { + moduleRanges.push([moduleStartError, moduleStopError]); + } + } + // TODO: More meaningful names for "rendererInterfaces" and "renderers". const fiberRoots = {}; const rendererInterfaces = new Map(); @@ -520,6 +538,13 @@ export function installHook(target: any): DevToolsHook | null { onCommitFiberRoot, onPostCommitFiberRoot, setStrictMode, + + // Schedule Profiler runtime helpers. + // These internal React modules to report their own boundaries + // which in turn enables the profiler to dim or filter internal frames. + getInternalModuleRanges, + registerInternalModuleStart, + registerInternalModuleStop, }; if (__TEST__) { diff --git a/packages/react-reconciler/src/SchedulingProfiler.js b/packages/react-reconciler/src/SchedulingProfiler.js index 03148d499282d..6afaa8bab8ef6 100644 --- a/packages/react-reconciler/src/SchedulingProfiler.js +++ b/packages/react-reconciler/src/SchedulingProfiler.js @@ -98,6 +98,29 @@ function markVersionMetadata() { markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`); } +function markInternalModuleRanges() { + /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ + if ( + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges === 'function' + ) { + const ranges = __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges(); + for (let i = 0; i < ranges.length; i++) { + const [startError, stopError] = ranges[i]; + + // Don't embed Error stack parsing logic into the reconciler. + // Just serialize the top stack frame and let the profiler parse it. + const startFrames = startError.stack.split('\n'); + const startFrame = startFrames.length > 1 ? startFrames[1] : ''; + const stopFrames = stopError.stack.split('\n'); + const stopFrame = stopFrames.length > 1 ? stopFrames[1] : ''; + + markAndClear(`--react-internal-module-start-${startFrame}`); + markAndClear(`--react-internal-module-stop-${stopFrame}`); + } + } +} + export function markCommitStarted(lanes: Lanes): void { if (enableSchedulingProfiler) { if (supportsUserTimingV3) { @@ -114,6 +137,7 @@ export function markCommitStarted(lanes: Lanes): void { // we can log this data only once (when started) and remove the per-commit logging. markVersionMetadata(); markLaneToLabelMetadata(); + markInternalModuleRanges(); } } } diff --git a/packages/shared/registerInternalModuleStart.js b/packages/shared/registerInternalModuleStart.js new file mode 100644 index 0000000000000..aa1154fe9f1bf --- /dev/null +++ b/packages/shared/registerInternalModuleStart.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ + +// Don't require this file directly; it's embedded by Rollup during build. + +if ( + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === + 'function' +) { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); +} diff --git a/packages/shared/registerInternalModuleStop.js b/packages/shared/registerInternalModuleStop.js new file mode 100644 index 0000000000000..dab139f2557f9 --- /dev/null +++ b/packages/shared/registerInternalModuleStop.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ + +// Don't require this file directly; it's embedded by Rollup during build. + +if ( + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop === + 'function' +) { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error()); +} diff --git a/scripts/rollup/wrappers.js b/scripts/rollup/wrappers.js index 836929dbec87f..dac287bab6dd9 100644 --- a/scripts/rollup/wrappers.js +++ b/scripts/rollup/wrappers.js @@ -1,5 +1,7 @@ 'use strict'; +const {resolve} = require('path'); +const {readFileSync} = require('fs'); const {bundleTypes, moduleTypes} = require('./bundles'); const reactVersion = require('../../package.json').version; @@ -25,6 +27,26 @@ const { const {RECONCILER} = moduleTypes; +function registerInternalModuleStart(globalName) { + const path = resolve( + __dirname, + '..', + '..', + 'packages/shared/registerInternalModuleStart.js' + ); + return String(readFileSync(path)).trim(); +} + +function registerInternalModuleStop(globalName) { + const path = resolve( + __dirname, + '..', + '..', + 'packages/shared/registerInternalModuleStop.js' + ); + return String(readFileSync(path)).trim(); +} + const license = ` * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the @@ -320,6 +342,25 @@ ${source} }; function wrapBundle(source, bundleType, globalName, filename, moduleType) { + switch (bundleType) { + case NODE_DEV: + case NODE_PROFILING: + case FB_WWW_DEV: + case FB_WWW_PROFILING: + case RN_OSS_DEV: + case RN_OSS_PROFILING: + case RN_FB_DEV: + case RN_FB_PROFILING: + // Profiling builds should self-register internal module boundaries with DevTools. + // This allows the Scheduling Profiler to de-emphasize (dim) internal stack frames. + source = ` + ${registerInternalModuleStart(globalName)} + ${source} + ${registerInternalModuleStop(globalName)} + `; + break; + } + if (moduleType === RECONCILER) { // Standalone reconciler is only used by third-party renderers. // It is handled separately.