diff --git a/docs/config/server-options.md b/docs/config/server-options.md index f31e1cc0a93d81..32fbdb2a673a5c 100644 --- a/docs/config/server-options.md +++ b/docs/config/server-options.md @@ -205,11 +205,11 @@ export default defineConfig({ - **Type:** `object | null` -File system watcher options to pass on to [chokidar](https://github.com/paulmillr/chokidar#api). +File system watcher options to pass on to [chokidar](https://github.com/paulmillr/chokidar#getting-started). If the `ignored` option is passed, Vite will also automatically convert any strings as [picomatch patterns](https://github.com/micromatch/picomatch#globbing-features). The Vite server watcher watches the `root` and skips the `.git/`, `node_modules/`, and Vite's `cacheDir` and `build.outDir` directories by default. When updating a watched file, Vite will apply HMR and update the page only if needed. -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. +If set to `null`, no files will be watched. `server.watcher` will not watch any files and calling `add` will have no effect. ::: warning Watching files in `node_modules` diff --git a/docs/guide/api-javascript.md b/docs/guide/api-javascript.md index c0a7a3d508fa33..a5bb3487f65a1c 100644 --- a/docs/guide/api-javascript.md +++ b/docs/guide/api-javascript.md @@ -110,8 +110,8 @@ interface ViteDevServer { httpServer: http.Server | null /** * Chokidar watcher instance. If `config.server.watch` is set to `null`, - * returns a noop event emitter. - * https://github.com/paulmillr/chokidar#api + * it will not watch any files and calling `add` will have no effect. + * https://github.com/paulmillr/chokidar#getting-started */ watcher: FSWatcher /** diff --git a/package.json b/package.json index 7a22e9aca2a6f0..8fb014254d2d9c 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,6 @@ "vite": "workspace:*" }, "patchedDependencies": { - "chokidar@3.6.0": "patches/chokidar@3.6.0.patch", "http-proxy@1.18.1": "patches/http-proxy@1.18.1.patch", "sirv@3.0.0": "patches/sirv@3.0.0.patch", "acorn@8.14.0": "patches/acorn@8.14.0.patch" diff --git a/packages/vite/LICENSE.md b/packages/vite/LICENSE.md index 247fa945327298..efbcdd2d5182b9 100644 --- a/packages/vite/LICENSE.md +++ b/packages/vite/LICENSE.md @@ -418,29 +418,6 @@ Repositories: chalk/ansi-regex, sindresorhus/bundle-name, sindresorhus/default-b --------------------------------------- -## anymatch -License: ISC -By: Elan Shanker -Repository: https://github.com/micromatch/anymatch - -> The ISC License -> -> Copyright (c) 2019 Elan Shanker, Paul Miller (https://paulmillr.com) -> -> Permission to use, copy, modify, and/or distribute this software for any -> purpose with or without fee is hereby granted, provided that the above -> copyright notice and this permission notice appear in all copies. -> -> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - ---------------------------------------- - ## artichokie License: MIT By: sapphi-red, Evan You @@ -498,23 +475,6 @@ Repository: https://github.com/davidbonnet/astring.git --------------------------------------- -## binary-extensions, is-binary-path -License: MIT -By: Sindre Sorhus -Repositories: sindresorhus/binary-extensions, sindresorhus/is-binary-path - -> MIT License -> -> Copyright (c) 2019 Sindre Sorhus (https://sindresorhus.com), Paul Miller (https://paulmillr.com) -> -> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------- - ## braces, fill-range, is-number, micromatch License: MIT By: Jon Schlinkert, Brian Woodward, Elan Shanker, Eugene Sharygin, hemanth.hm @@ -587,12 +547,12 @@ Repository: egoist/cac ## chokidar License: MIT -By: Paul Miller, Elan Shanker +By: Paul Miller Repository: git+https://github.com/paulmillr/chokidar.git > The MIT License (MIT) > -> Copyright (c) 2012-2019 Paul Miller (https://paulmillr.com), Elan Shanker +> Copyright (c) 2012 Paul Miller (https://paulmillr.com), Elan Shanker > > Permission is hereby granted, free of charge, to any person obtaining a copy > of this software and associated documentation files (the “Software”), to deal @@ -1186,29 +1146,6 @@ Repository: git+https://github.com/css-modules/generic-names.git --------------------------------------- -## glob-parent -License: ISC -By: Gulp Team, Elan Shanker, Blaine Bublitz -Repository: gulpjs/glob-parent - -> The ISC License -> -> Copyright (c) 2015, 2019 Elan Shanker -> -> Permission to use, copy, modify, and/or distribute this software for any -> purpose with or without fee is hereby granted, provided that the above -> copyright notice and this permission notice appear in all copies. -> -> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - ---------------------------------------- - ## http-proxy License: MIT By: Charlie Robbins, jcrugzz @@ -1253,64 +1190,6 @@ Repository: git+https://github.com/css-modules/icss-utils.git --------------------------------------- -## is-extglob -License: MIT -By: Jon Schlinkert -Repository: jonschlinkert/is-extglob - -> The MIT License (MIT) -> -> Copyright (c) 2014-2016, Jon Schlinkert -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -> THE SOFTWARE. - ---------------------------------------- - -## is-glob -License: MIT -By: Jon Schlinkert, Brian Woodward, Daniel Perez -Repository: micromatch/is-glob - -> The MIT License (MIT) -> -> Copyright (c) 2014-2017, Jon Schlinkert. -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -> THE SOFTWARE. - ---------------------------------------- - ## is-reference License: MIT By: Rich Harris @@ -1611,35 +1490,6 @@ Repository: vercel/ms --------------------------------------- -## normalize-path -License: MIT -By: Jon Schlinkert, Blaine Bublitz -Repository: jonschlinkert/normalize-path - -> The MIT License (MIT) -> -> Copyright (c) 2014-2018, Jon Schlinkert. -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -> THE SOFTWARE. - ---------------------------------------- - ## object-assign, pify License: MIT By: Sindre Sorhus diff --git a/packages/vite/package.json b/packages/vite/package.json index af6e6551ac7943..ae9c46f8a8f2e6 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -90,9 +90,6 @@ "postcss": "^8.4.47", "rollup": "^4.23.0" }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, "devDependencies": { "@ampproject/remapping": "^2.3.0", "@babel/parser": "^7.26.1", @@ -108,7 +105,7 @@ "@types/pnpapi": "^0.0.5", "artichokie": "^0.2.1", "cac": "^6.7.14", - "chokidar": "^3.6.0", + "chokidar": "^4.0.1", "connect": "^3.7.0", "convert-source-map": "^2.0.0", "cors": "^2.8.5", diff --git a/packages/vite/rollup.config.ts b/packages/vite/rollup.config.ts index e72752a82194f5..788eccbcd81962 100644 --- a/packages/vite/rollup.config.ts +++ b/packages/vite/rollup.config.ts @@ -96,7 +96,6 @@ const nodeConfig = defineConfig({ }, external: [ /^vite\//, - 'fsevents', 'lightningcss', 'rollup/parseAst', // postcss-load-config @@ -110,13 +109,6 @@ const nodeConfig = defineConfig({ // generate code that force require them upfront for side effects. // Shim them with eval() so rollup can skip these calls. shimDepsPlugin({ - // chokidar -> fsevents - 'fsevents-handler.js': [ - { - src: `require('fsevents')`, - replacement: `__require('fsevents')`, - }, - ], // postcss-import -> sugarss 'process-content.js': [ { @@ -172,7 +164,6 @@ const moduleRunnerConfig = defineConfig({ 'module-runner': path.resolve(__dirname, 'src/module-runner/index.ts'), }, external: [ - 'fsevents', 'lightningcss', 'rollup/parseAst', ...Object.keys(pkg.dependencies), @@ -198,7 +189,7 @@ const cjsConfig = defineConfig({ freeze: false, sourcemap: false, }, - external: ['fsevents', ...Object.keys(pkg.dependencies)], + external: Object.keys(pkg.dependencies), plugins: [...createSharedNodePlugins({}), bundleSizeLimit(175)], }) diff --git a/packages/vite/scripts/dev.ts b/packages/vite/scripts/dev.ts index 546f17f404108f..4e9e9ac73b7092 100644 --- a/packages/vite/scripts/dev.ts +++ b/packages/vite/scripts/dev.ts @@ -24,7 +24,6 @@ const serverOptions: BuildOptions = { external: [ ...Object.keys(packageJSON.dependencies), ...Object.keys(packageJSON.peerDependencies), - ...Object.keys(packageJSON.optionalDependencies), ...Object.keys(packageJSON.devDependencies), ], } diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 2f5c51a963dce8..922f28ff60c2f2 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -769,6 +769,7 @@ async function buildEnvironment( resolvedOutDirs, emptyOutDir, environment.config.cacheDir, + true /* isRollupChokidar3 */, ) const { watch } = await import('rollup') diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 481ea0952b579d..66808e03988321 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -210,11 +210,7 @@ export type { export type { Connect } from 'dep-types/connect' export type { WebSocket, WebSocketAlias } from 'dep-types/ws' export type { HttpProxy } from 'dep-types/http-proxy' -export type { - FSWatcher, - WatchOptions, - AwaitWriteFinishOptions, -} from 'dep-types/chokidar' +export type { FSWatcher, WatchOptions } from 'dep-types/chokidar' export type { Terser } from 'dep-types/terser' export type { RollupCommonJSOptions } from 'dep-types/commonjs' export type { RollupDynamicImportVarsOptions } from 'dep-types/dynamicImportVars' diff --git a/packages/vite/src/node/server/__tests__/watcher.spec.ts b/packages/vite/src/node/server/__tests__/watcher.spec.ts index 3c4a009f6f178a..d81fdbfa6f2346 100644 --- a/packages/vite/src/node/server/__tests__/watcher.spec.ts +++ b/packages/vite/src/node/server/__tests__/watcher.spec.ts @@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url' import { afterEach, describe, expect, it, vi } from 'vitest' import { type ViteDevServer, createServer } from '../index' -const stubGetWatchedCode = /getWatched\(\) \{.+?return \{\};.+?\}/s +const stubGetWatchedCode = /function\(\)\s*\{\s*return this;\s*\}/ describe('watcher configuration', () => { let server: ViteDevServer | undefined @@ -21,7 +21,7 @@ describe('watcher configuration', () => { watch: null, }, }) - expect(server.watcher.getWatched.toString()).toMatch(stubGetWatchedCode) + expect(server.watcher.add.toString()).toMatch(stubGetWatchedCode) }) it('when watcher is not disabled, return chokidar watcher', async () => { @@ -30,7 +30,7 @@ describe('watcher configuration', () => { watch: {}, }, }) - expect(server.watcher.getWatched.toString()).not.toMatch(stubGetWatchedCode) + expect(server.watcher.add.toString()).not.toMatch(stubGetWatchedCode) }) it('should watch the root directory, config file dependencies, dotenv files, and the public directory', async () => { diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 1ecc1e1743f91d..38c6756f700d2d 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -51,7 +51,6 @@ import type { Logger } from '../logger' import { printServerUrls } from '../logger' import { warnFutureDeprecation } from '../deprecations' import { - createNoopWatcher, getResolvedOutDirs, resolveChokidarOptions, resolveEmptyOutDir, @@ -121,7 +120,7 @@ export interface ServerOptions extends CommonServerOptions { } /** * chokidar watch options or null to disable FS watching - * https://github.com/paulmillr/chokidar#api + * https://github.com/paulmillr/chokidar#getting-started */ watch?: WatchOptions | null /** @@ -255,8 +254,9 @@ export interface ViteDevServer { */ httpServer: HttpServer | null /** - * chokidar watcher instance - * https://github.com/paulmillr/chokidar#api + * Chokidar watcher instance. If `config.server.watch` is set to `null`, + * it will not watch any files and calling `add` will have no effect. + * https://github.com/paulmillr/chokidar#getting-started */ watcher: FSWatcher /** @@ -446,10 +446,7 @@ export async function _createServer( resolvedOutDirs, ) const resolvedWatchOptions = resolveChokidarOptions( - { - disableGlobbing: true, - ...serverConfig.watch, - }, + serverConfig.watch, resolvedOutDirs, emptyOutDir, config.cacheDir, @@ -469,22 +466,26 @@ export async function _createServer( setClientErrorHandler(httpServer, config.logger) } - // 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, - ...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) - : createNoopWatcher(resolvedWatchOptions) + const watcher = chokidar.watch( + // config file dependencies and env file might be outside of root + [ + 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, + ) + // If watch is turned off, patch `.add()` as a noop to prevent programmatically + // watching additional files and to keep it fast. + // eslint-disable-next-line eqeqeq -- null means disabled + if (serverConfig.watch === null) { + watcher.add = function () { + return this + } + } const environments: Record = {} diff --git a/packages/vite/src/node/watch.ts b/packages/vite/src/node/watch.ts index d47916d8874289..f1cf61ac11bd80 100644 --- a/packages/vite/src/node/watch.ts +++ b/packages/vite/src/node/watch.ts @@ -1,6 +1,6 @@ -import { EventEmitter } from 'node:events' import path from 'node:path' -import type { FSWatcher, WatchOptions } from 'dep-types/chokidar' +import type { WatchOptions } from 'dep-types/chokidar' +import picomatch from 'picomatch' import type { OutputOptions } from 'rollup' import colors from 'picocolors' import { escapePath } from 'tinyglobby' @@ -49,13 +49,14 @@ export function resolveEmptyOutDir( } export function resolveChokidarOptions( - options: WatchOptions | undefined, + options: WatchOptions | null | undefined, resolvedOutDirs: Set, emptyOutDir: boolean, cacheDir: string, + isRollupChokidar3 = false, ): WatchOptions { const { ignored: ignoredList, ...otherOptions } = options ?? {} - const ignored: WatchOptions['ignored'] = [ + let ignored: WatchOptions['ignored'] = [ '**/.git/**', '**/node_modules/**', '**/test-results/**', // Playwright @@ -68,6 +69,23 @@ export function resolveChokidarOptions( ) } + if (!isRollupChokidar3) { + // If watch options is turned off, ignore watching anything, which essentially makes it noop + // eslint-disable-next-line eqeqeq -- null means disabled + if (options === null) { + ignored.push(() => true) + } + // Convert strings to picomatch pattern functions for compat + ignored = ignored.map((pattern) => { + if (typeof pattern === 'string') { + const matcher = picomatch(pattern, { dot: true }) + return (path: string) => matcher(path) + } else { + return pattern + } + }) + } + const resolvedWatchOptions: WatchOptions = { ignored, ignoreInitial: true, @@ -77,37 +95,3 @@ export function resolveChokidarOptions( return resolvedWatchOptions } - -class NoopWatcher extends EventEmitter implements FSWatcher { - constructor(public options: WatchOptions) { - super() - } - - add() { - return this - } - - unwatch() { - return this - } - - getWatched() { - return {} - } - - ref() { - return this - } - - unref() { - return this - } - - async close() { - // noop - } -} - -export function createNoopWatcher(options: WatchOptions): FSWatcher { - return new NoopWatcher(options) -} diff --git a/packages/vite/src/types/chokidar.d.ts b/packages/vite/src/types/chokidar.d.ts index 44d500f6a3ba9c..7be7ac356caa33 100644 --- a/packages/vite/src/types/chokidar.d.ts +++ b/packages/vite/src/types/chokidar.d.ts @@ -1,242 +1,131 @@ -// Inlined to avoid extra dependency (chokidar is bundled in the published build) - -// https://github.com/paulmillr/chokidar/blob/master/types/index.d.ts +// Inlined with the following changes: +// 1. Rename `ChokidarOptions` to `WatchOptions` (compat with chokidar v3) +// 2. Remove internal properties exposed from `FSWatcher` +// 3. Remove unneeded types from the tweaks above +// 4. Add spacing and formatted for readability + +// https://cdn.jsdelivr.net/npm/chokidar/index.d.ts +// https://cdn.jsdelivr.net/npm/chokidar/handler.d.ts // MIT Licensed https://github.com/paulmillr/chokidar/blob/master/LICENSE -/** -The MIT License (MIT) - -Copyright (c) 2012-2019 Paul Miller (https://paulmillr.com), Elan Shanker - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the “Software”), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -/// - -import type * as fs from 'node:fs' -import { EventEmitter } from 'node:events' -import type { Matcher } from './anymatch' - -export class FSWatcher extends EventEmitter implements fs.FSWatcher { - options: WatchOptions - - /** - * Constructs a new FSWatcher instance with optional WatchOptions parameter. - */ - constructor(options?: WatchOptions) - - /** - * When called, requests that the Node.js event loop not exit so long as the fs.FSWatcher is active. - * Calling watcher.ref() multiple times will have no effect. - */ - ref(): this - - /** - * When called, the active fs.FSWatcher object will not require the Node.js event loop to remain active. - * If there is no other activity keeping the event loop running, the process may exit before the fs.FSWatcher object's callback is invoked. - * Calling watcher.unref() multiple times will have no effect. - */ - unref(): this - - /** - * Add files, directories, or glob patterns for tracking. Takes an array of strings or just one - * string. - */ - add(paths: string | ReadonlyArray): this - - /** - * Stop watching files, directories, or glob patterns. Takes an array of strings or just one - * string. - */ - unwatch(paths: string | ReadonlyArray): this - - /** - * Returns an object representing all the paths on the file system being watched by this - * `FSWatcher` instance. The object's keys are all the directories (using absolute paths unless - * the `cwd` option was used), and the values are arrays of the names of the items contained in - * each directory. - */ - getWatched(): { - [directory: string]: string[] - } - - /** - * Removes all listeners from watched files. - */ - close(): Promise - - on( - event: 'add' | 'addDir' | 'change', - listener: (path: string, stats?: fs.Stats) => void, - ): this - - on( - event: 'all', - listener: ( - eventName: 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir', - path: string, - stats?: fs.Stats, - ) => void, - ): this - - /** - * Error occurred - */ - on(event: 'error', listener: (error: Error) => void): this - - /** - * Exposes the native Node `fs.FSWatcher events` - */ - on( - event: 'raw', - listener: (eventName: string, path: string, details: any) => void, - ): this - - /** - * Fires when the initial scan is complete - */ - on(event: 'ready', listener: () => void): this - - on(event: 'unlink' | 'unlinkDir', listener: (path: string) => void): this - - on(event: string, listener: (...args: any[]) => void): this +import type { Stats } from 'node:fs' +import type { EventEmitter } from 'node:events' + +// #region handler.d.ts + +declare const EVENTS: { + readonly ALL: 'all' + readonly READY: 'ready' + readonly ADD: 'add' + readonly CHANGE: 'change' + readonly ADD_DIR: 'addDir' + readonly UNLINK: 'unlink' + readonly UNLINK_DIR: 'unlinkDir' + readonly RAW: 'raw' + readonly ERROR: 'error' } +type EventName = (typeof EVENTS)[keyof typeof EVENTS] -export interface WatchOptions { - /** - * Indicates whether the process should continue to run as long as files are being watched. If - * set to `false` when using `fsevents` to watch, no more events will be emitted after `ready`, - * even if the process continues to run. - */ - persistent?: boolean - - /** - * ([anymatch](https://github.com/micromatch/anymatch)-compatible definition) Defines files/paths to - * be ignored. The whole relative or absolute path is tested, not just filename. If a function - * with two arguments is provided, it gets called twice per path - once with a single argument - * (the path), second time with two arguments (the path and the - * [`fs.Stats`](https://nodejs.org/api/fs.html#fs_class_fs_stats) object of that path). - */ - ignored?: Matcher +type Path = string - /** - * If set to `false` then `add`/`addDir` events are also emitted for matching paths while - * instantiating the watching as chokidar discovers these file paths (before the `ready` event). - */ - ignoreInitial?: boolean +// #endregion - /** - * When `false`, only the symlinks themselves will be watched for changes instead of following - * the link references and bubbling events through the link's path. - */ - followSymlinks?: boolean +// #region index.d.ts - /** - * The base directory from which watch `paths` are to be derived. Paths emitted with events will - * be relative to this. - */ +type AWF = { + stabilityThreshold: number + pollInterval: number +} +type BasicOpts = { + persistent: boolean + ignoreInitial: boolean + followSymlinks: boolean cwd?: string + usePolling: boolean + interval: number + binaryInterval: number + alwaysStat?: boolean + depth?: number + ignorePermissionErrors: boolean + atomic: boolean | number +} - /** - * If set to true then the strings passed to .watch() and .add() are treated as literal path - * names, even if they look like globs. - * - * @default false - */ - disableGlobbing?: boolean +export type WatchOptions = Partial< + BasicOpts & { + ignored: Matcher | Matcher[] + awaitWriteFinish: boolean | Partial + } +> - /** - * Whether to use fs.watchFile (backed by polling), or fs.watch. If polling leads to high CPU - * utilization, consider setting this to `false`. It is typically necessary to **set this to - * `true` to successfully watch files over a network**, and it may be necessary to successfully - * watch files in other non-standard situations. Setting to `true` explicitly on OS X overrides - * the `useFsEvents` default. - */ - usePolling?: boolean +export type FSWInstanceOptions = BasicOpts & { + ignored: Matcher[] + awaitWriteFinish: false | AWF +} - /** - * Whether to use the `fsevents` watching interface if available. When set to `true` explicitly - * and `fsevents` is available this supersedes the `usePolling` setting. When set to `false` on - * OS X, `usePolling: true` becomes the default. - */ - useFsEvents?: boolean +export type EmitArgs = [EventName, Path | Error, any?, any?, any?] - /** - * If relying upon the [`fs.Stats`](https://nodejs.org/api/fs.html#fs_class_fs_stats) object that - * may get passed with `add`, `addDir`, and `change` events, set this to `true` to ensure it is - * provided even in cases where it wasn't already available from the underlying watch events. - */ - alwaysStat?: boolean +export type MatchFunction = (val: string, stats?: Stats) => boolean - /** - * If set, limits how many levels of subdirectories will be traversed. - */ - depth?: number +export interface MatcherObject { + path: string + recursive?: boolean +} - /** - * Interval of file system polling. - */ - interval?: number +export type Matcher = string | RegExp | MatchFunction | MatcherObject - /** - * Interval of file system polling for binary files. ([see list of binary extensions](https://gi - * thub.com/sindresorhus/binary-extensions/blob/master/binary-extensions.json)) - */ - binaryInterval?: number +/** + * Watches files & directories for changes. Emitted events: + * `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error` + * + * new FSWatcher() + * .add(directories) + * .on('add', path => log('File', path, 'was added')) + */ +export declare class FSWatcher extends EventEmitter { + closed: boolean + options: FSWInstanceOptions - /** - * Indicates whether to watch files that don't have read permissions if possible. If watching - * fails due to `EPERM` or `EACCES` with this set to `true`, the errors will be suppressed - * silently. - */ - ignorePermissionErrors?: boolean + constructor(_opts?: WatchOptions) /** - * `true` if `useFsEvents` and `usePolling` are `false`. Automatically filters out artifacts - * that occur when using editors that use "atomic writes" instead of writing directly to the - * source file. If a file is re-added within 100 ms of being deleted, Chokidar emits a `change` - * event rather than `unlink` then `add`. If the default of 100 ms does not work well for you, - * you can override it by setting `atomic` to a custom value, in milliseconds. + * Adds paths to be watched on an existing FSWatcher instance. + * @param paths_ file or file list. Other arguments are unused */ - atomic?: boolean | number - + add(paths_: Path | Path[], _origAdd?: string, _internal?: boolean): FSWatcher /** - * can be set to an object in order to adjust timing params: + * Close watchers or start ignoring events from specified paths. */ - awaitWriteFinish?: AwaitWriteFinishOptions | boolean -} - -export interface AwaitWriteFinishOptions { + unwatch(paths_: Path | Path[]): FSWatcher /** - * Amount of time in milliseconds for a file size to remain constant before emitting its event. + * Close watchers and remove all listeners from watched paths. */ - stabilityThreshold?: number - + close(): Promise /** - * File size polling interval. + * Expose list of watched paths + * @returns for chaining */ - pollInterval?: number + getWatched(): Record + emitWithAll(event: EventName, args: EmitArgs): void } /** - * produces an instance of `FSWatcher`. + * Instantiates watcher with paths to be tracked. + * @param paths file / directory paths + * @param options opts, such as `atomic`, `awaitWriteFinish`, `ignored`, and others + * @returns an instance of FSWatcher for chaining. + * @example + * const watcher = watch('.').on('all', (event, path) => { console.log(event, path); }); + * watch('.', { atomic: true, awaitWriteFinish: true, ignored: (f, stats) => stats?.isFile() && !f.endsWith('.js') }) */ -export function watch( - paths: string | ReadonlyArray, +export declare function watch( + paths: string | string[], options?: WatchOptions, ): FSWatcher + +declare const _default: { + watch: typeof watch + FSWatcher: typeof FSWatcher +} +export default _default + +// #endregion diff --git a/patches/chokidar@3.6.0.patch b/patches/chokidar@3.6.0.patch deleted file mode 100644 index 70c2b0183971ae..00000000000000 --- a/patches/chokidar@3.6.0.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/lib/fsevents-handler.js b/lib/fsevents-handler.js -index fe29393c179d3d6673f996ca6f95bbc83f9a0699..08e425fa3135df4b6d1912329f4d8b8b99c8048c 100644 ---- a/lib/fsevents-handler.js -+++ b/lib/fsevents-handler.js -@@ -305,7 +305,8 @@ _watchWithFsEvents(watchPath, realPath, transform, globFilter) { - if (this.fsw.closed || this.fsw._isIgnored(watchPath)) return; - const opts = this.fsw.options; - const watchCallback = async (fullPath, flags, info) => { -- if (this.fsw.closed) return; -+ // PATCH: bypass the callback for better perf when fullPath hit the ignored file list -+ if (this.fsw.closed || this.fsw._isIgnored(fullPath)) return; - if ( - opts.depth !== undefined && - calcDepth(fullPath, realPath) > opts.depth diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index d4281ec1bbe5ae..1ebada2b272653 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -832,11 +832,20 @@ if (!isBuild) { ), ) const originalChildFileCode = readFile(childFile) - removeFile(childFile) - await untilUpdated( - () => page.textContent('.file-delete-restore'), - 'parent:not-child', - ) + await Promise.all([ + untilBrowserLogAfter( + () => removeFile(childFile), + `${childFile} is disposed`, + ), + untilUpdated( + () => page.textContent('.file-delete-restore'), + 'parent:not-child', + ), + // chokidar sometimes detect a file delete and create immediately after as a single + // `change` event, which isn't expected in this test, so we create an artificial delay + // here to ensure there's ample time to dtect the difference + new Promise((r) => setTimeout(r, 200)), + ]) await untilBrowserLogAfter(async () => { const loadPromise = page.waitForEvent('load') @@ -907,6 +916,7 @@ if (!isBuild) { }) test('deleted file should trigger dispose and prune callbacks', async () => { + browserLogs.length = 0 await page.goto(viteTestUrl) const parentFile = 'file-delete-restore/parent.js' @@ -943,6 +953,8 @@ if (!isBuild) { }) test('import.meta.hot?.accept', async () => { + await page.goto(viteTestUrl) + const el = await page.$('.optional-chaining') await untilBrowserLogAfter( () => diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 30bfc72e5d8fb9..e6b34d921b458c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,9 +13,6 @@ patchedDependencies: acorn@8.14.0: hash: d727lkisx442fz7p2mupxdcooa path: patches/acorn@8.14.0.patch - chokidar@3.6.0: - hash: bckcfsslxcffppz65mxcq6naau - path: patches/chokidar@3.6.0.patch http-proxy@1.18.1: hash: qqiqxx62zlcu62nljjmhlvexni path: patches/http-proxy@1.18.1.patch @@ -238,10 +235,6 @@ importers: rollup: specifier: ^4.23.0 version: 4.24.2 - optionalDependencies: - fsevents: - specifier: ~2.3.3 - version: 2.3.3 devDependencies: '@ampproject/remapping': specifier: ^2.3.0 @@ -286,8 +279,8 @@ importers: specifier: ^6.7.14 version: 6.7.14 chokidar: - specifier: ^3.6.0 - version: 3.6.0(patch_hash=bckcfsslxcffppz65mxcq6naau) + specifier: ^4.0.1 + version: 4.0.1 connect: specifier: ^3.7.0 version: 3.7.0 @@ -9406,7 +9399,7 @@ snapshots: check-error@2.1.1: {} - chokidar@3.6.0(patch_hash=bckcfsslxcffppz65mxcq6naau): + chokidar@3.6.0: dependencies: anymatch: 3.1.2 braces: 3.0.3 @@ -12381,7 +12374,7 @@ snapshots: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 - chokidar: 3.6.0(patch_hash=bckcfsslxcffppz65mxcq6naau) + chokidar: 3.6.0 didyoumean: 1.2.2 dlv: 1.1.3 fast-glob: 3.3.2