diff --git a/packages/react-client/src/ReactClientConsoleConfigBrowser.js b/packages/react-client/src/ReactClientConsoleConfigBrowser.js index 355edc9c08197..bc39763275240 100644 --- a/packages/react-client/src/ReactClientConsoleConfigBrowser.js +++ b/packages/react-client/src/ReactClientConsoleConfigBrowser.js @@ -7,6 +7,7 @@ * @flow */ +// Keep in sync with ReactServerConsoleConfig const badgeFormat = '%c%s%c '; // Same badge styling as DevTools. const badgeStyle = diff --git a/packages/react-client/src/ReactClientConsoleConfigPlain.js b/packages/react-client/src/ReactClientConsoleConfigPlain.js index 6b41ad4effe98..5fe553744a9fd 100644 --- a/packages/react-client/src/ReactClientConsoleConfigPlain.js +++ b/packages/react-client/src/ReactClientConsoleConfigPlain.js @@ -7,6 +7,7 @@ * @flow */ +// Keep in sync with ReactServerConsoleConfig const badgeFormat = '[%s] '; const pad = ' '; diff --git a/packages/react-client/src/ReactClientConsoleConfigServer.js b/packages/react-client/src/ReactClientConsoleConfigServer.js index efbcd2865d712..1978a4bc8b8de 100644 --- a/packages/react-client/src/ReactClientConsoleConfigServer.js +++ b/packages/react-client/src/ReactClientConsoleConfigServer.js @@ -7,6 +7,7 @@ * @flow */ +// Keep in sync with ReactServerConsoleConfig // This flips color using ANSI, then sets a color styling, then resets. const badgeFormat = '\x1b[0m\x1b[7m%c%s\x1b[0m%c '; // Same badge styling as DevTools. diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index f8926f5d69fd9..35c25b1890189 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -96,6 +96,7 @@ import { parseStackTrace, supportsComponentStorage, componentStorage, + unbadgeConsole, } from './ReactFlightServerConfig'; import { @@ -275,7 +276,15 @@ function patchConsole(consoleInst: typeof console, methodName: string) { ); request.pendingChunks++; const owner: null | ReactComponentInfo = resolveOwner(); - emitConsoleChunk(request, methodName, owner, stack, arguments); + const args = Array.from(arguments); + // Extract the env if this is a console log that was replayed from another env. + let env = unbadgeConsole(methodName, args); + if (env === null) { + // Otherwise add the current environment. + env = (0, request.environmentName)(); + } + + emitConsoleChunk(request, methodName, owner, env, stack, args); } // $FlowFixMe[prop-missing] return originalMethod.apply(this, arguments); @@ -4711,6 +4720,7 @@ function emitConsoleChunk( request: Request, methodName: string, owner: null | ReactComponentInfo, + env: string, stackTrace: ReactStackTrace, args: Array, ): void { @@ -4727,8 +4737,6 @@ function emitConsoleChunk( outlineComponentInfo(request, owner); } - // TODO: Don't double badge if this log came from another Flight Client. - const env = (0, request.environmentName)(); const payload = [methodName, stackTrace, owner, env]; // $FlowFixMe[method-unbinding] payload.push.apply(payload, args); diff --git a/packages/react-server/src/ReactServerConsoleConfigBrowser.js b/packages/react-server/src/ReactServerConsoleConfigBrowser.js new file mode 100644 index 0000000000000..d8ff2abb93c54 --- /dev/null +++ b/packages/react-server/src/ReactServerConsoleConfigBrowser.js @@ -0,0 +1,61 @@ +/** + * 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 + */ + +// Keep in sync with ReactClientConsoleConfig +const badgeFormat = '%c%s%c '; +// Same badge styling as DevTools. +const badgeStyle = + // We use a fixed background if light-dark is not supported, otherwise + // we use a transparent background. + 'background: #e6e6e6;' + + 'background: light-dark(rgba(0,0,0,0.1), rgba(255,255,255,0.25));' + + 'color: #000000;' + + 'color: light-dark(#000000, #ffffff);' + + 'border-radius: 2px'; + +const padLength = 1; + +// This mutates the args to remove any badges that was added by a FlightClient and +// returns the name in the badge. This is used when a FlightClient replays inside +// a FlightServer and we capture those replays. +export function unbadgeConsole( + methodName: string, + args: Array, +): null | string { + let offset = 0; + switch (methodName) { + case 'dir': + case 'dirxml': + case 'groupEnd': + case 'table': { + // These methods cannot be colorized because they don't take a formatting string. + // So we wouldn't have added any badge in the first place. + // $FlowFixMe + return null; + } + case 'assert': { + // assert takes formatting options as the second argument. + offset = 1; + } + } + const format = args[offset]; + const style = args[offset + 1]; + const badge = args[offset + 2]; + if ( + typeof format === 'string' && + format.startsWith(badgeFormat) && + style === badgeStyle && + typeof badge === 'string' + ) { + // Remove our badging from the arguments. + args.splice(offset, 4, format.slice(badgeFormat.length)); + return badge.slice(padLength, badge.length - padLength); + } + return null; +} diff --git a/packages/react-server/src/ReactServerConsoleConfigPlain.js b/packages/react-server/src/ReactServerConsoleConfigPlain.js new file mode 100644 index 0000000000000..602013cbe9761 --- /dev/null +++ b/packages/react-server/src/ReactServerConsoleConfigPlain.js @@ -0,0 +1,52 @@ +/** + * 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 + */ + +// Keep in sync with ReactClientConsoleConfig +const badgeFormat = '[%s] '; +const padLength = 1; +const pad = ' '; + +// This mutates the args to remove any badges that was added by a FlightClient and +// returns the name in the badge. This is used when a FlightClient replays inside +// a FlightServer and we capture those replays. +export function unbadgeConsole( + methodName: string, + args: Array, +): null | string { + let offset = 0; + switch (methodName) { + case 'dir': + case 'dirxml': + case 'groupEnd': + case 'table': { + // These methods cannot be colorized because they don't take a formatting string. + // So we wouldn't have added any badge in the first place. + // $FlowFixMe + return null; + } + case 'assert': { + // assert takes formatting options as the second argument. + offset = 1; + } + } + const format = args[offset]; + const badge = args[offset + 1]; + if ( + typeof format === 'string' && + format.startsWith(badgeFormat) && + typeof badge === 'string' && + badge.startsWith(pad) && + badge.endsWith(pad) + ) { + // Remove our badging from the arguments. + args.splice(offset, 2, format.slice(badgeFormat.length)); + return badge.slice(padLength, badge.length - padLength); + } + return null; +} diff --git a/packages/react-server/src/ReactServerConsoleConfigServer.js b/packages/react-server/src/ReactServerConsoleConfigServer.js new file mode 100644 index 0000000000000..fbeadff911b30 --- /dev/null +++ b/packages/react-server/src/ReactServerConsoleConfigServer.js @@ -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 + */ + +// Keep in sync with ReactClientConsoleConfig +const badgeFormat = '\x1b[0m\x1b[7m%c%s\x1b[0m%c '; +// Same badge styling as DevTools. +const badgeStyle = + // We use a fixed background if light-dark is not supported, otherwise + // we use a transparent background. + 'background: #e6e6e6;' + + 'background: light-dark(rgba(0,0,0,0.1), rgba(255,255,255,0.25));' + + 'color: #000000;' + + 'color: light-dark(#000000, #ffffff);' + + 'border-radius: 2px'; +const padLength = 1; + +// This mutates the args to remove any badges that was added by a FlightClient and +// returns the name in the badge. This is used when a FlightClient replays inside +// a FlightServer and we capture those replays. +export function unbadgeConsole( + methodName: string, + args: Array, +): null | string { + let offset = 0; + switch (methodName) { + case 'dir': + case 'dirxml': + case 'groupEnd': + case 'table': { + // These methods cannot be colorized because they don't take a formatting string. + // So we wouldn't have added any badge in the first place. + // $FlowFixMe + return null; + } + case 'assert': { + // assert takes formatting options as the second argument. + offset = 1; + } + } + const format = args[offset]; + const style = args[offset + 1]; + const badge = args[offset + 2]; + if ( + typeof format === 'string' && + format.startsWith(badgeFormat) && + style === badgeStyle && + typeof badge === 'string' + ) { + // Remove our badging from the arguments. + args.splice(offset, 4, format.slice(badgeFormat.length)); + return badge.slice(padLength, badge.length - padLength); + } + return null; +} diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.custom.js b/packages/react-server/src/forks/ReactFlightServerConfig.custom.js index 4302d6cee6400..fbb0168eee631 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.custom.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.custom.js @@ -15,6 +15,7 @@ export * from '../ReactFlightServerConfigBundlerCustom'; export * from '../ReactFlightServerConfigDebugNoop'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigPlain'; export type Hints = any; export type HintCode = any; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js index 634b5cd939a7f..f7abb24acfc71 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js @@ -23,3 +23,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNoop'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigBrowser'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js index ceadebb7d36f8..d4ab5fe86cf06 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js @@ -23,3 +23,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNoop'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigBrowser'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js index 6b4493f49b24e..a28bae93cac58 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js @@ -23,3 +23,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNoop'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigBrowser'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js index c2b55995b6f1f..a34bbf28c532b 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js @@ -23,3 +23,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNoop'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigBrowser'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js index 9ab251ed2d582..04fa6348123a1 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js @@ -23,3 +23,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNoop'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigPlain'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js index 4983c3efd21a9..0cd224a54055f 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js @@ -25,3 +25,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNoop'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigServer'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js index bd046f29599a5..67f82f59a6a20 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js @@ -25,3 +25,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNoop'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigServer'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js index 9c1f6b58e00f4..c7605c0fcbc7a 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js @@ -26,3 +26,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNoop'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigServer'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js index 4302d6cee6400..bc599ac0b4011 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js @@ -15,6 +15,7 @@ export * from '../ReactFlightServerConfigBundlerCustom'; export * from '../ReactFlightServerConfigDebugNoop'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigServer'; export type Hints = any; export type HintCode = any; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js index 3ded44f1353a2..04ece77cc4a45 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js @@ -26,3 +26,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNode'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigServer'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js index cb5d1f748b3c2..b4f0f98a2db42 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js @@ -26,3 +26,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNode'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigServer'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js index 85a7087721495..03975f6bf5fe5 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js @@ -26,3 +26,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNode'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigServer'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js index 626d134cebc51..f60f0f0242a27 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js @@ -26,3 +26,4 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNode'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigServer'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.markup.js b/packages/react-server/src/forks/ReactFlightServerConfig.markup.js index 7c879de39f271..59a1bac1eb491 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.markup.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.markup.js @@ -29,6 +29,7 @@ export const componentStorage: AsyncLocalStorage = export * from '../ReactFlightServerConfigDebugNoop'; export * from '../ReactFlightStackConfigV8'; +export * from '../ReactServerConsoleConfigPlain'; export type ClientManifest = null; export opaque type ClientReference = null; // eslint-disable-line no-unused-vars