Skip to content

Commit

Permalink
feat: support module graph connection js api (#8812)
Browse files Browse the repository at this point in the history
  • Loading branch information
SyMind authored Dec 23, 2024
1 parent db6a793 commit c698b83
Show file tree
Hide file tree
Showing 36 changed files with 389 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/reusable-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ jobs:
if: ${{ !inputs.skipable }}
uses: ./.github/actions/rustup
with:
save-cache: ${{ github.ref_name == 'main' }} # This should be safe because we have nightly building the cache every day
shared-key: build-${{ inputs.target }}-${{ inputs.profile }}

# Compile dependencies with optimization to make tests run faster
Expand Down Expand Up @@ -426,6 +427,7 @@ jobs:
- name: Install Rust Toolchain
uses: ./.github/actions/rustup
with:
save-cache: ${{ github.ref_name == 'main' }} # This should be safe because we have nightly building the cache every day
shared-key: build-${{ inputs.target }}-${{ inputs.profile }}

- name: Pnpm Cache
Expand Down
9 changes: 9 additions & 0 deletions crates/node_binding/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,18 @@ export declare class JsModule {

export declare class JsModuleGraph {
getModule(jsDependency: JsDependency): JsModule | null
getResolvedModule(jsDependency: JsDependency): JsModule | null
getUsedExports(jsModule: JsModule, jsRuntime: string | Array<string>): boolean | Array<string> | null
getIssuer(module: JsModule): JsModule | null
getExportsInfo(module: JsModule): JsExportsInfo
getConnection(dependency: JsDependency): JsModuleGraphConnection | null
getOutgoingConnections(module: JsModule): JsModuleGraphConnection[]
getIncomingConnections(module: JsModule): JsModuleGraphConnection[]
}

export declare class JsModuleGraphConnection {
get dependency(): JsDependency
get module(): JsModule | null
}

export declare class JsResolver {
Expand Down
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
70 changes: 69 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 napi_derive::napi;
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 @@ -39,6 +41,25 @@ impl JsModuleGraph {
Ok(js_module)
}

#[napi(ts_return_type = "JsModule | null")]
pub fn get_resolved_module(
&self,
js_dependency: &JsDependency,
) -> napi::Result<Option<JsModuleWrapper>> {
let (compilation, module_graph) = self.as_ref()?;
Ok(
match module_graph.connection_by_dependency_id(&js_dependency.dependency_id) {
Some(connection) => match connection.resolved_original_module_identifier {
Some(identifier) => compilation.module_by_identifier(&identifier).map(|module| {
JsModuleWrapper::new(module.as_ref(), compilation.id(), Some(compilation))
}),
None => None,
},
None => None,
},
)
}

#[napi]
pub fn get_used_exports(
&self,
Expand Down Expand Up @@ -87,4 +108,51 @@ impl JsModuleGraph {
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)
}),
)
}

#[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)
})
.collect::<Vec<_>>(),
)
}

#[napi(ts_return_type = "JsModuleGraphConnection[]")]
pub fn get_incoming_connections(
&self,
module: &JsModule,
) -> napi::Result<Vec<JsModuleGraphConnectionWrapper>> {
let (compilation, module_graph) = self.as_ref()?;
Ok(
module_graph
.get_incoming_connections(&module.identifier)
.map(|connection| {
JsModuleGraphConnectionWrapper::new(connection.dependency_id, compilation)
})
.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)
}
}
})
}
}
Empty file.
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()
]
};
19 changes: 19 additions & 0 deletions packages/rspack/etc/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { JsHtmlPluginTag } from '@rspack/binding';
import { JsLoaderItem } from '@rspack/binding';
import { JsModule } from '@rspack/binding';
import type { JsModuleGraph } from '@rspack/binding';
import type { 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 @@ -3758,11 +3759,29 @@ 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[];
// (undocumented)
getResolvedModule(dependency: Dependency): Module | null;
}

// @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
21 changes: 21 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 All @@ -19,6 +20,13 @@ export default class ModuleGraph {
return binding ? Module.__from_binding(binding) : null;
}

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

getIssuer(module: Module): Module | null {
const binding = this.#inner.getIssuer(Module.__to_binding(module));
return binding ? Module.__from_binding(binding) : null;
Expand All @@ -29,4 +37,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));
}
}
Loading

0 comments on commit c698b83

Please sign in to comment.