Skip to content

Commit

Permalink
feat: unify js dependency (#8394)
Browse files Browse the repository at this point in the history
  • Loading branch information
SyMind authored Nov 13, 2024
1 parent 58c2aaf commit 78aa2e0
Show file tree
Hide file tree
Showing 11 changed files with 334 additions and 208 deletions.
21 changes: 7 additions & 14 deletions crates/node_binding/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export declare class ExternalObject<T> {
}
}
export declare class EntryDataDto {
get dependencies(): Array<JsDependency>
get includeDependencies(): Array<JsDependency>
get dependencies(): JsDependency[]
get includeDependencies(): JsDependency[]
get options(): EntryOptionsDto
}
export type EntryDataDTO = EntryDataDto
Expand Down Expand Up @@ -108,7 +108,7 @@ export declare class JsContextModuleFactoryAfterResolveData {
set regExp(rawRegExp: RegExp | undefined)
get recursive(): boolean
set recursive(recursive: boolean)
get dependencies(): Array<JsDependencyMut>
get dependencies(): JsDependency[]
}

export declare class JsContextModuleFactoryBeforeResolveData {
Expand Down Expand Up @@ -138,22 +138,15 @@ export declare class JsDependencies {
}

export declare class JsDependenciesBlock {
get dependencies(): Array<JsDependency>
get blocks(): Array<JsDependenciesBlock>
get dependencies(): JsDependency[]
get blocks(): JsDependenciesBlock[]
}

export declare class JsDependency {
get type(): string
get category(): string
get request(): string | undefined
get critical(): boolean
}

export declare class JsDependencyMut {
get type(): string
get category(): string
get request(): string | undefined
get critical(): boolean
set critical(val: boolean)
}

Expand Down Expand Up @@ -181,8 +174,8 @@ export declare class JsModule {
get factoryMeta(): JsFactoryMeta | undefined
get type(): string
get layer(): string | undefined
get blocks(): Array<JsDependenciesBlock>
get dependencies(): Array<JsDependency>
get blocks(): JsDependenciesBlock[]
get dependencies(): JsDependency[]
size(ty?: string | undefined | null): number
get modules(): JsModule[] | undefined
get useSourceMap(): boolean
Expand Down
20 changes: 11 additions & 9 deletions crates/rspack_binding_values/src/compilation/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use napi_derive::napi;
use rspack_core::{ChunkLoading, Compilation, EntryData, EntryOptions, EntryRuntime};
use rspack_napi::napi::bindgen_prelude::*;

use crate::{dependency::JsDependency, entry::JsEntryOptions, library::JsLibraryOptions};
use crate::{
dependency::JsDependency, entry::JsEntryOptions, library::JsLibraryOptions, JsDependencyWrapper,
};

#[napi]
pub struct EntryOptionsDTO(EntryOptions);
Expand Down Expand Up @@ -163,12 +165,12 @@ impl From<JsEntryData> for EntryData {
dependencies: value
.dependencies
.into_iter()
.map(|dep| *dep.id())
.map(|dep| dep.dependency_id)
.collect::<Vec<_>>(),
include_dependencies: value
.include_dependencies
.into_iter()
.map(|dep| *dep.id())
.map(|dep| dep.dependency_id)
.collect::<Vec<_>>(),
options: value.options.into(),
}
Expand All @@ -183,8 +185,8 @@ pub struct EntryDataDTO {

#[napi]
impl EntryDataDTO {
#[napi(getter)]
pub fn dependencies(&'static self) -> Vec<JsDependency> {
#[napi(getter, ts_return_type = "JsDependency[]")]
pub fn dependencies(&'static self) -> Vec<JsDependencyWrapper> {
let module_graph = self.compilation.get_module_graph();
self
.entry_data
Expand All @@ -193,13 +195,13 @@ impl EntryDataDTO {
.map(|dependency_id| {
#[allow(clippy::unwrap_used)]
let dep = module_graph.dependency_by_id(dependency_id).unwrap();
JsDependency::new(dep)
JsDependencyWrapper::new(dep.as_ref(), self.compilation.id(), Some(self.compilation))
})
.collect::<Vec<_>>()
}

#[napi(getter)]
pub fn include_dependencies(&'static self) -> Vec<JsDependency> {
#[napi(getter, ts_return_type = "JsDependency[]")]
pub fn include_dependencies(&'static self) -> Vec<JsDependencyWrapper> {
let module_graph = self.compilation.get_module_graph();
self
.entry_data
Expand All @@ -208,7 +210,7 @@ impl EntryDataDTO {
.map(|dependency_id| {
#[allow(clippy::unwrap_used)]
let dep = module_graph.dependency_by_id(dependency_id).unwrap();
JsDependency::new(dep)
JsDependencyWrapper::new(dep.as_ref(), self.compilation.id(), Some(self.compilation))
})
.collect::<Vec<_>>()
}
Expand Down
10 changes: 5 additions & 5 deletions crates/rspack_binding_values/src/context_module_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use napi_derive::napi;
use rspack_core::{AfterResolveData, BeforeResolveData};
use rspack_regex::RspackRegex;

use crate::JsDependencyMut;
use crate::JsDependencyWrapper;

#[napi]
pub struct JsContextModuleFactoryBeforeResolveData(Box<BeforeResolveData>);
Expand Down Expand Up @@ -169,13 +169,13 @@ impl JsContextModuleFactoryAfterResolveData {
self.0.recursive = recursive;
}

#[napi(getter)]
pub fn dependencies(&mut self) -> Vec<JsDependencyMut> {
#[napi(getter, ts_return_type = "JsDependency[]")]
pub fn dependencies(&self) -> Vec<JsDependencyWrapper> {
self
.0
.dependencies
.iter_mut()
.map(JsDependencyMut::new)
.iter()
.map(|dep| JsDependencyWrapper::new(dep.as_ref(), self.0.compilation_id, None))
.collect::<Vec<_>>()
}
}
Expand Down
198 changes: 135 additions & 63 deletions crates/rspack_binding_values/src/dependency.rs
Original file line number Diff line number Diff line change
@@ -1,108 +1,180 @@
use std::{cell::RefCell, ptr::NonNull};

use napi::bindgen_prelude::ToNapiValue;
use napi_derive::napi;
use rspack_core::{BoxDependency, DependencyId};
use rspack_core::{Compilation, CompilationId, Dependency, DependencyId};
use rspack_napi::OneShotRef;
use rustc_hash::FxHashMap as HashMap;

// JsDependency allows JS-side access to a Dependency instance that has already
// been processed and stored in the Compilation.
#[napi]
pub struct JsDependency(&'static BoxDependency);
pub struct JsDependency {
pub(crate) compilation: Option<NonNull<Compilation>>,
pub(crate) dependency_id: DependencyId,
pub(crate) dependency: NonNull<dyn Dependency>,
}

impl JsDependency {
pub(crate) fn new(dependency: &BoxDependency) -> Self {
// SAFETY:
// The lifetime of the &mut BoxDependency reference is extended to 'static.
// Accessing fields and methods on the Rust object from the JS side after the Rust object's
// lifetime has ended is undefined behavior, which we currently disregard.
let dependency =
unsafe { std::mem::transmute::<&BoxDependency, &'static BoxDependency>(dependency) };
Self(dependency)
fn as_ref(&mut self) -> napi::Result<&dyn Dependency> {
if let Some(compilation) = self.compilation {
let compilation = unsafe { compilation.as_ref() };
let module_graph = compilation.get_module_graph();
if let Some(dependency) = module_graph.dependency_by_id(&self.dependency_id) {
self.dependency = {
#[allow(clippy::unwrap_used)]
NonNull::new(dependency.as_ref() as *const dyn Dependency as *mut dyn Dependency).unwrap()
};
Ok(unsafe { self.dependency.as_ref() })
} else {
Err(napi::Error::from_reason(format!(
"Unable to access dependency with id = {:?} now. The dependency have been removed on the Rust side.",
self.dependency_id
)))
}
} else {
// SAFETY:
// 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.
// We do not guarantee that the memory pointed to by the pointer remains valid when used outside the scope.
Ok(unsafe { self.dependency.as_ref() })
}
}

pub(crate) fn id(&self) -> &DependencyId {
self.0.id()
fn as_mut(&mut self) -> napi::Result<&mut dyn Dependency> {
// SAFETY:
// 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.
// We do not guarantee that the memory pointed to by the pointer remains valid when used outside the scope.
Ok(unsafe { self.dependency.as_mut() })
}
}

#[napi]
impl JsDependency {
#[napi(getter)]
pub fn get_type(&self) -> &str {
self.0.dependency_type().as_str()
pub fn get_type(&mut self) -> napi::Result<&str> {
let dependency = self.as_ref()?;

Ok(dependency.dependency_type().as_str())
}

#[napi(getter)]
pub fn category(&self) -> &str {
self.0.category().as_str()
pub fn category(&mut self) -> napi::Result<&str> {
let dependency = self.as_ref()?;

Ok(dependency.category().as_str())
}

#[napi(getter)]
pub fn request(&self) -> napi::Either<&str, ()> {
match self.0.as_module_dependency() {
pub fn request(&mut self) -> napi::Result<napi::Either<&str, ()>> {
let dependency = self.as_ref()?;

Ok(match dependency.as_module_dependency() {
Some(dep) => napi::Either::A(dep.request()),
None => napi::Either::B(()),
}
})
}

#[napi(getter)]
pub fn critical(&self) -> bool {
match self.0.as_context_dependency() {
pub fn critical(&mut self) -> napi::Result<bool> {
let dependency = self.as_ref()?;

Ok(match dependency.as_context_dependency() {
Some(dep) => dep.critical().is_some(),
None => false,
})
}

#[napi(setter)]
pub fn set_critical(&mut self, val: bool) -> napi::Result<()> {
let dependency = self.as_mut()?;

if let Some(dep) = dependency.as_context_dependency_mut() {
let critical = dep.critical_mut();
if !val {
*critical = None;
}
}
Ok(())
}
}

// 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 JsDependencyMut(&'static mut BoxDependency);
type DependencyInstanceRefs = HashMap<DependencyId, OneShotRef<JsDependency>>;

impl JsDependencyMut {
pub(crate) fn new(dependency: &mut BoxDependency) -> Self {
// SAFETY:
// The lifetime of the &mut BoxDependency reference is extended to 'static.
// Accessing fields and methods on the Rust object from the JS side after the Rust object's
// lifetime has ended is undefined behavior, which we currently disregard.
let dependency =
unsafe { std::mem::transmute::<&mut BoxDependency, &'static mut BoxDependency>(dependency) };
Self(dependency)
}
type DependencyInstanceRefsByCompilationId =
RefCell<HashMap<CompilationId, DependencyInstanceRefs>>;

thread_local! {
static DEPENDENCY_INSTANCE_REFS: DependencyInstanceRefsByCompilationId = Default::default();
}

#[napi]
impl JsDependencyMut {
#[napi(getter)]
pub fn get_type(&self) -> &str {
self.0.dependency_type().as_str()
}
pub struct JsDependencyWrapper {
dependency_id: DependencyId,
dependency: NonNull<dyn Dependency>,
compilation_id: CompilationId,
compilation: Option<NonNull<Compilation>>,
}

#[napi(getter)]
pub fn category(&self) -> &str {
self.0.category().as_str()
}
impl JsDependencyWrapper {
pub fn new(
dependency: &dyn Dependency,
compilation_id: CompilationId,
compilation: Option<&Compilation>,
) -> Self {
let dependency_id = *dependency.id();

#[napi(getter)]
pub fn request(&self) -> napi::Either<&str, ()> {
match self.0.as_module_dependency() {
Some(dep) => napi::Either::A(dep.request()),
None => napi::Either::B(()),
#[allow(clippy::unwrap_used)]
Self {
dependency_id,
dependency: NonNull::new(dependency as *const dyn Dependency as *mut dyn Dependency).unwrap(),
compilation_id,
compilation: compilation
.map(|c| NonNull::new(c as *const Compilation as *mut Compilation).unwrap()),
}
}

#[napi(getter)]
pub fn critical(&self) -> bool {
match self.0.as_context_dependency() {
Some(dep) => dep.critical().is_some(),
None => false,
}
pub fn cleanup_last_compilation(compilation_id: CompilationId) {
DEPENDENCY_INSTANCE_REFS.with(|refs| {
let mut refs_by_compilation_id = refs.borrow_mut();
refs_by_compilation_id.remove(&compilation_id)
});
}
}

#[napi(setter)]
pub fn set_critical(&mut self, val: bool) {
if let Some(dep) = self.0.as_context_dependency_mut() {
let critical = dep.critical_mut();
if !val {
*critical = None;
impl ToNapiValue for JsDependencyWrapper {
unsafe fn to_napi_value(
env: napi::sys::napi_env,
val: Self,
) -> napi::Result<napi::sys::napi_value> {
DEPENDENCY_INSTANCE_REFS.with(|refs| {
let mut refs_by_compilation_id = refs.borrow_mut();
let entry = refs_by_compilation_id.entry(val.compilation_id);
let refs = match entry {
std::collections::hash_map::Entry::Occupied(entry) => entry.into_mut(),
std::collections::hash_map::Entry::Vacant(entry) => {
let refs = HashMap::default();
entry.insert(refs)
}
};

match refs.entry(val.dependency_id) {
std::collections::hash_map::Entry::Occupied(occupied_entry) => {
let r = occupied_entry.get();
let instance = r.from_napi_mut_ref()?;
instance.compilation = val.compilation;
instance.dependency = val.dependency;

ToNapiValue::to_napi_value(env, r)
}
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
let js_dependency = JsDependency {
compilation: val.compilation,
dependency_id: val.dependency_id,
dependency: val.dependency,
};
let r = vacant_entry.insert(OneShotRef::new(env, js_dependency)?);
ToNapiValue::to_napi_value(env, r)
}
}
}
})
}
}
Loading

2 comments on commit 78aa2e0

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Benchmark detail: Open

Name Base (2024-11-13 0e01f8c) Current Change
10000_big_production-mode + exec 44.9 s ± 1.17 s 44.2 s ± 1.1 s -1.54 %
10000_development-mode + exec 1.81 s ± 45 ms 1.8 s ± 30 ms -0.66 %
10000_development-mode_hmr + exec 644 ms ± 7.8 ms 647 ms ± 13 ms +0.49 %
10000_production-mode + exec 2.43 s ± 49 ms 2.43 s ± 31 ms -0.11 %
arco-pro_development-mode + exec 1.77 s ± 77 ms 1.78 s ± 74 ms +0.59 %
arco-pro_development-mode_hmr + exec 431 ms ± 2 ms 430 ms ± 1.3 ms -0.16 %
arco-pro_production-mode + exec 3.17 s ± 70 ms 3.19 s ± 48 ms +0.52 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.23 s ± 79 ms 3.22 s ± 93 ms -0.18 %
threejs_development-mode_10x + exec 1.58 s ± 15 ms 1.58 s ± 13 ms -0.20 %
threejs_development-mode_10x_hmr + exec 778 ms ± 6.8 ms 771 ms ± 10 ms -0.95 %
threejs_production-mode_10x + exec 4.95 s ± 35 ms 4.97 s ± 36 ms +0.42 %

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Ran ecosystem CI: Open

suite result
modernjs ✅ success
_selftest ✅ success
rspress ✅ success
rslib ✅ success
rsbuild ❌ failure
examples ✅ success
devserver ✅ success

Please sign in to comment.