Skip to content

Commit 98d3e94

Browse files
Brian VaughnhimanshiLt
Brian Vaughn
authored andcommitted
Show DevTools backend and frontend versions in UI (facebook#23399)
This information can help with bug investigation for renderers (like React Native) that embed the DevTools backend into their source (separately from the DevTools frontend, which gets run by the user). If the DevTools backend is too old to report a version, or if the version reported is the same as the frontend (as will be the case with the browser extension) then only a single version string will be shown, as before. If a different version is reported, then both will be shown separately.
1 parent fb4fa82 commit 98d3e94

File tree

7 files changed

+213
-68
lines changed

7 files changed

+213
-68
lines changed

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ export default class Agent extends EventEmitter<{|
185185
bridge.addListener('clearWarningsForFiberID', this.clearWarningsForFiberID);
186186
bridge.addListener('copyElementPath', this.copyElementPath);
187187
bridge.addListener('deletePath', this.deletePath);
188+
bridge.addListener('getBackendVersion', this.getBackendVersion);
188189
bridge.addListener('getBridgeProtocol', this.getBridgeProtocol);
189190
bridge.addListener('getProfilingData', this.getProfilingData);
190191
bridge.addListener('getProfilingStatus', this.getProfilingStatus);
@@ -225,7 +226,12 @@ export default class Agent extends EventEmitter<{|
225226
bridge.send('profilingStatus', true);
226227
}
227228

228-
// Send the Bridge protocol after initialization in case the frontend has already requested it.
229+
// Send the Bridge protocol and backend versions, after initialization, in case the frontend has already requested it.
230+
// The Store may be instantiated beore the agent.
231+
const version = process.env.DEVTOOLS_VERSION;
232+
if (version) {
233+
this._bridge.send('backendVersion', version);
234+
}
229235
this._bridge.send('bridgeProtocol', currentBridgeProtocol);
230236

231237
// Notify the frontend if the backend supports the Storage API (e.g. localStorage).
@@ -322,6 +328,13 @@ export default class Agent extends EventEmitter<{|
322328
return null;
323329
}
324330

331+
getBackendVersion = () => {
332+
const version = process.env.DEVTOOLS_VERSION;
333+
if (version) {
334+
this._bridge.send('backendVersion', version);
335+
}
336+
};
337+
325338
getBridgeProtocol = () => {
326339
this._bridge.send('bridgeProtocol', currentBridgeProtocol);
327340
};

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ type SavedPreferencesParams = {|
185185
|};
186186

187187
export type BackendEvents = {|
188+
backendVersion: [string],
188189
bridgeProtocol: [BridgeProtocol],
189190
extensionBackendInitialized: [],
190191
fastRefreshScheduled: [],
@@ -219,6 +220,7 @@ type FrontendEvents = {|
219220
clearWarningsForFiberID: [ElementAndRendererID],
220221
copyElementPath: [CopyElementPathParams],
221222
deletePath: [DeletePath],
223+
getBackendVersion: [],
222224
getBridgeProtocol: [],
223225
getOwnersList: [ElementAndRendererID],
224226
getProfilingData: [{|rendererID: RendererID|}],

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export type Capabilities = {|
8585
* ContextProviders can subscribe to the Store for specific things they want to provide.
8686
*/
8787
export default class Store extends EventEmitter<{|
88+
backendVersion: [],
8889
collapseNodesByDefault: [],
8990
componentFilters: [],
9091
error: [Error],
@@ -98,6 +99,10 @@ export default class Store extends EventEmitter<{|
9899
unsupportedBridgeProtocolDetected: [],
99100
unsupportedRendererVersionDetected: [],
100101
|}> {
102+
// If the backend version is new enough to report its (NPM) version, this is it.
103+
// This version may be displayed by the frontend for debugging purposes.
104+
_backendVersion: string | null = null;
105+
101106
_bridge: FrontendBridge;
102107

103108
// Computed whenever _errorsAndWarnings Map changes.
@@ -264,6 +269,9 @@ export default class Store extends EventEmitter<{|
264269
bridge.addListener('bridgeProtocol', this.onBridgeProtocol);
265270
bridge.send('getBridgeProtocol');
266271
}
272+
273+
bridge.addListener('backendVersion', this.onBridgeBackendVersion);
274+
bridge.send('getBackendVersion');
267275
}
268276

269277
// This is only used in tests to avoid memory leaks.
@@ -301,6 +309,10 @@ export default class Store extends EventEmitter<{|
301309
}
302310
}
303311

312+
get backendVersion(): string | null {
313+
return this._backendVersion;
314+
}
315+
304316
get collapseNodesByDefault(): boolean {
305317
return this._collapseNodesByDefault;
306318
}
@@ -1333,6 +1345,7 @@ export default class Store extends EventEmitter<{|
13331345
'unsupportedRendererVersion',
13341346
this.onBridgeUnsupportedRendererVersion,
13351347
);
1348+
bridge.removeListener('backendVersion', this.onBridgeBackendVersion);
13361349
bridge.removeListener('bridgeProtocol', this.onBridgeProtocol);
13371350

13381351
if (this._onBridgeProtocolTimeoutID !== null) {
@@ -1359,6 +1372,11 @@ export default class Store extends EventEmitter<{|
13591372
this.emit('unsupportedRendererVersionDetected');
13601373
};
13611374

1375+
onBridgeBackendVersion = (backendVersion: string) => {
1376+
this._backendVersion = backendVersion;
1377+
this.emit('backendVersion');
1378+
};
1379+
13621380
onBridgeProtocol = (bridgeProtocol: BridgeProtocol) => {
13631381
if (this._onBridgeProtocolTimeoutID !== null) {
13641382
clearTimeout(this._onBridgeProtocolTimeoutID);

packages/react-devtools-shared/src/devtools/views/Settings/GeneralSettings.js

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,26 @@
88
*/
99

1010
import * as React from 'react';
11-
import {useContext} from 'react';
11+
import {useContext, useMemo} from 'react';
1212
import {SettingsContext} from './SettingsContext';
1313
import {StoreContext} from '../context';
1414
import {CHANGE_LOG_URL} from 'react-devtools-shared/src/constants';
1515

1616
import styles from './SettingsShared.css';
1717

18+
function getChangeLogUrl(version: ?string): string | null {
19+
if (!version) {
20+
return null;
21+
}
22+
23+
// Version numbers are in the format of: <major>.<minor>.<patch>-<sha>
24+
// e.g. "4.23.0-f0dd459e0"
25+
// GitHub CHANGELOG headers are in the format of: <major>.<minor>.<patch>
26+
// but the "." are stripped from anchor tags, becomming: <major><minor><patch>
27+
const versionAnchor = version.replace(/^(\d+)\.(\d+)\.(\d+).*/, '$1$2$3');
28+
return `${CHANGE_LOG_URL}#${versionAnchor}`;
29+
}
30+
1831
export default function GeneralSettings(_: {||}) {
1932
const {
2033
displayDensity,
@@ -25,7 +38,11 @@ export default function GeneralSettings(_: {||}) {
2538
traceUpdatesEnabled,
2639
} = useContext(SettingsContext);
2740

28-
const {supportsTraceUpdates} = useContext(StoreContext);
41+
const {backendVersion, supportsTraceUpdates} = useContext(StoreContext);
42+
const frontendVersion = process.env.DEVTOOLS_VERSION;
43+
44+
const showBackendVersion =
45+
backendVersion && backendVersion !== frontendVersion;
2946

3047
return (
3148
<div className={styles.Settings}>
@@ -70,15 +87,51 @@ export default function GeneralSettings(_: {||}) {
7087
)}
7188

7289
<div className={styles.ReleaseNotes}>
90+
{showBackendVersion && (
91+
<div>
92+
<ul className={styles.VersionsList}>
93+
<li>
94+
<Version
95+
label="DevTools backend version:"
96+
version={backendVersion}
97+
/>
98+
</li>
99+
<li>
100+
<Version
101+
label="DevTools frontend version:"
102+
version={frontendVersion}
103+
/>
104+
</li>
105+
</ul>
106+
</div>
107+
)}
108+
{!showBackendVersion && (
109+
<Version label="DevTools version:" version={frontendVersion} />
110+
)}
111+
</div>
112+
</div>
113+
);
114+
}
115+
116+
function Version({label, version}: {|label: string, version: ?string|}) {
117+
const changelogLink = useMemo(() => {
118+
return getChangeLogUrl(version);
119+
}, [version]);
120+
121+
if (version == null) {
122+
return null;
123+
} else {
124+
return (
125+
<>
126+
{label}{' '}
73127
<a
74128
className={styles.ReleaseNotesLink}
75129
target="_blank"
76130
rel="noopener noreferrer"
77-
href={CHANGE_LOG_URL}>
78-
View release notes
79-
</a>{' '}
80-
for DevTools version {process.env.DEVTOOLS_VERSION}
81-
</div>
82-
</div>
83-
);
131+
href={changelogLink}>
132+
{version}
133+
</a>
134+
</>
135+
);
136+
}
84137
}

packages/react-devtools-shared/src/devtools/views/Settings/SettingsShared.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,10 @@
149149

150150
.Warning {
151151
color: var(--color-error-text);
152+
}
153+
154+
.VersionsList {
155+
list-style: none;
156+
padding: 0;
157+
margin: 0;
152158
}

packages/react-devtools-shell/src/app/index.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import Iframe from './Iframe';
1010
import EditableProps from './EditableProps';
1111
import ElementTypes from './ElementTypes';
1212
import Hydration from './Hydration';
13-
import InlineWarnings from './InlineWarnings';
1413
import InspectableElements from './InspectableElements';
1514
import ReactNativeWeb from './ReactNativeWeb';
1615
import ToDoList from './ToDoList';
@@ -83,7 +82,6 @@ function mountTestApp() {
8382
mountApp(Hydration);
8483
mountApp(ElementTypes);
8584
mountApp(EditableProps);
86-
mountApp(InlineWarnings);
8785
mountApp(ReactNativeWeb);
8886
mountApp(Toggle);
8987
mountApp(ErrorBoundaries);

0 commit comments

Comments
 (0)