Skip to content

Commit

Permalink
feat: js module graph api (#8470)
Browse files Browse the repository at this point in the history
  • Loading branch information
SyMind authored Nov 20, 2024
1 parent 0805f70 commit 0891a65
Show file tree
Hide file tree
Showing 15 changed files with 258 additions and 8 deletions.
7 changes: 7 additions & 0 deletions crates/node_binding/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export declare class JsCompilation {
importModule(request: string, layer: string | undefined | null, publicPath: JsFilename | undefined | null, baseUri: string | undefined | null, originalModule: string | undefined | null, originalModuleContext: string | undefined | null, callback: any): void
get entries(): JsEntries
addRuntimeModule(chunkUkey: number, runtimeModule: JsAddingRuntimeModule): void
get moduleGraph(): JsModuleGraph
}

export declare class JsContextModuleFactoryAfterResolveData {
Expand Down Expand Up @@ -181,6 +182,12 @@ export declare class JsModule {
get useSourceMap(): boolean
}

export declare class JsModuleGraph {
getModule(jsDependency: JsDependency): JsModule | null
getUsedExports(jsModule: JsModule, jsRuntime: string | Array<string>): boolean | Array<string> | null
getIssuer(module: JsModule): JsModule | null
}

export declare class JsResolver {
resolveSync(path: string, request: string): string | false
withOptions(raw?: RawResolveOptionsWithDependencyType | undefined | null): JsResolver
Expand Down
7 changes: 7 additions & 0 deletions crates/rspack_binding_values/src/compilation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use rspack_plugin_runtime::RuntimeModuleFromJs;
use super::{JsFilename, PathWithInfo};
use crate::utils::callbackify;
use crate::JsAddingRuntimeModule;
use crate::JsModuleGraph;
use crate::JsModuleWrapper;
use crate::JsStatsOptimizationBailout;
use crate::LocalJsFilename;
Expand Down Expand Up @@ -694,6 +695,12 @@ impl JsCompilation {
)
.map_err(|e| Error::new(napi::Status::GenericFailure, format!("{e}")))
}

#[napi(getter)]
pub fn module_graph(&self) -> napi::Result<JsModuleGraph> {
let compilation = self.as_ref()?;
Ok(JsModuleGraph::new(compilation))
}
}

thread_local! {
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 @@ -13,6 +13,7 @@ mod filename;
mod html;
mod identifier;
mod module;
mod module_graph;
mod normal_module_factory;
mod options;
mod path_data;
Expand All @@ -37,6 +38,7 @@ pub use dependency_block::*;
pub use filename::*;
pub use html::*;
pub use module::*;
pub use module_graph::*;
pub use normal_module_factory::*;
pub use options::*;
pub use path_data::*;
Expand Down
2 changes: 1 addition & 1 deletion crates/rspack_binding_values/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct JsFactoryMeta {

#[napi]
pub struct JsModule {
identifier: ModuleIdentifier,
pub(crate) identifier: ModuleIdentifier,
module: NonNull<dyn Module>,
compilation_id: CompilationId,
compilation: Option<NonNull<Compilation>>,
Expand Down
83 changes: 83 additions & 0 deletions crates/rspack_binding_values/src/module_graph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use std::{ptr::NonNull, sync::Arc};

use napi::{Either, Env, JsString};
use napi_derive::napi;
use rspack_core::{Compilation, ModuleGraph, RuntimeSpec};
use rustc_hash::FxHashSet;

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

#[napi]
pub struct JsModuleGraph {
compilation: NonNull<Compilation>,
}

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

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 JsModuleGraph {
#[napi(ts_return_type = "JsModule | null")]
pub fn get_module(&self, js_dependency: &JsDependency) -> napi::Result<Option<JsModuleWrapper>> {
let (compilation, module_graph) = self.as_ref()?;
let module = module_graph.get_module_by_dependency_id(&js_dependency.dependency_id);
let js_module = module
.map(|module| JsModuleWrapper::new(module.as_ref(), compilation.id(), Some(compilation)));
Ok(js_module)
}

#[napi]
pub fn get_used_exports(
&self,
env: Env,
js_module: &JsModule,
js_runtime: Either<String, Vec<String>>,
) -> napi::Result<Option<Either<bool, Vec<JsString>>>> {
let (_, module_graph) = self.as_ref()?;

let mut runtime: FxHashSet<Arc<str>> = FxHashSet::default();
match js_runtime {
Either::A(s) => {
runtime.insert(Arc::from(s));
}
Either::B(vec) => {
runtime.extend(vec.into_iter().map(Arc::from));
}
};
let used_exports =
module_graph.get_used_exports(&js_module.identifier, Some(&RuntimeSpec::new(runtime)));
Ok(match used_exports {
rspack_core::UsedExports::Null => None,
rspack_core::UsedExports::Bool(b) => Some(Either::A(b)),
rspack_core::UsedExports::Vec(vec) => Some(Either::B(
vec
.into_iter()
.map(|atom| env.create_string(atom.as_str()))
.collect::<napi::Result<Vec<_>>>()?,
)),
})
}

#[napi(ts_return_type = "JsModule | null")]
pub fn get_issuer(&self, module: &JsModule) -> napi::Result<Option<JsModuleWrapper>> {
let (compilation, module_graph) = self.as_ref()?;
let issuer = module_graph.get_issuer(&module.identifier);
Ok(
issuer
.map(|module| JsModuleWrapper::new(module.as_ref(), compilation.id(), Some(compilation))),
)
}
}
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,32 @@
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 fooModule = Array.from(compilation.modules.values())
.find(module => normalize(module.request) === normalize(join(__dirname, 'foo.js')));
const issuer = compilation.moduleGraph.getIssuer(fooModule);
expect(normalize(issuer.request)).toBe(normalize(join(__dirname, 'index.js')));
});
});

}
}

/** @type {import("@rspack/core").Configuration} */
module.exports = {
target: "web",
node: {
__dirname: false,
__filename: false,
},
plugins: [
new Plugin()
]
};
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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 refModule = compilation.moduleGraph.getModule(entryDependency);
expect(normalize(refModule.request)).toBe(normalize(join(__dirname, 'index.js')));
});
});

}
}

/** @type {import("@rspack/core").Configuration} */
module.exports = {
target: "web",
node: {
__dirname: false,
__filename: false,
},
plugins: [
new Plugin()
]
};
28 changes: 27 additions & 1 deletion packages/rspack/etc/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { JsHtmlPluginTag } from '@rspack/binding';
import { JsLibraryOptions } from '@rspack/binding';
import { JsLoaderItem } from '@rspack/binding';
import { JsModule } from '@rspack/binding';
import type { JsModuleGraph } from '@rspack/binding';
import { JsRuntimeModule } from '@rspack/binding';
import type { JsStats } from '@rspack/binding';
import type { JsStatsCompilation } from '@rspack/binding';
Expand Down Expand Up @@ -713,6 +714,8 @@ export class Compilation {
addAll: (deps: Iterable<string>) => void;
};
// (undocumented)
moduleGraph: ModuleGraph;
// (undocumented)
get modules(): ReadonlySet<Module>;
// (undocumented)
name?: string;
Expand Down Expand Up @@ -1316,6 +1319,8 @@ class Dependency {
// (undocumented)
static __from_binding(binding: JsDependency): Dependency;
// (undocumented)
static __to_binding(data: Dependency): JsDependency;
// (undocumented)
readonly category: string;
// (undocumented)
critical: boolean;
Expand Down Expand Up @@ -1595,7 +1600,16 @@ interface Entry_2 {
}

// @public (undocumented)
type EntryData = binding.JsEntryData;
class EntryData {
// (undocumented)
static __from_binding(binding: binding.JsEntryData): EntryData;
// (undocumented)
dependencies: Dependency[];
// (undocumented)
includeDependencies: Dependency[];
// (undocumented)
options: binding.JsEntryOptions;
}

// @public
export type EntryDependOn = string | string[];
Expand Down Expand Up @@ -3542,6 +3556,8 @@ export class Module {
// (undocumented)
static __from_binding(binding: JsModule, compilation?: Compilation): Module;
// (undocumented)
static __to_binding(module: Module): JsModule;
// (undocumented)
readonly blocks: DependenciesBlock[];
readonly buildInfo: Record<string, any>;
readonly buildMeta: Record<string, any>;
Expand Down Expand Up @@ -3642,6 +3658,16 @@ type ModuleFilterItemTypes = RegExp | string | ((name: string, module: any, type
// @public (undocumented)
type ModuleFilterTypes = boolean | ModuleFilterItemTypes | ModuleFilterItemTypes[];

// @public (undocumented)
class ModuleGraph {
// (undocumented)
static __from_binding(binding: JsModuleGraph): ModuleGraph;
// (undocumented)
getIssuer(module: Module): Module | null;
// (undocumented)
getModule(dependency: Dependency): Module | null;
}

// @public (undocumented)
export type ModuleOptions = {
defaultRules?: RuleSetRules;
Expand Down
35 changes: 29 additions & 6 deletions packages/rspack/src/Compilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ import { ChunkGraph } from "./ChunkGraph";
import { ChunkGroup } from "./ChunkGroup";
import type { Compiler } from "./Compiler";
import type { ContextModuleFactory } from "./ContextModuleFactory";
import { Dependency } from "./Dependency";
import { Entrypoint } from "./Entrypoint";
import { cutOffLoaderExecution } from "./ErrorHelpers";
import { type CodeGenerationResult, Module } from "./Module";
import ModuleGraph from "./ModuleGraph";
import type { NormalModuleFactory } from "./NormalModuleFactory";
import type { ResolverFactory } from "./ResolverFactory";
import { JsRspackDiagnostic, type RspackError } from "./RspackError";
Expand Down Expand Up @@ -248,6 +250,7 @@ export class Compilation {
childrenCounters: Record<string, number>;
children: Compilation[];
chunkGraph: ChunkGraph;
moduleGraph: ModuleGraph;
fileSystemInfo = {
createSnapshot() {
// fake implement to support html-webpack-plugin
Expand Down Expand Up @@ -378,7 +381,9 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
this.logging = new Map();
this.childrenCounters = {};
this.children = [];

this.chunkGraph = new ChunkGraph(this);
this.moduleGraph = ModuleGraph.__from_binding(inner.moduleGraph);
}

get hash(): Readonly<string | null> {
Expand Down Expand Up @@ -1244,7 +1249,23 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
static PROCESS_ASSETS_STAGE_REPORT = 5000;
}

export type EntryData = binding.JsEntryData;
export class EntryData {
dependencies: Dependency[];
includeDependencies: Dependency[];
options: binding.JsEntryOptions;

static __from_binding(binding: binding.JsEntryData): EntryData {
return new EntryData(binding);
}

private constructor(binding: binding.JsEntryData) {
this.dependencies = binding.dependencies.map(Dependency.__from_binding);
this.includeDependencies = binding.includeDependencies.map(
Dependency.__from_binding
);
this.options = binding.options;
}
}

export class Entries implements Map<string, EntryData> {
#data: binding.JsEntries;
Expand All @@ -1259,13 +1280,14 @@ export class Entries implements Map<string, EntryData> {

forEach(
callback: (
value: binding.JsEntryData,
value: EntryData,
key: string,
map: Map<string, binding.JsEntryData>
map: Map<string, EntryData>
) => void,
thisArg?: any
): void {
for (const [key, value] of this) {
for (const [key, binding] of this) {
const value = EntryData.__from_binding(binding);
callback.call(thisArg, value, key, this);
}
}
Expand All @@ -1281,7 +1303,7 @@ export class Entries implements Map<string, EntryData> {
}

values(): ReturnType<Map<string, EntryData>["values"]> {
return this.#data.values()[Symbol.iterator]();
return this.#data.values().map(EntryData.__from_binding)[Symbol.iterator]();
}

[Symbol.iterator](): ReturnType<Map<string, EntryData>["entries"]> {
Expand All @@ -1306,7 +1328,8 @@ export class Entries implements Map<string, EntryData> {
}

get(key: string): EntryData | undefined {
return this.#data.get(key);
const binding = this.#data.get(key);
return binding ? EntryData.__from_binding(binding) : undefined;
}

keys(): ReturnType<Map<string, EntryData>["keys"]> {
Expand Down
Loading

2 comments on commit 0891a65

@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-20 522c39e) Current Change
10000_big_production-mode_disable-minimize + exec 40.1 s ± 912 ms 40 s ± 905 ms -0.09 %
10000_development-mode + exec 1.81 s ± 36 ms 1.77 s ± 31 ms -2.23 %
10000_development-mode_hmr + exec 640 ms ± 7.2 ms 635 ms ± 5.6 ms -0.78 %
10000_production-mode + exec 2.38 s ± 19 ms 2.38 s ± 16 ms -0.01 %
arco-pro_development-mode + exec 1.76 s ± 73 ms 1.78 s ± 63 ms +1.23 %
arco-pro_development-mode_hmr + exec 429 ms ± 1.7 ms 428 ms ± 2.7 ms -0.24 %
arco-pro_production-mode + exec 3.15 s ± 83 ms 3.11 s ± 89 ms -1.33 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.21 s ± 68 ms 3.14 s ± 52 ms -2.09 %
threejs_development-mode_10x + exec 1.59 s ± 20 ms 1.6 s ± 20 ms +0.76 %
threejs_development-mode_10x_hmr + exec 778 ms ± 8.9 ms 785 ms ± 13 ms +0.79 %
threejs_production-mode_10x + exec 4.93 s ± 38 ms 4.87 s ± 38 ms -1.15 %
10000_big_production-mode_disable-minimize + rss memory 12503 MiB ± 80.1 MiB 12869 MiB ± 109 MiB +2.93 %
10000_development-mode + rss memory 748 MiB ± 21.2 MiB 749 MiB ± 23.6 MiB +0.17 %
10000_development-mode_hmr + rss memory 1512 MiB ± 422 MiB 1652 MiB ± 405 MiB +9.28 %
10000_production-mode + rss memory 657 MiB ± 28.9 MiB 659 MiB ± 44 MiB +0.32 %
arco-pro_development-mode + rss memory 692 MiB ± 35.1 MiB 678 MiB ± 32.6 MiB -2.01 %
arco-pro_development-mode_hmr + rss memory 881 MiB ± 116 MiB 851 MiB ± 42.2 MiB -3.40 %
arco-pro_production-mode + rss memory 841 MiB ± 37 MiB 858 MiB ± 60.8 MiB +1.97 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 840 MiB ± 38 MiB 855 MiB ± 49.9 MiB +1.86 %
threejs_development-mode_10x + rss memory 795 MiB ± 51 MiB 815 MiB ± 52.3 MiB +2.54 %
threejs_development-mode_10x_hmr + rss memory 1561 MiB ± 92.7 MiB 1458 MiB ± 655 MiB -6.54 %
threejs_production-mode_10x + rss memory 1067 MiB ± 38.6 MiB 1028 MiB ± 52.8 MiB -3.72 %

@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 ❌ failure
_selftest ✅ success
rspress ✅ success
rslib ✅ success
rsbuild ✅ success
examples ✅ success
devserver ✅ success
nuxt ✅ success

Please sign in to comment.