diff --git a/crates/rspack_binding_values/src/context_module_factory.rs b/crates/rspack_binding_values/src/context_module_factory.rs index 6f20e1c0125..3f980798ce0 100644 --- a/crates/rspack_binding_values/src/context_module_factory.rs +++ b/crates/rspack_binding_values/src/context_module_factory.rs @@ -175,12 +175,12 @@ impl JsContextModuleFactoryAfterResolveData { } #[napi(getter)] - pub fn dependencies(&self) -> Vec { + pub fn dependencies(&mut self) -> Vec { self .0 .dependencies - .iter() - .map(|dep| JsDependency::new(dep.clone())) + .iter_mut() + .map(|dep| JsDependency::new(dep)) .collect::>() } } diff --git a/crates/rspack_binding_values/src/dependency.rs b/crates/rspack_binding_values/src/dependency.rs index b61fb8e2182..ae84d2bc218 100644 --- a/crates/rspack_binding_values/src/dependency.rs +++ b/crates/rspack_binding_values/src/dependency.rs @@ -4,6 +4,8 @@ use rspack_core::{ ModuleGraph, }; +// JsCompiledDependency allows JS-side access to a Dependency instance that has already +// been processed and stored in the Compilation. #[napi] pub struct JsCompiledDependency { pub(crate) dependency_id: DependencyId, @@ -65,11 +67,19 @@ impl JsCompiledDependency { } } +// JsDependency represents a Dependency instance that is currently being processed. +// It is in the make stage and has not yet been added to the Compilation. #[napi] -pub struct JsDependency(BoxDependency); +pub struct JsDependency(&'static mut BoxDependency); impl JsDependency { - pub(crate) fn new(dependency: BoxDependency) -> Self { + pub(crate) fn new(dependency: &mut BoxDependency) -> Self { + // SAFETY: + // The lifetime of the &mut BoxDependency reference is extended to 'static. + // This is safe because the JS side will guarantee that the JsDependency instance's + // lifetime is properly managed and restricted. + let dependency = + unsafe { std::mem::transmute::<&mut BoxDependency, &'static mut BoxDependency>(dependency) }; Self(dependency) } } diff --git a/packages/rspack/src/Compiler.ts b/packages/rspack/src/Compiler.ts index 8400d8e09f2..8e38aefb59d 100644 --- a/packages/rspack/src/Compiler.ts +++ b/packages/rspack/src/Compiler.ts @@ -33,6 +33,7 @@ import { ThreadsafeWritableNodeFS } from "./FileSystem"; import { CodeGenerationResult, ContextModuleFactoryAfterResolveData, + ContextModuleFactoryBeforeResolveData, Module } from "./Module"; import { NormalModuleFactory } from "./NormalModuleFactory"; @@ -1159,7 +1160,18 @@ class Compiler { | false | binding.JsContextModuleFactoryBeforeResolveData ) => { - return queried.promise(bindingData); + const data = bindingData + ? ContextModuleFactoryBeforeResolveData.__from_binding( + bindingData + ) + : false; + const result = await queried.promise(data); + if (data) { + ContextModuleFactoryBeforeResolveData.__drop(data); + } + return result + ? ContextModuleFactoryBeforeResolveData.__to_binding(result) + : false; } ), registerContextModuleFactoryAfterResolveTaps: @@ -1178,11 +1190,13 @@ class Compiler { bindingData ) : false; - const ret = await queried.promise(data); - const result = ret - ? ContextModuleFactoryAfterResolveData.__to_binding(ret) + const result = await queried.promise(data); + if (data) { + ContextModuleFactoryAfterResolveData.__drop(data); + } + return result + ? ContextModuleFactoryAfterResolveData.__to_binding(result) : false; - return result; } ), registerJavascriptModulesChunkHashTaps: this.#createHookRegisterTaps( diff --git a/packages/rspack/src/Dependency.ts b/packages/rspack/src/Dependency.ts index 1ff255af68a..2c037d147e8 100644 --- a/packages/rspack/src/Dependency.ts +++ b/packages/rspack/src/Dependency.ts @@ -2,6 +2,7 @@ import { JsDependency, JsCompiledDependency } from "@rspack/binding"; export class Dependency { #binding: JsDependency | JsCompiledDependency; + #dropped = false; static __from_binding( binding: JsDependency | JsCompiledDependency @@ -9,27 +10,44 @@ export class Dependency { return new Dependency(binding); } + static __drop(dependency: Dependency) { + dependency.#dropped = true; + } + + private ensureValidLifecycle() { + if (this.#dropped) { + throw new Error( + "The Dependency has exceeded its lifecycle and has been dropped by Rust." + ); + } + } + private constructor(binding: JsDependency | JsCompiledDependency) { this.#binding = binding; } get type(): string { + this.ensureValidLifecycle(); return this.#binding.type; } get category(): string { + this.ensureValidLifecycle(); return this.#binding.category; } get request(): string | undefined { + this.ensureValidLifecycle(); return this.#binding.request; } get critital(): boolean | undefined { + this.ensureValidLifecycle(); return this.#binding.critical; } set critital(critital: boolean | undefined) { + this.ensureValidLifecycle(); if ( typeof critital === "boolean" && this.#binding instanceof JsDependency diff --git a/packages/rspack/src/Module.ts b/packages/rspack/src/Module.ts index 774ac5a3073..8b38314d547 100644 --- a/packages/rspack/src/Module.ts +++ b/packages/rspack/src/Module.ts @@ -1,6 +1,7 @@ import type { JsCodegenerationResult, JsContextModuleFactoryAfterResolveData, + JsContextModuleFactoryBeforeResolveData, JsCreateData, JsFactoryMeta, JsModule, @@ -36,45 +37,151 @@ export type ResolveData = { createData?: CreateData; }; +export class ContextModuleFactoryBeforeResolveData { + #inner: JsContextModuleFactoryBeforeResolveData; + #dropped = false; + + static __from_binding(binding: JsContextModuleFactoryBeforeResolveData) { + return new ContextModuleFactoryBeforeResolveData(binding); + } + + static __to_binding( + data: ContextModuleFactoryBeforeResolveData + ): JsContextModuleFactoryBeforeResolveData { + return data.#inner; + } + + static __drop(data: ContextModuleFactoryBeforeResolveData) { + data.#dropped = true; + } + + private constructor(binding: JsContextModuleFactoryBeforeResolveData) { + this.#inner = binding; + } + + private ensureValidLifecycle() { + if (this.#dropped) { + throw new Error( + "The ContextModuleFactoryBeforeResolveData has exceeded its lifecycle and has been dropped by Rust." + ); + } + } + + get context(): string { + this.ensureValidLifecycle(); + return this.#inner.context; + } + + set context(val: string) { + this.ensureValidLifecycle(); + this.#inner.context = val; + } + + get request(): string { + this.ensureValidLifecycle(); + return this.#inner.request; + } + + set request(val: string) { + this.#inner.request = val; + } + + get regExp(): RegExp | undefined { + this.ensureValidLifecycle(); + if (!this.#inner.regExp) { + return undefined; + } + const { source, flags } = this.#inner.regExp; + return new RegExp(source, flags); + } + + set regExp(val: RegExp | undefined) { + this.ensureValidLifecycle(); + if (!val) { + this.#inner.regExp = undefined; + return; + } + this.#inner.regExp = { + source: val.source, + flags: val.flags + }; + } + + get recursive(): boolean { + this.ensureValidLifecycle(); + return this.#inner.recursive; + } + + set recursive(val: boolean) { + this.ensureValidLifecycle(); + this.#inner.recursive = val; + } +} + export type ContextModuleFactoryBeforeResolveResult = | false - | { - context: string; - request?: string; - }; + | ContextModuleFactoryBeforeResolveData; export class ContextModuleFactoryAfterResolveData { #inner: JsContextModuleFactoryAfterResolveData; + #resolvedDependencies?: Dependency[]; + #dropped = false; + #dropWarningMessage?: string; static __from_binding(binding: JsContextModuleFactoryAfterResolveData) { return new ContextModuleFactoryAfterResolveData(binding); } - static __to_binding(data: ContextModuleFactoryAfterResolveData) { + static __to_binding( + data: ContextModuleFactoryAfterResolveData + ): JsContextModuleFactoryAfterResolveData { return data.#inner; } - constructor(data: JsContextModuleFactoryAfterResolveData) { + static __drop(data: ContextModuleFactoryAfterResolveData) { + data.#dropped = true; + if (data.#resolvedDependencies) { + data.#resolvedDependencies.forEach(dependency => + Dependency.__drop(dependency) + ); + } + } + + private ensureValidLifecycle() { + if (this.#dropped) { + throw new Error( + this.#dropWarningMessage ?? + "The ContextModuleFactoryAfterResolveData has exceeded its lifecycle and has been dropped by Rust." + ); + } + } + + private constructor(data: JsContextModuleFactoryAfterResolveData) { this.#inner = data; } get resource(): string { + this.ensureValidLifecycle(); return this.#inner.resource; } set resource(val: string) { + this.ensureValidLifecycle(); this.#inner.resource = val; } get context(): string { + this.ensureValidLifecycle(); return this.#inner.context; } set context(val: string) { + this.ensureValidLifecycle(); this.#inner.context = val; } get request(): string { + this.ensureValidLifecycle(); return this.#inner.request; } @@ -83,6 +190,7 @@ export class ContextModuleFactoryAfterResolveData { } get regExp(): RegExp | undefined { + this.ensureValidLifecycle(); if (!this.#inner.regExp) { return undefined; } @@ -91,6 +199,7 @@ export class ContextModuleFactoryAfterResolveData { } set regExp(val: RegExp | undefined) { + this.ensureValidLifecycle(); if (!val) { this.#inner.regExp = undefined; return; @@ -102,15 +211,23 @@ export class ContextModuleFactoryAfterResolveData { } get recursive(): boolean { + this.ensureValidLifecycle(); return this.#inner.recursive; } set recursive(val: boolean) { + this.ensureValidLifecycle(); this.#inner.recursive = val; } get dependencies(): Dependency[] { - return this.#inner.dependencies.map(dep => Dependency.__from_binding(dep)); + this.ensureValidLifecycle(); + if (!this.#resolvedDependencies) { + this.#resolvedDependencies = this.#inner.dependencies.map(dep => + Dependency.__from_binding(dep) + ); + } + return this.#resolvedDependencies; } }