From 9cfef37f3167e8438081193351be4b8b7f068e21 Mon Sep 17 00:00:00 2001 From: nanianlisao <597219320@qq.com> Date: Sat, 11 May 2024 15:55:53 +0800 Subject: [PATCH 1/3] feat: support overlay display unhandled runtime errors --- packages/core/package.json | 3 +- packages/core/src/client/findSourceMap.ts | 91 ++++++++++++ packages/core/src/client/format.ts | 85 +++++++++++ packages/core/src/client/hmr.ts | 18 +-- packages/core/src/client/overlay.ts | 169 ++++++++++++++++++---- packages/core/src/types.ts | 12 ++ packages/shared/src/types/config/dev.ts | 7 +- pnpm-lock.yaml | 9 ++ 8 files changed, 355 insertions(+), 39 deletions(-) create mode 100644 packages/core/src/client/findSourceMap.ts diff --git a/packages/core/package.json b/packages/core/package.json index c02e879f69..32c873376e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,7 +58,8 @@ "@swc/helpers": "0.5.3", "core-js": "~3.36.0", "html-webpack-plugin": "npm:html-rspack-plugin@5.7.2", - "postcss": "^8.4.38" + "postcss": "^8.4.38", + "source-map": "0.5.7" }, "devDependencies": { "@types/node": "18.x", diff --git a/packages/core/src/client/findSourceMap.ts b/packages/core/src/client/findSourceMap.ts new file mode 100644 index 0000000000..e733de0918 --- /dev/null +++ b/packages/core/src/client/findSourceMap.ts @@ -0,0 +1,91 @@ +// @ts-expect-error +import { SourceMapConsumer } from 'source-map'; + +const fetchContent = (url: string) => fetch(url).then((r) => r.text()); + +const findSourceMap = async (fileSource: string, filename: string) => { + try { + // Prefer to get it via filename + '.map'. + const mapUrl = filename + '.map'; + return await fetchContent(mapUrl); + } catch (e) { + const mapUrl = fileSource.match(/\/\/# sourceMappingURL=(.*)$/)?.[1]; + if (mapUrl) return await fetchContent(mapUrl); + } +}; + +// Format line numbers to ensure alignment +const parseLineNumber = (start: number, end: number) => { + const digit = Math.max(start.toString().length, end.toString().length); + return (line: number) => line.toString().padStart(digit); +}; + +// Escapes html tags to prevent them from being parsed in pre tags +const escapeHTML = (str: string) => + str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + +// Based on the sourceMap information, beautify the source code and mark the error lines +const formatSourceCode = (sourceCode: string, pos: any) => { + // Note that the line starts at 1, not 0. + const { line: crtLine, column, name } = pos; + let lines = sourceCode.split('\n'); + + // Display up to 6 lines of source code + const lineCount = Math.min(lines.length, 6); + const result = []; + + const startLine = Math.max(1, crtLine - 2); + const endLine = Math.min(startLine + lineCount - 1, lines.length); + + const parse = parseLineNumber(startLine, endLine); + + for (let line = startLine; line <= endLine; line++) { + const prefix = `${line === crtLine ? '->' : ' '} ${parse(line)} | `; + const lineCode = escapeHTML(lines[line - 1] ?? ''); + result.push(prefix + lineCode); + + // When the sourcemap information includes specific column details, add an error hint below the error line. + if (line === crtLine && column > 0) { + const errorLine = + ' '.repeat(prefix.length + column) + + '' + + '^'.repeat(name?.length || 1) + + ''; + + result.push(errorLine); + } + } + + return result.filter(Boolean).join('\n'); +}; + +// Try to find the source based on the sourceMap information. +export const findSourceCode = async (sourceInfo: any) => { + const { filename, line, column } = sourceInfo; + const fileSource = await fetch(filename).then((r) => r.text()); + + const smContent = await findSourceMap(fileSource, filename); + + if (!smContent) return; + const rawSourceMap = JSON.parse(smContent); + + const consumer = await new SourceMapConsumer(rawSourceMap); + + // Use sourcemap to find the source code location + const pos = consumer.originalPositionFor({ + line: parseInt(line, 10), + column: parseInt(column, 10), + }); + + const url = `${pos.source}:${pos.line}:${pos.column}`; + const sourceCode = consumer.sourceContentFor(pos.source); + return { + sourceCode: formatSourceCode(sourceCode, pos), + sourceFile: url, + }; +}; diff --git a/packages/core/src/client/format.ts b/packages/core/src/client/format.ts index 4d13971e46..a08ff6eca2 100644 --- a/packages/core/src/client/format.ts +++ b/packages/core/src/client/format.ts @@ -1,4 +1,6 @@ import type { StatsCompilation, StatsError } from '@rspack/core'; +import { type OverlayError } from '../types'; +import { findSourceCode } from './findSourceMap'; function resolveFileName(stats: StatsError) { // Get the real source file path with stats.moduleIdentifier. @@ -59,3 +61,86 @@ export function formatStatsMessages( warnings: formattedWarnings, }; } + +function isRejectionEvent( + isRejection: boolean, + _event: any, +): _event is PromiseRejectionEvent { + return !!isRejection; +} + +export async function formatRuntimeErrors( + event: PromiseRejectionEvent, + isRejection: true, +): Promise; +export async function formatRuntimeErrors( + event: ErrorEvent, + isRejection: false, +): Promise; + +export async function formatRuntimeErrors( + event: PromiseRejectionEvent | ErrorEvent, + isRejection: boolean, +): Promise { + let error = isRejectionEvent(isRejection, event) + ? event.reason + : event?.error; + + if (!error) return; + const errorName = isRejection + ? 'Unhandled Rejection (' + error.name + ')' + : error.name; + + const stack = parseRuntimeStack(error.stack); + const content = await createRuntimeContent(error.stack); + return { + title: `${errorName}: ${error.message}`, + content: content?.sourceCode || error.stack, + type: 'runtime', + stack: stack, + sourceFile: content?.sourceFile, + }; +} + +export function formatBuildErrors(errors: StatsError[]): OverlayError { + const content = formatMessage(errors[0]); + + return { + title: 'Failed to compile', + type: 'build', + content: content, + }; +} + +function parseRuntimeStack(stack: string) { + let lines = stack.split('\n').slice(1); + lines = lines.map((info) => info.trim()).filter((line) => line !== ''); + return lines; +} + +/** + * Get the source code according to the error stack + * click on it and open the editor to jump to the corresponding source code location + */ +async function createRuntimeContent(stack: string) { + const lines = stack.split('\n').slice(1); + + // Matches file paths in the error stack, generated via chatgpt. + const regex = /(?:at|in)?(?http[^\s]+):(?\d+):(?\d+)/; + let sourceInfo = {} as any; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const match = line.match(regex); + if (match) { + const { filename, line, column } = match.groups as any; + sourceInfo = { filename, line, column }; + break; + } + } + if (!sourceInfo.filename) return; + + try { + const content = await findSourceCode(sourceInfo); + return content; + } catch (e) {} +} diff --git a/packages/core/src/client/hmr.ts b/packages/core/src/client/hmr.ts index 18bf0458e3..7fc5431182 100644 --- a/packages/core/src/client/hmr.ts +++ b/packages/core/src/client/hmr.ts @@ -6,7 +6,8 @@ */ import type { StatsError } from '@rsbuild/shared'; import type { ClientConfig } from '@rsbuild/shared'; -import { formatStatsMessages } from './format'; +import type { OverlayError } from '../types'; +import { formatBuildErrors, formatStatsMessages } from './format'; /** * hmr socket connect path @@ -62,11 +63,11 @@ function clearOutdatedErrors() { } } -let createOverlay: undefined | ((err: string[]) => void); +let createOverlay: undefined | ((err: OverlayError) => void); let clearOverlay: undefined | (() => void); export const registerOverlay = ( - createFn: (err: string[]) => void, + createFn: (err: OverlayError) => void, clearFn: () => void, ) => { createOverlay = createFn; @@ -124,18 +125,13 @@ function handleErrors(errors: StatsError[]) { hasCompileErrors = true; // "Massage" webpack messages. - const formatted = formatStatsMessages({ - errors, - warnings: [], - }); + const overlayError = formatBuildErrors(errors); // Also log them to the console. - for (const error of formatted.errors) { - console.error(error); - } + console.error(overlayError.content); if (createOverlay) { - createOverlay(formatted.errors); + createOverlay(overlayError); } // Do not attempt to reload now. diff --git a/packages/core/src/client/overlay.ts b/packages/core/src/client/overlay.ts index dae07a106d..b61d44596a 100644 --- a/packages/core/src/client/overlay.ts +++ b/packages/core/src/client/overlay.ts @@ -1,3 +1,5 @@ +import { type OverlayError } from '../types'; +import { formatRuntimeErrors } from './format'; import { registerOverlay } from './hmr'; function stripAnsi(content: string) { @@ -11,8 +13,13 @@ function stripAnsi(content: string) { return content.replace(regex, ''); } +function displayElement(el: Element) { + (el as HTMLElement).style.display = 'block'; +} + function linkedText(root: ShadowRoot, selector: string, text: string): void { const el = root.querySelector(selector)!; + displayElement(el); const fileRegex = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d+/g; let curIndex = 0; @@ -39,6 +46,24 @@ function linkedText(root: ShadowRoot, selector: string, text: string): void { el.appendChild(document.createTextNode(frag)); } +function updateLink(root: ShadowRoot, selector: string, file: string): void { + const el = root.querySelector(selector)!; + el.addEventListener('click', () => { + fetch(`/__open-in-editor?file=${encodeURIComponent(file)}`); + }); + el.classList.add('cursor-pointer'); +} + +function updateElement( + root: ShadowRoot, + selector: string, + innerHTML: string, +): void { + const el = root.querySelector(selector)!; + el.innerHTML = innerHTML; + displayElement(el); +} + const overlayTemplate = `
-

Compilation failed

-

-    
-

Fix error, click outside, or press Esc to close the overlay.

-

Disable overlay by setting Rsbuild's dev.client.overlay config to false.

-

+

+

+    

+    
+
`; @@ -166,7 +215,7 @@ const { } = typeof window !== 'undefined' ? window : globalThis; class ErrorOverlay extends HTMLElement { - constructor(message: string[]) { + constructor(error: OverlayError) { super(); if (!this.attachShadow) { @@ -176,28 +225,55 @@ class ErrorOverlay extends HTMLElement { return; } + const { type, content, title, stack, sourceFile } = error; const root = this.attachShadow({ mode: 'open' }); root.innerHTML = overlayTemplate; - linkedText(root, '.content', stripAnsi(message.join('/n')).trim()); + updateElement(root, '.title', title); - root.querySelector('.close')?.addEventListener('click', this.close); + if (type === 'build') { + linkedText(root, '.build-content', stripAnsi(content).trim()); + } else { + updateElement(root, '.runtime-content', content); + if (sourceFile) { + updateLink(root, '.runtime-content', sourceFile); + } + } - // close overlay when click outside - this.addEventListener('click', this.close); + if (stack?.length) { + updateElement( + root, + '.stack', + ` +
+ ${stack.length} stack frames +
${stack.join('\n')}
+
+ `, + ); + } - root.querySelector('.container')!.addEventListener('click', (e) => { - e.stopPropagation(); - }); + updateElement( + root, + '.footer', + type === 'build' + ? '

This error occurred during the build time and cannot be dismissed.

' + : "

Fix error, click outside, or press Esc to close the overlay.

Disable overlay by setting Rsbuild's dev.client.runtimeErrors config to false.

", + ); - const onEscKeydown = (e: KeyboardEvent) => { - if (e.key === 'Escape' || e.code === 'Escape') { - this.close(); - } - document.removeEventListener('keydown', onEscKeydown); - }; + if (type === 'runtime') { + displayElement(root.querySelector('.close') as Element); + root.querySelector('.close')?.addEventListener('click', this.close); - document.addEventListener('keydown', onEscKeydown); + const onEscKeydown = (e: KeyboardEvent) => { + if (e.key === 'Escape' || e.code === 'Escape') { + this.close(); + } + document.removeEventListener('keydown', onEscKeydown); + }; + + document.addEventListener('keydown', onEscKeydown); + } } close = () => { @@ -220,17 +296,42 @@ if (customElements && !customElements.get(overlayId)) { customElements.define(overlayId, ErrorOverlay); } -function createOverlay(err: string[]) { +const documentAvailable = typeof document !== 'undefined'; + +export function createOverlay(err: OverlayError) { + if (!documentAvailable) { + console.info( + '[Rsbuild] Failed to display error overlay as document is not available, you can disable the `dev.client.overlay` option.', + ); + return; + } + + if (!err) return; + + if (hasOverlay()) return; + clearOverlay(); document.body.appendChild(new ErrorOverlay(err)); } -function clearOverlay() { +export function clearOverlay() { + if (!documentAvailable) { + return; + } + // use NodeList's forEach api instead of dom.iterable // biome-ignore lint/complexity/noForEach: document.querySelectorAll(overlayId).forEach((n) => n.close()); } +export function hasOverlay() { + if (!documentAvailable) { + return false; + } + + return document.querySelector(overlayId) !== null; +} + if (typeof document !== 'undefined') { registerOverlay(createOverlay, clearOverlay); } else { @@ -238,3 +339,19 @@ if (typeof document !== 'undefined') { '[Rsbuild] Failed to display error overlay as document is not available, you can disable the `dev.client.overlay` option.', ); } + +const config = RSBUILD_CLIENT_CONFIG; + +if (config.overlay.runtimeErrors) { + window.addEventListener('error', async (event) => { + const formatted = await formatRuntimeErrors(event, false); + createOverlay(formatted); + }); + + window.addEventListener('unhandledrejection', async (event) => { + if (event.reason?.stack) { + const formatted = await formatRuntimeErrors(event, true); + createOverlay(formatted); + } + }); +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 28dcf8c475..7f61111c01 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -53,3 +53,15 @@ export type { NormalizedPerformanceConfig, NormalizedModuleFederationConfig, } from '@rsbuild/shared'; + +export interface OverlayError { + content: string; + + title: string; + + type: 'build' | 'runtime'; + + stack?: string[]; + + sourceFile?: string; +} diff --git a/packages/shared/src/types/config/dev.ts b/packages/shared/src/types/config/dev.ts index 48de313d9e..e92456f668 100644 --- a/packages/shared/src/types/config/dev.ts +++ b/packages/shared/src/types/config/dev.ts @@ -27,7 +27,12 @@ export type ClientConfig = { host?: string; protocol?: 'ws' | 'wss'; /** Shows an overlay in the browser when there are compiler errors. */ - overlay?: boolean; + overlay?: + | boolean + | { + // unhandled runtime errors + runtimeErrors?: boolean; + }; }; export type ChokidarWatchOptions = WatchOptions; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed0eace7cc..ed56e6ba5b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -695,6 +695,9 @@ importers: postcss: specifier: ^8.4.38 version: 8.4.38 + source-map: + specifier: 0.5.7 + version: 0.5.7 devDependencies: '@types/node': specifier: 18.x @@ -7874,6 +7877,10 @@ packages: source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -15967,6 +15974,8 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 + source-map@0.5.7: {} + source-map@0.6.1: {} source-map@0.7.4: {} From 075c2bb1fb78cf198da91683f0f5a1e1aaaf55ce Mon Sep 17 00:00:00 2001 From: nanianlisao <597219320@qq.com> Date: Sat, 11 May 2024 16:10:14 +0800 Subject: [PATCH 2/3] fix: fix lint error --- packages/core/src/client/findSourceMap.ts | 15 +++++---------- packages/core/src/client/format.ts | 6 +++--- packages/core/src/client/overlay.ts | 2 +- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/core/src/client/findSourceMap.ts b/packages/core/src/client/findSourceMap.ts index e733de0918..6302d9f35d 100644 --- a/packages/core/src/client/findSourceMap.ts +++ b/packages/core/src/client/findSourceMap.ts @@ -6,7 +6,7 @@ const fetchContent = (url: string) => fetch(url).then((r) => r.text()); const findSourceMap = async (fileSource: string, filename: string) => { try { // Prefer to get it via filename + '.map'. - const mapUrl = filename + '.map'; + const mapUrl = `${filename}.map`; return await fetchContent(mapUrl); } catch (e) { const mapUrl = fileSource.match(/\/\/# sourceMappingURL=(.*)$/)?.[1]; @@ -33,7 +33,7 @@ const escapeHTML = (str: string) => const formatSourceCode = (sourceCode: string, pos: any) => { // Note that the line starts at 1, not 0. const { line: crtLine, column, name } = pos; - let lines = sourceCode.split('\n'); + const lines = sourceCode.split('\n'); // Display up to 6 lines of source code const lineCount = Math.min(lines.length, 6); @@ -51,12 +51,7 @@ const formatSourceCode = (sourceCode: string, pos: any) => { // When the sourcemap information includes specific column details, add an error hint below the error line. if (line === crtLine && column > 0) { - const errorLine = - ' '.repeat(prefix.length + column) + - '' + - '^'.repeat(name?.length || 1) + - ''; - + const errorLine = `${' '.repeat(prefix.length + column)}${'^'.repeat(name?.length || 1)}`; result.push(errorLine); } } @@ -78,8 +73,8 @@ export const findSourceCode = async (sourceInfo: any) => { // Use sourcemap to find the source code location const pos = consumer.originalPositionFor({ - line: parseInt(line, 10), - column: parseInt(column, 10), + line: Number.parseInt(line, 10), + column: Number.parseInt(column, 10), }); const url = `${pos.source}:${pos.line}:${pos.column}`; diff --git a/packages/core/src/client/format.ts b/packages/core/src/client/format.ts index a08ff6eca2..3443d3003d 100644 --- a/packages/core/src/client/format.ts +++ b/packages/core/src/client/format.ts @@ -1,5 +1,5 @@ import type { StatsCompilation, StatsError } from '@rspack/core'; -import { type OverlayError } from '../types'; +import type { OverlayError } from '../types'; import { findSourceCode } from './findSourceMap'; function resolveFileName(stats: StatsError) { @@ -82,13 +82,13 @@ export async function formatRuntimeErrors( event: PromiseRejectionEvent | ErrorEvent, isRejection: boolean, ): Promise { - let error = isRejectionEvent(isRejection, event) + const error = isRejectionEvent(isRejection, event) ? event.reason : event?.error; if (!error) return; const errorName = isRejection - ? 'Unhandled Rejection (' + error.name + ')' + ? `Unhandled Rejection (${error.name})` : error.name; const stack = parseRuntimeStack(error.stack); diff --git a/packages/core/src/client/overlay.ts b/packages/core/src/client/overlay.ts index b61d44596a..71bcb3d853 100644 --- a/packages/core/src/client/overlay.ts +++ b/packages/core/src/client/overlay.ts @@ -1,4 +1,4 @@ -import { type OverlayError } from '../types'; +import type { OverlayError } from '../types'; import { formatRuntimeErrors } from './format'; import { registerOverlay } from './hmr'; From 5a2f9acf3ef7da1b7966bc65e71fc430ae8a6f37 Mon Sep 17 00:00:00 2001 From: nanianlisao <597219320@qq.com> Date: Mon, 20 May 2024 14:08:41 +0800 Subject: [PATCH 3/3] chore: use source-map-js --- e2e/cases/server/overlay/index.test.ts | 2 +- packages/core/modern.config.ts | 4 +++- packages/core/package.json | 6 +++++- packages/core/src/client/findSourceMap.ts | 7 ++++--- packages/core/src/client/overlay.ts | 17 ----------------- packages/core/src/client/runtimeErrors.ts | 14 ++++++++++++++ .../core/src/server/compilerDevMiddleware.ts | 9 +++++++++ pnpm-lock.yaml | 12 +++--------- 8 files changed, 39 insertions(+), 32 deletions(-) create mode 100644 packages/core/src/client/runtimeErrors.ts diff --git a/e2e/cases/server/overlay/index.test.ts b/e2e/cases/server/overlay/index.test.ts index 19dcc4895b..cb5908f940 100644 --- a/e2e/cases/server/overlay/index.test.ts +++ b/e2e/cases/server/overlay/index.test.ts @@ -39,7 +39,7 @@ test('should show overlay correctly', async ({ page }) => { fse.readFileSync(appPath, 'utf-8').replace('', ''), ); - await expect(errorOverlay.locator('.title')).toHaveText('Compilation failed'); + await expect(errorOverlay.locator('.title')).toHaveText('Failed to compile'); await rsbuild.close(); diff --git a/packages/core/modern.config.ts b/packages/core/modern.config.ts index be36b4bc4c..1ef88d3e24 100644 --- a/packages/core/modern.config.ts +++ b/packages/core/modern.config.ts @@ -18,6 +18,7 @@ const externals = [ ...commonExternals, '@rsbuild/core/client/hmr', '@rsbuild/core/client/overlay', + '@rsbuild/core/client/runtimeErrors', ]; // Since the relative paths of bundle and compiled have changed, @@ -69,10 +70,11 @@ export default defineConfig({ input: { hmr: 'src/client/hmr.ts', overlay: 'src/client/overlay.ts', + runtimeErrors: 'src/client/runtimeErrors.ts', }, target: BUILD_TARGET.client, dts: false, - externals: ['./hmr'], + externals: ['./hmr', './overlay'], outDir: './dist/client', autoExtension: true, externalHelpers: true, diff --git a/packages/core/package.json b/packages/core/package.json index e2888a911a..2855f6cb6d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -27,6 +27,10 @@ "types": "./dist-types/client/overlay.d.ts", "default": "./dist/client/overlay.js" }, + "./client/runtimeErrors": { + "types": "./dist/client/runtimeErrors.d.ts", + "default": "./dist/client/runtimeErrors.js" + }, "./types": { "types": "./types.d.ts" }, @@ -57,7 +61,7 @@ "core-js": "~3.36.0", "html-webpack-plugin": "npm:html-rspack-plugin@5.7.2", "postcss": "^8.4.38", - "source-map": "0.5.7" + "source-map-js": "^1.2.0" }, "devDependencies": { "@types/fs-extra": "^11.0.4", diff --git a/packages/core/src/client/findSourceMap.ts b/packages/core/src/client/findSourceMap.ts index 6302d9f35d..d0ef5b8414 100644 --- a/packages/core/src/client/findSourceMap.ts +++ b/packages/core/src/client/findSourceMap.ts @@ -1,6 +1,3 @@ -// @ts-expect-error -import { SourceMapConsumer } from 'source-map'; - const fetchContent = (url: string) => fetch(url).then((r) => r.text()); const findSourceMap = async (fileSource: string, filename: string) => { @@ -69,6 +66,8 @@ export const findSourceCode = async (sourceInfo: any) => { if (!smContent) return; const rawSourceMap = JSON.parse(smContent); + const { SourceMapConsumer } = await import('source-map-js'); + const consumer = await new SourceMapConsumer(rawSourceMap); // Use sourcemap to find the source code location @@ -81,6 +80,8 @@ export const findSourceCode = async (sourceInfo: any) => { const sourceCode = consumer.sourceContentFor(pos.source); return { sourceCode: formatSourceCode(sourceCode, pos), + // Please use an absolute path in order to open it in vscode. + // Take webpack as an example. Please configure it correctly for [output.devtoolModuleFilenameTemplate](https://www.webpackjs.com/configuration/output/#outputdevtoolmodulefilenametemplate) sourceFile: url, }; }; diff --git a/packages/core/src/client/overlay.ts b/packages/core/src/client/overlay.ts index 71bcb3d853..5451768d1b 100644 --- a/packages/core/src/client/overlay.ts +++ b/packages/core/src/client/overlay.ts @@ -1,5 +1,4 @@ import type { OverlayError } from '../types'; -import { formatRuntimeErrors } from './format'; import { registerOverlay } from './hmr'; function stripAnsi(content: string) { @@ -339,19 +338,3 @@ if (typeof document !== 'undefined') { '[Rsbuild] Failed to display error overlay as document is not available, you can disable the `dev.client.overlay` option.', ); } - -const config = RSBUILD_CLIENT_CONFIG; - -if (config.overlay.runtimeErrors) { - window.addEventListener('error', async (event) => { - const formatted = await formatRuntimeErrors(event, false); - createOverlay(formatted); - }); - - window.addEventListener('unhandledrejection', async (event) => { - if (event.reason?.stack) { - const formatted = await formatRuntimeErrors(event, true); - createOverlay(formatted); - } - }); -} diff --git a/packages/core/src/client/runtimeErrors.ts b/packages/core/src/client/runtimeErrors.ts new file mode 100644 index 0000000000..0aa6f0fdf6 --- /dev/null +++ b/packages/core/src/client/runtimeErrors.ts @@ -0,0 +1,14 @@ +import { formatRuntimeErrors } from './format'; +import { createOverlay } from './overlay'; + +window.addEventListener('error', async (event) => { + const formatted = await formatRuntimeErrors(event, false); + createOverlay(formatted); +}); + +window.addEventListener('unhandledrejection', async (event) => { + if (event.reason?.stack) { + const formatted = await formatRuntimeErrors(event, true); + createOverlay(formatted); + } +}); diff --git a/packages/core/src/server/compilerDevMiddleware.ts b/packages/core/src/server/compilerDevMiddleware.ts index c1c5333113..af6b75616f 100644 --- a/packages/core/src/server/compilerDevMiddleware.ts +++ b/packages/core/src/server/compilerDevMiddleware.ts @@ -31,6 +31,15 @@ function getClientPaths(devConfig: DevConfig) { clientPaths.push(`${require.resolve('@rsbuild/core/client/overlay')}`); } + if ( + typeof devConfig?.client?.overlay === 'object' && + devConfig.client.overlay.runtimeErrors + ) { + clientPaths.push( + `${require.resolve('@rsbuild/core/client/runtimeErrors')}`, + ); + } + return clientPaths; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e5f8d543be..e06ec7ef79 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -704,9 +704,9 @@ importers: postcss: specifier: ^8.4.38 version: 8.4.38 - source-map: - specifier: 0.5.7 - version: 0.5.7 + source-map-js: + specifier: ^1.2.0 + version: 1.2.0 devDependencies: '@types/fs-extra': specifier: ^11.0.4 @@ -7873,10 +7873,6 @@ packages: source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -15955,8 +15951,6 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 - source-map@0.5.7: {} - source-map@0.6.1: {} source-map@0.7.4: {}