Skip to content

Commit eb7639b

Browse files
Build console replay script after awaiting the rendering promise (#1649)
Console replay script generation now awaits the render request promise before generating, allowing it to capture console logs from asynchronous operations. The feature of replaying async console logs is implemented in node-renderer in this PR shakacode/react_on_rails_pro#440 * add support for console replay of console logs happen in async operations get console replay messages after promise resolves or rejects use isPromise to check if the result is a promise make consoleReplay function accept the list of logs to be added to the script make consoleHistory argument of consoleReplay optional add comments add comments update comment remove FlowFixMe comment * call clear console history from the generated render code * Revert "call clear console history from the generated render code" This reverts commit 2697ef7. * add comment about clearing console history
1 parent 616fe9f commit eb7639b

File tree

2 files changed

+19
-17
lines changed

2 files changed

+19
-17
lines changed

node_package/src/buildConsoleReplay.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ declare global {
99
}
1010
}
1111

12-
export function consoleReplay(): string {
12+
export function consoleReplay(customConsoleHistory: typeof console['history'] | undefined = undefined): string {
1313
// console.history is a global polyfill used in server rendering.
14-
if (!(console.history instanceof Array)) {
14+
const consoleHistory = customConsoleHistory ?? console.history;
15+
16+
if (!(Array.isArray(consoleHistory))) {
1517
return '';
1618
}
1719

18-
const lines = console.history.map(msg => {
20+
const lines = consoleHistory.map(msg => {
1921
const stringifiedList = msg.arguments.map(arg => {
2022
let val: string;
2123
try {
@@ -42,6 +44,6 @@ export function consoleReplay(): string {
4244
return lines.join('\n');
4345
}
4446

45-
export default function buildConsoleReplay(): string {
46-
return RenderUtils.wrapInScriptTags('consoleReplayLog', consoleReplay());
47+
export default function buildConsoleReplay(customConsoleHistory: typeof console['history'] | undefined = undefined): string {
48+
return RenderUtils.wrapInScriptTags('consoleReplayLog', consoleReplay(customConsoleHistory));
4749
}

node_package/src/serverRenderReactComponent.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,22 @@ function createResultObject(html: string | null, consoleReplayScript: string, re
105105

106106
async function createPromiseResult(
107107
renderState: RenderState & { result: Promise<string> },
108-
consoleReplayScript: string,
109108
componentName: string,
110109
throwJsErrors: boolean
111110
): Promise<RenderResult> {
111+
// Capture console history before awaiting the promise
112+
// Node renderer will reset the global console.history after executing the synchronous part of the request.
113+
// It resets it only if replayServerAsyncOperationLogs renderer config is set to false.
114+
// In both cases, we need to keep a reference to console.history to avoid losing console logs in case of reset.
115+
const consoleHistory = console.history;
112116
try {
113117
const html = await renderState.result;
118+
const consoleReplayScript = buildConsoleReplay(consoleHistory);
114119
return createResultObject(html, consoleReplayScript, renderState);
115120
} catch (e: unknown) {
116121
const errorRenderState = handleRenderingError(e, { componentName, throwJsErrors });
117-
return createResultObject(errorRenderState.result, consoleReplayScript, errorRenderState);
122+
const consoleReplayScript = buildConsoleReplay(consoleHistory);
123+
return createResultObject(errorRenderState.result, consoleReplayScript, renderState);
118124
}
119125
}
120126

@@ -123,20 +129,12 @@ function createFinalResult(
123129
componentName: string,
124130
throwJsErrors: boolean
125131
): null | string | Promise<RenderResult> {
126-
// Console history is stored globally in `console.history`.
127-
// If node renderer is handling a render request that returns a promise,
128-
// It can handle another request while awaiting the promise.
129-
// To prevent cross-request console logs leakage between these requests,
130-
// we build the consoleReplayScript before awaiting any promises.
131-
// The console history is reset after the synchronous part of each request.
132-
// This causes console logs happening during async operations to not be captured.
133-
const consoleReplayScript = buildConsoleReplay();
134-
135132
const { result } = renderState;
136133
if (isPromise(result)) {
137-
return createPromiseResult({ ...renderState, result }, consoleReplayScript, componentName, throwJsErrors);
134+
return createPromiseResult({ ...renderState, result }, componentName, throwJsErrors);
138135
}
139136

137+
const consoleReplayScript = buildConsoleReplay();
140138
return JSON.stringify(createResultObject(result, consoleReplayScript, renderState));
141139
}
142140

@@ -188,6 +186,8 @@ const serverRenderReactComponent: typeof serverRenderReactComponentInternal = (o
188186
} finally {
189187
// Reset console history after each render.
190188
// See `RubyEmbeddedJavaScript.console_polyfill` for initialization.
189+
// This is necessary when ExecJS and old versions of node renderer are used.
190+
// New versions of node renderer reset the console history automatically.
191191
console.history = [];
192192
}
193193
};

0 commit comments

Comments
 (0)