1- use std:: { cell:: RefCell , ptr:: NonNull } ;
1+ use std:: {
2+ cell:: RefCell ,
3+ ptr:: NonNull ,
4+ sync:: {
5+ Arc ,
6+ atomic:: { AtomicBool , Ordering } ,
7+ } ,
8+ } ;
29
310use napi:: {
411 Either , Env ,
512 bindgen_prelude:: { Array , Object , ToNapiValue } ,
613} ;
714use napi_derive:: napi;
8- use rspack_core:: { Compilation , CompilationId , DependencyId , internal} ;
15+ use rspack_core:: { BoxDependency , Compilation , CompilationId , DependencyId , internal} ;
916use rspack_napi:: OneShotInstanceRef ;
1017use rspack_plugin_javascript:: dependency:: {
1118 CommonJsExportRequireDependency , ESMExportImportedSpecifierDependency ,
@@ -22,6 +29,7 @@ pub struct Dependency {
2229 pub ( crate ) compilation_id : Option < CompilationId > ,
2330 pub ( crate ) dependency_id : DependencyId ,
2431 pub ( crate ) dependency : NonNull < dyn rspack_core:: Dependency > ,
32+ mutable_access : Option < Arc < AtomicBool > > ,
2533}
2634
2735impl Dependency {
@@ -43,12 +51,41 @@ impl Dependency {
4351 }
4452 } )
4553 } else {
54+ if self
55+ . mutable_access
56+ . as_ref ( )
57+ . is_some_and ( |access| !access. load ( Ordering :: Acquire ) )
58+ {
59+ return Err ( napi:: Error :: from_reason (
60+ "Unable to access dependency outside the succeedModule hook callback" ,
61+ ) ) ;
62+ }
4663 // SAFETY:
4764 // We need to make users aware in the documentation that values obtained within the JS hook callback should not be used outside the scope of the callback.
4865 // We do not guarantee that the memory pointed to by the pointer remains valid when used outside the scope.
4966 f ( unsafe { self . dependency . as_ref ( ) } , None )
5067 }
5168 }
69+
70+ fn with_mut < R > (
71+ & mut self ,
72+ f : impl FnOnce ( & mut dyn rspack_core:: Dependency ) -> napi:: Result < R > ,
73+ ) -> napi:: Result < R > {
74+ let Some ( mutable_access) = & self . mutable_access else {
75+ return Err ( napi:: Error :: from_reason (
76+ "Dependency.request can only be changed inside the succeedModule hook callback" ,
77+ ) ) ;
78+ } ;
79+ if !mutable_access. load ( Ordering :: Acquire ) {
80+ return Err ( napi:: Error :: from_reason (
81+ "Dependency.request cannot be changed after the succeedModule hook callback" ,
82+ ) ) ;
83+ }
84+
85+ // SAFETY: The succeedModule hook exclusively borrows the pending dependencies until the
86+ // JavaScript callback settles. `mutable_access` revokes this pointer before that borrow ends.
87+ f ( unsafe { self . dependency . as_mut ( ) } )
88+ }
5289}
5390
5491#[ napi]
@@ -92,6 +129,25 @@ impl Dependency {
92129 } )
93130 }
94131
132+ #[ napi( setter) ]
133+ pub fn set_request ( & mut self , request : String ) -> napi:: Result < ( ) > {
134+ self . with_mut ( |dependency| {
135+ if let Some ( dependency) = dependency. downcast_mut :: < ESMImportSpecifierDependency > ( ) {
136+ dependency. set_request ( request. clone ( ) . into ( ) ) ;
137+ return Ok ( ( ) ) ;
138+ }
139+ if let Some ( dependency) = dependency. downcast_mut :: < ESMExportImportedSpecifierDependency > ( ) {
140+ dependency. set_request ( request. into ( ) ) ;
141+ return Ok ( ( ) ) ;
142+ }
143+
144+ Err ( napi:: Error :: from_reason ( format ! (
145+ "Dependency.request cannot be changed for dependency type '{}'" ,
146+ dependency. dependency_type( ) . as_str( )
147+ ) ) )
148+ } )
149+ }
150+
95151 #[ napi( getter, ts_return_type = "Record<string, string> | undefined" ) ]
96152 pub fn attributes < ' a > ( & mut self , env : & ' a Env ) -> napi:: Result < Either < Object < ' a > , ( ) > > {
97153 self . with_ref ( |dependency, _| {
@@ -164,7 +220,28 @@ impl Dependency {
164220 Either :: B ( ( ) )
165221 }
166222 }
167- None => Either :: B ( ( ) ) ,
223+ None => {
224+ if let Some ( dependency) =
225+ dependency. downcast_ref :: < ESMExportImportedSpecifierDependency > ( )
226+ {
227+ let ids = dependency. ids ( ) ;
228+ let mut arr = env. create_array ( ids. len ( ) as u32 ) ?;
229+ for ( i, v) in ids. iter ( ) . enumerate ( ) {
230+ arr. set ( i as u32 , v. as_str ( ) ) ?;
231+ }
232+ Either :: A ( arr)
233+ } else if let Some ( dependency) = dependency. downcast_ref :: < ESMImportSpecifierDependency > ( )
234+ {
235+ let ids = dependency. ids ( ) ;
236+ let mut arr = env. create_array ( ids. len ( ) as u32 ) ?;
237+ for ( i, v) in ids. iter ( ) . enumerate ( ) {
238+ arr. set ( i as u32 , v. as_str ( ) ) ?;
239+ }
240+ Either :: A ( arr)
241+ } else {
242+ Either :: B ( ( ) )
243+ }
244+ }
168245 } )
169246 } )
170247 }
@@ -189,6 +266,7 @@ pub struct DependencyWrapper {
189266 dependency : NonNull < dyn rspack_core:: Dependency > ,
190267 compilation_id : CompilationId ,
191268 registered_compilation_id : Option < CompilationId > ,
269+ mutable_access : Option < Arc < AtomicBool > > ,
192270}
193271
194272impl DependencyWrapper {
@@ -220,6 +298,22 @@ impl DependencyWrapper {
220298 dependency,
221299 compilation_id,
222300 registered_compilation_id : compilation. map ( Compilation :: id) ,
301+ mutable_access : None ,
302+ }
303+ }
304+
305+ fn new_pending (
306+ dependency_id : DependencyId ,
307+ dependency : NonNull < dyn rspack_core:: Dependency > ,
308+ compilation_id : CompilationId ,
309+ mutable_access : Arc < AtomicBool > ,
310+ ) -> Self {
311+ Self {
312+ dependency_id,
313+ dependency,
314+ compilation_id,
315+ registered_compilation_id : None ,
316+ mutable_access : Some ( mutable_access) ,
223317 }
224318 }
225319
@@ -254,6 +348,7 @@ impl ToNapiValue for DependencyWrapper {
254348 let instance = & mut * * r;
255349 instance. compilation_id = val. registered_compilation_id ;
256350 instance. dependency = val. dependency ;
351+ instance. mutable_access = val. mutable_access ;
257352
258353 ToNapiValue :: to_napi_value ( env, r)
259354 }
@@ -262,6 +357,7 @@ impl ToNapiValue for DependencyWrapper {
262357 compilation_id : val. registered_compilation_id ,
263358 dependency_id : val. dependency_id ,
264359 dependency : val. dependency ,
360+ mutable_access : val. mutable_access ,
265361 } ;
266362 let r = vacant_entry. insert ( OneShotInstanceRef :: new ( env, js_dependency) ?) ;
267363 ToNapiValue :: to_napi_value ( env, r)
@@ -271,3 +367,80 @@ impl ToNapiValue for DependencyWrapper {
271367 }
272368 }
273369}
370+
371+ #[ derive( Debug , Clone , Copy ) ]
372+ struct PendingDependency {
373+ id : DependencyId ,
374+ dependency : NonNull < dyn rspack_core:: Dependency > ,
375+ }
376+
377+ #[ derive( Debug , Clone ) ]
378+ pub ( crate ) struct PendingDependencies {
379+ dependencies : Vec < PendingDependency > ,
380+ compilation_id : CompilationId ,
381+ mutable_access : Arc < AtomicBool > ,
382+ }
383+
384+ pub ( crate ) struct PendingDependenciesGuard ( Arc < AtomicBool > ) ;
385+
386+ impl Drop for PendingDependenciesGuard {
387+ fn drop ( & mut self ) {
388+ self . 0 . store ( false , Ordering :: Release ) ;
389+ }
390+ }
391+
392+ // SAFETY: The succeedModule hook owns the pending dependency slice while the JavaScript callback
393+ // runs. The callback is awaited, and `mutable_access` is revoked before that borrow is released.
394+ unsafe impl Send for PendingDependencies { }
395+ unsafe impl Sync for PendingDependencies { }
396+
397+ impl PendingDependencies {
398+ pub ( crate ) fn new ( dependencies : & mut [ BoxDependency ] , compilation_id : CompilationId ) -> Self {
399+ let dependencies = dependencies
400+ . iter_mut ( )
401+ . map ( |dependency| {
402+ let id = * dependency. id ( ) ;
403+ let dependency = dependency. as_mut ( ) as * mut dyn rspack_core:: Dependency ;
404+ // SAFETY: The pointer is only exposed while the hook retains the source slice.
405+ let dependency = unsafe {
406+ std:: mem:: transmute :: <
407+ * mut ( dyn rspack_core:: Dependency + ' _ ) ,
408+ * mut ( dyn rspack_core:: Dependency + ' static ) ,
409+ > ( dependency)
410+ } ;
411+ // SAFETY: `dependency` came from a valid mutable reference.
412+ let dependency = unsafe { NonNull :: new_unchecked ( dependency) } ;
413+ PendingDependency { id, dependency }
414+ } )
415+ . collect ( ) ;
416+
417+ Self {
418+ dependencies,
419+ compilation_id,
420+ mutable_access : Arc :: new ( AtomicBool :: new ( true ) ) ,
421+ }
422+ }
423+
424+ pub ( crate ) fn is_active ( & self ) -> bool {
425+ self . mutable_access . load ( Ordering :: Acquire )
426+ }
427+
428+ pub ( crate ) fn guard ( & self ) -> PendingDependenciesGuard {
429+ PendingDependenciesGuard ( self . mutable_access . clone ( ) )
430+ }
431+
432+ pub ( crate ) fn wrappers ( & self ) -> Vec < DependencyWrapper > {
433+ self
434+ . dependencies
435+ . iter ( )
436+ . map ( |dependency| {
437+ DependencyWrapper :: new_pending (
438+ dependency. id ,
439+ dependency. dependency ,
440+ self . compilation_id ,
441+ self . mutable_access . clone ( ) ,
442+ )
443+ } )
444+ . collect ( )
445+ }
446+ }
0 commit comments