Skip to content

Commit 24cf767

Browse files
authored
feat: implement deduplication for browser error logs (#6283)
1 parent 3133571 commit 24cf767

File tree

5 files changed

+56
-25
lines changed

5 files changed

+56
-25
lines changed
Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,43 @@
1+
import path, { join } from 'node:path';
12
import { gotoPage, rspackTest } from '@e2e/helper';
3+
import fse from 'fs-extra';
24

35
rspackTest(
4-
'should not output the same error log consecutively',
5-
async ({ devOnly, page }) => {
6-
const rsbuild = await devOnly();
6+
'should not output the same browser log',
7+
async ({ devOnly, page, editFile }) => {
8+
const tempDir = path.join(__dirname, 'test-temp-src');
9+
const srcDir = path.join(__dirname, 'src');
10+
await fse.copy(srcDir, tempDir);
11+
12+
const rsbuild = await devOnly({
13+
config: {
14+
source: {
15+
entry: {
16+
index: join(tempDir, 'index.js'),
17+
},
18+
},
19+
},
20+
});
21+
22+
// initial build
723
await gotoPage(page, rsbuild, '/', { hash: 'test1' });
8-
await rsbuild.expectLog('Uncaught Error: #test1');
24+
await rsbuild.expectLog('Error: value is #test1');
925
rsbuild.clearLogs();
1026

11-
await page.reload();
27+
// change hash
1228
await gotoPage(page, rsbuild, '/', { hash: 'test2' });
1329
await page.reload();
14-
await rsbuild.expectLog('Uncaught Error: #test2');
15-
rsbuild.expectNoLog('Uncaught Error: #test1');
30+
await rsbuild.expectLog('Error: value is #test2');
31+
rsbuild.expectNoLog('Error: value is #test1');
32+
rsbuild.clearLogs();
33+
34+
// after rebuild, logs can be printed again
35+
await editFile(join(tempDir, 'index.js'), (content) =>
36+
content.replace('value', 'value2'),
37+
);
38+
await rsbuild.expectLog('Error: value2 is #test2');
39+
await gotoPage(page, rsbuild, '/', { hash: 'test1' });
40+
await page.reload();
41+
await rsbuild.expectLog('Error: value2 is #test1');
1642
},
1743
);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
throw new Error(location.hash);
1+
throw new Error(`value is ${location.hash}`);

packages/core/src/server/assets-middleware/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export const setupServerHooks = ({
118118
});
119119

120120
compiler.hooks.done.tap('rsbuild-dev-server', (stats) => {
121-
socketServer.updateStats(stats, token);
121+
socketServer.onBuildDone(stats, token);
122122
});
123123
};
124124

packages/core/src/server/browserLogs.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,15 @@ const formatErrorLocation = async (
112112
return rawLocation;
113113
};
114114

115-
let lastRuntimeErrorLog: string;
116-
117115
/**
118-
* Processes runtime errors received from the browser and logs them with
116+
* Formats error messages received from the browser into a log string with
119117
* source location information.
120118
*/
121-
export const reportRuntimeError = async (
119+
export const formatBrowserErrorLog = async (
122120
message: ClientMessageError,
123121
context: InternalContext,
124122
fs: Rspack.OutputFileSystem,
125-
): Promise<void> => {
123+
): Promise<string> => {
126124
let log = `${color.cyan('[browser]')} ${color.red(message.message)}`;
127125

128126
if (message.stack) {
@@ -132,11 +130,5 @@ export const reportRuntimeError = async (
132130
}
133131
}
134132

135-
// Avoid outputting the same log consecutively
136-
if (log === lastRuntimeErrorLog) {
137-
return;
138-
}
139-
140-
logger.error(log);
141-
lastRuntimeErrorLog = log;
133+
return log;
142134
};

packages/core/src/server/socketServer.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import { formatStatsMessages } from '../helpers/format';
1010
import { logger } from '../logger';
1111
import type { DevConfig, InternalContext, Rspack } from '../types';
12-
import { reportRuntimeError } from './browserLogs';
12+
import { formatBrowserErrorLog } from './browserLogs';
1313
import { genOverlayHTML } from './overlay';
1414

1515
interface ExtWebSocket extends Ws {
@@ -86,6 +86,8 @@ export class SocketServer {
8686

8787
private getOutputFileSystem: () => Rspack.OutputFileSystem;
8888

89+
private reportedBrowserLogs: Set<string> = new Set();
90+
8991
constructor(
9092
context: InternalContext,
9193
options: DevConfig,
@@ -184,8 +186,9 @@ export class SocketServer {
184186
});
185187
}
186188

187-
public updateStats(stats: Rspack.Stats, token: string): void {
189+
public onBuildDone(stats: Rspack.Stats, token: string): void {
188190
this.stats[token] = stats;
191+
this.reportedBrowserLogs.clear();
189192

190193
if (!this.socketsMap.size) {
191194
return;
@@ -244,6 +247,7 @@ export class SocketServer {
244247
this.stats = {};
245248
this.initialChunks = {};
246249
this.socketsMap.clear();
250+
this.reportedBrowserLogs.clear();
247251

248252
return new Promise<void>((resolve, reject) => {
249253
this.wsServer.close((err) => {
@@ -264,7 +268,7 @@ export class SocketServer {
264268
socket.isAlive = true;
265269
});
266270

267-
socket.on('message', (data) => {
271+
socket.on('message', async (data) => {
268272
try {
269273
const message: ClientMessage = JSON.parse(
270274
// eslint-disable-next-line @typescript-eslint/no-base-to-string
@@ -278,7 +282,16 @@ export class SocketServer {
278282
// Do not report browser error when build failed
279283
!this.context.buildState.hasErrors
280284
) {
281-
reportRuntimeError(message, this.context, this.getOutputFileSystem());
285+
const log = await formatBrowserErrorLog(
286+
message,
287+
this.context,
288+
this.getOutputFileSystem(),
289+
);
290+
291+
if (!this.reportedBrowserLogs.has(log)) {
292+
this.reportedBrowserLogs.add(log);
293+
logger.error(log);
294+
}
282295
}
283296
} catch {}
284297
});

0 commit comments

Comments
 (0)