diff --git a/packages/vite/src/node/server/__tests__/watcher.spec.ts b/packages/vite/src/node/server/__tests__/watcher.spec.ts index 2d6998bea630f4..ba95067c550706 100644 --- a/packages/vite/src/node/server/__tests__/watcher.spec.ts +++ b/packages/vite/src/node/server/__tests__/watcher.spec.ts @@ -54,3 +54,52 @@ describe('watcher configuration', () => { }) }) }) + +describe('resolveChokidarOptions warning for ignored project root', () => { + it('warns when the project root matches a default ignore glob (#23523)', async () => { + const { resolveChokidarOptions } = await import('../../watch') + const warn = vi.fn() + const logger = { warn } as any + resolveChokidarOptions( + { disableGlobbing: true }, + new Set(), + false, + '/tmp/cache', + '/home/user/test-results/my-project', + logger, + ) + expect(warn).toHaveBeenCalledTimes(1) + expect(warn.mock.calls[0][0]).toContain('test-results') + expect(warn.mock.calls[0][0]).toContain('HMR will not work') + }) + + it('does not warn for a normal project root', async () => { + const { resolveChokidarOptions } = await import('../../watch') + const warn = vi.fn() + const logger = { warn } as any + resolveChokidarOptions( + { disableGlobbing: true }, + new Set(), + false, + '/tmp/cache', + '/home/user/my-project', + logger, + ) + expect(warn).not.toHaveBeenCalled() + }) + + it('does not warn when a user-supplied ignore matches the root, only defaults', async () => { + const { resolveChokidarOptions } = await import('../../watch') + const warn = vi.fn() + const logger = { warn } as any + resolveChokidarOptions( + { disableGlobbing: true, ignored: ['**/staging/**'] }, + new Set(), + false, + '/tmp/cache', + '/home/user/staging/my-project', + logger, + ) + expect(warn).not.toHaveBeenCalled() + }) +}) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index c93bbb6eb1e2d5..743015821c9802 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -563,6 +563,8 @@ export async function _createServer( resolvedOutDirs, emptyOutDir, config.cacheDir, + config.root, + config.logger, ) const middlewares = connect() as Connect.Server diff --git a/packages/vite/src/node/watch.ts b/packages/vite/src/node/watch.ts index ae6f5631f267b5..8bf98d955b7717 100644 --- a/packages/vite/src/node/watch.ts +++ b/packages/vite/src/node/watch.ts @@ -1,6 +1,7 @@ import { EventEmitter } from 'node:events' import path from 'node:path' import colors from 'picocolors' +import picomatch from 'picomatch' import type { OutputOptions, WatcherOptions } from 'rolldown' import type { DevWatchOptions } from 'rolldown/experimental' import { escapePath } from 'tinyglobby' @@ -63,6 +64,8 @@ export function resolveChokidarOptions( resolvedOutDirs: Set, emptyOutDir: boolean, cacheDir: string, + root?: string, + logger?: Logger, ): WatchOptions { const { ignored: ignoredList, @@ -75,10 +78,13 @@ export function resolveChokidarOptions( exclude, ...otherOptions } = options ?? {} - const ignored: WatchOptions['ignored'] = [ + const defaultIgnored = [ '**/.git/**', '**/node_modules/**', '**/test-results/**', // Playwright + ] + const ignored: WatchOptions['ignored'] = [ + ...defaultIgnored, escapePath(cacheDir) + '/**', ...arraify(ignoredList || []), ] @@ -88,6 +94,30 @@ export function resolveChokidarOptions( ) } + // A default ignore glob matching the project root itself (e.g. the root + // lives under an ancestor `test-results/` directory) silently makes the + // watcher observe nothing: no HMR, no invalidation, no error. Warn so this + // is diagnosable instead of looking like a downstream caching bug. + if (root && logger) { + const normalizedRoot = normalizePath(root) + const matchedDefault = defaultIgnored.find((glob) => + picomatch(glob, { dot: true })(normalizedRoot), + ) + if (matchedDefault) { + logger.warn( + colors.yellow( + `\n${colors.bold(`(!)`)} The project root ${colors.white( + colors.dim(normalizedRoot), + )} matches the default watch ignore pattern ${colors.white( + colors.dim(matchedDefault), + )}.\n` + + `The dev server watcher will observe no files, so HMR will not work. ` + + `Move the project out of that path.\n`, + ), + ) + } + } + const resolvedWatchOptions: WatchOptions = { ignored, ignoreInitial: true,