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 · 38 comments
Open
7 tasks done

Cannot watch specific dependencies in node_modules #8619

SystemParadox opened this issue Jun 16, 2022 · 38 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.

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

@JiaJiaJiang
Copy link

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.

@domoritz
Copy link

Hi, is there anyone still care about it?

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

@maylow22
Copy link

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.

@domoritz
Copy link

Oh nice. How fast is that?

I switched to parcel which seems to work with watching deps if I set --watch-dir ...

@domoritz
Copy link

Hmm, I tried your suggestion @maylow22 but vite doesn't seem to rebuild when I change the sources in my linked dependency. Do you have an example repo I can try or a specific config/plugin?

@maylow22
Copy link

It does not listen for changes in your source code, but rather in the built package. Therefore, I have a watcher script in a linked package that rebuilds the linked package (shared library) whenever a change in the source code occurs:

 "watch": "vite build --watch"

After this watcher finishes its build, another watcher in the main app triggers a build in the main app. I don't claim it's a perfect solution, but it works. It is slower than restarting a development server, and since it performs a production build, it might not be ideal.

sim31 added a commit to sim31/frapps that referenced this issue Jun 24, 2024
Tried to solve the same problem for gui. Currently you have to re-save
the vite.config.ts file in order for dev server to rebuild with changed
dependencies. Had to add force flag and optimizeDeps.force: true to
vite.config.ts to make this work. This problem is bugging a lot of vite
users: vitejs/vite#8619 .

Also refactored clientToNodeTransformer to not depend on z.instanceof,
because it is not reliable...
@wangrongding
Copy link

Waiting for a best practice.🤖

@SystemParadox
Copy link
Author

Chokidar v4 is going to drop support for globs altogether (see paulmillr/chokidar#1225). So this is now firmly in vite's arena. Vite now has the opportunity to show how this can be done right and hopefully other tools will follow vite's example.

As far as I can work out, for any kind of include/exclude system to avoid this problem you need 3 options:

  • include
  • exclude
  • forceInclude

This works pretty much as it does now, exclude would default to node_modules/**, and include defaults to **. But the user can also specify forceInclude to override things that are excluded.

Different naming or other combinations of 3 options may be possible (I considered defaultExclude, include and exclude but that doesn't work so well), but you need minimum 3 or you just end up with this problem where you can't include things within node_modules sanely.

Obviously there are more complicated options which would also work, such as an ordered list of entries with type: include/exclude against each of them.

@dominikg
Copy link
Contributor

The workaround with patching watcher options in configureServer can be improved a little by replacing the existing node_modules ignore, so that other custom ignores are preserved:

function pluginWatchNodeModules(modules) {
	let pattern = `/node_modules\\/(?!${modules.join('|')}).*/`;
	return {
		name: 'watch-node-modules',
		configureServer: (server: ViteDevServer) : void => {
			const node_modules_i = server.watcher.options.ignored.indexOf('**/node_modules/**');
			server.watcher.options.ignored.splice(node_modules_i,1,new RegExp(pattern));
			server.watcher._userIgnored=undefined;
		},
	}
}

This does give you file change events for files in the modules listed and triggers vite hmr, however plugins handleHotUpdate hook can get called with an incomplete context if you are using pnpm due to a mismatch between the symlink and real path of the module inside node_modules.

For that to work, vite needs to update

export async function handleHMRUpdate(
to use the real path of file before finding affected modules.

@lizyChy0329
Copy link

lizyChy0329 commented Aug 22, 2024

If you are creating with UI in vite, maybe you want vite build -w --mode lib to the lib directory and debug from vite CLI

Then you will frustrated to find that the page doesn't find the latest lib! But you can try it as follows:

  • add --force flag for scripts-dev in package.json
  • add optimizeDeps.exclude = [<your-package>] in vite.config.ts
  • add server.watch.ignored= ['!**/dist/**'] in vite.config.ts

It's work on my case

example

@zhangHongEn
Copy link

zhangHongEn commented Aug 31, 2024

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

MAC is successful, Windows is invalid
Does vite accept pull requests to fix this issue first?

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