Skip to content

Commit 71fef63

Browse files
authored
fix: render TS errors on error overlay (#6303)
1 parent fcc69bf commit 71fef63

File tree

4 files changed

+88
-72
lines changed

4 files changed

+88
-72
lines changed
Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,8 @@
11
import { expect, test } from '@e2e/helper';
2+
import type { Page } from 'playwright';
23

3-
// TODO: fixme
4-
test.skip('should display type errors on overlay correctly', async ({
5-
page,
6-
dev,
7-
logHelper,
8-
}) => {
9-
const { addLog, expectLog } = logHelper;
10-
page.on('console', (consoleMessage) => {
11-
addLog(consoleMessage.text());
12-
});
13-
14-
await dev();
15-
16-
await expectLog('TS2322:');
17-
4+
const expectErrorOverlay = async (page: Page) => {
5+
await page.waitForSelector('rsbuild-error-overlay', { state: 'attached' });
186
const errorOverlay = page.locator('rsbuild-error-overlay');
197
await expect(errorOverlay.locator('.title')).toHaveText('Build failed');
208

@@ -30,4 +18,23 @@ test.skip('should display type errors on overlay correctly', async ({
3018
expect(
3119
linkText?.endsWith('/src/index.ts:3:1') && linkText.startsWith('.'),
3220
).toBeTruthy();
21+
};
22+
23+
test('should display type errors on overlay correctly', async ({
24+
page,
25+
dev,
26+
logHelper,
27+
}) => {
28+
const { addLog, expectLog } = logHelper;
29+
page.on('console', (consoleMessage) => {
30+
addLog(consoleMessage.text());
31+
});
32+
33+
await dev();
34+
await expectLog('TS2322:');
35+
await expectErrorOverlay(page);
36+
37+
// The error overlay should be rendered after reloading the page
38+
page.reload();
39+
await expectErrorOverlay(page);
3340
});

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

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import type { ReadStream } from 'node:fs';
1010
import { createRequire } from 'node:module';
1111
import type { Compiler, MultiCompiler, Watching } from '@rspack/core';
12-
import { applyToCompiler, isMultiCompiler } from '../../helpers';
12+
import { applyToCompiler, isMultiCompiler, pick } from '../../helpers';
1313
import { logger } from '../../logger';
1414
import type {
1515
InternalContext,
@@ -94,11 +94,16 @@ const isNodeCompiler = (compiler: Compiler) => {
9494
return false;
9595
};
9696

97+
const isTsError = (error: Rspack.RspackError) =>
98+
'message' in error && error.stack?.includes('ts-checker-rspack-plugin');
99+
97100
export const setupServerHooks = ({
101+
context,
98102
compiler,
99103
token,
100104
socketServer,
101105
}: {
106+
context: InternalContext;
102107
compiler: Compiler;
103108
token: string;
104109
socketServer: SocketServer;
@@ -108,15 +113,53 @@ export const setupServerHooks = ({
108113
return;
109114
}
110115

116+
// Track errors count to detect if the `done` hook is called multiple times
117+
let errorsCount: number | null = null;
118+
111119
compiler.hooks.invalid.tap('rsbuild-dev-server', (fileName) => {
120+
errorsCount = null;
121+
112122
// reload page when HTML template changed
113123
if (typeof fileName === 'string' && fileName.endsWith('.html')) {
114124
socketServer.sockWrite({ type: 'static-changed' }, token);
115-
return;
116125
}
117126
});
118127

119-
compiler.hooks.done.tap('rsbuild-dev-server', () => {
128+
compiler.hooks.done.tap('rsbuild-dev-server', (stats) => {
129+
const { errors } = stats.compilation;
130+
if (errors.length === errorsCount) {
131+
return;
132+
}
133+
134+
const isRecalled = errorsCount !== null;
135+
errorsCount = errors.length;
136+
137+
/**
138+
* The ts-checker-rspack-plugin asynchronously pushes Type errors to `compilation.errors`
139+
* and calls the `done` hook again, so we need to detect changes in errors and render them
140+
* in the overlay.
141+
*/
142+
if (isRecalled) {
143+
const tsErrors = errors.filter(isTsError);
144+
if (!tsErrors.length) {
145+
return;
146+
}
147+
148+
const { stats: statsJson } = context.buildState;
149+
const statsErrors = tsErrors.map((item) =>
150+
pick(item, ['message', 'file']),
151+
);
152+
153+
if (statsJson) {
154+
statsJson.errors = statsJson.errors
155+
? [...statsJson.errors, ...statsErrors]
156+
: statsErrors;
157+
}
158+
159+
socketServer.sendError(statsErrors, token);
160+
return;
161+
}
162+
120163
socketServer.onBuildDone(token);
121164
});
122165
};
@@ -209,6 +252,7 @@ export const assetsMiddleware = async ({
209252

210253
// register hooks for each compilation, update socket stats if recompiled
211254
setupServerHooks({
255+
context,
212256
compiler,
213257
socketServer,
214258
token,

packages/core/src/server/socketServer.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,23 @@ export class SocketServer {
195195
this.sendStats({ token });
196196
}
197197

198+
/**
199+
* Send error messages to the client and render error overlay
200+
*/
201+
public sendError(errors: Rspack.StatsError[], token: string): void {
202+
const formattedErrors = errors.map((item) => formatStatsError(item));
203+
this.sockWrite(
204+
{
205+
type: 'errors',
206+
data: {
207+
text: formattedErrors,
208+
html: genOverlayHTML(formattedErrors, this.context.rootPath),
209+
},
210+
},
211+
token,
212+
);
213+
}
214+
198215
/**
199216
* Write message to each socket
200217
* @param message - The message to send
@@ -421,18 +438,7 @@ export class SocketServer {
421438
}
422439

423440
if (errors.length > 0) {
424-
const errorMessages = errors.map((item) => formatStatsError(item));
425-
426-
this.sockWrite(
427-
{
428-
type: 'errors',
429-
data: {
430-
text: errorMessages,
431-
html: genOverlayHTML(errorMessages, this.context.rootPath),
432-
},
433-
},
434-
token,
435-
);
441+
this.sendError(errors, token);
436442
return;
437443
}
438444

packages/core/tests/server.test.ts

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { rspack } from '@rspack/core';
22
import { defaultAllowedOrigins } from '../src/defaultConfig';
3-
import {
4-
isClientCompiler,
5-
setupServerHooks,
6-
} from '../src/server/assets-middleware';
3+
import { isClientCompiler } from '../src/server/assets-middleware';
74
import { formatRoutes, printServerURLs } from '../src/server/helper';
85

96
beforeEach(() => {
@@ -248,44 +245,6 @@ test('printServerURLs', () => {
248245
});
249246

250247
describe('test dev server', () => {
251-
test('should setupServerHooks correctly', () => {
252-
const compiler = rspack({
253-
target: 'web',
254-
});
255-
const onDoneFn = rstest.fn();
256-
const onInvalidFn = rstest.fn();
257-
258-
setupServerHooks({
259-
compiler,
260-
token: 'test',
261-
callbacks: {
262-
onDone: onDoneFn,
263-
onInvalid: onInvalidFn,
264-
},
265-
});
266-
267-
expect(compiler.hooks.done.taps.length).toBe(1);
268-
});
269-
270-
test('should not setupServerHooks when compiler is server', () => {
271-
const compiler = rspack({
272-
target: 'node',
273-
});
274-
const onDoneFn = rstest.fn();
275-
const onInvalidFn = rstest.fn();
276-
277-
setupServerHooks({
278-
compiler,
279-
token: 'test',
280-
callbacks: {
281-
onDone: onDoneFn,
282-
onInvalid: onInvalidFn,
283-
},
284-
});
285-
286-
expect(compiler.hooks.done.taps.length).toBe(0);
287-
});
288-
289248
test('check isClientCompiler', () => {
290249
expect(isClientCompiler(rspack({}))).toBeTruthy();
291250

0 commit comments

Comments
 (0)