From 45e41f97e5cc6fb8b48669a5b1dd60357f384247 Mon Sep 17 00:00:00 2001 From: Shohjahon-n Date: Tue, 7 Apr 2026 21:54:47 +0500 Subject: [PATCH 1/4] fix(server): handle rejected promise in watcher 'add'/'unlink' handlers --- packages/vite/src/node/server/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index a9e72f30314e5a..3398aca25b3898 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -904,10 +904,10 @@ export async function _createServer( }) watcher.on('add', (file) => { - onFileAddUnlink(file, false) + onFileAddUnlink(file, false).catch((e) => server.config.logger.error(e)) }) watcher.on('unlink', (file) => { - onFileAddUnlink(file, true) + onFileAddUnlink(file, true).catch((e) => server.config.logger.error(e)) }) if (!middlewareMode && httpServer) { From cb95e3a2252364c073ddacb0757c99738238dd67 Mon Sep 17 00:00:00 2001 From: Shohjahon-n Date: Thu, 9 Apr 2026 11:39:01 +0500 Subject: [PATCH 2/4] test(server): add tests for watcher add/unlink error handling --- .../src/node/__tests__/plugins/hooks.spec.ts | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts index 0ceb0497d3bc10..fce0f1d1268e3d 100644 --- a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts @@ -1,5 +1,5 @@ import path from 'node:path' -import { describe, expect, onTestFinished, test } from 'vitest' +import { describe, expect, onTestFinished, test, vi } from 'vitest' import { build } from '../../build' import type { Plugin } from '../../plugin' import { resolveConfig } from '../../config' @@ -346,3 +346,47 @@ describe('supports plugin context', () => { await server.close() }) }) + +describe('watcher add/unlink error handling', () => { + test("'add' event logs error when watchChange throws", async () => { + const { promise, resolve } = promiseWithResolvers() + const error = new Error('async watchChange error') + + const server = await createServerWithPlugin({ + name: 'test', + watchChange() { + return Promise.reject(error) + }, + }) + + vi.spyOn(server.config.logger, 'error').mockImplementation(() => resolve()) + + server.watcher.emit( + 'add', + path.resolve(import.meta.dirname, 'some-file.js'), + ) + + await promise + }) + + test("'unlink' event logs error when watchChange throws", async () => { + const { promise, resolve } = promiseWithResolvers() + const error = new Error('async watchChange error') + + const server = await createServerWithPlugin({ + name: 'test', + watchChange() { + return Promise.reject(error) + }, + }) + + vi.spyOn(server.config.logger, 'error').mockImplementation(() => resolve()) + + server.watcher.emit( + 'unlink', + path.resolve(import.meta.dirname, 'some-file.js'), + ) + + await promise + }) +}) From 9e64d01c2b2c1fc54486b5a2d72be22cf0ccce07 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:31:46 +0900 Subject: [PATCH 3/4] test: avoid mocks --- .../src/node/__tests__/plugins/hooks.spec.ts | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts index fce0f1d1268e3d..25745f6bf2589b 100644 --- a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts @@ -6,6 +6,7 @@ import { resolveConfig } from '../../config' import { createServer } from '../../server' import { preview } from '../../preview' import { promiseWithResolvers } from '../../../shared/utils' +import { type Logger, createLogger } from '../../logger' const resolveConfigWithPlugin = ( plugin: Plugin, @@ -33,12 +34,16 @@ const resolveEntryPlugin: Plugin = { }, } -const createServerWithPlugin = async (plugin: Plugin) => { +const createServerWithPlugin = async ( + plugin: Plugin, + customLogger?: Logger, +) => { const server = await createServer({ configFile: false, root: import.meta.dirname, plugins: [plugin, resolveEntryPlugin], logLevel: 'error', + customLogger, server: { middlewareMode: true, ws: false, @@ -352,14 +357,22 @@ describe('watcher add/unlink error handling', () => { const { promise, resolve } = promiseWithResolvers() const error = new Error('async watchChange error') - const server = await createServerWithPlugin({ - name: 'test', - watchChange() { - return Promise.reject(error) - }, - }) + const logError = vi.fn() + const logger = createLogger('error') + logger.error = (...args) => { + logError(...args) + resolve() + } - vi.spyOn(server.config.logger, 'error').mockImplementation(() => resolve()) + const server = await createServerWithPlugin( + { + name: 'test', + watchChange() { + return Promise.reject(error) + }, + }, + logger, + ) server.watcher.emit( 'add', @@ -367,20 +380,30 @@ describe('watcher add/unlink error handling', () => { ) await promise + expect(logError).toHaveBeenCalled() + expect(logError).toHaveBeenCalledWith(error) }) test("'unlink' event logs error when watchChange throws", async () => { const { promise, resolve } = promiseWithResolvers() const error = new Error('async watchChange error') - const server = await createServerWithPlugin({ - name: 'test', - watchChange() { - return Promise.reject(error) - }, - }) + const logError = vi.fn() + const logger = createLogger('error') + logger.error = (...args) => { + logError(...args) + resolve() + } - vi.spyOn(server.config.logger, 'error').mockImplementation(() => resolve()) + const server = await createServerWithPlugin( + { + name: 'test', + watchChange() { + return Promise.reject(error) + }, + }, + logger, + ) server.watcher.emit( 'unlink', @@ -388,5 +411,7 @@ describe('watcher add/unlink error handling', () => { ) await promise + expect(logError).toHaveBeenCalled() + expect(logError).toHaveBeenCalledWith(error) }) }) From 9c01a26af0524e7e4e0319702c16191c9cf91f1d Mon Sep 17 00:00:00 2001 From: Shohjahon Date: Wed, 15 Apr 2026 17:52:08 +0500 Subject: [PATCH 4/4] fix(dev): catch unhandled errors in watcher change handler --- .../src/node/__tests__/plugins/hooks.spec.ts | 31 +++++++++++++++++++ packages/vite/src/node/server/index.ts | 6 +++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts index 25745f6bf2589b..53438096c22011 100644 --- a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts @@ -384,6 +384,37 @@ describe('watcher add/unlink error handling', () => { expect(logError).toHaveBeenCalledWith(error) }) + test("'change' event logs error when watchChange throws", async () => { + const { promise, resolve } = promiseWithResolvers() + const error = new Error('async watchChange error') + + const logError = vi.fn() + const logger = createLogger('error') + logger.error = (...args) => { + logError(...args) + resolve() + } + + const server = await createServerWithPlugin( + { + name: 'test', + watchChange() { + return Promise.reject(error) + }, + }, + logger, + ) + + server.watcher.emit( + 'change', + path.resolve(import.meta.dirname, 'some-file.js'), + ) + + await promise + expect(logError).toHaveBeenCalled() + expect(logError).toHaveBeenCalledWith(error) + }) + test("'unlink' event logs error when watchChange throws", async () => { const { promise, resolve } = promiseWithResolvers() const error = new Error('async watchChange error') diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 3398aca25b3898..1b2501b280d8ab 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -887,7 +887,7 @@ export async function _createServer( await onHMRUpdate(isUnlink ? 'delete' : 'create', file) } - watcher.on('change', async (file) => { + const onFileChange = async (file: string) => { file = normalizePath(file) reloadOnTsconfigChange(server, file) @@ -901,6 +901,10 @@ export async function _createServer( environment.moduleGraph.onFileChange(file) } await onHMRUpdate('update', file) + } + + watcher.on('change', (file) => { + onFileChange(file).catch((e) => server.config.logger.error(e)) }) watcher.on('add', (file) => {