react/packages/shared/normalizeConsoleFormat.js
Sebastian Markbåge c0274063f0
[Flight] Prefix Replayed Console Logs with a Badge (#28403)
Builds on top of #28384.

This prefixes each log with a badge similar to how we badge built-ins
like "ForwardRef" and "Memo" in the React DevTools. The idea is that we
can add such badges in DevTools for Server Components too to carry on
the consistency.

This puts the "environment" name in the badge which defaults to
"Server". So you know which source it is coming from.

We try to use the same styling as the React DevTools. We use light-dark
mode where available to support the two different color styles, but if
it's not available I use a fixed background so that it's always readable
even in dark mode.

In Terminals, instead of hard coding colors that might not look good
with some themes, I use the ANSI color code to flip
background/foreground colors in that case.

In earlier commits I had it on the end of the line similar to the
DevTools badges but for multiline I found it better to prefix it. We
could try various options tough.

In most cases we can use both ANSI and the `%c` CSS color specifier,
because node will only use ANSI and hide the other. Chrome supports both
but the color overrides ANSI if it comes later (and Chrome doesn't
support color inverting anyway). Safari/Firefox prints the ANSI, so it
can only use CSS colors.

Therefore in browser builds I exclude ANSI.

On the server I support both so if you use Chrome inspector on the
server, you get nice colors on both terminal and in the inspector.

Since Bun uses WebKit inspector and it prints the ANSI we can't safely
emit both there. However, we also can't emit just the color specifier
because then it prints in the terminal.
https://github.com/oven-sh/bun/issues/9021 So we just use a plain string
prefix for now with a bracket until that's fixed.

Screen shots:

<img width="758" alt="Screenshot 2024-02-21 at 12 56 02 AM"
src="https://github.com/facebook/react/assets/63648/4f887ffe-fffe-4402-bf2a-b7890986d60c">
<img width="759" alt="Screenshot 2024-02-21 at 12 56 24 AM"
src="https://github.com/facebook/react/assets/63648/f32d432f-f738-4872-a700-ea0a78e6c745">
<img width="514" alt="Screenshot 2024-02-21 at 12 57 10 AM"
src="https://github.com/facebook/react/assets/63648/205d2e82-75b7-4e2b-9d9c-aa9e2cbedf39">
<img width="489" alt="Screenshot 2024-02-21 at 12 57 34 AM"
src="https://github.com/facebook/react/assets/63648/ea52d1e4-b9fa-431d-ae9e-ccb87631f399">
<img width="516" alt="Screenshot 2024-02-21 at 12 58 23 AM"
src="https://github.com/facebook/react/assets/63648/52b50fac-bec0-471d-a457-1a10d8df9172">
<img width="956" alt="Screenshot 2024-02-21 at 12 58 56 AM"
src="https://github.com/facebook/react/assets/63648/0096ed61-5eff-4aa9-8a8a-2204e754bd1f">
2024-02-21 14:59:08 -05:00

57 lines
1.7 KiB
JavaScript

/**
* 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
*/
// Takes a format string (first argument to console) and returns a normalized
// string that has the exact number of arguments as the args. That way it's safe
// to prepend or append to it.
export default function normalizeConsoleFormat(
formatString: string,
args: $ReadOnlyArray<mixed>,
firstArg: number,
): string {
let j = firstArg;
let normalizedString = '';
let last = 0;
for (let i = 0; i < formatString.length - 1; i++) {
if (formatString.charCodeAt(i) !== 37 /* "%" */) {
continue;
}
switch (formatString.charCodeAt(++i)) {
case 79 /* "O" */:
case 99 /* "c" */:
case 100 /* "d" */:
case 102 /* "f" */:
case 105 /* "i" */:
case 111 /* "o" */:
case 115 /* "s" */: {
if (j < args.length) {
// We have a matching argument.
j++;
} else {
// We have more format specifiers than arguments.
// So we need to escape this to print the literal.
normalizedString += formatString.slice(last, (last = i)) + '%';
}
}
}
}
normalizedString += formatString.slice(last, formatString.length);
// Pad with extra format specifiers for the rest.
while (j < args.length) {
if (normalizedString !== '') {
normalizedString += ' ';
}
// Not every environment has the same default.
// This seems to be what Chrome DevTools defaults to.
normalizedString += typeof args[j] === 'string' ? '%s' : '%o';
j++;
}
return normalizedString;
}