vite icon indicating copy to clipboard operation
vite copied to clipboard

Cannot watch specific dependencies in node_modules

Open SystemParadox opened this issue 2 years ago • 39 comments

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

SystemParadox avatar Jun 16 '22 11:06 SystemParadox

Vite's document seems to be wrong (or there was a behavior change on chokidar side?). See #7850.

sapphi-red avatar Jun 16 '22 12:06 sapphi-red

I also wonder if the order matters:

https://github.com/vitejs/vite/blob/d402ad3ae2b724fde7702eb255af502db7ca94d4/packages/vite/src/node/server/index.ts#L284-L288

say if we move the user-specified ones at the top before **/node_modules/** 🤔

bluwy avatar Jun 17 '22 05:06 bluwy

@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.

SystemParadox avatar Jun 18 '22 22:06 SystemParadox

@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/**',
            ]
        }
    }
}

ryzr avatar Jun 30 '22 04:06 ryzr

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

SystemParadox avatar Jun 30 '22 09:06 SystemParadox

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.

MadLittleMods avatar Sep 16 '22 18:09 MadLittleMods

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.

VanCoding avatar Dec 26 '22 11:12 VanCoding

The workaround above using a custom plugin doesn't work for a plain vite.build({ watch: true }) because the configureServer hook never gets called when you're not using the dev server.

I tried using the options universal hook to replace inputOptions.watch.chokidar.ignored as desired but doesn't seem to have any effect on what is actually watched/ignored.

Custom Vite plugin (doesn't work):

{
  name: 'watch-node-modules',
  options(inputOptions) {
    inputOptions.watch.chokidar.ignored = [
      /node_modules\/(?!hydrogen-view-sdk).*/,
      '**/.git/**',
    ];
    return inputOptions;
  }
}

When using Vite, inputOptions.watch.chokidar.ignored is normally:

[
  '**/.git/**',
  '**/node_modules/**',
  '**/test-results/**',
  '/home/eric/Documents/github/element/matrix-public-archive/node_modules/.vite/**'
]

MadLittleMods avatar May 27 '23 00:05 MadLittleMods

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',
	],
},

patricknelson avatar Jun 06 '23 22:06 patricknelson

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.

TheTedAdams avatar Jul 28 '23 04:07 TheTedAdams

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.

Many thanks. It works, but I need to Ctrl + S my vite.config.js (I am using react in my laravel codebase) to make my app load again to see changes in the excluded package. Is there any way that make vite automatically reload without manually saving vite.config.js again?

quyle92 avatar Aug 31 '23 05:08 quyle92

@quyle92 you may be running into cache stuff. Are you also listing your package in optimizeDeps.exclude? This is the final version of plugin I've been using:

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}/**`),
          },
        },
        optimizeDeps: {
          exclude: modules,
        },
      };
    },
  };
}

TheTedAdams avatar Sep 06 '23 05:09 TheTedAdams

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.

quyle92 avatar Sep 06 '23 08:09 quyle92

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.)

jfirebaugh avatar Oct 20 '23 20:10 jfirebaugh

I added the plugin in https://github.com/vitejs/vite/issues/8619#issuecomment-1707700396 and vite updates my linked package when I save the vite config but it still doesn't update when I change the source code. I have resolve.preserveSymlinks: true enabled. Any suggestions for how I can watch a linked package?

domoritz avatar Nov 26 '23 17:11 domoritz

English is not my native language, and there may be grammar errors in the following content. Please understand.

I tried this method, but not work.(https://github.com/antfu/vite-plugin-restart/issues/10) This is my method. Create a new .env file for the root path.

# .env
VITE_CHANGE_KEY=anything

and use node to change this file

// generateId.js
import { readFile, writeFile } from 'fs';

readFile('./.env', 'utf-8', (err, contents) => {
  if (err) {
    console.error(err);
    process.exit(1);
    return;
  }
  contents = `VITE_CHANGE_KEY=${Math.random()}`;
  writeFile('./.env', contents, 'utf-8', (err) => {
    if (err) {
      console.log(err);
    } else {
      console.log(contents);
    }
  });
});

Because Vite will observe changes in the. env file and then re-run. (https://vitejs.dev/guide/dep-pre-bundling.html#caching) image So, when your specific dependencies have changed, you can run node generateId.js. As for how to know if the dependency has changed, you can use nodemon

// nodemon.json
{
    "exec": "npm run *** && npm run generate-id", // generate-id just scripts command => 'node generateId.js'
}

It's stupid, but it works.

TY-LIU avatar Dec 07 '23 10:12 TY-LIU

@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
          },[]),
        },
      };
    },
  };
}

AttackXiaoJinJin avatar Dec 30 '23 04:12 AttackXiaoJinJin

@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'

sabarnix avatar Jan 19 '24 14:01 sabarnix

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
       }
      }
    }
  }
}

molaux avatar Feb 03 '24 15:02 molaux

Mb anyone worked with nuxt 3 Fix of @AttackXiaoJinJin work with small changes, but SSR updated only one time, to fix this we add to nuxt.config.ts

{
...
  nitro: {
    devServer: {
      watch: [...absoluteResolvedPackagePaths]
    }
  }
...
}

Kolobok12309 avatar Mar 20 '24 13:03 Kolobok12309

Did work for me:

function watchPackages(packageNames) {
  let isWatching = false;

  return {
    name: 'vite-plugin-watch-packages',

    buildStart() {
      if (!isWatching) {
        packageNames.forEach((packageName) => {
          const absPackagePath = path.resolve('node_modules', packageName);
          const realPackagePath = fs.realpathSync(absPackagePath);

          this.addWatchFile(realPackagePath);
        });

        isWatching = true;
      }
    },
  };
}

// in vite.config.js
{
  plugins: [watchPackages(['dayjs', 'foo/bar', '@some-scoped-package/utils'])]
}

But it works only for build --watch mode. Linked packages (npm link/npm i <../../your/fs/path>) will work too.

acupofspirt avatar Mar 26 '24 09:03 acupofspirt

Thanks for sharing @acupofspirt! This still doesn't seem to work for me in https://github.com/vega/editor if I link https://github.com/vega/vega-lite. I run yarn watch in Vega-Lite and make a change in the code but the editor does not refresh. Any idea what might be wrong?

domoritz avatar Mar 26 '24 20:03 domoritz

@domoritz It will work only for build --watch. I've updated my comment. Sorry for the confusion 😔

acupofspirt avatar Mar 27 '24 00:03 acupofspirt

Ah bummer, I really need it with vite serve.

domoritz avatar Mar 27 '24 01:03 domoritz

If it helps, it works with npx vite without any custom scripts when re-saving the vite config file while force is true

in the config: optimizeDeps: { force: true }, or if the command is started with: --force

HugoMcPhee avatar May 03 '24 15:05 HugoMcPhee

#6718

Hi @acupofspirt,

I tried using this plugin. It's great and worked well for the files inside the src or the root but not in the module graph.

But it did not work for the files inside the node_modules.

I did use the build --watch mode. I also tried to print the path the files it added, and the path to the files were valid.

May I know your vite version?

Thanks!

yzy415 avatar May 08 '24 14:05 yzy415

#6718

Hi @acupofspirt,

I tried using this plugin. It's great and worked well for the files inside the src or the root but not in the module graph.

But it did not work for the files inside the node_modules.

I did use the build --watch mode. I also tried to print the path the files it added, and the path to the files were valid.

May I know your vite version?

Thanks!

Any file of other projects inside the node_modules can't be watched expectedly either. It's weird.

yzy415 avatar May 08 '24 15:05 yzy415

Hi, is there anyone still care about it? ALL workarounds above not work for me, that's quite irritating... I am spending the whole night for this thing that shouldn't happen at all.

JiaJiaJiang avatar May 26 '24 18:05 JiaJiaJiang

Hi, is there anyone still care about it?

✋ me for sure. It's been bugging my team for a while.

domoritz avatar May 27 '24 15:05 domoritz

I dont use command "vite", which does not reflect changes in node_modules correctly. Instead I use these scripts:

 "preview": "vite preview",
 "build-watch": "vite build --watch",
 "dev": "concurrently \"npm:build-watch\" \"npm:preview\""

This checks for all changes (even in node_modules) and restarts the dev server. It is triggered by changes in my linked package without problem.

maylow22 avatar May 27 '24 21:05 maylow22