diff --git a/Cargo.lock b/Cargo.lock index 5277f37670c5..13420f32a53c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2420,6 +2420,7 @@ dependencies = [ "rspack_plugin_split_chunks_new", "rspack_plugin_swc_css_minimizer", "rspack_plugin_swc_js_minimizer", + "rspack_plugin_warn_sensitive_module", "rspack_plugin_wasm", "rspack_plugin_worker", "rspack_regex", @@ -3046,6 +3047,15 @@ dependencies = [ "swc_ecma_minifier", ] +[[package]] +name = "rspack_plugin_warn_sensitive_module" +version = "0.1.0" +dependencies = [ + "dashmap", + "rspack_core", + "rspack_error", +] + [[package]] name = "rspack_plugin_wasm" version = "0.1.0" @@ -3145,6 +3155,7 @@ dependencies = [ "rspack_plugin_library", "rspack_plugin_remove_empty_chunks", "rspack_plugin_runtime", + "rspack_plugin_warn_sensitive_module", "rspack_plugin_wasm", "rspack_regex", "rspack_tracing", diff --git a/crates/rspack_binding_options/Cargo.toml b/crates/rspack_binding_options/Cargo.toml index ce947b8f3017..da1180f1f63d 100644 --- a/crates/rspack_binding_options/Cargo.toml +++ b/crates/rspack_binding_options/Cargo.toml @@ -39,6 +39,7 @@ rspack_plugin_split_chunks = { path = "../rspack_plugin_split_chunk rspack_plugin_split_chunks_new = { path = "../rspack_plugin_split_chunks_new" } rspack_plugin_swc_css_minimizer = { path = "../rspack_plugin_swc_css_minimizer" } rspack_plugin_swc_js_minimizer = { path = "../rspack_plugin_swc_js_minimizer" } +rspack_plugin_warn_sensitive_module = { path = "../rspack_plugin_warn_sensitive_module" } rspack_plugin_wasm = { path = "../rspack_plugin_wasm" } rspack_plugin_worker = { path = "../rspack_plugin_worker" } rspack_regex = { path = "../rspack_regex" } diff --git a/crates/rspack_binding_options/src/options/mod.rs b/crates/rspack_binding_options/src/options/mod.rs index f8258fd9df1a..fd2c2786d7f6 100644 --- a/crates/rspack_binding_options/src/options/mod.rs +++ b/crates/rspack_binding_options/src/options/mod.rs @@ -178,6 +178,8 @@ impl RawOptionsApply for RawOptions { plugins.push(rspack_plugin_ensure_chunk_conditions::EnsureChunkConditionsPlugin.boxed()); + plugins.push(rspack_plugin_warn_sensitive_module::WarnCaseSensitiveModulesPlugin.boxed()); + Ok(Self::Options { context, mode, diff --git a/crates/rspack_ids/src/lib.rs b/crates/rspack_ids/src/lib.rs index b493887ea2e3..262dd706eb8e 100644 --- a/crates/rspack_ids/src/lib.rs +++ b/crates/rspack_ids/src/lib.rs @@ -3,7 +3,7 @@ mod deterministic_module_ids_plugin; pub use deterministic_module_ids_plugin::*; mod named_module_ids_plugin; pub use named_module_ids_plugin::*; -pub(crate) mod id_helpers; +pub mod id_helpers; mod named_chunk_ids_plugin; pub use named_chunk_ids_plugin::*; mod stable_named_chunk_ids_plugin; diff --git a/crates/rspack_plugin_warn_sensitive_module/Cargo.toml b/crates/rspack_plugin_warn_sensitive_module/Cargo.toml new file mode 100644 index 000000000000..080d47bc8540 --- /dev/null +++ b/crates/rspack_plugin_warn_sensitive_module/Cargo.toml @@ -0,0 +1,11 @@ +[package] +edition = "2021" +license = "MIT" +name = "rspack_plugin_warn_sensitive_module" +repository = "https://github.com/web-infra-dev/rspack" +version = "0.1.0" + +[dependencies] +dashmap = { workspace = true } +rspack_core = { path = "../rspack_core" } +rspack_error = { path = "../rspack_error" } diff --git a/crates/rspack_plugin_warn_sensitive_module/src/lib.rs b/crates/rspack_plugin_warn_sensitive_module/src/lib.rs new file mode 100644 index 000000000000..f0b54b612ada --- /dev/null +++ b/crates/rspack_plugin_warn_sensitive_module/src/lib.rs @@ -0,0 +1,96 @@ +// https://github.com/webpack/webpack/blob/main/lib/WarnCaseSensitiveModulesPlugin.js + +use std::collections::HashMap; + +use dashmap::DashSet; +use rspack_core::{Compilation, Logger, Module, ModuleGraph, Plugin}; +use rspack_error::Diagnostic; + +#[derive(Debug)] +pub struct WarnCaseSensitiveModulesPlugin; + +impl WarnCaseSensitiveModulesPlugin { + pub fn new() -> Self { + Self + } + + pub fn create_sensitive_modules_warning( + &self, + modules: &Vec<&&Box>, + graph: &ModuleGraph, + ) -> String { + let mut message = + format!("There are multiple modules with names that only differ in casing.\n"); + + for m in modules { + let mut module_msg = format!(" - {}\n", m.identifier().to_string()); + graph.get_incoming_connections(m).iter().for_each(|c| { + if let Some(original_identifier) = c.original_module_identifier.clone() { + module_msg.push_str(&format!(" - used by {}\n", original_identifier)); + } + }); + message.push_str(&module_msg); + } + + message + } +} + +// This Plugin warns when there are case sensitive modules in the compilation +// which can cause unexpected behavior when deployed on a case-insensitive environment +// it is executed in hook `compilation.seal` +impl Plugin for WarnCaseSensitiveModulesPlugin { + fn name(&self) -> &'static str { + "rspack.WarnCaseSensitiveModulesPlugin" + } + + fn module_ids(&self, compilation: &mut Compilation) -> rspack_error::Result<()> { + let logger = compilation.get_logger(self.name()); + let start = logger.time("check case sensitive modules"); + let diagnostics: DashSet = DashSet::default(); + let modules = compilation + .module_graph + .modules() + .values() + .collect::>(); + let mut module_without_case_map: HashMap>> = + HashMap::new(); + + for module in modules { + // Ignore `data:` URLs, because it's not a real path + if let Some(normal_module) = module.as_normal_module() { + if normal_module + .resource_resolved_data() + .encoded_content + .is_some() + { + continue; + } + } + + let identifier = module.identifier().to_string(); + let lower_identifier = identifier.to_lowercase(); + let lower_map = module_without_case_map + .entry(lower_identifier) + .or_insert(HashMap::new()); + lower_map.insert(identifier, module); + } + + for lower_map in module_without_case_map.values() { + if lower_map.len() > 1 { + let mut modules = lower_map.values().collect::>(); + modules.sort_by_key(|m| m.identifier()); + diagnostics.insert(Diagnostic::warn( + "Sensitive Modules Warn".to_string(), + self.create_sensitive_modules_warning(&modules, &compilation.module_graph), + 0, + 0, + )); + } + } + + compilation.push_batch_diagnostic(diagnostics.into_iter().collect()); + logger.time_end(start); + Ok(()) + } +} diff --git a/crates/rspack_testing/Cargo.toml b/crates/rspack_testing/Cargo.toml index 1108ad5e5c6c..a1b4d4cb41ba 100644 --- a/crates/rspack_testing/Cargo.toml +++ b/crates/rspack_testing/Cargo.toml @@ -35,6 +35,7 @@ rspack_plugin_json = { path = "../rspack_plugin_json" } rspack_plugin_library = { path = "../rspack_plugin_library" } rspack_plugin_remove_empty_chunks = { path = "../rspack_plugin_remove_empty_chunks" } rspack_plugin_runtime = { path = "../rspack_plugin_runtime" } +rspack_plugin_warn_sensitive_module = { path = "../rspack_plugin_warn_sensitive_module" } rspack_plugin_wasm = { path = "../rspack_plugin_wasm" } rspack_regex = { path = "../rspack_regex" } rspack_tracing = { path = "../rspack_tracing" } diff --git a/crates/rspack_testing/src/test_config.rs b/crates/rspack_testing/src/test_config.rs index 134486a931a3..00f50a399e89 100644 --- a/crates/rspack_testing/src/test_config.rs +++ b/crates/rspack_testing/src/test_config.rs @@ -555,6 +555,8 @@ impl TestConfig { // Notice the plugin need to be placed after SplitChunksPlugin plugins.push(rspack_plugin_remove_empty_chunks::RemoveEmptyChunksPlugin.boxed()); + plugins.push(rspack_plugin_warn_sensitive_module::WarnCaseSensitiveModulesPlugin.boxed()); + plugins.push(rspack_plugin_javascript::InferAsyncModulesPlugin {}.boxed()); if self.experiments.async_web_assembly { plugins.push(rspack_plugin_wasm::FetchCompileAsyncWasmPlugin {}.boxed()); diff --git a/webpack-test/cases/errors/case-sensitive/test.filter.js b/webpack-test/cases/errors/case-sensitive/test.filter.js index 0a55747a09fd..d5f8fc3ecd0e 100644 --- a/webpack-test/cases/errors/case-sensitive/test.filter.js +++ b/webpack-test/cases/errors/case-sensitive/test.filter.js @@ -6,5 +6,5 @@ module.exports = function(config) { return fs.existsSync(path.join(__dirname, "TEST.FILTER.JS")); }; */ -module.exports = () => {return "https://github.com/web-infra-dev/rspack/issues/4347"} +module.exports = () => true \ No newline at end of file