vite: Cannot watch specific dependencies in node_modules

Describe the bug

The documentation for server.watch contains the following example:

server: {
    watch: {
        ignored: ['!**/node_modules/your-package-name/**']
    }
},

This example does not work. It appears that the builtin **/node_modules/** exclude causes chokidar to not even look in node_modules despite the negation in the subdirectory.

It appears this was originally tested (see #5023 and #5239) with ignored: ['!**/node_modules/**']. This does work, but in a real project will almost immediately result in Error: ENOSPC: System limit for number of file watchers reached.

See https://github.com/paulmillr/chokidar/issues/1225. I played with various chokidar options but I couldn’t see a way to achieve this.

Reproduction

See chokidar issue.

System Info

System:
    OS: Linux 5.4 Linux Mint 20.3 (Una)
    CPU: (12) x64 AMD Ryzen 5 2600 Six-Core Processor
    Memory: 4.20 GB / 15.56 GB
    Container: Yes
    Shell: 5.0.17 - /bin/bash
  Binaries:
    Node: 16.15.1 - /usr/bin/node
    npm: 8.1.1 - ~/npm/bin/npm
  Browsers:
    Chrome: 102.0.5005.61
    Firefox: 101.0
  npmPackages:
    vite: ^2.9.12 => 2.9.12

Used Package Manager

npm

Logs

No response

Validations

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 31
  • Comments: 24 (4 by maintainers)

Commits related to this issue

Most upvoted comments

Thanks, that’s very helpful as a temporary workaround until chokidar provides an official recommendation of how to fix this properly.

To preempt anyone who tries to close this:

  1. I should not have to add a plugin to include/exclude files, especially since there is an option already for this - it just doesn’t work
  2. Negative regex lookaheads are absolutely not acceptable as a long term solution

Just packaged up @ryzr’s awesome little snippet into something reusable where you can also list multiple modules if you want 🚀

import { ViteDevServer } from 'vite';

export function pluginWatchNodeModules(modules) {
	// Merge module into pipe separated string for RegExp() below.
	let pattern = `/node_modules\\/(?!${modules.join('|')}).*/`;
	return {
		name: 'watch-node-modules',
		configureServer: (server: ViteDevServer) : void => {
			server.watcher.options = {
				...server.watcher.options,
				ignored: [
					new RegExp(pattern),
					'**/.git/**',
				]
			}
		}
	}
}

Then to use it, pass into your plugins array like so:

// Import from the separate file you might store this in, e.g. 'utils'
import { pluginWatchNodeModules } from './utils';

plugins: [
	// ... other plugins...
	pluginWatchNodeModules(['your-plugin', 'another-example']),
],

Edit: p.s. Don’t forget to ensure that you exclude these packages from optimizeDeps like so:

optimizeDeps: {
	exclude: [
		'your-plugin',
		'another-example',
	],
},

Similar to https://github.com/vitejs/vite/issues/6718, it would be nice to exclude locally linked packages from being ignored by default. When I make a change in a sub-dependency, I want the bundle to rebuild.

@SystemParadox

If it’s any help, creating a custom plugin to override the vite-enforced watch options seems to have worked for me

{
    name: 'watch-node-modules',
    configureServer: (server: ViteDevServer) : void => {
        server.watcher.options = {
            ...server.watcher.options,
            ignored: [
                /node_modules\/(?!my-package-name).*/,
                '**/.git/**',
            ]
        }
    }
}

I currently can’t use vite because of this issue. I have a monorepo, where I build dependencies separately. but vite doesn’t detect changes to them and there seems to be no way of making it detect them. The trick with the plugin by @bluwy didn’t work for me either.

The issue with using optimizeDeps.exclude for this is that this also excludes deps of that package from vite’s esm/cjs interop magic. So you then have to run through and include a bunch of your deps-of-deps in optimizeDeps.include to re-opt them in to esm/cjs interop. (@quyle92 that’s probably the issue you’re running into.)

I don’t know if something changed in the last month but @patricknelson snippet did not work for me. Been banging my head against this for hours but finally saw this in docs and so tried this:

import type { PluginOption } from 'vite';

export function watchNodeModules(modules: string[]): PluginOption {
  return {
    name: 'watch-node-modules',
    config() {
      return {
        server: {
          watch: {
            ignored: modules.map((m) => `!**/node_modules/${m}/**`),
          },
        },
      };
    },
  };
}

And it worked! The other “gotcha” I wanted to call out is that if you need to include a dep of your excluded dep, you need to include EXACTLY what is in your import line. For me it was react-icons and I wasted a few hours until I realized I had to include react-icons/fi/index.js because that is what was in the import line in my esm package.

@bluwy good thought but alas no, swapping the order doesn’t help:

ignored: [
    '!**/node_modules/foo/**',
    '**/node_modules/**',
],

Chokidar still seems to ignore the whole of node_modules and doesn’t bother looking inside it.

Ah bummer, I really need it with vite serve.

Just packaged up @ryzr’s awesome little snippet into something reusable where you can also list multiple modules if you want 🚀

import { ViteDevServer } from 'vite';

export function pluginWatchNodeModules(modules) {
	// Merge module into pipe separated string for RegExp() below.
	let pattern = `/node_modules\\/(?!${modules.join('|')}).*/`;
	return {
		name: 'watch-node-modules',
		configureServer: (server: ViteDevServer) : void => {
			server.watcher.options = {
				...server.watcher.options,
				ignored: [
					new RegExp(pattern),
					'**/.git/**',
				]
			}
		}
	}
}

Then to use it, pass into your plugins array like so:

// Import from the separate file you might store this in, e.g. 'utils'
import { pluginWatchNodeModules } from './utils';

plugins: [
	// ... other plugins...
	pluginWatchNodeModules(['your-plugin', 'another-example']),
],

Edit: p.s. Don’t forget to ensure that you exclude these packages from optimizeDeps like so:

optimizeDeps: {
	exclude: [
		'your-plugin',
		'another-example',
	],
},

Was unable to get it working until I added server.watcher._userIgnored = undefined picked from chokidar source (when configureServer is called, server.watcher is already instanciated).

export function pluginWatchNodeModules (modules) {
  return {
    name: 'watch-node-modules',
    configureServer: (server) => {
      const regexp = `/node_modules\\/(?!${modules.join('|')}).*/`
        server.watcher.options = {
          ...server.watcher.options,
          ignored: [
            '**/.git/**',
            '**/test-results/**',
            new RegExp(regexp)
          ]
        }
        server.watcher._userIgnored = undefined
    },
    config () {
      return {
        optimizeDeps: {
          exclude: modules
       }
      }
    }
  }
}

@quyle92 you can read package’s dependencies and put them to vite optimizeDeps. include

// vite.config.js

import path from 'path'
import {readFileSync} from 'fs'

export function watchNodeModules(modules) {
  return {
    name: 'watch-node-modules',
    config() {
      return {
        server: {
          watch: {
            ignored: modules.map((m) => `!**/node_modules/${m}/**`),
          },
        },
        optimizeDeps: {
          exclude: modules,
          include:modules.reduce((totalIncludes,m)=>{
            const url=path.join(process.cwd(), `node_modules/${m}/package.json`)
            const source = readFileSync(url, 'utf-8');
            const pkg = JSON.parse(source);
            const dependencies=pkg.dependencies
            // https://vitejs.dev/config/dep-optimization-options.html#optimizedeps-exclude
            const include=Object.keys(dependencies).reduce((includes,d)=>{
              // remove types package
              if(d.includes('@types')){
                return includes
              }
              includes.push(`${m} > ${d}`)
              return includes
            },[])
            totalIncludes=[...new Set([...totalIncludes,...include])]
            return totalIncludes
          },[]),
        },
      };
    },
  };
}

This throws error does not provide an export named ‘default’

Hi @TheTedAdams , Thanks for your reply. If I add optimizeDeps: { exclude: modules, }, vite failed to build my app and throw error at node_modules/uncontrollable/lib/esm/utils.js with message being Uncaught SyntaxError: ambiguous indirect export: default.