Skip to content

Commit 04ec50e

Browse files
authored
[DevTools] Add Filtering of Environment Names (#30850)
Stacked on #30842. This adds a filter to be able to exclude Components from a certain environment. Default to Client or Server. The available options are computed into a dropdown based on the names that are currently used on the page (or an option that were previously used). In addition to the hardcoded "Client". Meaning that if you have Server Components on the page you see "Server" or "Client" as possible options but it can be anything if there are multiple RSC environments on the page. "Client" in this case means Function and Class Components in Fiber - excluding built-ins. If a Server Component has two environments (primary and secondary) then both have to be filtered to exclude it. We don't show the option at all if there are no Server Components used in the page to avoid confusing existing users that are just using Client Components and wouldn't know the difference between Server vs Client. <img width="815" alt="Screenshot 2024-08-30 at 12 56 42 AM" src="https://github.com/user-attachments/assets/e06b225a-e85d-4cdc-8707-d4630fede19e">
1 parent 4f60494 commit 04ec50e

File tree

10 files changed

+253
-26
lines changed

10 files changed

+253
-26
lines changed

packages/react-devtools-shared/src/__tests__/utils.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,19 @@ export function createHOCFilter(isEnabled: boolean = true) {
284284
};
285285
}
286286

287+
export function createEnvironmentNameFilter(
288+
env: string,
289+
isEnabled: boolean = true,
290+
) {
291+
const Types = require('react-devtools-shared/src/frontend/types');
292+
return {
293+
type: Types.ComponentFilterEnvironmentName,
294+
isEnabled,
295+
isValid: true,
296+
value: env,
297+
};
298+
}
299+
287300
export function createElementTypeFilter(
288301
elementType: ElementType,
289302
isEnabled: boolean = true,

packages/react-devtools-shared/src/backend/agent.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ export default class Agent extends EventEmitter<{
220220
this.updateConsolePatchSettings,
221221
);
222222
bridge.addListener('updateComponentFilters', this.updateComponentFilters);
223+
bridge.addListener('getEnvironmentNames', this.getEnvironmentNames);
223224

224225
// Temporarily support older standalone front-ends sending commands to newer embedded backends.
225226
// We do this because React Native embeds the React DevTools backend,
@@ -814,6 +815,24 @@ export default class Agent extends EventEmitter<{
814815
}
815816
};
816817

818+
getEnvironmentNames: () => void = () => {
819+
let accumulatedNames = null;
820+
for (const rendererID in this._rendererInterfaces) {
821+
const renderer = this._rendererInterfaces[+rendererID];
822+
const names = renderer.getEnvironmentNames();
823+
if (accumulatedNames === null) {
824+
accumulatedNames = names;
825+
} else {
826+
for (let i = 0; i < names.length; i++) {
827+
if (accumulatedNames.indexOf(names[i]) === -1) {
828+
accumulatedNames.push(names[i]);
829+
}
830+
}
831+
}
832+
}
833+
this._bridge.send('environmentNames', accumulatedNames || []);
834+
};
835+
817836
onTraceUpdates: (nodes: Set<HostInstance>) => void = nodes => {
818837
this.emit('traceUpdates', nodes);
819838
};

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
ComponentFilterElementType,
1515
ComponentFilterHOC,
1616
ComponentFilterLocation,
17+
ComponentFilterEnvironmentName,
1718
ElementTypeClass,
1819
ElementTypeContext,
1920
ElementTypeFunction,
@@ -721,6 +722,11 @@ export function getInternalReactConstants(version: string): {
721722
};
722723
}
723724

725+
// All environment names we've seen so far. This lets us create a list of filters to apply.
726+
// This should ideally include env of filtered Components too so that you can add those as
727+
// filters at the same time as removing some other filter.
728+
const knownEnvironmentNames: Set<string> = new Set();
729+
724730
// Map of one or more Fibers in a pair to their unique id number.
725731
// We track both Fibers to support Fast Refresh,
726732
// which may forcefully replace one of the pair as part of hot reloading.
@@ -1099,6 +1105,7 @@ export function attach(
10991105
const hideElementsWithDisplayNames: Set<RegExp> = new Set();
11001106
const hideElementsWithPaths: Set<RegExp> = new Set();
11011107
const hideElementsWithTypes: Set<ElementType> = new Set();
1108+
const hideElementsWithEnvs: Set<string> = new Set();
11021109

11031110
// Highlight updates
11041111
let traceUpdatesEnabled: boolean = false;
@@ -1108,6 +1115,7 @@ export function attach(
11081115
hideElementsWithTypes.clear();
11091116
hideElementsWithDisplayNames.clear();
11101117
hideElementsWithPaths.clear();
1118+
hideElementsWithEnvs.clear();
11111119

11121120
componentFilters.forEach(componentFilter => {
11131121
if (!componentFilter.isEnabled) {
@@ -1133,6 +1141,9 @@ export function attach(
11331141
case ComponentFilterHOC:
11341142
hideElementsWithDisplayNames.add(new RegExp('\\('));
11351143
break;
1144+
case ComponentFilterEnvironmentName:
1145+
hideElementsWithEnvs.add(componentFilter.value);
1146+
break;
11361147
default:
11371148
console.warn(
11381149
`Invalid component filter type "${componentFilter.type}"`,
@@ -1215,7 +1226,14 @@ export function attach(
12151226
flushPendingEvents();
12161227
}
12171228

1218-
function shouldFilterVirtual(data: ReactComponentInfo): boolean {
1229+
function getEnvironmentNames(): Array<string> {
1230+
return Array.from(knownEnvironmentNames);
1231+
}
1232+
1233+
function shouldFilterVirtual(
1234+
data: ReactComponentInfo,
1235+
secondaryEnv: null | string,
1236+
): boolean {
12191237
// For purposes of filtering Server Components are always Function Components.
12201238
// Environment will be used to filter Server vs Client.
12211239
// Technically they can be forwardRef and memo too but those filters will go away
@@ -1236,6 +1254,14 @@ export function attach(
12361254
}
12371255
}
12381256

1257+
if (
1258+
(data.env == null || hideElementsWithEnvs.has(data.env)) &&
1259+
(secondaryEnv === null || hideElementsWithEnvs.has(secondaryEnv))
1260+
) {
1261+
// If a Component has two environments, you have to filter both for it not to appear.
1262+
return true;
1263+
}
1264+
12391265
return false;
12401266
}
12411267

@@ -1294,6 +1320,26 @@ export function attach(
12941320
}
12951321
}
12961322

1323+
if (hideElementsWithEnvs.has('Client')) {
1324+
// If we're filtering out the Client environment we should filter out all
1325+
// "Client Components". Technically that also includes the built-ins but
1326+
// since that doesn't actually include any additional code loading it's
1327+
// useful to not filter out the built-ins. Those can be filtered separately.
1328+
// There's no other way to filter out just Function components on the Client.
1329+
// Therefore, this only filters Class and Function components.
1330+
switch (tag) {
1331+
case ClassComponent:
1332+
case IncompleteClassComponent:
1333+
case IncompleteFunctionComponent:
1334+
case FunctionComponent:
1335+
case IndeterminateComponent:
1336+
case ForwardRef:
1337+
case MemoComponent:
1338+
case SimpleMemoComponent:
1339+
return true;
1340+
}
1341+
}
1342+
12971343
/* DISABLED: https://github.com/facebook/react/pull/28417
12981344
if (hideElementsWithPaths.size > 0) {
12991345
const source = getSourceForFiber(fiber);
@@ -2489,7 +2535,14 @@ export function attach(
24892535
}
24902536
// Scan up until the next Component to see if this component changed environment.
24912537
const componentInfo: ReactComponentInfo = (debugEntry: any);
2492-
if (shouldFilterVirtual(componentInfo)) {
2538+
const secondaryEnv = getSecondaryEnvironmentName(fiber._debugInfo, i);
2539+
if (componentInfo.env != null) {
2540+
knownEnvironmentNames.add(componentInfo.env);
2541+
}
2542+
if (secondaryEnv !== null) {
2543+
knownEnvironmentNames.add(secondaryEnv);
2544+
}
2545+
if (shouldFilterVirtual(componentInfo, secondaryEnv)) {
24932546
// Skip.
24942547
continue;
24952548
}
@@ -2511,10 +2564,6 @@ export function attach(
25112564
);
25122565
}
25132566
previousVirtualInstance = createVirtualInstance(componentInfo);
2514-
const secondaryEnv = getSecondaryEnvironmentName(
2515-
fiber._debugInfo,
2516-
i,
2517-
);
25182567
recordVirtualMount(
25192568
previousVirtualInstance,
25202569
reconcilingParent,
@@ -2919,7 +2968,17 @@ export function attach(
29192968
continue;
29202969
}
29212970
const componentInfo: ReactComponentInfo = (debugEntry: any);
2922-
if (shouldFilterVirtual(componentInfo)) {
2971+
const secondaryEnv = getSecondaryEnvironmentName(
2972+
nextChild._debugInfo,
2973+
i,
2974+
);
2975+
if (componentInfo.env != null) {
2976+
knownEnvironmentNames.add(componentInfo.env);
2977+
}
2978+
if (secondaryEnv !== null) {
2979+
knownEnvironmentNames.add(secondaryEnv);
2980+
}
2981+
if (shouldFilterVirtual(componentInfo, secondaryEnv)) {
29232982
continue;
29242983
}
29252984
if (level === virtualLevel) {
@@ -2983,10 +3042,6 @@ export function attach(
29833042
} else {
29843043
// Otherwise we create a new instance.
29853044
const newVirtualInstance = createVirtualInstance(componentInfo);
2986-
const secondaryEnv = getSecondaryEnvironmentName(
2987-
nextChild._debugInfo,
2988-
i,
2989-
);
29903045
recordVirtualMount(
29913046
newVirtualInstance,
29923047
reconcilingParent,
@@ -3925,7 +3980,7 @@ export function attach(
39253980
owner = ownerFiber._debugOwner;
39263981
} else {
39273982
const ownerInfo: ReactComponentInfo = (owner: any); // Refined
3928-
if (!shouldFilterVirtual(ownerInfo)) {
3983+
if (!shouldFilterVirtual(ownerInfo, null)) {
39293984
return ownerInfo;
39303985
}
39313986
owner = ownerInfo.owner;
@@ -5750,5 +5805,6 @@ export function attach(
57505805
storeAsGlobal,
57515806
unpatchConsoleForStrictMode,
57525807
updateComponentFilters,
5808+
getEnvironmentNames,
57535809
};
57545810
}

packages/react-devtools-shared/src/backend/legacy/renderer.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,11 @@ export function attach(
10781078
// Not implemented.
10791079
}
10801080

1081+
function getEnvironmentNames(): Array<string> {
1082+
// No RSC support.
1083+
return [];
1084+
}
1085+
10811086
function setTraceUpdatesEnabled(enabled: boolean) {
10821087
// Not implemented.
10831088
}
@@ -1152,5 +1157,6 @@ export function attach(
11521157
storeAsGlobal,
11531158
unpatchConsoleForStrictMode,
11541159
updateComponentFilters,
1160+
getEnvironmentNames,
11551161
};
11561162
}

packages/react-devtools-shared/src/backend/types.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ export type RendererInterface = {
416416
) => void,
417417
unpatchConsoleForStrictMode: () => void,
418418
updateComponentFilters: (componentFilters: Array<ComponentFilter>) => void,
419+
getEnvironmentNames: () => Array<string>,
419420

420421
// Timeline profiler interface
421422

packages/react-devtools-shared/src/bridge.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ export type BackendEvents = {
189189
operations: [Array<number>],
190190
ownersList: [OwnersList],
191191
overrideComponentFilters: [Array<ComponentFilter>],
192+
environmentNames: [Array<string>],
192193
profilingData: [ProfilingDataBackend],
193194
profilingStatus: [boolean],
194195
reloadAppForProfiling: [],
@@ -237,6 +238,7 @@ type FrontendEvents = {
237238
stopProfiling: [],
238239
storeAsGlobal: [StoreAsGlobalParams],
239240
updateComponentFilters: [Array<ComponentFilter>],
241+
getEnvironmentNames: [],
240242
updateConsolePatchSettings: [ConsolePatchSettings],
241243
viewAttributeSource: [ViewAttributeSourceParams],
242244
viewElementSource: [ElementAndRendererID],

0 commit comments

Comments
 (0)