Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Cannot watch specific dependencies in node_modules #8619

Open
7 tasks done
SystemParadox opened this issue Jun 16, 2022 · 27 comments
Open
7 tasks done

Cannot watch specific dependencies in node_modules #8619

SystemParadox opened this issue Jun 16, 2022 · 27 comments
Labels
p2-edge-case Bug, but has workaround or limited in scope (priority)

Comments

@SystemParadox
Copy link

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 paulmillr/chokidar#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

@sapphi-red
Copy link
Member

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

@bluwy
Copy link
Member

bluwy commented Jun 17, 2022

I also wonder if the order matters:

ignored: [
'**/node_modules/**',
'**/.git/**',
...(Array.isArray(ignored) ? ignored : [ignored])
],

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

@SystemParadox
Copy link
Author

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

@ryzr
Copy link

ryzr commented Jun 30, 2022

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

@SystemParadox
Copy link
Author

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

@bluwy bluwy added bug p2-edge-case Bug, but has workaround or limited in scope (priority) and removed pending triage labels Jul 1, 2022
@MadLittleMods
Copy link
Contributor

MadLittleMods commented Sep 16, 2022

Similar to #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.

nvh95 added a commit to nvh95/vitest-preview that referenced this issue Oct 22, 2022
@VanCoding
Copy link

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.

@MadLittleMods
Copy link
Contributor

MadLittleMods commented May 27, 2023

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 added a commit to MadLittleMods/vite that referenced this issue May 27, 2023
The current `server.watch` docs aren't accurate and the solution
proposed just doesn't work. See vitejs#8619

There is a different workaround in the issue using a custom Vite plugin
if you're using the Vite dev server, vitejs#8619 (comment)
MadLittleMods added a commit to MadLittleMods/vite that referenced this issue May 27, 2023
The current `server.watch` docs aren't accurate and the solution
proposed just doesn't work. See vitejs#8619

There is a different workaround in the issue using a custom Vite plugin
if you're using the Vite dev server, vitejs#8619 (comment)
MadLittleMods added a commit to MadLittleMods/vite that referenced this issue May 27, 2023
The current `server.watch` docs aren't accurate and the solution
proposed just doesn't work. See vitejs#8619

There is a different workaround in the issue using a custom Vite plugin
if you're using the Vite dev server, vitejs#8619 (comment)
@patricknelson
Copy link

patricknelson commented Jun 6, 2023

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

@TheTedAdams
Copy link

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.

@quyle92
Copy link

quyle92 commented Aug 31, 2023

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?

@TheTedAdams
Copy link

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

@quyle92
Copy link

quyle92 commented Sep 6, 2023

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.

@jfirebaugh
Copy link

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

@domoritz
Copy link

I added the plugin in #8619 (comment) 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?

@TY-LIU
Copy link

TY-LIU commented Dec 7, 2023

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.(antfu/vite-plugin-restart#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.

@AttackXiaoJinJin
Copy link

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

@sabarnix
Copy link

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

@molaux
Copy link

molaux commented Feb 3, 2024

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

@patak-dev patak-dev removed the bug label Feb 10, 2024
@Kolobok12309
Copy link

Kolobok12309 commented Mar 20, 2024

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

@acupofspirt
Copy link

acupofspirt commented Mar 26, 2024

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.

@domoritz
Copy link

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?

@acupofspirt
Copy link

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

@domoritz
Copy link

Ah bummer, I really need it with vite serve.

@HugoMcPhee
Copy link

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

@yzy415
Copy link

yzy415 commented May 8, 2024

#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
Copy link

yzy415 commented May 8, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p2-edge-case Bug, but has workaround or limited in scope (priority)
Projects
None yet
Development

No branches or pull requests