diff --git a/src/cache.rs b/src/cache.rs index 81252119..5a90a1e0 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -278,6 +278,11 @@ impl CachedPathImpl { ctx: &mut Ctx, ) -> Option { if let Some(nm) = self.node_modules.get() { + // Replay ctx tracking from the cold path: module_directory -> is_dir calls + // ctx.add_missing_dependency when node_modules doesn't exist on disk. + if nm.is_none() { + ctx.add_missing_dependency(self.path.join("node_modules")); + } return nm.clone(); } self diff --git a/src/tests/dependencies.rs b/src/tests/dependencies.rs index 4ba5fa3d..be9fe04f 100644 --- a/src/tests/dependencies.rs +++ b/src/tests/dependencies.rs @@ -1,5 +1,60 @@ //! https://github.com/webpack/enhanced-resolve/blob/main/test/dependencies.test.js +// Warm-cache calls must report the same missing_dependencies as the cold path so that +// webpack/rspack watchers can track non-existent node_modules dirs across multiple resolves. +#[cfg(not(target_os = "windows"))] +mod warm_cache_missing_dependencies { + use std::path::PathBuf; + + use super::super::memory_fs::MemoryFS; + use crate::{ResolveContext, ResolveOptions, ResolverGeneric}; + + fn file_system() -> MemoryFS { + MemoryFS::new(&[ + ("/a/b/c/some.js", ""), // makes /a/b/c and /a/b real dirs; neither has node_modules + ("/a/node_modules/module/index.js", ""), + ]) + } + + #[tokio::test] + async fn node_modules_missing_deps_same_on_warm_cache() { + let resolver = ResolverGeneric::::new_with_file_system( + file_system(), + ResolveOptions { + extensions: vec![".js".into()], + ..ResolveOptions::default() + }, + ); + + let path = PathBuf::from("/a/b/c"); + + let mut ctx_cold = ResolveContext::default(); + let cold = resolver + .resolve_with_context(path.clone(), "module", &mut ctx_cold) + .await; + + let mut ctx_warm = ResolveContext::default(); + let warm = resolver + .resolve_with_context(path.clone(), "module", &mut ctx_warm) + .await; + + assert_eq!(cold.map(|r| r.full_path()), warm.map(|r| r.full_path())); + + assert_eq!( + ctx_cold.missing_dependencies, ctx_warm.missing_dependencies, + "cold: {:?}\nwarm: {:?}", + ctx_cold.missing_dependencies, ctx_warm.missing_dependencies, + ); + + // enhanced-resolve lists traversed-but-absent node_modules dirs in missingDependencies + for dir in ["/a/b/c/node_modules", "/a/b/node_modules"] { + assert!(ctx_warm + .missing_dependencies + .contains(&PathBuf::from(dir).into())); + } + } +} + #[cfg(not(target_os = "windows"))] // MemoryFS's path separator is always `/` so the test will not pass in windows. mod windows { use std::path::PathBuf;