diff --git a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts index 0ceb0497d3bc10..53438096c22011 100644 --- a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts @@ -1,11 +1,12 @@ 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' 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, @@ -346,3 +351,98 @@ 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 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( + 'add', + path.resolve(import.meta.dirname, 'some-file.js'), + ) + + await promise + expect(logError).toHaveBeenCalled() + 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') + + 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( + 'unlink', + path.resolve(import.meta.dirname, 'some-file.js'), + ) + + await promise + expect(logError).toHaveBeenCalled() + expect(logError).toHaveBeenCalledWith(error) + }) +}) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index a9e72f30314e5a..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,13 +901,17 @@ 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) => { - 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) {