Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ rspack_napi_macros = { version = "=0.7.0-beta.0", path = "cr
rspack_paths = { version = "=0.7.0-beta.0", path = "crates/rspack_paths", default-features = false }
rspack_plugin_asset = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_asset", default-features = false }
rspack_plugin_banner = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_banner", default-features = false }
rspack_plugin_case_sensitive = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_case_sensitive", default-features = false }
rspack_plugin_circular_dependencies = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_circular_dependencies", default-features = false }
rspack_plugin_copy = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_copy", default-features = false }
rspack_plugin_css = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_css", default-features = false }
Expand Down Expand Up @@ -233,7 +234,6 @@ rspack_plugin_size_limits = { version = "=0.7.0-beta.0", path = "cr
rspack_plugin_split_chunks = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_split_chunks", default-features = false }
rspack_plugin_sri = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_sri", default-features = false }
rspack_plugin_swc_js_minimizer = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_swc_js_minimizer", default-features = false }
rspack_plugin_warn_sensitive_module = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_warn_sensitive_module", default-features = false }
rspack_plugin_wasm = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_wasm", default-features = false }
rspack_plugin_web_worker_template = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_web_worker_template", default-features = false }
rspack_plugin_worker = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_worker", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion crates/node_binding/napi-binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ export declare enum BuiltinPluginName {
RealContentHashPlugin = 'RealContentHashPlugin',
RemoveEmptyChunksPlugin = 'RemoveEmptyChunksPlugin',
EnsureChunkConditionsPlugin = 'EnsureChunkConditionsPlugin',
WarnCaseSensitiveModulesPlugin = 'WarnCaseSensitiveModulesPlugin',
CaseSensitivePlugin = 'CaseSensitivePlugin',
DataUriPlugin = 'DataUriPlugin',
FileUriPlugin = 'FileUriPlugin',
RuntimePlugin = 'RuntimePlugin',
Expand Down
2 changes: 1 addition & 1 deletion crates/rspack_binding_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ rspack_loader_testing = { workspace = true }
rspack_napi_macros = { workspace = true }
rspack_plugin_asset = { workspace = true }
rspack_plugin_banner = { workspace = true }
rspack_plugin_case_sensitive = { workspace = true }
rspack_plugin_circular_dependencies = { workspace = true }
rspack_plugin_copy = { workspace = true }
rspack_plugin_css = { workspace = true }
Expand Down Expand Up @@ -115,7 +116,6 @@ rspack_plugin_size_limits = { workspace = true }
rspack_plugin_split_chunks = { workspace = true }
rspack_plugin_sri = { workspace = true }
rspack_plugin_swc_js_minimizer = { workspace = true }
rspack_plugin_warn_sensitive_module = { workspace = true }
rspack_plugin_wasm = { workspace = true }
rspack_plugin_web_worker_template = { workspace = true }
rspack_plugin_worker = { workspace = true }
Expand Down
8 changes: 4 additions & 4 deletions crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use rspack_ids::{
};
use rspack_plugin_asset::AssetPlugin;
use rspack_plugin_banner::BannerPlugin;
use rspack_plugin_case_sensitive::CaseSensitivePlugin;
use rspack_plugin_circular_dependencies::CircularDependencyRspackPlugin;
use rspack_plugin_copy::{CopyRspackPlugin, CopyRspackPluginOptions};
use rspack_plugin_css::CssPlugin;
Expand Down Expand Up @@ -94,7 +95,6 @@ use rspack_plugin_schemes::{DataUriPlugin, FileUriPlugin};
use rspack_plugin_size_limits::SizeLimitsPlugin;
use rspack_plugin_sri::{SubresourceIntegrityPlugin, SubresourceIntegrityPluginOptions};
use rspack_plugin_swc_js_minimizer::SwcJsMinimizerRspackPlugin;
use rspack_plugin_warn_sensitive_module::WarnCaseSensitiveModulesPlugin;
use rspack_plugin_wasm::{
AsyncWasmPlugin, FetchCompileAsyncWasmPlugin, enable_wasm_loading_plugin,
};
Expand Down Expand Up @@ -183,7 +183,7 @@ pub enum BuiltinPluginName {
RealContentHashPlugin,
RemoveEmptyChunksPlugin,
EnsureChunkConditionsPlugin,
WarnCaseSensitiveModulesPlugin,
CaseSensitivePlugin,
DataUriPlugin,
FileUriPlugin,
RuntimePlugin,
Expand Down Expand Up @@ -545,8 +545,8 @@ impl<'a> BuiltinPlugin<'a> {
BuiltinPluginName::EnsureChunkConditionsPlugin => {
plugins.push(EnsureChunkConditionsPlugin::default().boxed())
}
BuiltinPluginName::WarnCaseSensitiveModulesPlugin => {
plugins.push(WarnCaseSensitiveModulesPlugin::default().boxed())
BuiltinPluginName::CaseSensitivePlugin => {
plugins.push(CaseSensitivePlugin::default().boxed())
}
BuiltinPluginName::DataUriPlugin => plugins.push(DataUriPlugin::default().boxed()),
BuiltinPluginName::FileUriPlugin => plugins.push(FileUriPlugin::default().boxed()),
Expand Down
31 changes: 1 addition & 30 deletions crates/rspack_core/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ mod rebuild;
use std::sync::{Arc, atomic::AtomicU32};

use futures::future::join_all;
use itertools::Itertools;
use rspack_error::Result;
use rspack_fs::{IntermediateFileSystem, NativeFileSystem, ReadableFileSystem, WritableFileSystem};
use rspack_hook::define_hook;
use rspack_paths::{Utf8Path, Utf8PathBuf};
use rspack_sources::BoxSource;
use rspack_tasks::{CompilerContext, within_compiler_context};
use rspack_util::{node_path::NodePath, tracing_preset::TRACING_BENCH_TARGET};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use rustc_hash::FxHashMap as HashMap;
use tracing::instrument;

pub use self::rebuild::CompilationRecords;
Expand Down Expand Up @@ -386,34 +385,6 @@ impl Compiler {
.call(&mut self.compilation)
.await?;

// Check for case-sensitive conflicts before emitting assets
// Only check for filenames that differ in casing (not query strings)
// Only report conflict if filenames have same lowercase but different casing
let mut case_map: HashMap<String, HashSet<String>> = HashMap::default();
for filename in self.compilation.assets().keys() {
let (target_file, _query) = filename.split_once('?').unwrap_or((filename, ""));
let lower_key = cow_utils::CowUtils::cow_to_lowercase(target_file);
case_map
.entry(lower_key.to_string())
.or_default()
.insert(target_file.to_string());
}

// Found conflict: multiple filenames with same lowercase representation but different casing
for (_lower_key, filenames) in case_map.iter() {
// Only report conflict if there are multiple unique filenames (different casing)
if filenames.len() > 1 {
let filenames_str = filenames.iter().map(|f| format!(" - {f}")).join("\n");
self.compilation.push_diagnostic(
rspack_error::error!(
"Prevent writing to file that only differs in casing or query string from already written file.\nThis will lead to a race-condition and corrupted files on case-insensitive file systems.\n{}",
filenames_str
)
.into(),
);
}
}

let mut new_emitted_asset_versions = HashMap::default();

rspack_futures::scope(|token| {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[package]
description = "rspack sensitive module plugin"
description = "rspack case sensitive plugin"
edition.workspace = true
license = "MIT"
name = "rspack_plugin_warn_sensitive_module"
name = "rspack_plugin_case_sensitive"
repository = "https://github.com/web-infra-dev/rspack"
version.workspace = true

[dependencies]
cow-utils = { workspace = true }
itertools = { workspace = true }
rspack_collections = { workspace = true }
rspack_core = { workspace = true }
rspack_error = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
// https://github.com/webpack/webpack/blob/main/lib/WarnCaseSensitiveModulesPlugin.js

use std::collections::HashMap;

use cow_utils::CowUtils;
use itertools::Itertools;
use rspack_collections::{Identifier, IdentifierSet};
use rspack_core::{Compilation, CompilationSeal, Logger, ModuleGraph, Plugin};
use rspack_core::{Compilation, CompilationSeal, CompilerEmit, Logger, ModuleGraph, Plugin};
use rspack_error::{Diagnostic, Result};
use rspack_hook::{plugin, plugin_hook};
use rustc_hash::{FxBuildHasher, FxHashMap};
use rustc_hash::{FxBuildHasher, FxHashMap as HashMap, FxHashSet as HashSet};

#[plugin]
#[derive(Debug, Default)]
pub struct WarnCaseSensitiveModulesPlugin;
pub struct CaseSensitivePlugin;

impl WarnCaseSensitiveModulesPlugin {
impl CaseSensitivePlugin {
pub fn create_sensitive_modules_warning(
&self,
modules: Vec<Identifier>,
Expand Down Expand Up @@ -41,18 +40,28 @@ impl WarnCaseSensitiveModulesPlugin {

message
}

pub fn create_sensitive_assets_warning(&self, filenames: &HashSet<String>) -> String {
let filenames_str = filenames.iter().map(|f| format!(" - {f}")).join("\n");
format!(
r#"Prevent writing to file that only differs in casing or query string from already written file.
This will lead to a race-condition and corrupted files on case-insensitive file systems.
{}"#,
filenames_str
)
}
}

#[plugin_hook(CompilationSeal for WarnCaseSensitiveModulesPlugin)]
#[plugin_hook(CompilationSeal for CaseSensitivePlugin)]
async fn seal(&self, compilation: &mut Compilation) -> Result<()> {
let logger = compilation.get_logger(self.name());
let start = logger.time("check case sensitive modules");
let mut diagnostics: Vec<Diagnostic> = vec![];
let module_graph = compilation.get_module_graph();
let all_modules = module_graph.modules();
let mut not_conflect: FxHashMap<String, Identifier> =
let mut not_conflect: HashMap<String, Identifier> =
HashMap::with_capacity_and_hasher(all_modules.len(), FxBuildHasher);
let mut conflict: FxHashMap<String, IdentifierSet> = FxHashMap::default();
let mut conflict: HashMap<String, IdentifierSet> = HashMap::default();

for module in all_modules.values() {
// Ignore `data:` URLs, because it's not a real path
Expand Down Expand Up @@ -87,7 +96,7 @@ async fn seal(&self, compilation: &mut Compilation) -> Result<()> {
let mut case_modules = set.iter().copied().collect::<Vec<_>>();
case_modules.sort_unstable();
diagnostics.push(Diagnostic::warn(
"Sensitive Modules Warn".to_string(),
"Sensitive Warn".to_string(),
self.create_sensitive_modules_warning(case_modules, &compilation.get_module_graph()),
));
}
Expand All @@ -97,16 +106,49 @@ async fn seal(&self, compilation: &mut Compilation) -> Result<()> {
Ok(())
}

#[plugin_hook(CompilerEmit for CaseSensitivePlugin)]
async fn emit(&self, compilation: &mut Compilation) -> Result<()> {
let mut diagnostics: Vec<Diagnostic> = vec![];

// Check for case-sensitive conflicts before emitting assets
// Only check for filenames that differ in casing (not query strings)
// Only report conflict if filenames have same lowercase but different casing
let mut case_map: HashMap<String, HashSet<String>> = HashMap::default();
for filename in compilation.assets().keys() {
let (target_file, _query) = filename.split_once('?').unwrap_or((filename, ""));
let lower_key = cow_utils::CowUtils::cow_to_lowercase(target_file);
case_map
.entry(lower_key.to_string())
.or_default()
.insert(target_file.to_string());
}

// Found conflict: multiple filenames with same lowercase representation but different casing
for (_lower_key, filenames) in case_map.iter() {
// Only report conflict if there are multiple unique filenames (different casing)
if filenames.len() > 1 {
diagnostics.push(Diagnostic::warn(
"Sensitive Warn".to_string(),
self.create_sensitive_assets_warning(filenames),
));
}
}

compilation.extend_diagnostics(diagnostics);
Ok(())
}

// 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 {
impl Plugin for CaseSensitivePlugin {
fn name(&self) -> &'static str {
"rspack.WarnCaseSensitiveModulesPlugin"
"rspack.CaseSensitivePlugin"
}

fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
ctx.compilation_hooks.seal.tap(seal::new(self));
ctx.compiler_hooks.emit.tap(emit::new(self));
Ok(())
}
}
27 changes: 15 additions & 12 deletions packages/rspack/etc/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,19 @@ interface CallExpression extends ExpressionBase {
// @public (undocumented)
type CallFn = (...args: any[]) => any;

// @public (undocumented)
const CaseSensitivePlugin: {
new (): {
name: string;
_args: [];
affectedHooks: keyof CompilerHooks | undefined;
raw(compiler: Compiler): BuiltinPlugin;
apply(compiler: Compiler): void;
};
};
export { CaseSensitivePlugin }
export { CaseSensitivePlugin as WarnCaseSensitiveModulesPlugin }

// @public (undocumented)
interface CatchClause extends Node_4, HasSpan {
// (undocumented)
Expand Down Expand Up @@ -6584,6 +6597,8 @@ declare namespace rspackExports {
ProgressPluginArgument,
ProvidePluginOptions,
BannerPlugin,
CaseSensitivePlugin,
CaseSensitivePlugin as WarnCaseSensitiveModulesPlugin,
DefinePlugin,
DynamicEntryPlugin,
EntryPlugin,
Expand All @@ -6595,7 +6610,6 @@ declare namespace rspackExports {
ProgressPlugin,
ProvidePlugin,
RuntimePlugin,
WarnCaseSensitiveModulesPlugin,
DllPlugin,
DllPluginOptions,
DllReferencePlugin,
Expand Down Expand Up @@ -9148,17 +9162,6 @@ class VirtualModulesPlugin {
writeModule(filePath: string, contents: string): void;
}

// @public (undocumented)
export const WarnCaseSensitiveModulesPlugin: {
new (): {
name: string;
_args: [];
affectedHooks: keyof CompilerHooks | undefined;
raw(compiler: Compiler): BuiltinPlugin;
apply(compiler: Compiler): void;
};
};

// @public (undocumented)
interface Wasm {
// (undocumented)
Expand Down
3 changes: 2 additions & 1 deletion packages/rspack/scripts/check-documentation-coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ function checkPluginsDocumentationCoverage() {
"RuntimePlugin", // This plugin only provides hooks, should not be used separately
"RsdoctorPlugin", // This plugin is not stable yet
"RstestPlugin", // This plugin is not stable yet
"RslibPlugin" // This plugin is not stable yet
"RslibPlugin", // This plugin is not stable yet
"WarnCaseSensitiveModulesPlugin" // This plugin is deprecated and will be replaced with CaseSensitivePlugin
];

const undocumentedPlugins = Array.from(implementedPlugins).filter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { BuiltinPluginName } from "@rspack/binding";

import { create } from "./base";

export const WarnCaseSensitiveModulesPlugin = create(
BuiltinPluginName.WarnCaseSensitiveModulesPlugin,
export const CaseSensitivePlugin = create(
BuiltinPluginName.CaseSensitivePlugin,
() => {},
"compilation"
);
Loading
Loading