Skip to content

Commit e8e9a34

Browse files
authored
fix(dev): handle errors when sending messages to vite server (#22450)
1 parent 115129e commit e8e9a34

3 files changed

Lines changed: 135 additions & 22 deletions

File tree

packages/vite/src/shared/__tests__/forwardConsole.spec.ts

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
import { describe, expect, test } from 'vitest'
2-
import { formatConsoleArgs } from '../forwardConsole'
1+
import { setTimeout } from 'node:timers/promises'
2+
import { describe, expect, test, vi } from 'vitest'
3+
import {
4+
formatConsoleArgs,
5+
setupForwardConsoleHandler,
6+
} from '../forwardConsole'
7+
import {
8+
type NormalizedModuleRunnerTransport,
9+
SendBeforeConnectError,
10+
} from '../moduleRunnerTransport'
311

412
describe('formatConsoleArgs', () => {
513
test('formats placeholders', () => {
@@ -59,3 +67,73 @@ describe('formatConsoleArgs', () => {
5967
)
6068
})
6169
})
70+
71+
describe('setupForwardConsoleHandler', () => {
72+
function createMockConsole() {
73+
return {
74+
error: vi.fn(),
75+
warn: vi.fn(),
76+
info: vi.fn(),
77+
log: vi.fn(),
78+
debug: vi.fn(),
79+
} as unknown as Console
80+
}
81+
82+
function createMockTransport(
83+
send: (...args: any[]) => Promise<void>,
84+
): NormalizedModuleRunnerTransport {
85+
return {
86+
connect: () => Promise.resolve(),
87+
disconnect: () => Promise.resolve(),
88+
send,
89+
invoke: () => Promise.resolve({ result: undefined } as any),
90+
}
91+
}
92+
93+
test('ignore SendBeforeConnectError from transport.send', async () => {
94+
const transport = createMockTransport(() =>
95+
Promise.reject(new SendBeforeConnectError('not connected yet')),
96+
)
97+
const console = createMockConsole()
98+
99+
setupForwardConsoleHandler(
100+
transport,
101+
{
102+
enabled: true,
103+
unhandledErrors: false,
104+
logLevels: ['log'],
105+
},
106+
console,
107+
)
108+
109+
console.log('hi')
110+
await setTimeout(50)
111+
112+
expect(console.error).not.toHaveBeenCalled()
113+
})
114+
115+
test('log errors from transport.send', async () => {
116+
const transport = createMockTransport(() =>
117+
Promise.reject(new Error('other error')),
118+
)
119+
const console = createMockConsole()
120+
121+
setupForwardConsoleHandler(
122+
transport,
123+
{
124+
enabled: true,
125+
unhandledErrors: false,
126+
logLevels: ['log'],
127+
},
128+
console,
129+
)
130+
131+
console.log('hi')
132+
await setTimeout(50)
133+
134+
expect(console.error).toHaveBeenCalledWith(
135+
'Failed to send error to Vite server:',
136+
new Error('other error'),
137+
)
138+
})
139+
})

packages/vite/src/shared/forwardConsole.ts

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { ForwardConsolePayload } from '#types/customEvent'
2-
import type { NormalizedModuleRunnerTransport } from './moduleRunnerTransport'
2+
import {
3+
type NormalizedModuleRunnerTransport,
4+
SendBeforeConnectError,
5+
} from './moduleRunnerTransport'
36

47
export type ForwardConsoleLogLevel =
58
| 'error'
@@ -23,13 +26,14 @@ export interface ResolvedForwardConsoleOptions {
2326
export function setupForwardConsoleHandler(
2427
transport: NormalizedModuleRunnerTransport,
2528
options: ResolvedForwardConsoleOptions,
29+
console: Console = globalThis.console,
2630
): void {
2731
if (!options.enabled) {
2832
return
2933
}
3034

31-
function sendError(type: 'error' | 'unhandled-rejection', error: any) {
32-
transport.send({
35+
async function sendError(type: 'error' | 'unhandled-rejection', error: any) {
36+
await transport.send({
3337
type: 'custom',
3438
event: 'vite:forward-console',
3539
data: {
@@ -43,20 +47,32 @@ export function setupForwardConsoleHandler(
4347
})
4448
}
4549

46-
function sendLog(level: ForwardConsoleLogLevel, args: unknown[]) {
47-
transport.send({
48-
type: 'custom',
49-
event: 'vite:forward-console',
50-
data: {
51-
type: 'log',
50+
async function sendLog(level: ForwardConsoleLogLevel, args: unknown[]) {
51+
try {
52+
await transport.send({
53+
type: 'custom',
54+
event: 'vite:forward-console',
5255
data: {
53-
level,
54-
message: formatConsoleArgs(args),
55-
},
56-
} satisfies ForwardConsolePayload,
57-
})
56+
type: 'log',
57+
data: {
58+
level,
59+
message: formatConsoleArgs(args),
60+
},
61+
} satisfies ForwardConsolePayload,
62+
})
63+
} catch (err) {
64+
try {
65+
await sendError('unhandled-rejection', err)
66+
} catch (err) {
67+
if (!(err instanceof SendBeforeConnectError)) {
68+
originalConsoleError('Failed to send error to Vite server:', err)
69+
}
70+
}
71+
}
5872
}
5973

74+
const originalConsoleError = console.error
75+
6076
for (const level of options.logLevels) {
6177
const original = (console as any)[level]
6278
if (typeof original !== 'function') {
@@ -69,18 +85,30 @@ export function setupForwardConsoleHandler(
6985
}
7086

7187
if (options.unhandledErrors && typeof window !== 'undefined') {
72-
window.addEventListener('error', (event) => {
88+
window.addEventListener('error', async (event) => {
7389
// `ErrorEvent` doesn't necessarily have `ErrorEvent.error`.
7490
// Use `ErrorEvent.message` as fallback e.g. for ResizeObserver error.
7591
// https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent/error
7692
// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver#observation_errors
7793
const error =
7894
event.error ?? (event.message ? new Error(event.message) : event)
79-
sendError('error', error)
95+
try {
96+
await sendError('error', error)
97+
} catch (err) {
98+
if (!(err instanceof SendBeforeConnectError)) {
99+
originalConsoleError('Failed to send error to Vite server:', err)
100+
}
101+
}
80102
})
81103

82-
window.addEventListener('unhandledrejection', (event) => {
83-
sendError('unhandled-rejection', event.reason)
104+
window.addEventListener('unhandledrejection', async (event) => {
105+
try {
106+
await sendError('unhandled-rejection', event.reason)
107+
} catch (err) {
108+
if (!(err instanceof SendBeforeConnectError)) {
109+
originalConsoleError('Failed to send error to Vite server:', err)
110+
}
111+
}
84112
})
85113
}
86114
}

packages/vite/src/shared/moduleRunnerTransport.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ export const normalizeModuleRunnerTransport = (
237237
if (connectingPromise) {
238238
await connectingPromise
239239
} else {
240-
throw new Error('send was called before connect')
240+
throw new SendBeforeConnectError('send was called before connect')
241241
}
242242
}
243243
await invokeableTransport.send(data)
@@ -247,14 +247,21 @@ export const normalizeModuleRunnerTransport = (
247247
if (connectingPromise) {
248248
await connectingPromise
249249
} else {
250-
throw new Error('invoke was called before connect')
250+
throw new SendBeforeConnectError('invoke was called before connect')
251251
}
252252
}
253253
return invokeableTransport.invoke(name, data)
254254
},
255255
}
256256
}
257257

258+
export class SendBeforeConnectError extends Error {
259+
constructor(message: string) {
260+
super(message)
261+
this.name = 'SendBeforeConnectError'
262+
}
263+
}
264+
258265
export const createWebSocketModuleRunnerTransport = (options: {
259266
// eslint-disable-next-line n/no-unsupported-features/node-builtins
260267
createConnection: () => WebSocket

0 commit comments

Comments
 (0)