Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions e2e/cases/browser-logs/dedupe-log/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
import path, { join } from 'node:path';
import { gotoPage, rspackTest } from '@e2e/helper';
import fse from 'fs-extra';

rspackTest(
'should not output the same error log consecutively',
async ({ devOnly, page }) => {
const rsbuild = await devOnly();
'should not output the same browser log',
async ({ devOnly, page, editFile }) => {
const tempDir = path.join(__dirname, 'test-temp-src');
const srcDir = path.join(__dirname, 'src');
await fse.copy(srcDir, tempDir);

const rsbuild = await devOnly({
config: {
source: {
entry: {
index: join(tempDir, 'index.js'),
},
},
},
});

// initial build
await gotoPage(page, rsbuild, '/', { hash: 'test1' });
await rsbuild.expectLog('Uncaught Error: #test1');
await rsbuild.expectLog('Error: value is #test1');
rsbuild.clearLogs();

await page.reload();
// change hash
await gotoPage(page, rsbuild, '/', { hash: 'test2' });
await page.reload();
await rsbuild.expectLog('Uncaught Error: #test2');
rsbuild.expectNoLog('Uncaught Error: #test1');
await rsbuild.expectLog('Error: value is #test2');
rsbuild.expectNoLog('Error: value is #test1');
rsbuild.clearLogs();

// after rebuild, logs can be printed again
await editFile(join(tempDir, 'index.js'), (content) =>
content.replace('value', 'value2'),
);
await rsbuild.expectLog('Error: value2 is #test2');
await gotoPage(page, rsbuild, '/', { hash: 'test1' });
await page.reload();
await rsbuild.expectLog('Error: value2 is #test1');
},
);
2 changes: 1 addition & 1 deletion e2e/cases/browser-logs/dedupe-log/src/index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
throw new Error(location.hash);
throw new Error(`value is ${location.hash}`);
2 changes: 1 addition & 1 deletion packages/core/src/server/assets-middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const setupServerHooks = ({
});

compiler.hooks.done.tap('rsbuild-dev-server', (stats) => {
socketServer.updateStats(stats, token);
socketServer.onBuildDone(stats, token);
});
};

Expand Down
16 changes: 4 additions & 12 deletions packages/core/src/server/browserLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,15 @@ const formatErrorLocation = async (
return rawLocation;
};

let lastRuntimeErrorLog: string;

/**
* Processes runtime errors received from the browser and logs them with
* Formats error messages received from the browser into a log string with
* source location information.
*/
export const reportRuntimeError = async (
export const formatBrowserErrorLog = async (
message: ClientMessageError,
context: InternalContext,
fs: Rspack.OutputFileSystem,
): Promise<void> => {
): Promise<string> => {
let log = `${color.cyan('[browser]')} ${color.red(message.message)}`;

if (message.stack) {
Expand All @@ -132,11 +130,5 @@ export const reportRuntimeError = async (
}
}

// Avoid outputting the same log consecutively
if (log === lastRuntimeErrorLog) {
return;
}

logger.error(log);
lastRuntimeErrorLog = log;
return log;
};
21 changes: 17 additions & 4 deletions packages/core/src/server/socketServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { formatStatsMessages } from '../helpers/format';
import { logger } from '../logger';
import type { DevConfig, InternalContext, Rspack } from '../types';
import { reportRuntimeError } from './browserLogs';
import { formatBrowserErrorLog } from './browserLogs';
import { genOverlayHTML } from './overlay';

interface ExtWebSocket extends Ws {
Expand Down Expand Up @@ -86,6 +86,8 @@ export class SocketServer {

private getOutputFileSystem: () => Rspack.OutputFileSystem;

private reportedBrowserLogs: Set<string> = new Set();

constructor(
context: InternalContext,
options: DevConfig,
Expand Down Expand Up @@ -184,8 +186,9 @@ export class SocketServer {
});
}

public updateStats(stats: Rspack.Stats, token: string): void {
public onBuildDone(stats: Rspack.Stats, token: string): void {
this.stats[token] = stats;
this.reportedBrowserLogs.clear();

if (!this.socketsMap.size) {
return;
Expand Down Expand Up @@ -244,6 +247,7 @@ export class SocketServer {
this.stats = {};
this.initialChunks = {};
this.socketsMap.clear();
this.reportedBrowserLogs.clear();

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

socket.on('message', (data) => {
socket.on('message', async (data) => {
try {
const message: ClientMessage = JSON.parse(
// eslint-disable-next-line @typescript-eslint/no-base-to-string
Expand All @@ -278,7 +282,16 @@ export class SocketServer {
// Do not report browser error when build failed
!this.context.buildState.hasErrors
) {
reportRuntimeError(message, this.context, this.getOutputFileSystem());
const log = await formatBrowserErrorLog(
message,
this.context,
this.getOutputFileSystem(),
);

if (!this.reportedBrowserLogs.has(log)) {
this.reportedBrowserLogs.add(log);
logger.error(log);
}
}
} catch {}
});
Expand Down
Loading