diff --git a/e2e/cases/browser-logs/stack-trace-none/index.test.ts b/e2e/cases/browser-logs/stack-trace-none/index.test.ts new file mode 100644 index 0000000000..a9004c260d --- /dev/null +++ b/e2e/cases/browser-logs/stack-trace-none/index.test.ts @@ -0,0 +1,11 @@ +import { rspackTest } from '@e2e/helper'; + +const EXPECTED_LOG = 'error [browser] Uncaught Error: test'; + +rspackTest( + 'should hide stack trace when stackTrace is none', + async ({ dev }) => { + const rsbuild = await dev(); + await rsbuild.expectLog(EXPECTED_LOG, { posix: true, strict: true }); + }, +); diff --git a/e2e/cases/browser-logs/stack-trace-none/rsbuild.config.ts b/e2e/cases/browser-logs/stack-trace-none/rsbuild.config.ts new file mode 100644 index 0000000000..7768f6a468 --- /dev/null +++ b/e2e/cases/browser-logs/stack-trace-none/rsbuild.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from '@rsbuild/core'; + +export default defineConfig({ + dev: { + browserLogs: { + stackTrace: 'none', + }, + }, +}); diff --git a/e2e/cases/browser-logs/stack-trace-none/src/index.js b/e2e/cases/browser-logs/stack-trace-none/src/index.js new file mode 100644 index 0000000000..bc9eaa0135 --- /dev/null +++ b/e2e/cases/browser-logs/stack-trace-none/src/index.js @@ -0,0 +1 @@ +throw new Error('test'); diff --git a/packages/core/src/defaultConfig.ts b/packages/core/src/defaultConfig.ts index 41d0c93ca9..e7f4134ba1 100644 --- a/packages/core/src/defaultConfig.ts +++ b/packages/core/src/defaultConfig.ts @@ -45,7 +45,9 @@ const require = createRequire(import.meta.url); const getDefaultDevConfig = (): NormalizedDevConfig => ({ hmr: true, liveReload: true, - browserLogs: true, + browserLogs: { + stackTrace: 'summary', + }, watchFiles: [], // Temporary placeholder, default: `${server.base}` assetPrefix: DEFAULT_ASSET_PREFIX, diff --git a/packages/core/src/server/browserLogs.ts b/packages/core/src/server/browserLogs.ts index 1278061cb0..8a0db54388 100644 --- a/packages/core/src/server/browserLogs.ts +++ b/packages/core/src/server/browserLogs.ts @@ -5,7 +5,7 @@ import { SCRIPT_REGEX } from '../constants'; import { color } from '../helpers'; import { requireCompiledPackage } from '../helpers/vendors'; import { logger } from '../logger'; -import type { InternalContext, Rspack } from '../types'; +import type { BrowserLogsStackTrace, InternalContext, Rspack } from '../types'; import { getFileFromUrl } from './assets-middleware/getFileFromUrl'; import type { OutputFileSystem } from './assets-middleware/index'; import type { ClientMessageError } from './socketServer'; @@ -131,10 +131,11 @@ export const formatBrowserErrorLog = async ( message: ClientMessageError, context: InternalContext, fs: Rspack.OutputFileSystem, + stackTrace: BrowserLogsStackTrace, ): Promise => { let log = `${color.cyan('[browser]')} ${color.red(message.message)}`; - if (message.stack) { + if (message.stack && stackTrace !== 'none') { const rawLocation = await formatErrorLocation(message.stack, context, fs); if (rawLocation) { log += color.dim(` (${rawLocation})`); diff --git a/packages/core/src/server/socketServer.ts b/packages/core/src/server/socketServer.ts index 7367192bc6..ecd24d0905 100644 --- a/packages/core/src/server/socketServer.ts +++ b/packages/core/src/server/socketServer.ts @@ -2,6 +2,7 @@ import type { IncomingMessage } from 'node:http'; import type { Socket } from 'node:net'; import type Ws from '../../compiled/ws/index.js'; import { formatStatsError } from '../helpers/format'; +import { isObject } from '../helpers/index'; import { getStatsErrors, getStatsWarnings } from '../helpers/stats'; import { requireCompiledPackage } from '../helpers/vendors'; import { logger } from '../logger'; @@ -290,17 +291,22 @@ export class SocketServer { typeof data === 'string' ? data : data.toString(), ); + const { browserLogs } = this.context.normalizedConfig?.dev || {}; if ( message.type === 'client-error' && // Do not report browser error when using webpack this.context.bundlerType === 'rspack' && // Do not report browser error when build failed - !this.context.buildState.hasErrors + !this.context.buildState.hasErrors && + browserLogs ) { + const stackTrace = + (isObject(browserLogs) && browserLogs.stackTrace) || 'summary'; const log = await formatBrowserErrorLog( message, this.context, this.getOutputFileSystem(), + stackTrace, ); if (!this.reportedBrowserLogs.has(log)) { diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts index b7593bd3ab..8b35b27427 100644 --- a/packages/core/src/types/config.ts +++ b/packages/core/src/types/config.ts @@ -1712,14 +1712,27 @@ export type CliShortcut = { export type WriteToDisk = boolean | ((filename: string) => boolean); +export type BrowserLogsStackTrace = 'summary' | 'none'; + export interface DevConfig { /** * Controls whether to forward browser runtime errors to the terminal. When `true`, the dev * client listens for window `error` events in the browser and send them to the dev server, * where they are printed in the terminal (prefixed with `[browser]`). - * @default true + * @default { stackTrace: 'summary' } */ - browserLogs?: boolean; + browserLogs?: + | boolean + | { + /** + * Controls how the error stack trace is displayed in the terminal when forwarding + * browser errors. + * - `'summary'` – Show only the first frame (e.g. `(src/App.jsx:3:0)`). + * - `'none'` – Hide stack traces. + * @default 'summary' + */ + stackTrace?: BrowserLogsStackTrace; + }; /** * Whether to enable Hot Module Replacement. * @default true diff --git a/packages/core/tests/__snapshots__/environments.test.ts.snap b/packages/core/tests/__snapshots__/environments.test.ts.snap index db6bda9298..349dd306df 100644 --- a/packages/core/tests/__snapshots__/environments.test.ts.snap +++ b/packages/core/tests/__snapshots__/environments.test.ts.snap @@ -4,7 +4,9 @@ exports[`environment config > should normalize environment config correctly 1`] { "dev": { "assetPrefix": "/", - "browserLogs": true, + "browserLogs": { + "stackTrace": "summary", + }, "cliShortcuts": false, "client": { "host": "", @@ -158,7 +160,9 @@ exports[`environment config > should normalize environment config correctly 2`] { "dev": { "assetPrefix": "/foo", - "browserLogs": true, + "browserLogs": { + "stackTrace": "summary", + }, "cliShortcuts": false, "client": { "host": "", @@ -313,7 +317,9 @@ exports[`environment config > should print environment config when inspect confi "ssr": { "dev": { "assetPrefix": "/", - "browserLogs": true, + "browserLogs": { + "stackTrace": "summary", + }, "cliShortcuts": false, "client": { "host": "", @@ -467,7 +473,9 @@ exports[`environment config > should print environment config when inspect confi "web": { "dev": { "assetPrefix": "/", - "browserLogs": true, + "browserLogs": { + "stackTrace": "summary", + }, "cliShortcuts": false, "client": { "host": "", @@ -641,7 +649,9 @@ exports[`environment config > should support modify environment config by api.mo "ssr": { "dev": { "assetPrefix": "/", - "browserLogs": true, + "browserLogs": { + "stackTrace": "summary", + }, "cliShortcuts": false, "client": { "host": "", @@ -795,7 +805,9 @@ exports[`environment config > should support modify environment config by api.mo "web": { "dev": { "assetPrefix": "/", - "browserLogs": true, + "browserLogs": { + "stackTrace": "summary", + }, "cliShortcuts": false, "client": { "host": "", @@ -950,7 +962,9 @@ exports[`environment config > should support modify environment config by api.mo "web1": { "dev": { "assetPrefix": "/", - "browserLogs": true, + "browserLogs": { + "stackTrace": "summary", + }, "cliShortcuts": false, "client": { "host": "", @@ -1108,7 +1122,9 @@ exports[`environment config > should support modify single environment config by "ssr": { "dev": { "assetPrefix": "/", - "browserLogs": true, + "browserLogs": { + "stackTrace": "summary", + }, "cliShortcuts": false, "client": { "host": "", @@ -1262,7 +1278,9 @@ exports[`environment config > should support modify single environment config by "web": { "dev": { "assetPrefix": "/", - "browserLogs": true, + "browserLogs": { + "stackTrace": "summary", + }, "cliShortcuts": false, "client": { "host": "",