Skip to content

Commit

Permalink
Add --experimental-debugger-frontend flag, restore 0.72 flow as base (#…
Browse files Browse the repository at this point in the history
…40766)

Summary:
Pull Request resolved: #40766

This changeset allows users to opt into the new debugger frontend experience by passing `--experimental-debugger` to `react-native start`. **We are defaulting this option to `true`** for now, but will continue to evaluate this feature before 0.73 ships. It restores Flipper (via `flipper://`) as the default handling for `/open-debugger` (matching 0.72 behaviour) when this flag is not enabled.

Detailed changes:

- Replaces `enableCustomDebuggerFrontend` experiment in `dev-middleware` with `enableNewDebugger`. The latter now hard-swaps between the Flipper and new launch flows.
    - Removes now-unused switching of `devtoolsFrontendUrl`.
- Implements `deprecated_openFlipperMiddleware` (matching previous RN CLI implementation).
- Disables "`j` to debug" key handler by default.
- Marks "`j` to debug" and `/open-debugger` console logs as experimental.

Changelog:
[Changed][General] Gate new debugger frontend behind `--experimental-debugger` flag, restore Flipper as base launch flow

Reviewed By: motiz88

Differential Revision: D50084590

fbshipit-source-id: 5234634f20110cb7933b1787bd2c86f645411fff
  • Loading branch information
huntie authored and facebook-github-bot committed Oct 10, 2023
1 parent 4dcd5f1 commit 9e068ac
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 51 deletions.
29 changes: 29 additions & 0 deletions flow-typed/npm/open_v7.x.x.js
@@ -0,0 +1,29 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/

declare module 'open' {
import type {ChildProcess} from 'child_process';

declare export type Options = $ReadOnly<{
wait?: boolean,
background?: boolean,
newInstance?: boolean,
allowNonzeroExitCode?: boolean,
...
}>;

declare type open = (
target: string,
options?: Options,
) => Promise<ChildProcess>;

declare module.exports: open;
}
Expand Up @@ -24,13 +24,15 @@ export default function attachKeyHandlers({
cliConfig,
devServerUrl,
messageSocket,
experimentalDebuggerFrontend,
}: {
cliConfig: Config,
devServerUrl: string,
messageSocket: $ReadOnly<{
broadcast: (type: string, params?: Record<string, mixed> | null) => void,
...
}>,
experimentalDebuggerFrontend: boolean,
}) {
if (process.stdin.isTTY !== true) {
logger.debug('Interactive mode is not supported in this environment');
Expand Down Expand Up @@ -76,6 +78,9 @@ export default function attachKeyHandlers({
).stdout?.pipe(process.stdout);
break;
case 'j':
if (!experimentalDebuggerFrontend) {
return;
}
await fetch(devServerUrl + '/open-debugger', {method: 'POST'});
break;
case CTRL_C:
Expand All @@ -92,12 +97,16 @@ export default function attachKeyHandlers({
keyPressHandler.startInterceptingKeyStrokes();

logger.log(
`
${chalk.bold('i')} - run on iOS
${chalk.bold('a')} - run on Android
${chalk.bold('d')} - open Dev Menu
${chalk.bold('j')} - open debugger
${chalk.bold('r')} - reload app
`,
[
'',
`${chalk.bold('i')} - run on iOS`,
`${chalk.bold('a')} - run on Android`,
`${chalk.bold('d')} - open Dev Menu`,
...(experimentalDebuggerFrontend
? [`${chalk.bold('j')} - open debugger (experimental)`]
: []),
`${chalk.bold('r')} - reload app`,
'',
].join('\n'),
);
}
10 changes: 10 additions & 0 deletions packages/community-cli-plugin/src/commands/start/index.js
Expand Up @@ -95,6 +95,16 @@ const startCommand: Command = {
name: '--no-interactive',
description: 'Disables interactive mode',
},
{
name: '--experimental-debugger [bool]',
description:
"[Experimental] Enable the new debugger experience and 'j' to " +
'debug. This enables the new frontend experience only: connection ' +
'reliability and some basic features are unstable in this release.',
parse: (val: ?string): boolean =>
val !== undefined && val !== 'false' && val !== '0',
default: true,
},
],
};

Expand Down
Expand Up @@ -33,6 +33,7 @@ export type StartCommandArgs = {
assetPlugins?: string[],
cert?: string,
customLogReporterPath?: string,
experimentalDebugger: boolean,
host?: string,
https?: boolean,
maxWorkers?: number,
Expand Down Expand Up @@ -118,7 +119,7 @@ async function runServer(
logger,
unstable_experiments: {
// NOTE: Only affects the /open-debugger endpoint
enableCustomDebuggerFrontend: true,
enableNewDebugger: args.experimentalDebugger,
},
});

Expand All @@ -138,6 +139,7 @@ async function runServer(
cliConfig: ctx,
devServerUrl,
messageSocket: messageSocketEndpoint,
experimentalDebuggerFrontend: args.experimentalDebugger,
});
}
},
Expand Down
1 change: 1 addition & 0 deletions packages/dev-middleware/package.json
Expand Up @@ -29,6 +29,7 @@
"connect": "^3.6.5",
"debug": "^2.2.0",
"node-fetch": "^2.2.0",
"open": "^7.0.3",
"serve-static": "^1.13.1",
"temp-dir": "^2.0.0"
},
Expand Down
23 changes: 14 additions & 9 deletions packages/dev-middleware/src/createDevMiddleware.js
Expand Up @@ -19,6 +19,7 @@ import reactNativeDebuggerFrontendPath from '@react-native/debugger-frontend';
import connect from 'connect';
import path from 'path';
import serveStaticMiddleware from 'serve-static';
import deprecated_openFlipperMiddleware from './middleware/deprecated_openFlipperMiddleware';
import openDebuggerMiddleware from './middleware/openDebuggerMiddleware';
import InspectorProxy from './inspector-proxy/InspectorProxy';
import DefaultBrowserLauncher from './utils/DefaultBrowserLauncher';
Expand Down Expand Up @@ -85,14 +86,18 @@ export default function createDevMiddleware({
const middleware = connect()
.use(
'/open-debugger',
openDebuggerMiddleware({
serverBaseUrl,
inspectorProxy,
browserLauncher: unstable_browserLauncher,
eventReporter: unstable_eventReporter,
experiments,
logger,
}),
experiments.enableNewDebugger
? openDebuggerMiddleware({
serverBaseUrl,
inspectorProxy,
browserLauncher: unstable_browserLauncher,
eventReporter: unstable_eventReporter,
experiments,
logger,
})
: deprecated_openFlipperMiddleware({
logger,
}),
)
.use(
'/debugger-frontend',
Expand All @@ -110,7 +115,7 @@ export default function createDevMiddleware({

function getExperiments(config: ExperimentsConfig): Experiments {
return {
enableCustomDebuggerFrontend: config.enableCustomDebuggerFrontend ?? false,
enableNewDebugger: config.enableNewDebugger ?? false,
enableOpenDebuggerRedirect: config.enableOpenDebuggerRedirect ?? false,
};
}
@@ -0,0 +1,60 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/

import type {NextHandleFunction} from 'connect';
import type {IncomingMessage, ServerResponse} from 'http';
import type {Logger} from '../types/Logger';

import open from 'open';

const FLIPPER_SELF_CONNECT_URL =
'flipper://null/Hermesdebuggerrn?device=React%20Native';

type Options = $ReadOnly<{
logger?: Logger,
}>;

/**
* Open the legacy Flipper debugger (Hermes).
*
* @deprecated This replicates the pre-0.73 workflow of opening Flipper via the
* `flipper://` URL scheme, failing if Flipper is not installed locally. This
* flow will be removed in a future version.
*/
export default function deprecated_openFlipperMiddleware({
logger,
}: Options): NextHandleFunction {
return async (
req: IncomingMessage,
res: ServerResponse,
next: (err?: Error) => void,
) => {
if (req.method === 'POST') {
logger?.info('Launching JS debugger...');

try {
logger?.warn(
'Attempting to debug JS in Flipper (deprecated). This requires ' +
'Flipper to be installed on your system to handle the ' +
"'flipper://' URL scheme.",
);
await open(FLIPPER_SELF_CONNECT_URL);
res.end();
} catch (e) {
logger?.error(
'Error launching Flipper: ' + e.message ?? 'Unknown error',
);
res.writeHead(500);
res.end();
}
}
};
}
Expand Up @@ -72,7 +72,7 @@ export default function openDebuggerMiddleware({
if (typeof appId === 'string') {
logger?.info(
(launchType === 'launch' ? 'Launching' : 'Redirecting to') +
' JS debugger...',
' JS debugger (experimental)...',
);
target = targets.find(_target => _target.description === appId);
} else {
Expand Down Expand Up @@ -108,7 +108,6 @@ export default function openDebuggerMiddleware({
getDevToolsFrontendUrl(
target.webSocketDebuggerUrl,
serverBaseUrl,
experiments,
),
),
);
Expand All @@ -120,7 +119,6 @@ export default function openDebuggerMiddleware({
target.webSocketDebuggerUrl,
// Use a relative URL.
'',
experiments,
),
});
res.end();
Expand Down
7 changes: 4 additions & 3 deletions packages/dev-middleware/src/types/Experiments.js
Expand Up @@ -10,10 +10,11 @@

export type Experiments = $ReadOnly<{
/**
* Enables the use of the custom debugger frontend (@react-native/debugger-frontend)
* in the /open-debugger endpoint.
* Enables the new JS debugger launch flow and custom debugger frontend
* (@react-native/debugger-frontend). When disabled, /open-debugger will
* trigger the legacy Flipper connection flow.
*/
enableCustomDebuggerFrontend: boolean,
enableNewDebugger: boolean,

/**
* Enables the handling of GET requests in the /open-debugger endpoint,
Expand Down
32 changes: 5 additions & 27 deletions packages/dev-middleware/src/utils/getDevToolsFrontendUrl.js
Expand Up @@ -9,40 +9,18 @@
* @oncall react_native
*/

import type {Experiments} from '../types/Experiments';

/**
* The Chrome DevTools frontend revision to use. This should be set to the
* latest version known to be compatible with Hermes.
*
* Revision should be the full identifier from:
* https://chromium.googlesource.com/chromium/src.git
*/
const DEVTOOLS_FRONTEND_REV = 'd9568d04d7dd79269c5a655d7ada69650c5a8336'; // Chrome 100.0.4896.75

/**
* Construct the URL to Chrome DevTools connected to a given debugger target.
* Get the DevTools frontend URL to debug a given React Native CDP target.
*/
export default function getDevToolsFrontendUrl(
webSocketDebuggerUrl: string,
devServerUrl: string,
experiments: Experiments,
): string {
const scheme = new URL(webSocketDebuggerUrl).protocol.slice(0, -1);
const webSocketUrlWithoutProtocol = webSocketDebuggerUrl.replace(
/^wss?:\/\//,
'',
const appUrl = `${devServerUrl}/debugger-frontend/rn_inspector.html`;
const webSocketUrlWithoutProtocol = encodeURIComponent(
webSocketDebuggerUrl.replace(/^wss?:\/\//, ''),
);

if (experiments.enableCustomDebuggerFrontend) {
const urlBase = `${devServerUrl}/debugger-frontend/rn_inspector.html`;
return `${urlBase}?${scheme}=${encodeURIComponent(
webSocketUrlWithoutProtocol,
)}&sources.hide_add_folder=true`;
}

const urlBase = `https://chrome-devtools-frontend.appspot.com/serve_rev/@${DEVTOOLS_FRONTEND_REV}/devtools_app.html`;
return `${urlBase}?panel=console&${scheme}=${encodeURIComponent(
webSocketUrlWithoutProtocol,
)}`;
return `${appUrl}?${scheme}=${webSocketUrlWithoutProtocol}&sources.hide_add_folder=true`;
}
10 changes: 9 additions & 1 deletion yarn.lock
Expand Up @@ -5983,7 +5983,7 @@ is-wsl@^1.1.0:
resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=

is-wsl@^2.2.0:
is-wsl@^2.1.1, is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
Expand Down Expand Up @@ -7617,6 +7617,14 @@ open@^6.2.0:
dependencies:
is-wsl "^1.1.0"

open@^7.0.3:
version "7.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
dependencies:
is-docker "^2.0.0"
is-wsl "^2.1.1"

optionator@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
Expand Down

0 comments on commit 9e068ac

Please sign in to comment.