mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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
This commit is contained in:
@@ -15,7 +15,7 @@ Start the React Native development server.
|
||||
#### Usage
|
||||
|
||||
```sh
|
||||
npx react-native start [options]
|
||||
npx @react-native-community/cli start [options]
|
||||
```
|
||||
|
||||
#### Options
|
||||
@@ -37,6 +37,7 @@ npx react-native start [options]
|
||||
| `--cert <path>` | Specify path to a custom SSL cert. |
|
||||
| `--config <string>` | Path to the CLI configuration file. |
|
||||
| `--no-interactive` | Disable interactive mode. |
|
||||
| `--client-logs` | **[Deprecated]** Enable plain text JavaScript log streaming for all connected apps. |
|
||||
|
||||
### `bundle`
|
||||
|
||||
@@ -45,7 +46,7 @@ Build the bundle for the provided JavaScript entry file.
|
||||
#### Usage
|
||||
|
||||
```sh
|
||||
npx react-native bundle --entry-file <path> [options]
|
||||
npx @react-native-community/cli bundle --entry-file <path> [options]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
@@ -95,6 +95,14 @@ const startCommand: Command = {
|
||||
name: '--no-interactive',
|
||||
description: 'Disables interactive mode',
|
||||
},
|
||||
{
|
||||
name: '--client-logs',
|
||||
description:
|
||||
'[Deprecated] Enable plain text JavaScript log streaming for all ' +
|
||||
'connected apps. This feature is deprecated and will be removed in ' +
|
||||
'future.',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ export type StartCommandArgs = {
|
||||
config?: string,
|
||||
projectRoot?: string,
|
||||
interactive: boolean,
|
||||
clientLogs: boolean,
|
||||
};
|
||||
|
||||
async function runServer(
|
||||
@@ -96,6 +97,11 @@ async function runServer(
|
||||
require.resolve(plugin),
|
||||
);
|
||||
}
|
||||
// TODO(T214991636): Remove legacy Metro log forwarding
|
||||
if (!args.clientLogs) {
|
||||
// $FlowIgnore[cannot-write] Assigning to readonly property
|
||||
metroConfig.server.forwardClientLogs = false;
|
||||
}
|
||||
|
||||
let reportEvent: (event: TerminalReportableEvent) => void;
|
||||
const terminal = new Terminal(process.stdout);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import type {CreateCustomMessageHandlerFn} from './inspector-proxy/CustomMessageHandler';
|
||||
import type {BrowserLauncher} from './types/BrowserLauncher';
|
||||
import type {EventReporter} from './types/EventReporter';
|
||||
import type {EventReporter, ReportableEvent} from './types/EventReporter';
|
||||
import type {Experiments, ExperimentsConfig} from './types/Experiments';
|
||||
import type {Logger} from './types/Logger';
|
||||
import type {NextHandleFunction} from 'connect';
|
||||
@@ -81,11 +81,15 @@ export default function createDevMiddleware({
|
||||
unstable_customInspectorMessageHandler,
|
||||
}: Options): DevMiddlewareAPI {
|
||||
const experiments = getExperiments(experimentConfig);
|
||||
const eventReporter = createWrappedEventReporter(
|
||||
unstable_eventReporter,
|
||||
logger,
|
||||
);
|
||||
|
||||
const inspectorProxy = new InspectorProxy(
|
||||
projectRoot,
|
||||
serverBaseUrl,
|
||||
unstable_eventReporter,
|
||||
eventReporter,
|
||||
experiments,
|
||||
unstable_customInspectorMessageHandler,
|
||||
);
|
||||
@@ -97,7 +101,7 @@ export default function createDevMiddleware({
|
||||
serverBaseUrl,
|
||||
inspectorProxy,
|
||||
browserLauncher: unstable_browserLauncher,
|
||||
eventReporter: unstable_eventReporter,
|
||||
eventReporter,
|
||||
experiments,
|
||||
logger,
|
||||
}),
|
||||
@@ -129,3 +133,32 @@ function getExperiments(config: ExperimentsConfig): Experiments {
|
||||
enableNetworkInspector: config.enableNetworkInspector ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wrapped EventReporter that locally intercepts events to
|
||||
* log to the terminal.
|
||||
*/
|
||||
function createWrappedEventReporter(
|
||||
reporter: ?EventReporter,
|
||||
logger: ?Logger,
|
||||
): EventReporter {
|
||||
return {
|
||||
logEvent(event: ReportableEvent) {
|
||||
switch (event.type) {
|
||||
case 'fusebox_console_notice':
|
||||
logger?.info(
|
||||
'\n' +
|
||||
'\u001B[7m' +
|
||||
' \u001B[1m💡 JavaScript logs have moved!\u001B[22m They can now be ' +
|
||||
'viewed in React Native DevTools. Tip: Type \u001B[1mj\u001B[22m in ' +
|
||||
'the terminal to open (requires Google Chrome or Microsoft Edge).' +
|
||||
'\u001B[27m' +
|
||||
'\n',
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
reporter?.logEvent(event);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ const PAGES_POLLING_INTERVAL = 1000;
|
||||
// more details.
|
||||
const FILE_PREFIX = 'file://';
|
||||
|
||||
let fuseboxConsoleNoticeLogged = false;
|
||||
|
||||
type DebuggerConnection = {
|
||||
// Debugger web socket connection
|
||||
socket: WS,
|
||||
@@ -513,6 +515,7 @@ export default class Device {
|
||||
// created instead of manually checking this on every getPages result.
|
||||
for (const page of this.#pages.values()) {
|
||||
if (this.#pageHasCapability(page, 'nativePageReloads')) {
|
||||
this.#logFuseboxConsoleNotice();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1067,4 +1070,14 @@ export default class Device {
|
||||
dangerouslyGetSocket(): WS {
|
||||
return this.#deviceSocket;
|
||||
}
|
||||
|
||||
// TODO(T214991636): Remove notice
|
||||
#logFuseboxConsoleNotice() {
|
||||
if (fuseboxConsoleNoticeLogged) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#deviceEventReporter?.logFuseboxConsoleNotice();
|
||||
fuseboxConsoleNoticeLogged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +212,12 @@ class DeviceEventReporter {
|
||||
});
|
||||
}
|
||||
|
||||
logFuseboxConsoleNotice(): void {
|
||||
this.#eventReporter.logEvent({
|
||||
type: 'fusebox_console_notice',
|
||||
});
|
||||
}
|
||||
|
||||
#logExpiredCommand(pendingCommand: PendingCommand): void {
|
||||
this.#eventReporter.logEvent({
|
||||
type: 'debugger_command',
|
||||
|
||||
@@ -77,6 +77,9 @@ export type ReportableEvent =
|
||||
| 'PROTOCOL_ERROR',
|
||||
>,
|
||||
}
|
||||
| {
|
||||
type: 'fusebox_console_notice',
|
||||
}
|
||||
| {
|
||||
type: 'proxy_error',
|
||||
status: 'error',
|
||||
|
||||
@@ -42,9 +42,8 @@ if (__DEV__) {
|
||||
if (!Platform.isTesting) {
|
||||
const HMRClient = require('../Utilities/HMRClient');
|
||||
|
||||
if (global.__FUSEBOX_HAS_FULL_CONSOLE_SUPPORT__) {
|
||||
HMRClient.unstable_notifyFuseboxConsoleEnabled();
|
||||
} else if (console._isPolyfilled) {
|
||||
// TODO(T214991636): Remove legacy Metro log forwarding
|
||||
if (console._isPolyfilled) {
|
||||
// We assume full control over the console and send JavaScript logs to Metro.
|
||||
[
|
||||
'trace',
|
||||
|
||||
@@ -26,7 +26,6 @@ let hmrUnavailableReason: string | null = null;
|
||||
let currentCompileErrorMessage: string | null = null;
|
||||
let didConnect: boolean = false;
|
||||
let pendingLogs: Array<[LogLevel, $ReadOnlyArray<mixed>]> = [];
|
||||
let pendingFuseboxConsoleNotification = false;
|
||||
|
||||
type LogLevel =
|
||||
| 'trace'
|
||||
@@ -52,7 +51,6 @@ export type HMRClientNativeInterface = {|
|
||||
isEnabled: boolean,
|
||||
scheme?: string,
|
||||
): void,
|
||||
unstable_notifyFuseboxConsoleEnabled(): void,
|
||||
|};
|
||||
|
||||
/**
|
||||
@@ -142,29 +140,6 @@ const HMRClient: HMRClientNativeInterface = {
|
||||
}
|
||||
},
|
||||
|
||||
unstable_notifyFuseboxConsoleEnabled() {
|
||||
if (!hmrClient) {
|
||||
pendingFuseboxConsoleNotification = true;
|
||||
return;
|
||||
}
|
||||
hmrClient.send(
|
||||
JSON.stringify({
|
||||
type: 'log',
|
||||
level: 'info',
|
||||
data: [
|
||||
'\n' +
|
||||
'\u001B[7m' +
|
||||
' \u001B[1m💡 JavaScript logs have moved!\u001B[22m They can now be ' +
|
||||
'viewed in React Native DevTools. Tip: Type \u001B[1mj\u001B[22m in ' +
|
||||
'the terminal to open (requires Google Chrome or Microsoft Edge).' +
|
||||
'\u001B[27m' +
|
||||
'\n',
|
||||
],
|
||||
}),
|
||||
);
|
||||
pendingFuseboxConsoleNotification = false;
|
||||
},
|
||||
|
||||
// Called once by the bridge on startup, even if Fast Refresh is off.
|
||||
// It creates the HMR client but doesn't actually set up the socket yet.
|
||||
setup(
|
||||
@@ -341,9 +316,6 @@ function flushEarlyLogs(client: MetroHMRClient) {
|
||||
pendingLogs.forEach(([level, data]) => {
|
||||
HMRClient.log(level, data);
|
||||
});
|
||||
if (pendingFuseboxConsoleNotification) {
|
||||
HMRClient.unstable_notifyFuseboxConsoleEnabled();
|
||||
}
|
||||
} finally {
|
||||
pendingLogs.length = 0;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ const HMRClientProdShim: HMRClientNativeInterface = {
|
||||
disable() {},
|
||||
registerBundle() {},
|
||||
log() {},
|
||||
unstable_notifyFuseboxConsoleEnabled() {},
|
||||
};
|
||||
|
||||
module.exports = HMRClientProdShim;
|
||||
|
||||
@@ -8942,7 +8942,6 @@ export type HMRClientNativeInterface = {|
|
||||
isEnabled: boolean,
|
||||
scheme?: string
|
||||
): void,
|
||||
unstable_notifyFuseboxConsoleEnabled(): void,
|
||||
|};
|
||||
declare const HMRClient: HMRClientNativeInterface;
|
||||
declare module.exports: HMRClient;
|
||||
|
||||
Reference in New Issue
Block a user