Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d138a89
perf(core): reuse chunk graph with stable async outgoings
matthewdavis-oai Jul 14, 2026
91cc3c8
test: format async outgoing watch fixture
matthewdavis-oai Jul 14, 2026
e8740f6
perf(core): tighten chunk graph reuse and release connection wrappers
matthewdavis-oai Jul 14, 2026
4710507
perf(copy-plugin): reuse unchanged static copy patterns during watch
matthewdavis-oai Jul 14, 2026
8887a86
fix(cache): release compilation-scoped plugin state on rebuild
matthewdavis-oai Jul 14, 2026
ce7ca20
perf(core): keep stable async and existing leaf chunk graphs
matthewdavis-oai Jul 14, 2026
f10f433
perf(hmr): skip unchanged chunks without dropping installed updates
matthewdavis-oai Jul 14, 2026
0aabc05
fix(watch): preserve recursive contexts and copy invalidation
matthewdavis-oai Jul 14, 2026
601ac45
fix(cache): keep compilation dependency deltas scoped
matthewdavis-oai Jul 14, 2026
182ab3c
test(binding): guard module graph connection cleanup
matthewdavis-oai Jul 14, 2026
32d9804
fix(hmr): consolidate lazy compilation and runtime correctness
matthewdavis-oai Jul 14, 2026
2ee3fd6
fix(watch): keep dependent lazy generations and completed deltas correct
matthewdavis-oai Jul 14, 2026
cd55890
fix(watcher): keep native aggregate delivery lossless across generations
matthewdavis-oai Jul 14, 2026
5936572
fix(hmr): keep dynamic and global entry rebuilds correct
matthewdavis-oai Jul 14, 2026
f861edd
fix(runtime): keep full-hash chunk filenames correct and cheap
matthewdavis-oai Jul 14, 2026
f521bcc
fix(watch): scale stale cleanup and recover Copy patterns safely
matthewdavis-oai Jul 14, 2026
6f0d4e3
fix(lazy): route compiler prefixes and watch deltas precisely
matthewdavis-oai Jul 14, 2026
17d545c
fix(watch): coalesce in-flight lazy and file generations
matthewdavis-oai Jul 14, 2026
ee631e7
test(wasm): skip unsupported native watcher variants
matthewdavis-oai Jul 14, 2026
c73ba1f
perf(copy-plugin): keep ordered pattern emission compact and determin…
matthewdavis-oai Jul 14, 2026
af562a1
fix(watcher): serialize native watcher lifecycle safely
matthewdavis-oai Jul 14, 2026
43a2264
fix(hmr): isolate concurrent runners and dispose transient modules
matthewdavis-oai Jul 14, 2026
5a6b4f2
fix(copy-plugin): satisfy ordered-future clippy lint
matthewdavis-oai Jul 14, 2026
99af575
test(hmr): refresh disposed-module hot snapshots
matthewdavis-oai Jul 14, 2026
f49bfbc
fix(watcher): preserve concrete context-child events during HMR
matthewdavis-oai Jul 14, 2026
df63754
fix(copy-plugin): keep rendered template asset keys portable
matthewdavis-oai Jul 15, 2026
ef90a0f
fix(hmr): keep extracted css reload single-owned
matthewdavis-oai Jul 15, 2026
eb10e07
perf(copy-plugin): reuse unchanged patterns for lazy rebuilds
matthewdavis-oai Jul 15, 2026
e92e185
fix(watcher): isolate stale native watch generations
matthewdavis-oai Jul 15, 2026
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
12 changes: 4 additions & 8 deletions crates/node_binding/napi-binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ export declare class JsCompiler {
/** Build with the given option passed to the constructor */
build(callback: (err: null | Error) => void): void
/** Rebuild with the given option passed to the constructor */
rebuild(changed_files: string[], removed_files: string[], callback: (err: null | Error) => void): void
rebuild(changed_files: string[], removed_files: string[], callback: (err: null | Error) => void, is_lazy_watch_rebuild?: boolean): void
close(): Promise<void>
getVirtualFileStore(): VirtualFileStore | null
getCompilerId(): ExternalObject<CompilerId>
Expand Down Expand Up @@ -466,20 +466,16 @@ export declare class NativeWatcher {
constructor(options: NativeWatcherOptions)
watch(files: [Array<string>, Array<string>], directories: [Array<string>, Array<string>], missing: [Array<string>, Array<string>], startTime: bigint, callback: (err: Error | null, result: NativeWatchResult) => void, callbackUndelayed: (event: NativeWatchUndelayedEvent) => void): void
triggerEvent(kind: 'change' | 'remove' | 'create', path: string): void
/**
* # Safety
*
* This function is unsafe because it uses `&mut self` to call the watcher asynchronously.
* It's important to ensure that the watcher is not used in any other places before this function is finished.
* You must ensure that the watcher not call watch, close or pause in the same time, otherwise it may lead to undefined behavior.
*/
takePendingEvents(): NativeWatchResult
acknowledgePendingEvents(generation: number): void
close(): Promise<void>
pause(): void
}

export declare class NativeWatchResult {
changedFiles: Array<string>
removedFiles: Array<string>
generation: number
}


Expand Down
31 changes: 31 additions & 0 deletions crates/rspack/tests/compilation_dependencies.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use rspack::builder::Builder as _;
use rspack_core::Compiler;
use rspack_paths::ArcPath;
use rspack_tasks::within_compiler_context_for_testing_sync;

#[test]
fn compilation_level_dependencies_stay_in_their_own_added_iterators() {
within_compiler_context_for_testing_sync(|| {
let mut compiler = Compiler::builder().build().expect("compiler should build");
let compilation = &mut compiler.compilation;
let file = ArcPath::from("/project/file.tsx");
let context = ArcPath::from("/project/context");
let missing = ArcPath::from("/project/missing.tsx");
let build = ArcPath::from("/project/build.config.js");

compilation.file_dependencies.insert(file.clone());
compilation.context_dependencies.insert(context.clone());
compilation.missing_dependencies.insert(missing.clone());
compilation.build_dependencies.insert(build.clone());

let (_, file_added, _, _) = compilation.file_dependencies();
let (_, context_added, _, _) = compilation.context_dependencies();
let (_, missing_added, _, _) = compilation.missing_dependencies();
let (_, build_added, _, _) = compilation.build_dependencies();

assert_eq!(file_added.cloned().collect::<Vec<_>>(), [file]);
assert_eq!(context_added.cloned().collect::<Vec<_>>(), [context]);
assert_eq!(missing_added.cloned().collect::<Vec<_>>(), [missing]);
assert_eq!(build_added.cloned().collect::<Vec<_>>(), [build]);
});
}
2 changes: 1 addition & 1 deletion crates/rspack_binding_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
simd-json = { workspace = true }
swc_core = { workspace = true, default-features = false, features = ["ecma_transforms_react"] }
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros", "test-util", "tracing", "parking_lot"] }
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros", "sync", "test-util", "tracing", "parking_lot"] }
ustr = { workspace = true }

[package.metadata.cargo-shear]
Expand Down
41 changes: 19 additions & 22 deletions crates/rspack_binding_api/src/compilation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -802,20 +802,19 @@ impl JsCompilation {
Some(js_opts) => js_opts.into(),
None => EntryOptions::default(),
};
let dependency = if let Some(map) = entry_dependencies_map.get(&js_dependency.request)
&& let Some(dependency) = map.get(&options)
{
let cache_key = (
js_context.clone(),
js_dependency.request.clone(),
options.name.clone(),
layer.clone(),
);
let dependency = if let Some(dependency) = entry_dependencies_map.get(&cache_key) {
js_dependency.dependency_id = Some(*dependency.id());
dependency.clone()
} else {
let dependency = js_dependency.resolve(js_context.into(), layer)?;
if let Some(map) = entry_dependencies_map.get_mut(&js_dependency.request) {
map.insert(options.clone(), dependency.clone());
} else {
let mut map = FxHashMap::default();
map.insert(options.clone(), dependency.clone());
entry_dependencies_map.insert(js_dependency.request.clone(), map);
}
let dependency =
js_dependency.resolve(js_context.into(), layer, options.name.is_none())?;
entry_dependencies_map.insert(cache_key, dependency.clone());
dependency
};
Ok((dependency, options))
Expand Down Expand Up @@ -906,20 +905,18 @@ impl JsCompilation {
Some(js_opts) => js_opts.into(),
None => EntryOptions::default(),
};
let dependency = if let Some(map) = include_dependencies_map.get(&js_dependency.request)
&& let Some(dependency) = map.get(&options)
{
let cache_key = (
js_context.clone(),
js_dependency.request.clone(),
options.name.clone(),
layer.clone(),
);
let dependency = if let Some(dependency) = include_dependencies_map.get(&cache_key) {
js_dependency.dependency_id = Some(*dependency.id());
dependency.clone()
} else {
let dependency = js_dependency.resolve(js_context.into(), layer)?;
if let Some(map) = include_dependencies_map.get_mut(&js_dependency.request) {
map.insert(options.clone(), dependency.clone());
} else {
let mut map = FxHashMap::default();
map.insert(options.clone(), dependency.clone());
include_dependencies_map.insert(js_dependency.request.clone(), map);
}
let dependency = js_dependency.resolve(js_context.into(), layer, true)?;
include_dependencies_map.insert(cache_key, dependency.clone());
dependency
};
Ok((dependency, options))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ impl EntryDependency {
&mut self,
context: rspack_core::Context,
layer: Option<String>,
is_global: bool,
) -> napi::Result<Box<dyn rspack_core::Dependency>> {
match &self.dependency_id {
Some(dependency_id) => Err(napi::Error::from_reason(format!(
Expand All @@ -21,7 +22,7 @@ impl EntryDependency {
self.request.clone(),
context,
layer,
false,
is_global,
)) as rspack_core::BoxDependency;
self.dependency_id = Some(*dependency.id());
Ok(dependency)
Expand Down
17 changes: 11 additions & 6 deletions crates/rspack_binding_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ use std::{
use napi::{CallContext, bindgen_prelude::*};
pub use raw_options::{CustomPluginBuilder, register_custom_plugin};
use rspack_core::{
BoxDependency, Compilation, CompilerId, CompilerPlatform, EntryOptions, ModuleIdentifier,
PluginExt,
BoxDependency, Compilation, CompilerId, CompilerPlatform, ModuleIdentifier, PluginExt,
};
use rspack_error::Diagnostic;
use rspack_fs::{IntermediateFileSystem, NativeFileSystem, ReadableFileSystem};
Expand All @@ -131,6 +130,7 @@ use crate::{
error::{ErrorCode, RspackResultToNapiResultExt},
fs_node::{HybridFileSystem, NodeFileSystem, ThreadsafeNodeFS},
module::ModuleObject,
module_graph_connection::ModuleGraphConnectionWrapper,
platform::RawCompilerPlatform,
plugins::{
JsCleanupPlugin, JsHooksAdapterPlugin, RegisterJsTapKind, RegisterJsTaps, buildtime_plugins,
Expand All @@ -154,6 +154,8 @@ thread_local! {
static COMPILER_REFERENCES: RefCell<FxHashMap<CompilerId, WeakReference<JsCompiler>>> = Default::default();
}

type EntryDependencyCacheKey = (String, String, Option<String>, Option<String>);

#[js_function(1)]
fn cleanup_revoked_modules(ctx: CallContext) -> Result<()> {
let external = ctx.get::<&mut External<(CompilerId, Vec<ModuleIdentifier>)>>(0)?;
Expand All @@ -172,8 +174,8 @@ struct JsCompiler {
// call drop manually to avoid unnecessary drop overhead in cli build
compiler: ManuallyDrop<Compiler>,
state: CompilerState,
include_dependencies_map: FxHashMap<String, FxHashMap<EntryOptions, BoxDependency>>,
entry_dependencies_map: FxHashMap<String, FxHashMap<EntryOptions, BoxDependency>>,
include_dependencies_map: FxHashMap<EntryDependencyCacheKey, BoxDependency>,
entry_dependencies_map: FxHashMap<EntryDependencyCacheKey, BoxDependency>,
compiler_context: Arc<CompilerContext>,
virtual_file_store: Option<Arc<RwLock<dyn VirtualFileStore>>>,
}
Expand Down Expand Up @@ -388,24 +390,26 @@ impl JsCompiler {

/// Rebuild with the given option passed to the constructor
#[napi(
ts_args_type = "changed_files: string[], removed_files: string[], callback: (err: null | Error) => void"
ts_args_type = "changed_files: string[], removed_files: string[], callback: (err: null | Error) => void, is_lazy_watch_rebuild?: boolean"
)]
pub fn rebuild(
&mut self,
reference: Reference<JsCompiler>,
changed_files: Vec<String>,
removed_files: Vec<String>,
f: Function<'static>,
is_lazy_watch_rebuild: Option<bool>,
) -> Result<(), ErrorCode> {
unsafe {
self.run(reference, |compiler, guard| {
callbackify(
f,
async move {
let result = compiler
.rebuild(
.rebuild_with_invalidation_provenance(
changed_files.into_iter().collect::<FxHashSet<_>>(),
removed_files.into_iter().collect::<FxHashSet<_>>(),
is_lazy_watch_rebuild.unwrap_or(false),
)
.await
.to_napi_result_with_message(|e| {
Expand Down Expand Up @@ -528,6 +532,7 @@ impl JsCompiler {
ChunkGroupWrapper::cleanup_last_compilation(compilation_id);
DependencyWrapper::cleanup_last_compilation(compilation_id);
AsyncDependenciesBlockWrapper::cleanup_last_compilation(compilation_id);
ModuleGraphConnectionWrapper::cleanup_last_compilation(compilation_id);
}
}

Expand Down
Loading
Loading