Skip to content

Commit

Permalink
Show DevTools backend and frontend versions in UI (#23399)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Brian Vaughn authored Mar 2, 2022
1 parent f0dd459 commit 0e0b1a4
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 68 deletions.
15 changes: 14 additions & 1 deletion packages/react-devtools-shared/src/backend/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export default class Agent extends EventEmitter<{|
bridge.addListener('clearWarningsForFiberID', this.clearWarningsForFiberID);
bridge.addListener('copyElementPath', this.copyElementPath);
bridge.addListener('deletePath', this.deletePath);
bridge.addListener('getBackendVersion', this.getBackendVersion);
bridge.addListener('getBridgeProtocol', this.getBridgeProtocol);
bridge.addListener('getProfilingData', this.getProfilingData);
bridge.addListener('getProfilingStatus', this.getProfilingStatus);
Expand Down Expand Up @@ -225,7 +226,12 @@ export default class Agent extends EventEmitter<{|
bridge.send('profilingStatus', true);
}

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

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

getBackendVersion = () => {
const version = process.env.DEVTOOLS_VERSION;
if (version) {
this._bridge.send('backendVersion', version);
}
};

getBridgeProtocol = () => {
this._bridge.send('bridgeProtocol', currentBridgeProtocol);
};
Expand Down
2 changes: 2 additions & 0 deletions packages/react-devtools-shared/src/bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ type SavedPreferencesParams = {|
|};

export type BackendEvents = {|
backendVersion: [string],
bridgeProtocol: [BridgeProtocol],
extensionBackendInitialized: [],
fastRefreshScheduled: [],
Expand Down Expand Up @@ -219,6 +220,7 @@ type FrontendEvents = {|
clearWarningsForFiberID: [ElementAndRendererID],
copyElementPath: [CopyElementPathParams],
deletePath: [DeletePath],
getBackendVersion: [],
getBridgeProtocol: [],
getOwnersList: [ElementAndRendererID],
getProfilingData: [{|rendererID: RendererID|}],
Expand Down
18 changes: 18 additions & 0 deletions packages/react-devtools-shared/src/devtools/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export type Capabilities = {|
* ContextProviders can subscribe to the Store for specific things they want to provide.
*/
export default class Store extends EventEmitter<{|
backendVersion: [],
collapseNodesByDefault: [],
componentFilters: [],
error: [Error],
Expand All @@ -98,6 +99,10 @@ export default class Store extends EventEmitter<{|
unsupportedBridgeProtocolDetected: [],
unsupportedRendererVersionDetected: [],
|}> {
// If the backend version is new enough to report its (NPM) version, this is it.
// This version may be displayed by the frontend for debugging purposes.
_backendVersion: string | null = null;

_bridge: FrontendBridge;

// Computed whenever _errorsAndWarnings Map changes.
Expand Down Expand Up @@ -264,6 +269,9 @@ export default class Store extends EventEmitter<{|
bridge.addListener('bridgeProtocol', this.onBridgeProtocol);
bridge.send('getBridgeProtocol');
}

bridge.addListener('backendVersion', this.onBridgeBackendVersion);
bridge.send('getBackendVersion');
}

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

get backendVersion(): string | null {
return this._backendVersion;
}

get collapseNodesByDefault(): boolean {
return this._collapseNodesByDefault;
}
Expand Down Expand Up @@ -1333,6 +1345,7 @@ export default class Store extends EventEmitter<{|
'unsupportedRendererVersion',
this.onBridgeUnsupportedRendererVersion,
);
bridge.removeListener('backendVersion', this.onBridgeBackendVersion);
bridge.removeListener('bridgeProtocol', this.onBridgeProtocol);

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

onBridgeBackendVersion = (backendVersion: string) => {
this._backendVersion = backendVersion;
this.emit('backendVersion');
};

onBridgeProtocol = (bridgeProtocol: BridgeProtocol) => {
if (this._onBridgeProtocolTimeoutID !== null) {
clearTimeout(this._onBridgeProtocolTimeoutID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,26 @@
*/

import * as React from 'react';
import {useContext} from 'react';
import {useContext, useMemo} from 'react';
import {SettingsContext} from './SettingsContext';
import {StoreContext} from '../context';
import {CHANGE_LOG_URL} from 'react-devtools-shared/src/constants';

import styles from './SettingsShared.css';

function getChangeLogUrl(version: ?string): string | null {
if (!version) {
return null;
}

// Version numbers are in the format of: <major>.<minor>.<patch>-<sha>
// e.g. "4.23.0-f0dd459e0"
// GitHub CHANGELOG headers are in the format of: <major>.<minor>.<patch>
// but the "." are stripped from anchor tags, becomming: <major><minor><patch>
const versionAnchor = version.replace(/^(\d+)\.(\d+)\.(\d+).*/, '$1$2$3');
return `${CHANGE_LOG_URL}#${versionAnchor}`;
}

export default function GeneralSettings(_: {||}) {
const {
displayDensity,
Expand All @@ -25,7 +38,11 @@ export default function GeneralSettings(_: {||}) {
traceUpdatesEnabled,
} = useContext(SettingsContext);

const {supportsTraceUpdates} = useContext(StoreContext);
const {backendVersion, supportsTraceUpdates} = useContext(StoreContext);
const frontendVersion = process.env.DEVTOOLS_VERSION;

const showBackendVersion =
backendVersion && backendVersion !== frontendVersion;

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

<div className={styles.ReleaseNotes}>
{showBackendVersion && (
<div>
<ul className={styles.VersionsList}>
<li>
<Version
label="DevTools backend version:"
version={backendVersion}
/>
</li>
<li>
<Version
label="DevTools frontend version:"
version={frontendVersion}
/>
</li>
</ul>
</div>
)}
{!showBackendVersion && (
<Version label="DevTools version:" version={frontendVersion} />
)}
</div>
</div>
);
}

function Version({label, version}: {|label: string, version: ?string|}) {
const changelogLink = useMemo(() => {
return getChangeLogUrl(version);
}, [version]);

if (version == null) {
return null;
} else {
return (
<>
{label}{' '}
<a
className={styles.ReleaseNotesLink}
target="_blank"
rel="noopener noreferrer"
href={CHANGE_LOG_URL}>
View release notes
</a>{' '}
for DevTools version {process.env.DEVTOOLS_VERSION}
</div>
</div>
);
href={changelogLink}>
{version}
</a>
</>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,10 @@

.Warning {
color: var(--color-error-text);
}

.VersionsList {
list-style: none;
padding: 0;
margin: 0;
}
2 changes: 0 additions & 2 deletions packages/react-devtools-shell/src/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import Iframe from './Iframe';
import EditableProps from './EditableProps';
import ElementTypes from './ElementTypes';
import Hydration from './Hydration';
import InlineWarnings from './InlineWarnings';
import InspectableElements from './InspectableElements';
import ReactNativeWeb from './ReactNativeWeb';
import ToDoList from './ToDoList';
Expand Down Expand Up @@ -83,7 +82,6 @@ function mountTestApp() {
mountApp(Hydration);
mountApp(ElementTypes);
mountApp(EditableProps);
mountApp(InlineWarnings);
mountApp(ReactNativeWeb);
mountApp(Toggle);
mountApp(ErrorBoundaries);
Expand Down
Loading

0 comments on commit 0e0b1a4

Please sign in to comment.