From 49783da298bc45f3f3c5ad4ce2fb1260ee8856bb Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Thu, 14 Nov 2024 16:13:41 +0800 Subject: [PATCH] revert: use chokidar v3 (#18659) --- docs/config/server-options.md | 6 +- docs/guide/api-javascript.md | 4 +- package.json | 3 +- packages/vite/LICENSE.md | 238 +++++++++++++- packages/vite/package.json | 5 +- packages/vite/rollup.config.ts | 11 +- packages/vite/scripts/dev.ts | 1 + packages/vite/src/node/build.ts | 1 - .../src/node/server/__tests__/watcher.spec.ts | 2 +- packages/vite/src/node/server/index.ts | 39 ++- packages/vite/src/node/watch.ts | 60 ++-- packages/vite/src/types/chokidar.d.ts | 292 ++++++++++++------ patches/chokidar@3.6.0.patch | 14 + playground/hmr/__tests__/hmr.spec.ts | 4 - pnpm-lock.yaml | 15 +- 15 files changed, 530 insertions(+), 165 deletions(-) create mode 100644 patches/chokidar@3.6.0.patch diff --git a/docs/config/server-options.md b/docs/config/server-options.md index 83524b8b11e11d..9aa26b1aca79af 100644 --- a/docs/config/server-options.md +++ b/docs/config/server-options.md @@ -214,11 +214,11 @@ export default defineConfig({ - **Type:** `object | null` -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). +File system watcher options to pass on to [chokidar](https://github.com/paulmillr/chokidar/tree/3.6.0#api). 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 not watch any files and calling `add` will have no effect. +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 Watching files in `node_modules` @@ -235,7 +235,7 @@ To fix it, you could either: - **Recommended**: Use WSL2 applications to edit your files. - It is also recommended to move the project folder outside of a Windows filesystem. Accessing Windows filesystem from WSL2 is slow. Removing that overhead will improve performance. - Set `{ usePolling: true }`. - - Note that [`usePolling` leads to high CPU utilization](https://github.com/paulmillr/chokidar#performance). + - Note that [`usePolling` leads to high CPU utilization](https://github.com/paulmillr/chokidar/tree/3.6.0#performance). ::: diff --git a/docs/guide/api-javascript.md b/docs/guide/api-javascript.md index a5bb3487f65a1c..46ea0259011a0d 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`, - * it will not watch any files and calling `add` will have no effect. - * https://github.com/paulmillr/chokidar#getting-started + * it will not watch any files and calling `add` or `unwatch` will have no effect. + * https://github.com/paulmillr/chokidar/tree/3.6.0#api */ watcher: FSWatcher /** diff --git a/package.json b/package.json index 6643148a23aa26..0bb1139b4da462 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,8 @@ }, "patchedDependencies": { "http-proxy@1.18.1": "patches/http-proxy@1.18.1.patch", - "sirv@3.0.0": "patches/sirv@3.0.0.patch" + "sirv@3.0.0": "patches/sirv@3.0.0.patch", + "chokidar@3.6.0": "patches/chokidar@3.6.0.patch" }, "peerDependencyRules": { "allowedVersions": { diff --git a/packages/vite/LICENSE.md b/packages/vite/LICENSE.md index 346c32951f32c7..d9c9262f393d36 100644 --- a/packages/vite/LICENSE.md +++ b/packages/vite/LICENSE.md @@ -401,6 +401,29 @@ Repository: rollup/plugins --------------------------------------- +## 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 @@ -431,6 +454,61 @@ Repository: git+https://github.com/sapphi-red/artichokie.git --------------------------------------- +## binary-extensions +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/binary-extensions + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> Copyright (c) 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 +License: MIT +By: Jon Schlinkert, Brian Woodward, Elan Shanker, Eugene Sharygin, hemanth.hm +Repository: micromatch/braces + +License: MIT +By: Jon Schlinkert, Edo Rivai, Paul Miller, Rouven Weßling +Repository: jonschlinkert/fill-range + +License: MIT +By: Jon Schlinkert, Olsten Larck, Rouven Weßling +Repository: jonschlinkert/is-number + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, 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. + +--------------------------------------- + ## bundle-name, default-browser, default-browser-id, define-lazy-prop, is-docker, is-inside-container, is-wsl, open, run-applescript License: MIT By: Sindre Sorhus @@ -479,12 +557,12 @@ Repository: egoist/cac ## chokidar License: MIT -By: Paul Miller +By: Paul Miller, Elan Shanker Repository: git+https://github.com/paulmillr/chokidar.git > The MIT License (MIT) > -> Copyright (c) 2012 Paul Miller (https://paulmillr.com), Elan Shanker +> 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 @@ -1078,6 +1156,29 @@ 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 @@ -1122,6 +1223,81 @@ Repository: git+https://github.com/css-modules/icss-utils.git --------------------------------------- +## is-binary-path +License: MIT +By: Sindre Sorhus +Repository: 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. + +--------------------------------------- + +## 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 @@ -1422,6 +1598,35 @@ 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 License: MIT By: Sindre Sorhus @@ -2044,6 +2249,35 @@ Repository: git+https://github.com/SuperchupuDev/tinyglobby.git --------------------------------------- +## to-regex-range +License: MIT +By: Jon Schlinkert, Rouven Weßling +Repository: micromatch/to-regex-range + +> The MIT License (MIT) +> +> Copyright (c) 2015-present, 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. + +--------------------------------------- + ## tsconfck License: MIT By: dominikg diff --git a/packages/vite/package.json b/packages/vite/package.json index 2f9e9bbf43eb3b..9d7e3abf004ade 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -90,6 +90,9 @@ "postcss": "^8.4.48", "rollup": "^4.23.0" }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, "devDependencies": { "@ampproject/remapping": "^2.3.0", "@babel/parser": "^7.26.2", @@ -105,7 +108,7 @@ "@types/pnpapi": "^0.0.5", "artichokie": "^0.2.1", "cac": "^6.7.14", - "chokidar": "^4.0.1", + "chokidar": "^3.6.0", "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 c094e12d50d3a0..1cf3015f15d52f 100644 --- a/packages/vite/rollup.config.ts +++ b/packages/vite/rollup.config.ts @@ -109,6 +109,7 @@ const nodeConfig = defineConfig({ }, external: [ /^vite\//, + 'fsevents', 'rollup/parseAst', /^tsx\//, ...Object.keys(pkg.dependencies), @@ -119,6 +120,13 @@ 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': [ { @@ -179,6 +187,7 @@ const moduleRunnerConfig = defineConfig({ 'module-runner': path.resolve(__dirname, 'src/module-runner/index.ts'), }, external: [ + 'fsevents', 'lightningcss', 'rollup/parseAst', ...Object.keys(pkg.dependencies), @@ -200,7 +209,7 @@ const cjsConfig = defineConfig({ chunkFileNames: 'node-cjs/chunks/dep-[hash].js', format: 'cjs', }, - external: Object.keys(pkg.dependencies), + external: ['fsevents', ...Object.keys(pkg.dependencies)], plugins: [ ...createSharedNodePlugins({}), bundleSizeLimit(175), diff --git a/packages/vite/scripts/dev.ts b/packages/vite/scripts/dev.ts index 4e9e9ac73b7092..546f17f404108f 100644 --- a/packages/vite/scripts/dev.ts +++ b/packages/vite/scripts/dev.ts @@ -24,6 +24,7 @@ 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 8e5067cbe2c591..e5d80841ad4289 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -786,7 +786,6 @@ async function buildEnvironment( resolvedOutDirs, emptyOutDir, environment.config.cacheDir, - true /* isRollupChokidar3 */, ) const { watch } = await import('rollup') diff --git a/packages/vite/src/node/server/__tests__/watcher.spec.ts b/packages/vite/src/node/server/__tests__/watcher.spec.ts index d81fdbfa6f2346..2d6998bea630f4 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 = /function\(\)\s*\{\s*return this;\s*\}/ +const stubGetWatchedCode = /\(\)\s*\{\s*return this;\s*\}/ describe('watcher configuration', () => { let server: ViteDevServer | undefined diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index b6bfdf5a211228..607ede5999f5fc 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -52,6 +52,7 @@ import type { Logger } from '../logger' import { printServerUrls } from '../logger' import { warnFutureDeprecation } from '../deprecations' import { + createNoopWatcher, getResolvedOutDirs, resolveChokidarOptions, resolveEmptyOutDir, @@ -121,7 +122,7 @@ export interface ServerOptions extends CommonServerOptions { } /** * chokidar watch options or null to disable FS watching - * https://github.com/paulmillr/chokidar#getting-started + * https://github.com/paulmillr/chokidar/tree/3.6.0#api */ watch?: WatchOptions | null /** @@ -248,8 +249,8 @@ export interface ViteDevServer { httpServer: HttpServer | null /** * 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 + * it will not watch any files and calling `add` or `unwatch` will have no effect. + * https://github.com/paulmillr/chokidar/tree/3.6.0#api */ watcher: FSWatcher /** @@ -439,7 +440,10 @@ export async function _createServer( resolvedOutDirs, ) const resolvedWatchOptions = resolveChokidarOptions( - serverConfig.watch, + { + disableGlobbing: true, + ...serverConfig.watch, + }, resolvedOutDirs, emptyOutDir, config.cacheDir, @@ -459,12 +463,12 @@ export async function _createServer( setClientErrorHandler(httpServer, config.logger) } - const watcher = chokidar.watch( - // config file dependencies and env file might be outside of root - // eslint-disable-next-line eqeqeq -- null means disabled - serverConfig.watch === null - ? [] - : [ + // 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), @@ -472,17 +476,10 @@ export async function _createServer( // 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 - } - await watcher.close() - } + + resolvedWatchOptions, + ) as FSWatcher) + : createNoopWatcher(resolvedWatchOptions) const environments: Record = {} diff --git a/packages/vite/src/node/watch.ts b/packages/vite/src/node/watch.ts index f1cf61ac11bd80..d47916d8874289 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 { WatchOptions } from 'dep-types/chokidar' -import picomatch from 'picomatch' +import type { FSWatcher, WatchOptions } from 'dep-types/chokidar' import type { OutputOptions } from 'rollup' import colors from 'picocolors' import { escapePath } from 'tinyglobby' @@ -49,14 +49,13 @@ export function resolveEmptyOutDir( } export function resolveChokidarOptions( - options: WatchOptions | null | undefined, + options: WatchOptions | undefined, resolvedOutDirs: Set, emptyOutDir: boolean, cacheDir: string, - isRollupChokidar3 = false, ): WatchOptions { const { ignored: ignoredList, ...otherOptions } = options ?? {} - let ignored: WatchOptions['ignored'] = [ + const ignored: WatchOptions['ignored'] = [ '**/.git/**', '**/node_modules/**', '**/test-results/**', // Playwright @@ -69,23 +68,6 @@ 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, @@ -95,3 +77,37 @@ 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 7be7ac356caa33..a3318b84c3d035 100644 --- a/packages/vite/src/types/chokidar.d.ts +++ b/packages/vite/src/types/chokidar.d.ts @@ -1,131 +1,219 @@ -// 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 - -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] +// Inlined to avoid extra dependency (chokidar is bundled in the published build) -type Path = string +// https://github.com/paulmillr/chokidar/blob/3.6.0/types/index.d.ts +// MIT Licensed https://github.com/paulmillr/chokidar/blob/3.6.0/LICENSE -// #endregion +/// -// #region index.d.ts +import type * as fs from 'node:fs' +import { EventEmitter } from 'node:events' +import type { Matcher } from './anymatch' -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 -} +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 -export type WatchOptions = Partial< - BasicOpts & { - ignored: Matcher | Matcher[] - awaitWriteFinish: boolean | Partial + /** + * 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[] } -> -export type FSWInstanceOptions = BasicOpts & { - ignored: Matcher[] - awaitWriteFinish: false | AWF -} + /** + * Removes all listeners from watched files. + */ + close(): Promise + + on( + event: 'add' | 'addDir' | 'change', + listener: (path: string, stats?: fs.Stats) => void, + ): this -export type EmitArgs = [EventName, Path | Error, any?, any?, any?] + on( + event: 'all', + listener: ( + eventName: 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir', + path: string, + stats?: fs.Stats, + ) => void, + ): this -export type MatchFunction = (val: string, stats?: Stats) => boolean + /** + * 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 -export interface MatcherObject { - path: string - recursive?: boolean + /** + * 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 } -export type Matcher = string | RegExp | MatchFunction | MatcherObject +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 -/** - * 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 + /** + * ([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 - constructor(_opts?: WatchOptions) + /** + * 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 /** - * Adds paths to be watched on an existing FSWatcher instance. - * @param paths_ file or file list. Other arguments are unused + * 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. */ - add(paths_: Path | Path[], _origAdd?: string, _internal?: boolean): FSWatcher + followSymlinks?: boolean + /** - * Close watchers or start ignoring events from specified paths. + * The base directory from which watch `paths` are to be derived. Paths emitted with events will + * be relative to this. */ - unwatch(paths_: Path | Path[]): FSWatcher + cwd?: string + /** - * Close watchers and remove all listeners from watched paths. + * 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 */ - close(): Promise + disableGlobbing?: boolean + + /** + * 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 + + /** + * 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 + + /** + * 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 + /** - * Expose list of watched paths - * @returns for chaining + * If set, limits how many levels of subdirectories will be traversed. */ - getWatched(): Record - emitWithAll(event: EventName, args: EmitArgs): void + depth?: number + + /** + * Interval of file system polling. + */ + interval?: number + + /** + * 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 + + /** + * 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 + + /** + * `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. + */ + atomic?: boolean | number + + /** + * can be set to an object in order to adjust timing params: + */ + awaitWriteFinish?: AwaitWriteFinishOptions | boolean +} + +export interface AwaitWriteFinishOptions { + /** + * Amount of time in milliseconds for a file size to remain constant before emitting its event. + */ + stabilityThreshold?: number + + /** + * File size polling interval. + */ + pollInterval?: number } /** - * 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') }) + * produces an instance of `FSWatcher`. */ -export declare function watch( - paths: string | string[], +export function watch( + paths: string | ReadonlyArray, 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 new file mode 100644 index 00000000000000..3b141c8fe9362f --- /dev/null +++ b/patches/chokidar@3.6.0.patch @@ -0,0 +1,14 @@ +diff --git a/lib/fsevents-handler.js b/lib/fsevents-handler.js +index fe29393c179d3d6673f996ca6f95bbc83f9a0699..0a341f3d6a2f1497486a23ea99b3c6da003c026f 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 103fbc73b07f07..f474f16801afc3 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -841,10 +841,6 @@ if (!isBuild) { () => 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 () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71dd2452846c51..4841818600d355 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,9 @@ overrides: packageExtensionsChecksum: 632c5477927702fb9badd3e595784820 patchedDependencies: + chokidar@3.6.0: + hash: r6f2jac4ef543toizf7sfnkaom + path: patches/chokidar@3.6.0.patch http-proxy@1.18.1: hash: qqiqxx62zlcu62nljjmhlvexni path: patches/http-proxy@1.18.1.patch @@ -229,6 +232,10 @@ importers: rollup: specifier: ^4.23.0 version: 4.24.4 + optionalDependencies: + fsevents: + specifier: ~2.3.3 + version: 2.3.3 devDependencies: '@ampproject/remapping': specifier: ^2.3.0 @@ -273,8 +280,8 @@ importers: specifier: ^6.7.14 version: 6.7.14 chokidar: - specifier: ^4.0.1 - version: 4.0.1 + specifier: ^3.6.0 + version: 3.6.0(patch_hash=r6f2jac4ef543toizf7sfnkaom) connect: specifier: ^3.7.0 version: 3.7.0 @@ -9484,7 +9491,7 @@ snapshots: check-error@2.1.1: {} - chokidar@3.6.0: + chokidar@3.6.0(patch_hash=r6f2jac4ef543toizf7sfnkaom): dependencies: anymatch: 3.1.3 braces: 3.0.3 @@ -12490,7 +12497,7 @@ snapshots: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 - chokidar: 3.6.0 + chokidar: 3.6.0(patch_hash=r6f2jac4ef543toizf7sfnkaom) didyoumean: 1.2.2 dlv: 1.1.3 fast-glob: 3.3.2