Skip to content

Commit 969eb3f

Browse files
authoredFeb 12, 2025··
Restore Metro log streaming via CLI flag (#49356)
Summary: This change adds an opt-in to restore JavaScript log streaming via the Metro dev server, [removed from React Native core in 0.77](https://reactnative.dev/blog/2025/01/21/version-0.77#removal-of-consolelog-streaming-in-metro). Users can opt into this legacy behaviour by adding the `--client-logs` flag to `npx react-native-community/cli start`. - The default experience remains without streamed JS logs. - The existing "JavaScript logs have moved! ..." notice is printed in all cases, and we do not advertise the new flag for new users. - Under non-Community CLI dev servers (i.e. Expo), log streaming is restored implicitly. We will clean up this functionality again when we eventually remove JS log streaming over `HMRClient`, tasked in T214991636. **Implementation notes** - Logs are always sent over `HMRClient` (previous status quo), even with log streaming off in the dev server. This is a necessary evil to be able to flag this functionality in a user-accessible place, and to move fast for 0.78. - Necessarily, emitting `fusebox_console_notice` moves to the dev server itself, on first device (Fusebox) connection. Changelog: [General][Added] - Add opt in for legacy Metro log streaming via `--client-logs` flag
1 parent 475f797 commit 969eb3f

File tree

11 files changed

+77
-38
lines changed

11 files changed

+77
-38
lines changed
 

Diff for: ‎packages/community-cli-plugin/README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Start the React Native development server.
1515
#### Usage
1616

1717
```sh
18-
npx react-native start [options]
18+
npx @react-native-community/cli start [options]
1919
```
2020

2121
#### Options
@@ -37,6 +37,7 @@ npx react-native start [options]
3737
| `--cert <path>` | Specify path to a custom SSL cert. |
3838
| `--config <string>` | Path to the CLI configuration file. |
3939
| `--no-interactive` | Disable interactive mode. |
40+
| `--client-logs` | **[Deprecated]** Enable plain text JavaScript log streaming for all connected apps. |
4041

4142
### `bundle`
4243

@@ -45,7 +46,7 @@ Build the bundle for the provided JavaScript entry file.
4546
#### Usage
4647

4748
```sh
48-
npx react-native bundle --entry-file <path> [options]
49+
npx @react-native-community/cli bundle --entry-file <path> [options]
4950
```
5051

5152
#### Options

Diff for: ‎packages/community-cli-plugin/src/commands/start/index.js

+8
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ const startCommand: Command = {
9595
name: '--no-interactive',
9696
description: 'Disables interactive mode',
9797
},
98+
{
99+
name: '--client-logs',
100+
description:
101+
'[Deprecated] Enable plain text JavaScript log streaming for all ' +
102+
'connected apps. This feature is deprecated and will be removed in ' +
103+
'future.',
104+
default: false,
105+
},
98106
],
99107
};
100108

Diff for: ‎packages/community-cli-plugin/src/commands/start/runServer.js

+6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export type StartCommandArgs = {
4444
config?: string,
4545
projectRoot?: string,
4646
interactive: boolean,
47+
clientLogs: boolean,
4748
};
4849

4950
async function runServer(
@@ -96,6 +97,11 @@ async function runServer(
9697
require.resolve(plugin),
9798
);
9899
}
100+
// TODO(T214991636): Remove legacy Metro log forwarding
101+
if (!args.clientLogs) {
102+
// $FlowIgnore[cannot-write] Assigning to readonly property
103+
metroConfig.server.forwardClientLogs = false;
104+
}
99105

100106
let reportEvent: (event: TerminalReportableEvent) => void;
101107
const terminal = new Terminal(process.stdout);

Diff for: ‎packages/dev-middleware/src/createDevMiddleware.js

+36-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import type {CreateCustomMessageHandlerFn} from './inspector-proxy/CustomMessageHandler';
1313
import type {BrowserLauncher} from './types/BrowserLauncher';
14-
import type {EventReporter} from './types/EventReporter';
14+
import type {EventReporter, ReportableEvent} from './types/EventReporter';
1515
import type {Experiments, ExperimentsConfig} from './types/Experiments';
1616
import type {Logger} from './types/Logger';
1717
import type {NextHandleFunction} from 'connect';
@@ -81,11 +81,15 @@ export default function createDevMiddleware({
8181
unstable_customInspectorMessageHandler,
8282
}: Options): DevMiddlewareAPI {
8383
const experiments = getExperiments(experimentConfig);
84+
const eventReporter = createWrappedEventReporter(
85+
unstable_eventReporter,
86+
logger,
87+
);
8488

8589
const inspectorProxy = new InspectorProxy(
8690
projectRoot,
8791
serverBaseUrl,
88-
unstable_eventReporter,
92+
eventReporter,
8993
experiments,
9094
unstable_customInspectorMessageHandler,
9195
);
@@ -97,7 +101,7 @@ export default function createDevMiddleware({
97101
serverBaseUrl,
98102
inspectorProxy,
99103
browserLauncher: unstable_browserLauncher,
100-
eventReporter: unstable_eventReporter,
104+
eventReporter,
101105
experiments,
102106
logger,
103107
}),
@@ -129,3 +133,32 @@ function getExperiments(config: ExperimentsConfig): Experiments {
129133
enableNetworkInspector: config.enableNetworkInspector ?? false,
130134
};
131135
}
136+
137+
/**
138+
* Creates a wrapped EventReporter that locally intercepts events to
139+
* log to the terminal.
140+
*/
141+
function createWrappedEventReporter(
142+
reporter: ?EventReporter,
143+
logger: ?Logger,
144+
): EventReporter {
145+
return {
146+
logEvent(event: ReportableEvent) {
147+
switch (event.type) {
148+
case 'fusebox_console_notice':
149+
logger?.info(
150+
'\n' +
151+
'\u001B[7m' +
152+
' \u001B[1m💡 JavaScript logs have moved!\u001B[22m They can now be ' +
153+
'viewed in React Native DevTools. Tip: Type \u001B[1mj\u001B[22m in ' +
154+
'the terminal to open (requires Google Chrome or Microsoft Edge).' +
155+
'\u001B[27m' +
156+
'\n',
157+
);
158+
break;
159+
}
160+
161+
reporter?.logEvent(event);
162+
},
163+
};
164+
}

Diff for: ‎packages/dev-middleware/src/inspector-proxy/Device.js

+13
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const PAGES_POLLING_INTERVAL = 1000;
4141
// more details.
4242
const FILE_PREFIX = 'file://';
4343

44+
let fuseboxConsoleNoticeLogged = false;
45+
4446
type DebuggerConnection = {
4547
// Debugger web socket connection
4648
socket: WS,
@@ -513,6 +515,7 @@ export default class Device {
513515
// created instead of manually checking this on every getPages result.
514516
for (const page of this.#pages.values()) {
515517
if (this.#pageHasCapability(page, 'nativePageReloads')) {
518+
this.#logFuseboxConsoleNotice();
516519
continue;
517520
}
518521

@@ -1067,4 +1070,14 @@ export default class Device {
10671070
dangerouslyGetSocket(): WS {
10681071
return this.#deviceSocket;
10691072
}
1073+
1074+
// TODO(T214991636): Remove notice
1075+
#logFuseboxConsoleNotice() {
1076+
if (fuseboxConsoleNoticeLogged) {
1077+
return;
1078+
}
1079+
1080+
this.#deviceEventReporter?.logFuseboxConsoleNotice();
1081+
fuseboxConsoleNoticeLogged = true;
1082+
}
10701083
}

Diff for: ‎packages/dev-middleware/src/inspector-proxy/DeviceEventReporter.js

+6
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,12 @@ class DeviceEventReporter {
212212
});
213213
}
214214

215+
logFuseboxConsoleNotice(): void {
216+
this.#eventReporter.logEvent({
217+
type: 'fusebox_console_notice',
218+
});
219+
}
220+
215221
#logExpiredCommand(pendingCommand: PendingCommand): void {
216222
this.#eventReporter.logEvent({
217223
type: 'debugger_command',

Diff for: ‎packages/dev-middleware/src/types/EventReporter.js

+3
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ export type ReportableEvent =
7777
| 'PROTOCOL_ERROR',
7878
>,
7979
}
80+
| {
81+
type: 'fusebox_console_notice',
82+
}
8083
| {
8184
type: 'proxy_error',
8285
status: 'error',

Diff for: ‎packages/react-native/Libraries/Core/setUpDeveloperTools.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@ if (__DEV__) {
4242
if (!Platform.isTesting) {
4343
const HMRClient = require('../Utilities/HMRClient');
4444

45-
if (global.__FUSEBOX_HAS_FULL_CONSOLE_SUPPORT__) {
46-
HMRClient.unstable_notifyFuseboxConsoleEnabled();
47-
} else if (console._isPolyfilled) {
45+
// TODO(T214991636): Remove legacy Metro log forwarding
46+
if (console._isPolyfilled) {
4847
// We assume full control over the console and send JavaScript logs to Metro.
4948
[
5049
'trace',

Diff for: ‎packages/react-native/Libraries/Utilities/HMRClient.js

-28
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ let hmrUnavailableReason: string | null = null;
2626
let currentCompileErrorMessage: string | null = null;
2727
let didConnect: boolean = false;
2828
let pendingLogs: Array<[LogLevel, $ReadOnlyArray<mixed>]> = [];
29-
let pendingFuseboxConsoleNotification = false;
3029

3130
type LogLevel =
3231
| 'trace'
@@ -52,7 +51,6 @@ export type HMRClientNativeInterface = {|
5251
isEnabled: boolean,
5352
scheme?: string,
5453
): void,
55-
unstable_notifyFuseboxConsoleEnabled(): void,
5654
|};
5755

5856
/**
@@ -142,29 +140,6 @@ const HMRClient: HMRClientNativeInterface = {
142140
}
143141
},
144142

145-
unstable_notifyFuseboxConsoleEnabled() {
146-
if (!hmrClient) {
147-
pendingFuseboxConsoleNotification = true;
148-
return;
149-
}
150-
hmrClient.send(
151-
JSON.stringify({
152-
type: 'log',
153-
level: 'info',
154-
data: [
155-
'\n' +
156-
'\u001B[7m' +
157-
' \u001B[1m💡 JavaScript logs have moved!\u001B[22m They can now be ' +
158-
'viewed in React Native DevTools. Tip: Type \u001B[1mj\u001B[22m in ' +
159-
'the terminal to open (requires Google Chrome or Microsoft Edge).' +
160-
'\u001B[27m' +
161-
'\n',
162-
],
163-
}),
164-
);
165-
pendingFuseboxConsoleNotification = false;
166-
},
167-
168143
// Called once by the bridge on startup, even if Fast Refresh is off.
169144
// It creates the HMR client but doesn't actually set up the socket yet.
170145
setup(
@@ -341,9 +316,6 @@ function flushEarlyLogs(client: MetroHMRClient) {
341316
pendingLogs.forEach(([level, data]) => {
342317
HMRClient.log(level, data);
343318
});
344-
if (pendingFuseboxConsoleNotification) {
345-
HMRClient.unstable_notifyFuseboxConsoleEnabled();
346-
}
347319
} finally {
348320
pendingLogs.length = 0;
349321
}

Diff for: ‎packages/react-native/Libraries/Utilities/HMRClientProdShim.js

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ const HMRClientProdShim: HMRClientNativeInterface = {
2525
disable() {},
2626
registerBundle() {},
2727
log() {},
28-
unstable_notifyFuseboxConsoleEnabled() {},
2928
};
3029

3130
module.exports = HMRClientProdShim;

Diff for: ‎packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap

-1
Original file line numberDiff line numberDiff line change
@@ -8942,7 +8942,6 @@ export type HMRClientNativeInterface = {|
89428942
isEnabled: boolean,
89438943
scheme?: string
89448944
): void,
8945-
unstable_notifyFuseboxConsoleEnabled(): void,
89468945
|};
89478946
declare const HMRClient: HMRClientNativeInterface;
89488947
declare module.exports: HMRClient;

0 commit comments

Comments
 (0)
Please sign in to comment.