From 4d83eb58cdea0d2e4ec4f0da6e1dd6b72014e67e Mon Sep 17 00:00:00 2001 From: Chris Ackerman Date: Mon, 22 Apr 2024 20:42:37 -0700 Subject: [PATCH] fix(dev): watch publicDir explicitly to include it outside the root (#16502) --- .../src/node/server/__tests__/watcher.spec.ts | 63 ++++++++++++++++++- packages/vite/src/node/server/index.ts | 10 +-- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/packages/vite/src/node/server/__tests__/watcher.spec.ts b/packages/vite/src/node/server/__tests__/watcher.spec.ts index 90b68fcfd88d0f..df0e8c0641d0d7 100644 --- a/packages/vite/src/node/server/__tests__/watcher.spec.ts +++ b/packages/vite/src/node/server/__tests__/watcher.spec.ts @@ -1,9 +1,47 @@ -import { describe, expect, it } from 'vitest' +import { resolve } from 'node:path' +import { + type MockInstance, + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from 'vitest' +import chokidar from 'chokidar' import { createServer } from '../index' const stubGetWatchedCode = /getWatched\(\) \{.+?return \{\};.+?\}/s +let watchSpy: MockInstance< + Parameters, + ReturnType +> + +vi.mock('../../config', async () => { + const config: typeof import('../../config') = + await vi.importActual('../../config') + const resolveConfig = config.resolveConfig + vi.spyOn(config, 'resolveConfig').mockImplementation(async (...args) => { + const resolved: Awaited> = + await resolveConfig.call(config, ...args) + resolved.configFileDependencies.push( + resolve('fake/config/dependency.js').replace(/\\/g, '/'), + ) + return resolved + }) + return config +}) + describe('watcher configuration', () => { + beforeEach(() => { + watchSpy = vi.spyOn(chokidar, 'watch') + }) + + afterEach(() => { + watchSpy.mockRestore() + }) + it('when watcher is disabled, return noop watcher', async () => { const server = await createServer({ server: { @@ -21,4 +59,27 @@ describe('watcher configuration', () => { }) expect(server.watcher.getWatched.toString()).not.toMatch(stubGetWatchedCode) }) + + it('should watch the root directory, config file dependencies, dotenv files, and the public directory', async () => { + await createServer({ + server: { + watch: {}, + }, + publicDir: '__test_public__', + }) + expect(watchSpy).toHaveBeenLastCalledWith( + expect.arrayContaining( + [ + process.cwd(), + resolve('fake/config/dependency.js'), + resolve('.env'), + resolve('.env.local'), + resolve('.env.development'), + resolve('.env.development.local'), + resolve('__test_public__'), + ].map((file) => file.replace(/\\/g, '/')), + ), + expect.anything(), + ) + }) }) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index c7a73456187938..964b8e37a85000 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -466,6 +466,9 @@ export async function _createServer( config.server.hmr.channels.forEach((channel) => hot.addChannel(channel)) } + const publicFiles = await initPublicFilesPromise + const { publicDir } = config + if (httpServer) { setClientErrorHandler(httpServer, config.logger) } @@ -479,6 +482,9 @@ export async function _createServer( root, ...config.configFileDependencies, ...getEnvFilesForMode(config.mode, config.envDir), + // Watch the public directory explicitly because it might be outside + // of the root directory. + ...(publicDir && publicFiles ? [publicDir] : []), ], resolvedWatchOptions, ) as FSWatcher) @@ -745,8 +751,6 @@ export async function _createServer( } } - const publicFiles = await initPublicFilesPromise - const onHMRUpdate = async ( type: 'create' | 'delete' | 'update', file: string, @@ -763,8 +767,6 @@ export async function _createServer( } } - const { publicDir } = config - const onFileAddUnlink = async (file: string, isUnlink: boolean) => { file = normalizePath(file) await container.watchChange(file, { event: isUnlink ? 'delete' : 'create' })