Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow passing down "null" to disable server watcher #14208

Merged
merged 8 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/config/server-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ The error that appears in the Browser when the fallback happens can be ignored.

## server.watch

- **Type:** `object`
- **Type:** `object | null`

File system watcher options to pass on to [chokidar](https://github.com/paulmillr/chokidar#api).

Expand All @@ -197,6 +197,8 @@ export default defineConfig({
})
```

If set to `null`, no files will be watched. `server.watcher` will provide a compatible event emitter, but calling `add` or `unwatch` will have no effect.

::: warning Using Vite on Windows Subsystem for Linux (WSL) 2

When running Vite on WSL2, file system watching does not work when a file is edited by Windows applications (non-WSL2 process). This is due to [a WSL2 limitation](https://github.com/microsoft/WSL/issues/4739). This also applies to running on Docker with a WSL2 backend.
Expand Down
3 changes: 2 additions & 1 deletion docs/guide/api-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ interface ViteDevServer {
*/
httpServer: http.Server | null
/**
* Chokidar watcher instance.
* Chokidar watcher instance. If watcher was disabled in the config,
* returns noop event emitter.
sheremet-va marked this conversation as resolved.
Show resolved Hide resolved
* https://github.com/paulmillr/chokidar#api
*/
watcher: FSWatcher
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const test = 'initial text'
66 changes: 66 additions & 0 deletions packages/vite/src/node/server/__tests__/watcher.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { writeFileSync } from 'node:fs'
import { afterEach, describe, expect, it } from 'vitest'
import { createServer } from '../index'

const __dirname = dirname(fileURLToPath(import.meta.url))

afterEach(() => {
writeFileSync(
resolve(__dirname, 'fixtures/watched/index.js'),
"export const test = 'initial text'\n",
)
})

// watcher is unstable on windows
describe.skipIf(process.platform === 'win32')('watcher configuration', () => {
it("when watcher is disabled, editing files doesn't trigger watcher", async () => {
const server = await createServer({
server: {
watch: null,
},
})
server.watcher.on('change', () => {
expect.unreachable()
})

server.watcher.add(resolve(__dirname, 'fixtures/watched/index.js'))
writeFileSync(
resolve(__dirname, 'fixtures/watched/index.js'),
'export const test = "new text"',
)

// make sure watcher doesn't trigger
await new Promise((resolve) => setTimeout(resolve, 500))
})

it('when watcher is not disable, editing files triggers watcher', async () => {
expect.assertions(1)
const server = await createServer({
server: {
// "ready" event might not be triggered on macos otherwise
watch:
process.platform === 'darwin'
? {
useFsEvents: false,
usePolling: false,
}
: {},
},
})
const filename = resolve(__dirname, 'fixtures/watched/index.js')

return new Promise<void>((resolve) => {
server.watcher.on('change', (e) => {
expect(e).toMatch('/fixtures/watched/index.js')
resolve()
})

server.watcher.on('ready', () => {
server.watcher.add(filename)
writeFileSync(filename, 'export const test = "new text"')
})
})
})
})
20 changes: 12 additions & 8 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import type { BindShortcutsOptions } from '../shortcuts'
import { CLIENT_DIR, DEFAULT_DEV_PORT } from '../constants'
import type { Logger } from '../logger'
import { printServerUrls } from '../logger'
import { resolveChokidarOptions } from '../watch'
import { createNoopWatcher, resolveChokidarOptions } from '../watch'
import type { PluginContainer } from './pluginContainer'
import { createPluginContainer } from './pluginContainer'
import type { WebSocketServer } from './ws'
Expand Down Expand Up @@ -85,10 +85,10 @@ export interface ServerOptions extends CommonServerOptions {
*/
hmr?: HmrOptions | boolean
/**
* chokidar watch options
* chokidar watch options or null to disable FS watching
* https://github.com/paulmillr/chokidar#api
*/
watch?: WatchOptions
watch?: WatchOptions | null
/**
* Create Vite dev server to be used as a middleware in an existing server
* @default false
Expand Down Expand Up @@ -359,11 +359,15 @@ export async function _createServer(
setClientErrorHandler(httpServer, config.logger)
}

const watcher = chokidar.watch(
// config file dependencies and env file might be outside of root
[root, ...config.configFileDependencies, config.envDir],
resolvedWatchOptions,
) as FSWatcher
// eslint-disable-next-line eqeqeq
const watchEnabled = serverConfig.watch !== null
const watcher = watchEnabled
? (chokidar.watch(
// config file dependencies and env file might be outside of root
[root, ...config.configFileDependencies, config.envDir],
resolvedWatchOptions,
) as FSWatcher)
: createNoopWatcher(resolvedWatchOptions)

const moduleGraph: ModuleGraph = new ModuleGraph((url, ssr) =>
container.resolveId(url, undefined, { ssr }),
Expand Down
29 changes: 28 additions & 1 deletion packages/vite/src/node/watch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EventEmitter } from 'node:events'
import glob from 'fast-glob'
import type { WatchOptions } from 'dep-types/chokidar'
import type { FSWatcher, WatchOptions } from 'dep-types/chokidar'
import type { ResolvedConfig } from '.'

export function resolveChokidarOptions(
Expand All @@ -23,3 +24,29 @@ export function resolveChokidarOptions(

return resolvedWatchOptions
}

class NoopWatcher extends EventEmitter implements FSWatcher {
constructor(public options: WatchOptions) {
super()
}

add() {
return this
}

unwatch() {
return this
}

getWatched() {
return {}
}

async close() {
// noop
}
}

export function createNoopWatcher(options: WatchOptions): FSWatcher {
return new NoopWatcher(options)
}