Repository: facebook/react
Pull request: Allow to use the Profiler when no client is connected in standalone DevTools

## Summary

Allows the Profiler tab to be used without the need of having a connected client in the DevTools standalone version (issue: #22127). The option to open the Profiler was added to the introduction/greeting page and redirects the user to the DevTools component. The impact in the DevTools component is minimum since no changes were made directly to it, only the react-devtools-core and the splash page were modified.
As I mentioned in the issue discussion, a fake Bridge and Store were created in order to render the DevTools, there might exist a better workaround and should be reviewed.
Also, the idea was to open the Profiler tab by default, but there are two props that can affect that behaviour: overrideTab (which prevents the tab from being changed because it is constantly overridden) and defaultTab (which sets the initial value for the LocalStorage entry that contains the tab that is open, but if a value is already stored in the LocalStorage, it is returned and the defaultValue is not used). It means that to change that behaviour it would be necessary to add an extra prop to the DevTools component and change the way the opened tab is selected at first.

## How did you test this change?
It was tested by building and running the DevTools with the changes made.
The instruction splash page would look like this:
<img width="912" alt="Screen Shot 2021-10-13 at 12 49 27 PM" src="https://user-images.githubusercontent.com/39140902/137186324-3754e85c-80db-40dd-a1fe-e1fdfc5eaddb.png">
After pressing the "Profiler tab" link, it redirects the user to the DevTools:
<img width="912" alt="Screen Shot 2021-10-13 at 12 50 58 PM" src="https://user-images.githubusercontent.com/39140902/137186519-b994e69a-cc92-46da-a672-31838a3538b9.png">
If a user loads a saved profile, the profiler can be used as usual:
<img width="912" alt="Screen Shot 2021-10-13 at 12 51 40 PM" src="https://user-images.githubusercontent.com/39140902/137186613-0b21233d-1258-4983-82dc-33e7d678a3ac.png">
And when a connection is established with a client it updates and show the component tree and allows the profiler to record activity:
![Screen Shot 2021-10-13 at 2 05 24 PM](https://user-images.githubusercontent.com/39140902/137197302-8f69dd34-01cf-4a93-9063-b07f36afdcad.png)




Changed files:
filename: packages/react-devtools-core/src/standalone.js
patch: @@ -30,7 +30,11 @@ import {readFileSync} from 'fs';
 import {installHook} from 'react-devtools-shared/src/hook';
 import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
 import {doesFilePathExist, launchEditor} from './editor';
-import {__DEBUG__} from 'react-devtools-shared/src/constants';
+import {
+  __DEBUG__,
+  LOCAL_STORAGE_DEFAULT_TAB_KEY,
+} from 'react-devtools-shared/src/constants';
+import {localStorageSetItem} from '../../react-devtools-shared/src/storage';
 
 import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
 import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types';
@@ -179,6 +183,20 @@ function onError({code, message}) {
   }
 }
 
+function openProfiler() {
+  // Mocked up bridge and store to allow the DevTools to be rendered
+  bridge = new Bridge({listen: () => {}, send: () => {}});
+  store = new Store(bridge, {});
+
+  // Ensure the Profiler tab is shown initially.
+  localStorageSetItem(
+    LOCAL_STORAGE_DEFAULT_TAB_KEY,
+    JSON.stringify('profiler'),
+  );
+
+  reload();
+}
+
 function initialize(socket: WebSocket) {
   const listeners = [];
   socket.onmessage = event => {
@@ -372,6 +390,7 @@ const DevtoolsUI = {
   setProjectRoots,
   setStatusListener,
   startServer,
+  openProfiler,
 };
 
 export default DevtoolsUI;

filename: packages/react-devtools-shared/src/constants.js
patch: @@ -20,6 +20,8 @@ export const TREE_OPERATION_UPDATE_TREE_BASE_DURATION = 4;
 export const TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS = 5;
 export const TREE_OPERATION_REMOVE_ROOT = 6;
 
+export const LOCAL_STORAGE_DEFAULT_TAB_KEY = 'React::DevTools::defaultTab';
+
 export const LOCAL_STORAGE_FILTER_PREFERENCES_KEY =
   'React::DevTools::componentFilters';
 

filename: packages/react-devtools-shared/src/devtools/views/DevTools.js
patch: @@ -38,6 +38,7 @@ import UnsupportedVersionDialog from './UnsupportedVersionDialog';
 import WarnIfLegacyBackendDetected from './WarnIfLegacyBackendDetected';
 import {useLocalStorage} from './hooks';
 import ThemeProvider from './ThemeProvider';
+import {LOCAL_STORAGE_DEFAULT_TAB_KEY} from '../../constants';
 
 import styles from './DevTools.css';
 
@@ -143,7 +144,7 @@ export default function DevTools({
   hideViewSourceAction,
 }: Props) {
   const [currentTab, setTab] = useLocalStorage<TabID>(
-    'React::DevTools::defaultTab',
+    LOCAL_STORAGE_DEFAULT_TAB_KEY,
     defaultTab,
   );
 

filename: packages/react-devtools/app.html
patch: @@ -142,6 +142,16 @@
             <strong>before</strong> importing React DOM.
           </div>
         </div>
+        <div class="box">
+          <div class="box-header">Profiler</div>
+          <div class="box-content">
+            Open the <a
+              id="profiler"
+              class="link"
+              href="#"
+            >Profiler tab</a> to inspect saved profiles.
+          </div>
+        </div>
         <div id="loading-status">Starting the server…</div>
       </div>
     </div>
@@ -201,6 +211,12 @@
         }
       }
 
+      function openProfiler() {
+        window.devtools
+          .setContentDOMNode(document.getElementById("container"))
+          .openProfiler();
+      }
+
       const link = $('#rn-help-link');
       link.addEventListener('click', event => {
         event.preventDefault();
@@ -217,6 +233,9 @@
       $byIp.addEventListener('click', selectAllAndCopy);
       $byIp.addEventListener('focus', selectAllAndCopy);
 
+      const $profiler = $("#profiler");
+      $profiler.addEventListener('click', openProfiler);
+
       let devtools;
       try {
         devtools = require("react-devtools-core/standalone").default;