Skip to content

Commit 5ef691b

Browse files
committed
fix
1 parent 9046777 commit 5ef691b

File tree

3 files changed

+125
-118
lines changed

3 files changed

+125
-118
lines changed

e2e/cases/browser-logs/unhandled-rejection/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ test('should forward browser unhandled rejection logs to terminal', async ({
1919
'error [browser] Uncaught (in promise) AbortError: Aborted',
2020
);
2121
await rsbuild.expectLog(
22-
'error [browser] Uncaught (in promise) Error: Thrown in async (src/index.js:12:0)',
22+
'error [browser] Uncaught (in promise) Error: Thrown in async (src/index.js:11:0)',
2323
);
2424
await rsbuild.expectLog(
25-
'error [browser] Uncaught (in promise) AbortError: signal is aborted without reason (src/index.js:17:0)',
25+
'error [browser] Uncaught (in promise) AbortError: signal is aborted without reason (src/index.js:16:0)',
2626
);
2727
});
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import path from 'node:path';
2+
import { promisify } from 'node:util';
3+
import { parse as parseStack } from 'stacktrace-parser';
4+
import { JS_REGEX } from '../constants.js';
5+
import { color } from '../helpers';
6+
import { logger } from '../logger';
7+
import type { EnvironmentContext, InternalContext, Rspack } from '../types';
8+
import { getFileFromUrl } from './assets-middleware/getFileFromUrl.js';
9+
import type { OutputFileSystem } from './assets-middleware/index.js';
10+
import type { ClientMessageRuntimeError } from './socketServer.js';
11+
12+
/**
13+
* Maps a position in compiled code to its original source position using
14+
* source maps.
15+
*/
16+
async function mapSourceMapPosition(
17+
rawSourceMap: string,
18+
line: number,
19+
column: number,
20+
) {
21+
const { SourceMapConsumer } = await import(
22+
'../../compiled/source-map/index.js'
23+
);
24+
const consumer = await new SourceMapConsumer(rawSourceMap);
25+
const originalPosition = consumer.originalPositionFor({ line, column });
26+
consumer.destroy();
27+
return originalPosition;
28+
}
29+
30+
/**
31+
* Resolve source filename and original position from runtime stack trace
32+
*/
33+
const resolveSourceLocation = async (
34+
stack: string,
35+
fs: Rspack.OutputFileSystem,
36+
environments: Record<string, EnvironmentContext>,
37+
) => {
38+
const parsed = parseStack(stack);
39+
if (!parsed.length) {
40+
return;
41+
}
42+
43+
// only parse JS files
44+
const { file, column, lineNumber } = parsed[0];
45+
if (
46+
file === null ||
47+
column === null ||
48+
lineNumber === null ||
49+
!JS_REGEX.test(file)
50+
) {
51+
return;
52+
}
53+
54+
const sourceMapInfo = getFileFromUrl(
55+
`${file}.map`,
56+
fs as OutputFileSystem,
57+
environments,
58+
);
59+
60+
if (!sourceMapInfo || 'errorCode' in sourceMapInfo) {
61+
return;
62+
}
63+
64+
const readFile = promisify(fs.readFile);
65+
try {
66+
const sourceMap = await readFile(sourceMapInfo.filename);
67+
if (sourceMap) {
68+
return await mapSourceMapPosition(
69+
sourceMap.toString(),
70+
lineNumber,
71+
column,
72+
);
73+
}
74+
} catch {}
75+
};
76+
77+
/**
78+
* Formats error location information into a readable relative path string.
79+
*/
80+
const formatErrorLocation = async (
81+
stack: string,
82+
context: InternalContext,
83+
fs: Rspack.OutputFileSystem,
84+
) => {
85+
const parsed = await resolveSourceLocation(stack, fs, context.environments);
86+
87+
if (!parsed) {
88+
return;
89+
}
90+
91+
const { source, line, column } = parsed;
92+
if (!source) {
93+
return;
94+
}
95+
96+
let rawLocation = path.relative(context.rootPath, source);
97+
if (line !== null) {
98+
rawLocation += column === null ? `:${line}` : `:${line}:${column}`;
99+
}
100+
return rawLocation;
101+
};
102+
103+
/**
104+
* Processes runtime errors received from the browser and logs them with
105+
* source location information.
106+
*/
107+
export const reportRuntimeError = async (
108+
message: ClientMessageRuntimeError,
109+
context: InternalContext,
110+
fs: Rspack.OutputFileSystem,
111+
): Promise<void> => {
112+
let log = `${color.cyan('[browser]')} ${color.red(message.message)}`;
113+
114+
if (message.stack) {
115+
const rawLocation = await formatErrorLocation(message.stack, context, fs);
116+
log += color.dim(` (${rawLocation})`);
117+
}
118+
119+
logger.error(log);
120+
};

packages/core/src/server/socketServer.ts

Lines changed: 3 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
11
import type { IncomingMessage } from 'node:http';
22
import type { Socket } from 'node:net';
3-
import path from 'node:path';
4-
import { promisify } from 'node:util';
5-
import { parse as parseStack } from 'stacktrace-parser';
63
import type Ws from '../../compiled/ws/index.js';
7-
import { JS_REGEX } from '../constants.js';
84
import {
9-
color,
105
getAllStatsErrors,
116
getAllStatsWarnings,
127
getStatsOptions,
138
} from '../helpers';
149
import { formatStatsMessages } from '../helpers/format';
1510
import { logger } from '../logger';
16-
import type {
17-
DevConfig,
18-
EnvironmentContext,
19-
InternalContext,
20-
Rspack,
21-
} from '../types';
22-
import { getFileFromUrl } from './assets-middleware/getFileFromUrl.js';
23-
import type { OutputFileSystem } from './assets-middleware/index.js';
11+
import type { DevConfig, InternalContext, Rspack } from '../types';
12+
import { reportRuntimeError } from './browserLogs.js';
2413
import { genOverlayHTML } from './overlay';
2514

2615
interface ExtWebSocket extends Ws {
@@ -80,108 +69,6 @@ const parseQueryString = (req: IncomingMessage) => {
8069
return queryStr ? Object.fromEntries(new URLSearchParams(queryStr)) : {};
8170
};
8271

83-
async function mapSourceMapPosition(
84-
rawSourceMap: string,
85-
line: number,
86-
column: number,
87-
) {
88-
const { SourceMapConsumer } = await import(
89-
'../../compiled/source-map/index.js'
90-
);
91-
const consumer = await new SourceMapConsumer(rawSourceMap);
92-
const originalPosition = consumer.originalPositionFor({ line, column });
93-
consumer.destroy();
94-
return originalPosition;
95-
}
96-
97-
/**
98-
* Resolve source filename and original position from runtime stack trace
99-
*/
100-
const resolveSourceLocation = async (
101-
stack: string,
102-
fs: Rspack.OutputFileSystem,
103-
environments: Record<string, EnvironmentContext>,
104-
) => {
105-
const parsed = parseStack(stack);
106-
if (!parsed.length) {
107-
return;
108-
}
109-
110-
// only parse JS files
111-
const { file, column, lineNumber } = parsed[0];
112-
if (
113-
file === null ||
114-
column === null ||
115-
lineNumber === null ||
116-
!JS_REGEX.test(file)
117-
) {
118-
return;
119-
}
120-
121-
const sourceMapInfo = getFileFromUrl(
122-
`${file}.map`,
123-
fs as OutputFileSystem,
124-
environments,
125-
);
126-
127-
if (!sourceMapInfo || 'errorCode' in sourceMapInfo) {
128-
return;
129-
}
130-
131-
const readFile = promisify(fs.readFile);
132-
try {
133-
const sourceMap = await readFile(sourceMapInfo.filename);
134-
if (sourceMap) {
135-
return await mapSourceMapPosition(
136-
sourceMap.toString(),
137-
lineNumber,
138-
column,
139-
);
140-
}
141-
} catch {}
142-
};
143-
144-
const formatErrorLocation = async (
145-
stack: string,
146-
context: InternalContext,
147-
fs: Rspack.OutputFileSystem,
148-
) => {
149-
const parsed = await resolveSourceLocation(stack, fs, context.environments);
150-
151-
if (!parsed) {
152-
return;
153-
}
154-
155-
const { source, line, column } = parsed;
156-
if (!source) {
157-
return;
158-
}
159-
160-
let rawLocation = path.relative(context.rootPath, source);
161-
if (line !== null) {
162-
rawLocation += column === null ? `:${line}` : `:${line}:${column}`;
163-
}
164-
return rawLocation;
165-
};
166-
167-
/**
168-
* Handle runtime errors from browser
169-
*/
170-
const onRuntimeError = async (
171-
message: ClientMessageRuntimeError,
172-
context: InternalContext,
173-
fs: Rspack.OutputFileSystem,
174-
) => {
175-
let log = `${color.cyan('[browser]')} ${color.red(message.message)}`;
176-
177-
if (message.stack) {
178-
const rawLocation = await formatErrorLocation(message.stack, context, fs);
179-
log += color.dim(` (${rawLocation})`);
180-
}
181-
182-
logger.error(log);
183-
};
184-
18572
export class SocketServer {
18673
private wsServer!: Ws.Server;
18774

@@ -385,7 +272,7 @@ export class SocketServer {
385272
);
386273

387274
if (message.type === 'runtime-error') {
388-
onRuntimeError(message, this.context, this.getOutputFileSystem());
275+
reportRuntimeError(message, this.context, this.getOutputFileSystem());
389276
}
390277
} catch {}
391278
});

0 commit comments

Comments
 (0)