Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support module graph connection js api #8782

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
1,039 changes: 1,032 additions & 7 deletions crates/node_binding/binding.d.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions crates/rspack_binding_values/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod html;
mod identifier;
mod module;
mod module_graph;
mod module_graph_connection;
mod normal_module_factory;
mod options;
mod path_data;
Expand Down Expand Up @@ -47,6 +48,7 @@ pub use filename::*;
pub use html::*;
pub use module::*;
pub use module_graph::*;
pub use module_graph_connection::*;
pub use normal_module_factory::*;
pub use options::*;
pub use path_data::*;
Expand Down
35 changes: 34 additions & 1 deletion crates/rspack_binding_values/src/module_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
use rspack_core::{Compilation, ModuleGraph, RuntimeSpec};
use rustc_hash::FxHashSet;

use crate::{JsDependency, JsExportsInfo, JsModule, JsModuleWrapper};
use crate::{
JsDependency, JsExportsInfo, JsModule, JsModuleGraphConnectionWrapper, JsModuleWrapper,
};

#[napi]
pub struct JsModuleGraph {
Expand Down Expand Up @@ -87,4 +89,35 @@
let exports_info = module_graph.get_exports_info(&module.identifier);
Ok(JsExportsInfo::new(exports_info, compilation))
}

#[napi(ts_return_type = "JsModuleGraphConnection | null")]
pub fn get_connection(
&self,
dependency: &JsDependency,
) -> napi::Result<Option<JsModuleGraphConnectionWrapper>> {
let (compilation, module_graph) = self.as_ref()?;
Ok(
module_graph
.connection_by_dependency_id(&dependency.dependency_id)
.map(|connection| {
JsModuleGraphConnectionWrapper::new(connection.dependency_id, &compilation)

Check failure on line 103 in crates/rspack_binding_values/src/module_graph.rs

View workflow job for this annotation

GitHub Actions / Rust check

this expression creates a reference which is immediately dereferenced by the compiler
}),
)
}

#[napi(ts_return_type = "JsModuleGraphConnection[]")]
pub fn get_outgoing_connections(
&self,
module: &JsModule,
) -> napi::Result<Vec<JsModuleGraphConnectionWrapper>> {
let (compilation, module_graph) = self.as_ref()?;
Ok(
module_graph
.get_outgoing_connections(&module.identifier)
.map(|connection| {
JsModuleGraphConnectionWrapper::new(connection.dependency_id, &compilation)

Check failure on line 118 in crates/rspack_binding_values/src/module_graph.rs

View workflow job for this annotation

GitHub Actions / Rust check

this expression creates a reference which is immediately dereferenced by the compiler
})
.collect::<Vec<_>>(),
)
}
}
125 changes: 125 additions & 0 deletions crates/rspack_binding_values/src/module_graph_connection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use std::{cell::RefCell, ptr::NonNull};

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

use crate::{JsDependencyWrapper, JsModuleWrapper};

#[napi]
pub struct JsModuleGraphConnection {
compilation: NonNull<Compilation>,
dependency_id: DependencyId,
}

impl JsModuleGraphConnection {
fn as_ref(&self) -> napi::Result<(&'static Compilation, ModuleGraph<'static>)> {
let compilation = unsafe { self.compilation.as_ref() };
let module_graph = compilation.get_module_graph();

Ok((compilation, module_graph))
}
}

#[napi]
impl JsModuleGraphConnection {
#[napi(getter, ts_return_type = "JsDependency")]
pub fn dependency(&self) -> napi::Result<JsDependencyWrapper> {
let (compilation, module_graph) = self.as_ref()?;
if let Some(dependency) = module_graph.dependency_by_id(&self.dependency_id) {
Ok(JsDependencyWrapper::new(
dependency.as_ref(),
compilation.id(),
Some(compilation),
))
} 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
)))
}
}

#[napi(getter, ts_return_type = "JsModule | null")]
pub fn module(&self) -> napi::Result<Option<JsModuleWrapper>> {
let (compilation, module_graph) = self.as_ref()?;
if let Some(connection) = module_graph.connection_by_dependency_id(&self.dependency_id) {
let module = module_graph.module_by_identifier(connection.module_identifier());
Ok(module.map(|m| JsModuleWrapper::new(m.as_ref(), compilation.id(), Some(compilation))))
} else {
Err(napi::Error::from_reason(format!(
"Unable to access ModuleGraphConnection with id = {:#?} now. The ModuleGraphConnection have been removed on the Rust side.",
self.dependency_id
)))
}
}
}

type ModuleGraphConnectionRefs = HashMap<DependencyId, OneShotRef<JsModuleGraphConnection>>;

type ModuleGraphConnectionRefsByCompilationId =
RefCell<HashMap<CompilationId, ModuleGraphConnectionRefs>>;

thread_local! {
static MODULE_GRAPH_CONNECTION_INSTANCE_REFS: ModuleGraphConnectionRefsByCompilationId = Default::default();
}

pub struct JsModuleGraphConnectionWrapper {
compilation_id: CompilationId,
compilation: NonNull<Compilation>,
dependency_id: DependencyId,
}

impl JsModuleGraphConnectionWrapper {
pub fn new(dependency_id: DependencyId, compilation: &Compilation) -> Self {
#[allow(clippy::unwrap_used)]
Self {
dependency_id,
compilation_id: compilation.id(),
compilation: NonNull::new(compilation as *const Compilation as *mut Compilation).unwrap(),
}
}

pub fn cleanup_last_compilation(compilation_id: CompilationId) {
MODULE_GRAPH_CONNECTION_INSTANCE_REFS.with(|refs| {
let mut refs_by_compilation_id = refs.borrow_mut();
refs_by_compilation_id.remove(&compilation_id)
});
}
}

impl ToNapiValue for JsModuleGraphConnectionWrapper {
unsafe fn to_napi_value(
env: napi::sys::napi_env,
val: Self,
) -> napi::Result<napi::sys::napi_value> {
MODULE_GRAPH_CONNECTION_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();
ToNapiValue::to_napi_value(env, r)
}
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
let js_dependency = JsModuleGraphConnection {
compilation: val.compilation,
dependency_id: val.dependency_id,
};
let r = vacant_entry.insert(OneShotRef::new(env, js_dependency)?);
ToNapiValue::to_napi_value(env, r)
}
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./foo";
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const { normalize, join } = require('path');

const PLUGIN_NAME = "Test";

class Plugin {
/**
* @param {import("@rspack/core").Compiler} compiler
*/
apply(compiler) {
compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
compilation.hooks.finishModules.tap(PLUGIN_NAME, () => {
const entry = Array.from(compilation.entries.values())[0];
const entryDependency = entry.dependencies[0];
const connection = compilation.moduleGraph.getConnection(entryDependency);
const outgoingConnection = compilation.moduleGraph.getOutgoingConnections(connection.module)[0];
expect(normalize(outgoingConnection.module.request)).toBe(normalize(join(__dirname, 'foo.js')));
});
});

}
}

/** @type {import("@rspack/core").Configuration} */
module.exports = {
target: "web",
node: {
__dirname: false,
__filename: false,
},
plugins: [
new Plugin()
]
};
17 changes: 17 additions & 0 deletions packages/rspack/etc/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { JsLibraryOptions } from '@rspack/binding';
import { JsLoaderItem } from '@rspack/binding';
import { JsModule } from '@rspack/binding';
import type { JsModuleGraph } from '@rspack/binding';
import { JsModuleGraphConnection } from '@rspack/binding';
import { JsRuntimeModule } from '@rspack/binding';
import type { JsStats } from '@rspack/binding';
import type { JsStatsCompilation } from '@rspack/binding';
Expand Down Expand Up @@ -3761,11 +3762,27 @@ class ModuleGraph {
// (undocumented)
static __from_binding(binding: JsModuleGraph): ModuleGraph;
// (undocumented)
getConnection(dependency: Dependency): ModuleGraphConnection | null;
// (undocumented)
getExportsInfo(module: Module): ExportsInfo;
// (undocumented)
getIssuer(module: Module): Module | null;
// (undocumented)
getModule(dependency: Dependency): Module | null;
// (undocumented)
getOutgoingConnections(module: Module): ModuleGraphConnection[];
}

// @public (undocumented)
class ModuleGraphConnection {
// (undocumented)
static __from_binding(binding: JsModuleGraphConnection): ModuleGraphConnection;
// (undocumented)
static __to_binding(data: ModuleGraphConnection): JsModuleGraphConnection;
// (undocumented)
readonly dependency: Dependency;
// (undocumented)
readonly module: Module | null;
}

// @public (undocumented)
Expand Down
14 changes: 14 additions & 0 deletions packages/rspack/src/ModuleGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { JsModuleGraph } from "@rspack/binding";
import { Dependency } from "./Dependency";
import { ExportsInfo } from "./ExportsInfo";
import { Module } from "./Module";
import { ModuleGraphConnection } from "./ModuleGraphConnection";

export default class ModuleGraph {
static __from_binding(binding: JsModuleGraph) {
Expand Down Expand Up @@ -29,4 +30,17 @@ export default class ModuleGraph {
this.#inner.getExportsInfo(Module.__to_binding(module))
);
}

getConnection(dependency: Dependency): ModuleGraphConnection | null {
const binding = this.#inner.getConnection(
Dependency.__to_binding(dependency)
);
return binding ? ModuleGraphConnection.__from_binding(binding) : null;
}

getOutgoingConnections(module: Module): ModuleGraphConnection[] {
return this.#inner
.getOutgoingConnections(Module.__to_binding(module))
.map(binding => ModuleGraphConnection.__from_binding(binding));
}
}
48 changes: 48 additions & 0 deletions packages/rspack/src/ModuleGraphConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { JsModuleGraphConnection } from "@rspack/binding";
import { Dependency } from "./Dependency";
import { Module } from "./Module";

const MODULE_GRAPH_CONNECTION_MAPPINGS = new WeakMap<
JsModuleGraphConnection,
ModuleGraphConnection
>();

export class ModuleGraphConnection {
declare readonly module: Module | null;
declare readonly dependency: Dependency;

#inner: JsModuleGraphConnection;

static __from_binding(binding: JsModuleGraphConnection) {
let connection = MODULE_GRAPH_CONNECTION_MAPPINGS.get(binding);
if (connection) {
return connection;
}
connection = new ModuleGraphConnection(binding);
MODULE_GRAPH_CONNECTION_MAPPINGS.set(binding, connection);
return connection;
}

static __to_binding(data: ModuleGraphConnection): JsModuleGraphConnection {
return data.#inner;
}

private constructor(binding: JsModuleGraphConnection) {
this.#inner = binding;

Object.defineProperties(this, {
module: {
enumerable: true,
get(): Module | null {
return binding.module ? Module.__from_binding(binding.module) : null;
}
},
dependency: {
enumerable: true,
get(): Dependency {
return Dependency.__from_binding(binding.dependency);
}
}
});
}
}
Loading