From b73a00a6fc18ec0f1a852e9cab3fdfa2222c6779 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Fri, 17 Jan 2025 16:53:29 +0800 Subject: [PATCH 1/4] feat: support rspack native plugin --- Cargo.lock | 22 + Cargo.toml | 1 + crates/node_binding/Cargo.toml | 1 + crates/node_binding/binding.d.ts | 169 ++++++- .../node_binding/src/plugins/interceptor.rs | 171 ++++++- crates/node_binding/src/plugins/mod.rs | 67 +++ crates/rspack_binding_values/Cargo.toml | 1 + crates/rspack_binding_values/src/lib.rs | 2 + .../src/raw_options/raw_builtins/mod.rs | 9 +- crates/rspack_binding_values/src/rsdoctor.rs | 472 ++++++++++++++++++ .../src/chunk_graph/chunk_graph_module.rs | 10 + crates/rspack_core/src/chunk_group.rs | 2 +- .../rspack_core/src/compiler/compilation.rs | 7 + crates/rspack_core/src/concatenated_module.rs | 4 + crates/rspack_plugin_rsdoctor/Cargo.toml | 27 + crates/rspack_plugin_rsdoctor/LICENSE | 22 + .../rspack_plugin_rsdoctor/src/chunk_graph.rs | 265 ++++++++++ crates/rspack_plugin_rsdoctor/src/data.rs | 202 ++++++++ crates/rspack_plugin_rsdoctor/src/drive.rs | 21 + crates/rspack_plugin_rsdoctor/src/lib.rs | 12 + .../src/module_graph.rs | 236 +++++++++ crates/rspack_plugin_rsdoctor/src/plugin.rs | 463 +++++++++++++++++ .../tests/configCases/rsdoctor/assets/a.js | 7 + .../tests/configCases/rsdoctor/assets/b.js | 7 + .../tests/configCases/rsdoctor/assets/c.js | 1 + .../tests/configCases/rsdoctor/assets/d.js | 1 + .../rsdoctor/assets/rspack.config.js | 95 ++++ .../configCases/rsdoctor/assets/shared.js | 2 + .../rsdoctor/assets/test.config.js | 7 + .../configCases/rsdoctor/chunkGraph/a.js | 7 + .../configCases/rsdoctor/chunkGraph/b.js | 7 + .../configCases/rsdoctor/chunkGraph/c.js | 1 + .../configCases/rsdoctor/chunkGraph/d.js | 1 + .../rsdoctor/chunkGraph/rspack.config.js | 59 +++ .../configCases/rsdoctor/chunkGraph/shared.js | 2 + .../rsdoctor/chunkGraph/test.config.js | 7 + .../configCases/rsdoctor/moduleGraph/index.js | 6 + .../configCases/rsdoctor/moduleGraph/lib/a.js | 1 + .../configCases/rsdoctor/moduleGraph/lib/b.js | 1 + .../configCases/rsdoctor/moduleGraph/lib/c.js | 2 + .../rsdoctor/moduleGraph/rspack.config.js | 69 +++ .../configCases/rsdoctor/moduleIds/index.js | 6 + .../configCases/rsdoctor/moduleIds/lib/a.js | 1 + .../configCases/rsdoctor/moduleIds/lib/b.js | 1 + .../configCases/rsdoctor/moduleIds/lib/c.js | 2 + .../rsdoctor/moduleIds/rspack.config.js | 25 + .../rsdoctor/moduleSources/index.js | 6 + .../rsdoctor/moduleSources/lib/a.js | 1 + .../rsdoctor/moduleSources/lib/b.js | 1 + .../rsdoctor/moduleSources/lib/c.js | 2 + .../rsdoctor/moduleSources/rspack.config.js | 34 ++ packages/rspack/etc/core.api.md | 54 ++ packages/rspack/rspack | 1 + .../scripts/check-documentation-coverage.ts | 2 +- packages/rspack/src/Compiler.ts | 7 +- .../src/builtin-plugin/RsdoctorPlugin.ts | 228 +++++++++ packages/rspack/src/builtin-plugin/index.ts | 1 + packages/rspack/src/exports.ts | 11 +- 58 files changed, 2842 insertions(+), 10 deletions(-) create mode 100644 crates/rspack_binding_values/src/rsdoctor.rs create mode 100644 crates/rspack_plugin_rsdoctor/Cargo.toml create mode 100644 crates/rspack_plugin_rsdoctor/LICENSE create mode 100644 crates/rspack_plugin_rsdoctor/src/chunk_graph.rs create mode 100644 crates/rspack_plugin_rsdoctor/src/data.rs create mode 100644 crates/rspack_plugin_rsdoctor/src/drive.rs create mode 100644 crates/rspack_plugin_rsdoctor/src/lib.rs create mode 100644 crates/rspack_plugin_rsdoctor/src/module_graph.rs create mode 100644 crates/rspack_plugin_rsdoctor/src/plugin.rs create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/assets/a.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/assets/b.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/assets/c.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/assets/d.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/assets/rspack.config.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/assets/shared.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/assets/test.config.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/a.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/b.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/c.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/d.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/rspack.config.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/shared.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/test.config.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/index.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/a.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/b.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/c.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/rspack.config.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/index.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/a.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/b.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/c.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/rspack.config.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/index.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/a.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/b.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/c.js create mode 100644 packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/rspack.config.js create mode 120000 packages/rspack/rspack create mode 100644 packages/rspack/src/builtin-plugin/RsdoctorPlugin.ts diff --git a/Cargo.lock b/Cargo.lock index 2a992b83ac1e..a5e9b640d08f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4278,6 +4278,7 @@ dependencies = [ "rspack_plugin_real_content_hash", "rspack_plugin_remove_duplicate_modules", "rspack_plugin_remove_empty_chunks", + "rspack_plugin_rsdoctor", "rspack_plugin_runtime", "rspack_plugin_runtime_chunk", "rspack_plugin_schemes", @@ -4681,6 +4682,7 @@ dependencies = [ "rspack_paths", "rspack_plugin_html", "rspack_plugin_javascript", + "rspack_plugin_rsdoctor", "rspack_tracing", "rspack_util", "tracing", @@ -5185,6 +5187,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "rspack_plugin_rsdoctor" +version = "0.2.0" +dependencies = [ + "async-trait", + "dashmap 6.1.0", + "futures", + "indexmap 2.7.0", + "rayon", + "rspack_collections", + "rspack_core", + "rspack_error", + "rspack_hook", + "rspack_paths", + "rspack_util", + "rustc-hash 2.1.0", + "tokio", + "tracing", +] + [[package]] name = "rspack_plugin_runtime" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index d65afb739b4c..d3f7aac8db4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,6 +172,7 @@ rspack_plugin_progress = { version = "0.2.0", path = "crates/rsp rspack_plugin_real_content_hash = { version = "0.2.0", path = "crates/rspack_plugin_real_content_hash" } rspack_plugin_remove_duplicate_modules = { version = "0.2.0", path = "crates/rspack_plugin_remove_duplicate_modules" } rspack_plugin_remove_empty_chunks = { version = "0.2.0", path = "crates/rspack_plugin_remove_empty_chunks" } +rspack_plugin_rsdoctor = { version = "0.2.0", path = "crates/rspack_plugin_rsdoctor" } rspack_plugin_runtime = { version = "0.2.0", path = "crates/rspack_plugin_runtime" } rspack_plugin_runtime_chunk = { version = "0.2.0", path = "crates/rspack_plugin_runtime_chunk" } rspack_plugin_schemes = { version = "0.2.0", path = "crates/rspack_plugin_schemes" } diff --git a/crates/node_binding/Cargo.toml b/crates/node_binding/Cargo.toml index 8f22cf09c42b..4bc683996947 100644 --- a/crates/node_binding/Cargo.toml +++ b/crates/node_binding/Cargo.toml @@ -28,6 +28,7 @@ rspack_napi = { workspace = true } rspack_paths = { workspace = true } rspack_plugin_html = { workspace = true } rspack_plugin_javascript = { workspace = true } +rspack_plugin_rsdoctor = { workspace = true } rspack_util = { workspace = true } rspack_tracing = { workspace = true } diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 3ea10d5d3bef..c555a955bd73 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -366,6 +366,7 @@ export declare enum BuiltinPluginName { LightningCssMinimizerRspackPlugin = 'LightningCssMinimizerRspackPlugin', BundlerInfoRspackPlugin = 'BundlerInfoRspackPlugin', CssExtractRspackPlugin = 'CssExtractRspackPlugin', + RsdoctorPlugin = 'RsdoctorPlugin', JsLoaderRspackPlugin = 'JsLoaderRspackPlugin', LazyCompilationPlugin = 'LazyCompilationPlugin' } @@ -813,6 +814,157 @@ export interface JsResourceData { fragment?: string } +export interface JsRsdoctorAsset { + ukey: number + path: string + chunks: Array + size: number +} + +export interface JsRsdoctorAssetPatch { + assets: Array + chunkAssets: Array + entrypointAssets: Array +} + +export interface JsRsdoctorChunk { + ukey: number + name: string + initial: boolean + entry: boolean + dependencies: Array + imported: Array +} + +export interface JsRsdoctorChunkAssets { + chunk: number + assets: Array +} + +export interface JsRsdoctorChunkGraph { + chunks: Array + entrypoints: Array +} + +export interface JsRsdoctorChunkModules { + chunk: number + modules: Array +} + +export interface JsRsdoctorDependency { + ukey: number + kind: string + request: string + module: number + dependency: number +} + +export interface JsRsdoctorEntrypoint { + ukey: number + name: string + chunks: Array +} + +export interface JsRsdoctorEntrypointAssets { + entrypoint: number + assets: Array +} + +export interface JsRsdoctorExportInfo { + ukey: number + name: string + from?: number + variable?: number + identifier?: JsRsdoctorStatement + sideEffects: Array +} + +export interface JsRsdoctorModule { + ukey: number + identifier: string + path: string + isEntry: boolean + kind: 'normal' | 'concatenated' + layer?: string + dependencies: Array + imported: Array + modules: Array + belongModules: Array + chunks: Array +} + +export interface JsRsdoctorModuleGraph { + modules: Array + dependencies: Array + chunkModules: Array +} + +export interface JsRsdoctorModuleGraphModule { + ukey: number + module: number + exports: Array + sideEffects: Array + variables: Array + dynamic: boolean +} + +export interface JsRsdoctorModuleId { + module: number + renderId: string +} + +export interface JsRsdoctorModuleIdsPatch { + moduleIds: Array +} + +export interface JsRsdoctorModuleOriginalSource { + module: number + source: string + size: number +} + +export interface JsRsdoctorModuleSourcesPatch { + moduleOriginalSources: Array +} + +export interface JsRsdoctorSideEffect { + ukey: number + name: string + originName?: string + module: number + identifier: JsRsdoctorStatement + isNameSpace: boolean + fromDependency?: number + exports: Array + variable?: number +} + +export interface JsRsdoctorSourcePosition { + line?: number + column?: number + index?: number +} + +export interface JsRsdoctorSourceRange { + start: JsRsdoctorSourcePosition + end?: JsRsdoctorSourcePosition +} + +export interface JsRsdoctorStatement { + module: number + sourcePosition?: JsRsdoctorSourceRange + transformedPosition: JsRsdoctorSourceRange +} + +export interface JsRsdoctorVariable { + ukey: number + name: string + module: number + usedInfo: string + identififer: JsRsdoctorStatement + exported?: number +} + export interface JsRspackDiagnostic { severity: JsRspackSeverity error: JsRspackError @@ -1947,6 +2099,11 @@ export interface RawResolveTsconfigOptions { references?: Array } +export interface RawRsdoctorPluginOptions { + moduleGraphFeatures: boolean | Array<'graph' | 'ids' | 'sources'> + chunkGraphFeatures: boolean | Array<'graph' | 'assets'> +} + export interface RawRspackFuture { } @@ -2117,7 +2274,12 @@ export declare enum RegisterJsTapKind { HtmlPluginAlterAssetTagGroups = 37, HtmlPluginAfterTemplateExecution = 38, HtmlPluginBeforeEmit = 39, - HtmlPluginAfterEmit = 40 + HtmlPluginAfterEmit = 40, + RsdoctorPluginModuleGraph = 41, + RsdoctorPluginChunkGraph = 42, + RsdoctorPluginModuleIds = 43, + RsdoctorPluginModuleSources = 44, + RsdoctorPluginAssets = 45 } export interface RegisterJsTaps { @@ -2162,6 +2324,11 @@ export interface RegisterJsTaps { registerHtmlPluginAfterTemplateExecutionTaps: (stages: Array) => Array<{ function: ((arg: JsAfterTemplateExecutionData) => JsAfterTemplateExecutionData); stage: number; }> registerHtmlPluginBeforeEmitTaps: (stages: Array) => Array<{ function: ((arg: JsBeforeEmitData) => JsBeforeEmitData); stage: number; }> registerHtmlPluginAfterEmitTaps: (stages: Array) => Array<{ function: ((arg: JsAfterEmitData) => JsAfterEmitData); stage: number; }> + registerRsdoctorPluginModuleGraphTaps: (stages: Array) => Array<{ function: ((arg: JsRsdoctorModuleGraph) => Promise); stage: number; }> + registerRsdoctorPluginChunkGraphTaps: (stages: Array) => Array<{ function: ((arg: JsRsdoctorChunkGraph) => Promise); stage: number; }> + registerRsdoctorPluginModuleIdsTaps: (stages: Array) => Array<{ function: ((arg: JsRsdoctorModuleIdsPatch) => Promise); stage: number; }> + registerRsdoctorPluginModuleSourcesTaps: (stages: Array) => Array<{ function: ((arg: JsRsdoctorModuleSourcesPatch) => Promise); stage: number; }> + registerRsdoctorPluginAssetsTaps: (stages: Array) => Array<{ function: ((arg: JsRsdoctorAssetPatch) => Promise); stage: number; }> } export interface ThreadsafeNodeFS { diff --git a/crates/node_binding/src/plugins/interceptor.rs b/crates/node_binding/src/plugins/interceptor.rs index dfe1e60e1854..0c034cc28711 100644 --- a/crates/node_binding/src/plugins/interceptor.rs +++ b/crates/node_binding/src/plugins/interceptor.rs @@ -19,8 +19,10 @@ use rspack_binding_values::{ JsContextModuleFactoryBeforeResolveDataWrapper, JsContextModuleFactoryBeforeResolveResult, JsCreateData, JsExecuteModuleArg, JsFactorizeArgs, JsFactorizeOutput, JsModuleWrapper, JsNormalModuleFactoryCreateModuleArgs, JsResolveArgs, JsResolveForSchemeArgs, - JsResolveForSchemeOutput, JsResolveOutput, JsRuntimeGlobals, JsRuntimeModule, JsRuntimeModuleArg, - JsRuntimeRequirementInTreeArg, JsRuntimeRequirementInTreeResult, ToJsCompatSourceOwned, + JsResolveForSchemeOutput, JsResolveOutput, JsRsdoctorAssetPatch, JsRsdoctorChunkGraph, + JsRsdoctorModuleGraph, JsRsdoctorModuleIdsPatch, JsRsdoctorModuleSourcesPatch, JsRuntimeGlobals, + JsRuntimeModule, JsRuntimeModuleArg, JsRuntimeRequirementInTreeArg, + JsRuntimeRequirementInTreeResult, ToJsCompatSourceOwned, }; use rspack_collections::IdentifierSet; use rspack_core::{ @@ -67,6 +69,13 @@ use rspack_plugin_html::{ HtmlPluginBeforeAssetTagGenerationHook, HtmlPluginBeforeEmit, HtmlPluginBeforeEmitHook, }; use rspack_plugin_javascript::{JavascriptModulesChunkHash, JavascriptModulesChunkHashHook}; +use rspack_plugin_rsdoctor::{ + RsdoctorAssetPatch, RsdoctorChunkGraph, RsdoctorModuleGraph, RsdoctorModuleIdsPatch, + RsdoctorModuleSourcesPatch, RsdoctorPluginAssets, RsdoctorPluginAssetsHook, + RsdoctorPluginChunkGraph, RsdoctorPluginChunkGraphHook, RsdoctorPluginModuleGraph, + RsdoctorPluginModuleGraphHook, RsdoctorPluginModuleIds, RsdoctorPluginModuleIdsHook, + RsdoctorPluginModuleSources, RsdoctorPluginModuleSourcesHook, +}; #[napi(object)] pub struct JsTap<'f> { @@ -386,6 +395,11 @@ pub enum RegisterJsTapKind { HtmlPluginAfterTemplateExecution, HtmlPluginBeforeEmit, HtmlPluginAfterEmit, + RsdoctorPluginModuleGraph, + RsdoctorPluginChunkGraph, + RsdoctorPluginModuleIds, + RsdoctorPluginModuleSources, + RsdoctorPluginAssets, } #[derive(Default, Clone)] @@ -559,6 +573,7 @@ pub struct RegisterJsTaps { ts_type = "(stages: Array) => Array<{ function: ((arg: JsChunk) => Buffer); stage: number; }>" )] pub register_javascript_modules_chunk_hash_taps: RegisterFunction, + // html plugin #[napi( ts_type = "(stages: Array) => Array<{ function: ((arg: JsBeforeAssetTagGenerationData) => JsBeforeAssetTagGenerationData); stage: number; }>" )] @@ -589,6 +604,32 @@ pub struct RegisterJsTaps { )] pub register_html_plugin_after_emit_taps: RegisterFunction>, + // rsdoctor plugin + #[napi( + ts_type = "(stages: Array) => Array<{ function: ((arg: JsRsdoctorModuleGraph) => Promise); stage: number; }>" + )] + pub register_rsdoctor_plugin_module_graph_taps: + RegisterFunction>>, + #[napi( + ts_type = "(stages: Array) => Array<{ function: ((arg: JsRsdoctorChunkGraph) => Promise); stage: number; }>" + )] + pub register_rsdoctor_plugin_chunk_graph_taps: + RegisterFunction>>, + #[napi( + ts_type = "(stages: Array) => Array<{ function: ((arg: JsRsdoctorModuleIdsPatch) => Promise); stage: number; }>" + )] + pub register_rsdoctor_plugin_module_ids_taps: + RegisterFunction>>, + #[napi( + ts_type = "(stages: Array) => Array<{ function: ((arg: JsRsdoctorModuleSourcesPatch) => Promise); stage: number; }>" + )] + pub register_rsdoctor_plugin_module_sources_taps: + RegisterFunction>>, + #[napi( + ts_type = "(stages: Array) => Array<{ function: ((arg: JsRsdoctorAssetPatch) => Promise); stage: number; }>" + )] + pub register_rsdoctor_plugin_assets_taps: + RegisterFunction>>, } /* Compiler Hooks */ @@ -936,6 +977,52 @@ define_register!( skip = true, ); +/* Rsdoctor Plugin Hooks */ +define_register!( + RegisterRsdoctorPluginModuleGraphTaps, + tap = RsdoctorPluginModuleGraphTap>> @ RsdoctorPluginModuleGraphHook, + cache = true, + sync = false, + kind = RegisterJsTapKind::RsdoctorPluginModuleGraph, + skip = true, +); + +define_register!( + RegisterRsdoctorPluginChunkGraphTaps, + tap = RsdoctorPluginChunkGraphTap>> @ RsdoctorPluginChunkGraphHook, + cache = true, + sync = false, + kind = RegisterJsTapKind::RsdoctorPluginChunkGraph, + skip = true, +); + +define_register!( + RegisterRsdoctorPluginAssetsTaps, + tap = RsdoctorPluginAssetsTap>> @ RsdoctorPluginAssetsHook, + cache = true, + sync = false, + kind = RegisterJsTapKind::RsdoctorPluginAssets, + skip = true, +); + +define_register!( + RegisterRsdoctorPluginModuleIdsTaps, + tap = RsdoctorPluginModuleIdsTap>> @ RsdoctorPluginModuleIdsHook, + cache = true, + sync = false, + kind = RegisterJsTapKind::RsdoctorPluginModuleIds, + skip = true, +); + +define_register!( + RegisterRsdoctorPluginModuleSourcesTaps, + tap = RsdoctorPluginModuleSourcesTap>> @ RsdoctorPluginModuleSourcesHook, + cache = true, + sync = false, + kind = RegisterJsTapKind::RsdoctorPluginModuleSources, + skip = true, +); + #[async_trait] impl CompilerThisCompilation for CompilerThisCompilationTap { async fn run( @@ -1768,3 +1855,83 @@ impl HtmlPluginAfterEmit for HtmlPluginAfterEmitTap { self.stage } } + +#[async_trait] +impl RsdoctorPluginModuleGraph for RsdoctorPluginModuleGraphTap { + async fn run(&self, data: &mut RsdoctorModuleGraph) -> rspack_error::Result> { + let data = std::mem::take(data); + let bail = self + .function + .call_with_promise(JsRsdoctorModuleGraph::from(data)) + .await?; + Ok(bail) + } + + fn stage(&self) -> i32 { + self.stage + } +} + +#[async_trait] +impl RsdoctorPluginChunkGraph for RsdoctorPluginChunkGraphTap { + async fn run(&self, data: &mut RsdoctorChunkGraph) -> rspack_error::Result> { + let data = std::mem::take(data); + let bail = self + .function + .call_with_promise(JsRsdoctorChunkGraph::from(data)) + .await?; + Ok(bail) + } + + fn stage(&self) -> i32 { + self.stage + } +} + +#[async_trait] +impl RsdoctorPluginModuleIds for RsdoctorPluginModuleIdsTap { + async fn run(&self, data: &mut RsdoctorModuleIdsPatch) -> rspack_error::Result> { + let data = std::mem::take(data); + let bail = self + .function + .call_with_promise(JsRsdoctorModuleIdsPatch::from(data)) + .await?; + Ok(bail) + } + + fn stage(&self) -> i32 { + self.stage + } +} + +#[async_trait] +impl RsdoctorPluginModuleSources for RsdoctorPluginModuleSourcesTap { + async fn run(&self, data: &mut RsdoctorModuleSourcesPatch) -> rspack_error::Result> { + let data = std::mem::take(data); + let bail = self + .function + .call_with_promise(JsRsdoctorModuleSourcesPatch::from(data)) + .await?; + Ok(bail) + } + + fn stage(&self) -> i32 { + self.stage + } +} + +#[async_trait] +impl RsdoctorPluginAssets for RsdoctorPluginAssetsTap { + async fn run(&self, data: &mut RsdoctorAssetPatch) -> rspack_error::Result> { + let data = std::mem::take(data); + let bail = self + .function + .call_with_promise(JsRsdoctorAssetPatch::from(data)) + .await?; + Ok(bail) + } + + fn stage(&self) -> i32 { + self.stage + } +} diff --git a/crates/node_binding/src/plugins/mod.rs b/crates/node_binding/src/plugins/mod.rs index 5e792a537b35..73ec3fef04ae 100644 --- a/crates/node_binding/src/plugins/mod.rs +++ b/crates/node_binding/src/plugins/mod.rs @@ -14,6 +14,7 @@ use rspack_hook::plugin_hook; use rspack_hook::Hook as _; use rspack_plugin_html::HtmlRspackPlugin; use rspack_plugin_javascript::JsPlugin; +use rspack_plugin_rsdoctor::RsdoctorPlugin; use self::interceptor::*; @@ -67,6 +68,11 @@ pub struct JsHooksAdapterPlugin { register_html_plugin_after_template_execution_taps: RegisterHtmlPluginAfterTemplateExecutionTaps, register_html_plugin_before_emit_taps: RegisterHtmlPluginBeforeEmitTaps, register_html_plugin_after_emit_taps: RegisterHtmlPluginAfterEmitTaps, + register_rsdoctor_plugin_module_graph_taps: RegisterRsdoctorPluginModuleGraphTaps, + register_rsdoctor_plugin_chunk_graph_taps: RegisterRsdoctorPluginChunkGraphTaps, + register_rsdoctor_plugin_assets_taps: RegisterRsdoctorPluginAssetsTaps, + register_rsdoctor_plugin_module_ids_taps: RegisterRsdoctorPluginModuleIdsTaps, + register_rsdoctor_plugin_module_sources_taps: RegisterRsdoctorPluginModuleSourcesTaps, } impl fmt::Debug for JsHooksAdapterPlugin { @@ -311,6 +317,12 @@ impl rspack_core::Plugin for JsHooksAdapterPlugin { .compilation .tap(html_hooks_adapter_compilation::new(self)); + ctx + .context + .compiler_hooks + .compilation + .tap(rsdoctor_hooks_adapter_compilation::new(self)); + Ok(()) } @@ -396,6 +408,15 @@ impl rspack_core::Plugin for JsHooksAdapterPlugin { .clear_cache(); self.register_html_plugin_before_emit_taps.clear_cache(); self.register_html_plugin_after_emit_taps.clear_cache(); + self + .register_rsdoctor_plugin_module_graph_taps + .clear_cache(); + self.register_rsdoctor_plugin_chunk_graph_taps.clear_cache(); + self.register_rsdoctor_plugin_assets_taps.clear_cache(); + self.register_rsdoctor_plugin_module_ids_taps.clear_cache(); + self + .register_rsdoctor_plugin_module_sources_taps + .clear_cache(); } } @@ -448,6 +469,32 @@ async fn html_hooks_adapter_compilation( Ok(()) } +#[plugin_hook(CompilerCompilation for JsHooksAdapterPlugin)] +async fn rsdoctor_hooks_adapter_compilation( + &self, + compilation: &mut Compilation, + _params: &mut CompilationParams, +) -> rspack_error::Result<()> { + let mut hooks = RsdoctorPlugin::get_compilation_hooks_mut(compilation); + hooks + .module_graph + .intercept(self.register_rsdoctor_plugin_module_graph_taps.clone()); + hooks + .chunk_graph + .intercept(self.register_rsdoctor_plugin_chunk_graph_taps.clone()); + hooks + .assets + .intercept(self.register_rsdoctor_plugin_assets_taps.clone()); + hooks + .module_ids + .intercept(self.register_rsdoctor_plugin_module_ids_taps.clone()); + hooks + .module_sources + .intercept(self.register_rsdoctor_plugin_module_sources_taps.clone()); + + Ok(()) +} + impl JsHooksAdapterPlugin { pub fn from_js_hooks(_env: Env, register_js_taps: RegisterJsTaps) -> Result { let non_skippable_registers = NonSkippableRegisters::default(); @@ -632,6 +679,26 @@ impl JsHooksAdapterPlugin { register_js_taps.register_html_plugin_after_emit_taps, non_skippable_registers.clone(), ), + register_rsdoctor_plugin_module_graph_taps: RegisterRsdoctorPluginModuleGraphTaps::new( + register_js_taps.register_rsdoctor_plugin_module_graph_taps, + non_skippable_registers.clone(), + ), + register_rsdoctor_plugin_chunk_graph_taps: RegisterRsdoctorPluginChunkGraphTaps::new( + register_js_taps.register_rsdoctor_plugin_chunk_graph_taps, + non_skippable_registers.clone(), + ), + register_rsdoctor_plugin_assets_taps: RegisterRsdoctorPluginAssetsTaps::new( + register_js_taps.register_rsdoctor_plugin_assets_taps, + non_skippable_registers.clone(), + ), + register_rsdoctor_plugin_module_ids_taps: RegisterRsdoctorPluginModuleIdsTaps::new( + register_js_taps.register_rsdoctor_plugin_module_ids_taps, + non_skippable_registers.clone(), + ), + register_rsdoctor_plugin_module_sources_taps: RegisterRsdoctorPluginModuleSourcesTaps::new( + register_js_taps.register_rsdoctor_plugin_module_sources_taps, + non_skippable_registers.clone(), + ), non_skippable_registers, } .into(), diff --git a/crates/rspack_binding_values/Cargo.toml b/crates/rspack_binding_values/Cargo.toml index 6589bea6052d..e3f5e00d5e6d 100644 --- a/crates/rspack_binding_values/Cargo.toml +++ b/crates/rspack_binding_values/Cargo.toml @@ -75,6 +75,7 @@ rspack_plugin_progress = { workspace = true } rspack_plugin_real_content_hash = { workspace = true } rspack_plugin_remove_duplicate_modules = { workspace = true } rspack_plugin_remove_empty_chunks = { workspace = true } +rspack_plugin_rsdoctor = { workspace = true } rspack_plugin_runtime = { workspace = true } rspack_plugin_runtime_chunk = { workspace = true } rspack_plugin_schemes = { workspace = true } diff --git a/crates/rspack_binding_values/src/lib.rs b/crates/rspack_binding_values/src/lib.rs index 35201ffc78b0..dd09e8cc25ca 100644 --- a/crates/rspack_binding_values/src/lib.rs +++ b/crates/rspack_binding_values/src/lib.rs @@ -26,6 +26,7 @@ mod plugins; mod raw_options; mod resolver; mod resource_data; +mod rsdoctor; mod rspack_error; mod runtime; mod source; @@ -57,6 +58,7 @@ pub(crate) use plugins::*; pub use raw_options::*; pub use resolver::*; pub use resource_data::*; +pub use rsdoctor::*; pub use rspack_error::*; pub use runtime::*; pub use source::*; diff --git a/crates/rspack_binding_values/src/raw_options/raw_builtins/mod.rs b/crates/rspack_binding_values/src/raw_options/raw_builtins/mod.rs index 7490c287f7e5..e009fa5803f9 100644 --- a/crates/rspack_binding_values/src/raw_options/raw_builtins/mod.rs +++ b/crates/rspack_binding_values/src/raw_options/raw_builtins/mod.rs @@ -68,6 +68,7 @@ use rspack_plugin_progress::ProgressPlugin; use rspack_plugin_real_content_hash::RealContentHashPlugin; use rspack_plugin_remove_duplicate_modules::RemoveDuplicateModulesPlugin; use rspack_plugin_remove_empty_chunks::RemoveEmptyChunksPlugin; +use rspack_plugin_rsdoctor::RsdoctorPlugin; use rspack_plugin_runtime::{ enable_chunk_loading_plugin, ArrayPushCallbackChunkFormatPlugin, BundlerInfoPlugin, ChunkPrefetchPreloadPlugin, CommonJsChunkFormatPlugin, ModuleChunkFormatPlugin, RuntimePlugin, @@ -102,7 +103,7 @@ use self::{ raw_runtime_chunk::RawRuntimeChunkOptions, raw_size_limits::RawSizeLimitsPluginOptions, }; -use crate::entry::JsEntryPluginOptions; +use crate::{entry::JsEntryPluginOptions, RawRsdoctorPluginOptions}; use crate::{ plugins::JsLoaderRspackPlugin, JsLoaderRunner, RawContextReplacementPluginOptions, RawDynamicEntryPluginOptions, RawEvalDevToolModulePluginOptions, RawExternalItemWrapper, @@ -192,6 +193,7 @@ pub enum BuiltinPluginName { LightningCssMinimizerRspackPlugin, BundlerInfoRspackPlugin, CssExtractRspackPlugin, + RsdoctorPlugin, // rspack js adapter plugins // naming format follow XxxRspackPlugin @@ -558,6 +560,11 @@ impl BuiltinPlugin { let options = raw_options.into(); plugins.push(DllReferenceAgencyPlugin::new(options).boxed()); } + BuiltinPluginName::RsdoctorPlugin => { + let raw_options = downcast_into::(self.options)?; + let options = raw_options.into(); + plugins.push(RsdoctorPlugin::new(options).boxed()); + } } Ok(()) } diff --git a/crates/rspack_binding_values/src/rsdoctor.rs b/crates/rspack_binding_values/src/rsdoctor.rs new file mode 100644 index 000000000000..dbe3974515bd --- /dev/null +++ b/crates/rspack_binding_values/src/rsdoctor.rs @@ -0,0 +1,472 @@ +use std::collections::HashSet; + +use napi::Either; +use napi_derive::napi; +use rspack_plugin_rsdoctor::{ + RsdoctorAsset, RsdoctorAssetPatch, RsdoctorChunk, RsdoctorChunkAssets, RsdoctorChunkGraph, + RsdoctorChunkModules, RsdoctorDependency, RsdoctorEntrypoint, RsdoctorEntrypointAssets, + RsdoctorExportInfo, RsdoctorModule, RsdoctorModuleGraph, RsdoctorModuleGraphModule, + RsdoctorModuleId, RsdoctorModuleIdsPatch, RsdoctorModuleOriginalSource, + RsdoctorModuleSourcesPatch, RsdoctorPluginChunkGraphFeature, RsdoctorPluginModuleGraphFeature, + RsdoctorPluginOptions, RsdoctorSideEffect, RsdoctorSourcePosition, RsdoctorSourceRange, + RsdoctorStatement, RsdoctorVariable, +}; + +#[napi(object)] +pub struct JsRsdoctorModule { + pub ukey: i32, + pub identifier: String, + pub path: String, + pub is_entry: bool, + #[napi(ts_type = "'normal' | 'concatenated'")] + pub kind: String, + pub layer: Option, + pub dependencies: Vec, + pub imported: Vec, + pub modules: Vec, + pub belong_modules: Vec, + pub chunks: Vec, +} + +impl From for JsRsdoctorModule { + fn from(value: RsdoctorModule) -> Self { + JsRsdoctorModule { + ukey: value.ukey, + identifier: value.identifier.to_string(), + path: value.path, + is_entry: value.is_entry, + kind: value.kind.into(), + layer: value.layer, + dependencies: value.dependencies.into_iter().collect::>(), + imported: value.imported.into_iter().collect::>(), + modules: value.modules.into_iter().collect::>(), + chunks: value.chunks.into_iter().collect::>(), + belong_modules: value.belong_modules.into_iter().collect::>(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorDependency { + pub ukey: i32, + pub kind: String, + pub request: String, + pub module: i32, + pub dependency: i32, +} + +impl From for JsRsdoctorDependency { + fn from(value: RsdoctorDependency) -> Self { + JsRsdoctorDependency { + ukey: value.ukey, + kind: value.kind.to_string(), + request: value.request, + module: value.module, + dependency: value.dependency, + } + } +} + +#[napi(object)] +pub struct JsRsdoctorChunk { + pub ukey: i32, + pub name: String, + pub initial: bool, + pub entry: bool, + pub dependencies: Vec, + pub imported: Vec, +} + +impl From for JsRsdoctorChunk { + fn from(value: RsdoctorChunk) -> Self { + JsRsdoctorChunk { + ukey: value.ukey, + name: value.name, + initial: value.initial, + entry: value.entry, + dependencies: value.dependencies.into_iter().collect::>(), + imported: value.imported.into_iter().collect::>(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorEntrypoint { + pub ukey: i32, + pub name: String, + pub chunks: Vec, +} + +impl From for JsRsdoctorEntrypoint { + fn from(value: RsdoctorEntrypoint) -> Self { + JsRsdoctorEntrypoint { + ukey: value.ukey, + name: value.name, + chunks: value.chunks.into_iter().collect::>(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorAsset { + pub ukey: i32, + pub path: String, + pub chunks: Vec, + pub size: i32, +} + +impl From for JsRsdoctorAsset { + fn from(value: RsdoctorAsset) -> Self { + JsRsdoctorAsset { + ukey: value.ukey, + path: value.path, + chunks: value.chunks.into_iter().collect::>(), + size: value.size, + } + } +} + +#[napi(object)] +pub struct JsRsdoctorModuleGraphModule { + pub ukey: i32, + pub module: i32, + pub exports: Vec, + pub side_effects: Vec, + pub variables: Vec, + pub dynamic: bool, +} + +impl From for JsRsdoctorModuleGraphModule { + fn from(value: RsdoctorModuleGraphModule) -> Self { + JsRsdoctorModuleGraphModule { + ukey: value.ukey, + module: value.module, + exports: value.exports.into_iter().collect::>(), + side_effects: value.side_effects.into_iter().collect::>(), + variables: value.variables.into_iter().collect::>(), + dynamic: value.dynamic, + } + } +} + +#[napi(object)] +pub struct JsRsdoctorSideEffect { + pub ukey: i32, + pub name: String, + pub origin_name: Option, + pub module: i32, + pub identifier: JsRsdoctorStatement, + pub is_name_space: bool, + pub from_dependency: Option, + pub exports: Vec, + pub variable: Option, +} + +impl From for JsRsdoctorSideEffect { + fn from(value: RsdoctorSideEffect) -> Self { + JsRsdoctorSideEffect { + ukey: value.ukey, + name: value.name, + origin_name: value.origin_name, + module: value.module, + identifier: value.identifier.into(), + is_name_space: value.is_name_space, + from_dependency: value.from_dependency, + exports: value.exports.into_iter().collect::>(), + variable: value.variable, + } + } +} + +#[napi(object)] +pub struct JsRsdoctorVariable { + pub ukey: i32, + pub name: String, + pub module: i32, + pub used_info: String, + pub identififer: JsRsdoctorStatement, + pub exported: Option, +} + +impl From for JsRsdoctorVariable { + fn from(value: RsdoctorVariable) -> Self { + JsRsdoctorVariable { + ukey: value.ukey, + name: value.name, + module: value.module, + used_info: value.used_info, + identififer: value.identififer.into(), + exported: value.exported, + } + } +} + +#[napi(object)] +pub struct JsRsdoctorExportInfo { + pub ukey: i32, + pub name: String, + pub from: Option, + pub variable: Option, + pub identifier: Option, + pub side_effects: Vec, +} + +impl From for JsRsdoctorExportInfo { + fn from(value: RsdoctorExportInfo) -> Self { + JsRsdoctorExportInfo { + ukey: value.ukey, + name: value.name, + from: value.from, + variable: value.variable, + identifier: value.identifier.map(|i| i.into()), + side_effects: value.side_effects.into_iter().collect::>(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorStatement { + pub module: i32, + pub source_position: Option, + pub transformed_position: JsRsdoctorSourceRange, +} + +impl From for JsRsdoctorStatement { + fn from(value: RsdoctorStatement) -> Self { + JsRsdoctorStatement { + module: value.module, + source_position: value.source_position.map(|p| p.into()), + transformed_position: value.transformed_position.into(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorSourceRange { + pub start: JsRsdoctorSourcePosition, + pub end: Option, +} + +impl From for JsRsdoctorSourceRange { + fn from(value: RsdoctorSourceRange) -> Self { + JsRsdoctorSourceRange { + start: value.start.into(), + end: value.end.map(|p| p.into()), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorSourcePosition { + pub line: Option, + pub column: Option, + pub index: Option, +} + +impl From for JsRsdoctorSourcePosition { + fn from(value: RsdoctorSourcePosition) -> Self { + JsRsdoctorSourcePosition { + line: value.line, + column: value.column, + index: value.index, + } + } +} + +#[napi(object)] +pub struct JsRsdoctorChunkModules { + pub chunk: i32, + pub modules: Vec, +} + +impl From for JsRsdoctorChunkModules { + fn from(value: RsdoctorChunkModules) -> Self { + JsRsdoctorChunkModules { + chunk: value.chunk, + modules: value.modules.into_iter().collect::>(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorModuleGraph { + pub modules: Vec, + pub dependencies: Vec, + pub chunk_modules: Vec, +} + +impl From for JsRsdoctorModuleGraph { + fn from(value: RsdoctorModuleGraph) -> Self { + JsRsdoctorModuleGraph { + modules: value.modules.into_iter().map(|m| m.into()).collect(), + dependencies: value.dependencies.into_iter().map(|d| d.into()).collect(), + chunk_modules: value.chunk_modules.into_iter().map(|c| c.into()).collect(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorChunkGraph { + pub chunks: Vec, + pub entrypoints: Vec, +} + +impl From for JsRsdoctorChunkGraph { + fn from(value: RsdoctorChunkGraph) -> Self { + JsRsdoctorChunkGraph { + chunks: value.chunks.into_iter().map(|c| c.into()).collect(), + entrypoints: value.entrypoints.into_iter().map(|e| e.into()).collect(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorModuleId { + pub module: i32, + pub render_id: String, +} + +impl From for JsRsdoctorModuleId { + fn from(value: RsdoctorModuleId) -> Self { + JsRsdoctorModuleId { + module: value.module, + render_id: value.render_id, + } + } +} + +#[napi(object)] +pub struct JsRsdoctorModuleOriginalSource { + pub module: i32, + pub source: String, + pub size: i32, +} + +impl From for JsRsdoctorModuleOriginalSource { + fn from(value: RsdoctorModuleOriginalSource) -> Self { + JsRsdoctorModuleOriginalSource { + module: value.module, + source: value.source, + size: value.size, + } + } +} + +#[napi(object)] +pub struct JsRsdoctorModuleSourcesPatch { + pub module_original_sources: Vec, +} + +impl From for JsRsdoctorModuleSourcesPatch { + fn from(value: RsdoctorModuleSourcesPatch) -> Self { + JsRsdoctorModuleSourcesPatch { + module_original_sources: value + .module_original_sources + .into_iter() + .map(|m| m.into()) + .collect(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorModuleIdsPatch { + pub module_ids: Vec, +} + +impl From for JsRsdoctorModuleIdsPatch { + fn from(value: RsdoctorModuleIdsPatch) -> Self { + JsRsdoctorModuleIdsPatch { + module_ids: value.module_ids.into_iter().map(|m| m.into()).collect(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorChunkAssets { + pub chunk: i32, + pub assets: Vec, +} + +impl From for JsRsdoctorChunkAssets { + fn from(value: RsdoctorChunkAssets) -> Self { + JsRsdoctorChunkAssets { + chunk: value.chunk, + assets: value.assets.into_iter().collect::>(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorEntrypointAssets { + pub entrypoint: i32, + pub assets: Vec, +} + +impl From for JsRsdoctorEntrypointAssets { + fn from(value: RsdoctorEntrypointAssets) -> Self { + JsRsdoctorEntrypointAssets { + entrypoint: value.entrypoint, + assets: value.assets.into_iter().collect::>(), + } + } +} + +#[napi(object)] +pub struct JsRsdoctorAssetPatch { + pub assets: Vec, + pub chunk_assets: Vec, + pub entrypoint_assets: Vec, +} + +impl From for JsRsdoctorAssetPatch { + fn from(value: RsdoctorAssetPatch) -> Self { + JsRsdoctorAssetPatch { + assets: value.assets.into_iter().map(|a| a.into()).collect(), + chunk_assets: value.chunk_assets.into_iter().map(|c| c.into()).collect(), + entrypoint_assets: value + .entrypoint_assets + .into_iter() + .map(|e| e.into()) + .collect(), + } + } +} + +#[napi(object, object_to_js = false)] +pub struct RawRsdoctorPluginOptions { + #[napi(ts_type = "boolean | Array<'graph' | 'ids' | 'sources'>")] + pub module_graph_features: Either>, + #[napi(ts_type = "boolean | Array<'graph' | 'assets'>")] + pub chunk_graph_features: Either>, +} + +impl From for RsdoctorPluginOptions { + fn from(value: RawRsdoctorPluginOptions) -> Self { + Self { + module_graph_features: match value.module_graph_features { + Either::A(true) => HashSet::from([ + RsdoctorPluginModuleGraphFeature::ModuleGraph, + RsdoctorPluginModuleGraphFeature::ModuleIds, + RsdoctorPluginModuleGraphFeature::ModuleSources, + ]), + Either::A(false) => HashSet::new(), + Either::B(features) => features + .into_iter() + .map(RsdoctorPluginModuleGraphFeature::from) + .collect::>(), + }, + chunk_graph_features: match value.chunk_graph_features { + Either::A(true) => HashSet::from([ + RsdoctorPluginChunkGraphFeature::ChunkGraph, + RsdoctorPluginChunkGraphFeature::Assets, + ]), + Either::A(false) => HashSet::new(), + Either::B(features) => features + .into_iter() + .map(RsdoctorPluginChunkGraphFeature::from) + .collect::>(), + }, + } + } +} diff --git a/crates/rspack_core/src/chunk_graph/chunk_graph_module.rs b/crates/rspack_core/src/chunk_graph/chunk_graph_module.rs index 9c09a8b74693..7e2bb3f56a50 100644 --- a/crates/rspack_core/src/chunk_graph/chunk_graph_module.rs +++ b/crates/rspack_core/src/chunk_graph/chunk_graph_module.rs @@ -255,6 +255,16 @@ impl ChunkGraph { .set_hashes(module_identifier, hashes); } + pub fn try_get_module_chunks( + &self, + module_identifier: &ModuleIdentifier, + ) -> Option<&UkeySet> { + self + .chunk_graph_module_by_module_identifier + .get(module_identifier) + .map(|cgm| &cgm.chunks) + } + #[instrument("chunk_graph:get_module_graph_hash", skip_all, fields(module = ?module.identifier()))] pub fn get_module_graph_hash( &self, diff --git a/crates/rspack_core/src/chunk_group.rs b/crates/rspack_core/src/chunk_group.rs index 18eaa996af2b..8ddcd4e8f81c 100644 --- a/crates/rspack_core/src/chunk_group.rs +++ b/crates/rspack_core/src/chunk_group.rs @@ -42,7 +42,7 @@ pub struct ChunkGroup { pub(crate) module_post_order_indices: IdentifierMap, // keep order for children - pub(crate) children: IndexSet, + pub children: IndexSet, async_entrypoints: UkeySet, // ChunkGroupInfo pub(crate) next_pre_order_index: usize, diff --git a/crates/rspack_core/src/compiler/compilation.rs b/crates/rspack_core/src/compiler/compilation.rs index 0a3cdc708b72..7e2e0f7dd488 100644 --- a/crates/rspack_core/src/compiler/compilation.rs +++ b/crates/rspack_core/src/compiler/compilation.rs @@ -84,6 +84,7 @@ define_hook!(CompilationRuntimeRequirementInChunk: SyncSeriesBail(compilation: & define_hook!(CompilationAdditionalTreeRuntimeRequirements: AsyncSeries(compilation: &mut Compilation, chunk_ukey: &ChunkUkey, runtime_requirements: &mut RuntimeGlobals)); define_hook!(CompilationRuntimeRequirementInTree: SyncSeriesBail(compilation: &mut Compilation, chunk_ukey: &ChunkUkey, all_runtime_requirements: &RuntimeGlobals, runtime_requirements: &RuntimeGlobals, runtime_requirements_mut: &mut RuntimeGlobals)); define_hook!(CompilationOptimizeCodeGeneration: SyncSeries(compilation: &mut Compilation)); +define_hook!(CompilationAfterCodeGeneration: SyncSeries(compilation: &mut Compilation)); define_hook!(CompilationChunkHash: AsyncSeries(compilation: &Compilation, chunk_ukey: &ChunkUkey, hasher: &mut RspackHash)); define_hook!(CompilationContentHash: AsyncSeries(compilation: &Compilation, chunk_ukey: &ChunkUkey, hashes: &mut HashMap)); define_hook!(CompilationRenderManifest: AsyncSeries(compilation: &Compilation, chunk_ukey: &ChunkUkey, manifest: &mut Vec, diagnostics: &mut Vec)); @@ -117,6 +118,7 @@ pub struct CompilationHooks { pub additional_tree_runtime_requirements: CompilationAdditionalTreeRuntimeRequirementsHook, pub runtime_requirement_in_tree: CompilationRuntimeRequirementInTreeHook, pub optimize_code_generation: CompilationOptimizeCodeGenerationHook, + pub after_code_generation: CompilationAfterCodeGenerationHook, pub chunk_hash: CompilationChunkHashHook, pub content_hash: CompilationContentHashHook, pub render_manifest: CompilationRenderManifestHook, @@ -1441,6 +1443,11 @@ impl Compilation { self.get_module_graph().modules().keys().copied().collect() }; self.code_generation(code_generation_modules)?; + + plugin_driver + .compilation_hooks + .after_code_generation + .call(self)?; logger.time_end(start); let start = logger.time("runtime requirements"); diff --git a/crates/rspack_core/src/concatenated_module.rs b/crates/rspack_core/src/concatenated_module.rs index 322f79717e34..1a93163a945f 100644 --- a/crates/rspack_core/src/concatenated_module.rs +++ b/crates/rspack_core/src/concatenated_module.rs @@ -438,6 +438,10 @@ impl ConcatenatedModule { self.id } + pub fn get_root(&self) -> ModuleIdentifier { + self.root_module_ctxt.id + } + pub fn get_modules(&self) -> &[ConcatenatedInnerModule] { self.modules.as_slice() } diff --git a/crates/rspack_plugin_rsdoctor/Cargo.toml b/crates/rspack_plugin_rsdoctor/Cargo.toml new file mode 100644 index 000000000000..62f409a6b5cc --- /dev/null +++ b/crates/rspack_plugin_rsdoctor/Cargo.toml @@ -0,0 +1,27 @@ +[package] +description = "rspack rsdoctor native plugin" +edition = "2021" +license = "MIT" +name = "rspack_plugin_rsdoctor" +repository = "https://github.com/web-infra-dev/rspack" +version = "0.2.0" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = { workspace = true } +dashmap = { workspace = true } +futures = { workspace = true } +indexmap = { workspace = true } +rayon = { workspace = true } +rspack_collections = { workspace = true } +rspack_core = { workspace = true } +rspack_error = { workspace = true } +rspack_hook = { workspace = true } +rspack_paths = { workspace = true } +rspack_util = { workspace = true } +rustc-hash = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } + +[package.metadata.cargo-shear] +ignored = ["tracing"] diff --git a/crates/rspack_plugin_rsdoctor/LICENSE b/crates/rspack_plugin_rsdoctor/LICENSE new file mode 100644 index 000000000000..46310101ad8a --- /dev/null +++ b/crates/rspack_plugin_rsdoctor/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022-present Bytedance, Inc. and its affiliates. + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/rspack_plugin_rsdoctor/src/chunk_graph.rs b/crates/rspack_plugin_rsdoctor/src/chunk_graph.rs new file mode 100644 index 000000000000..226abe6167c3 --- /dev/null +++ b/crates/rspack_plugin_rsdoctor/src/chunk_graph.rs @@ -0,0 +1,265 @@ +use std::sync::{atomic::AtomicI32, Arc}; + +use indexmap::IndexMap; +use rayon::iter::{IntoParallelRefIterator, ParallelBridge, ParallelIterator}; +use rspack_collections::Identifier; +use rspack_core::{Chunk, ChunkGraph, ChunkGroupByUkey, ChunkUkey, CompilationAsset, ModuleGraph}; +use rspack_core::{ChunkByUkey, ChunkGroupUkey}; +use rspack_util::fx_hash::FxDashMap; +use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; + +use crate::{ + ChunkUkey as RsdoctorChunkUkey, EntrypointUkey as RsdoctorEntrypointUkey, RsdoctorAsset, + RsdoctorChunk, RsdoctorChunkAssets, RsdoctorChunkModules, RsdoctorEntrypoint, + RsdoctorEntrypointAssets, +}; + +pub fn collect_chunks( + chunks: &HashMap<&ChunkUkey, &Chunk>, + chunk_graph: &ChunkGraph, + chunk_group_by_ukey: &ChunkGroupByUkey, +) -> HashMap { + chunks + .par_iter() + .map(|(chunk_id, chunk)| { + let names = chunk.name().map(|n| vec![n.to_owned()]).unwrap_or_default(); + let files: Vec<_> = { + let mut vec = chunk.files().iter().cloned().collect::>(); + vec.sort_unstable(); + vec + }; + let name = if names.is_empty() { + if files.is_empty() { + Default::default() + } else { + files.join("| ") + } + } else { + names.join("") + }; + ( + **chunk_id, + RsdoctorChunk { + ukey: chunk_id.as_u32() as RsdoctorChunkUkey, + name, + initial: chunk.can_be_initial(chunk_group_by_ukey), + entry: chunk.has_entry_module(chunk_graph), + dependencies: HashSet::default(), + imported: HashSet::default(), + }, + ) + }) + .collect::>() +} + +pub fn collect_chunk_dependencies( + chunks: &HashMap<&ChunkUkey, &Chunk>, + rsd_chunks: &HashMap, + chunk_group_by_ukey: &ChunkGroupByUkey, + chunk_by_ukey: &ChunkByUkey, +) -> HashMap, HashSet)> { + chunks + .par_iter() + .map(|(chunk_id, chunk)| { + let mut parents = HashSet::default(); + let mut children = HashSet::default(); + + for cg in chunk.groups() { + if let Some(cg) = chunk_group_by_ukey.get(cg) { + for p in &cg.parents { + if let Some(pg) = chunk_group_by_ukey.get(p) { + for c in &pg.chunks { + if chunk_by_ukey.get(c).is_some() { + parents.insert(*c); + } + } + } + } + + for p in &cg.children { + if let Some(pg) = chunk_group_by_ukey.get(p) { + for c in &pg.chunks { + if chunk_by_ukey.get(c).is_some() { + children.insert(*c); + } + } + } + } + } + } + + ( + **chunk_id, + ( + parents + .into_iter() + .filter_map(|c| rsd_chunks.get(&c).map(|c| c.ukey)) + .collect::>(), + children + .into_iter() + .filter_map(|c| rsd_chunks.get(&c).map(|c| c.ukey)) + .collect::>(), + ), + ) + }) + .collect::>() +} + +pub fn collect_entrypoints( + entrypoints: &IndexMap, + rsd_chunks: &HashMap, + chunk_group_by_ukey: &ChunkGroupByUkey, +) -> HashMap { + entrypoints + .par_iter() + .map(|(name, ukey)| { + let cg = chunk_group_by_ukey.get(ukey); + let chunks = cg + .map(|cg| { + cg.chunks + .iter() + .filter_map(|c| rsd_chunks.get(c).map(|c| c.ukey)) + .collect::>() + }) + .unwrap_or_default(); + + ( + ukey.to_owned(), + RsdoctorEntrypoint { + ukey: ukey.as_u32() as RsdoctorEntrypointUkey, + name: name.to_string(), + chunks, + }, + ) + }) + .collect::>() +} + +pub fn collect_assets( + assets: &HashMap, + chunk_by_ukey: &ChunkByUkey, +) -> HashMap { + let asset_ukey_counter: Arc = Arc::new(AtomicI32::new(0)); + let mut compilation_file_to_chunks: HashMap<&String, Vec<&ChunkUkey>> = HashMap::default(); + for (chunk_ukey, chunk) in chunk_by_ukey.iter() { + for file in chunk.files() { + let chunks = compilation_file_to_chunks.entry(file).or_default(); + chunks.push(chunk_ukey); + } + } + assets + .par_iter() + .map(|(path, asset)| { + let chunks = compilation_file_to_chunks + .get(path) + .map(|chunks| { + chunks + .iter() + .map(|c| c.as_u32() as RsdoctorChunkUkey) + .collect::>() + }) + .unwrap_or_default(); + ( + path.to_string(), + RsdoctorAsset { + ukey: asset_ukey_counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed), + path: path.to_string(), + chunks, + size: asset + .get_source() + .map(|source| source.size()) + .unwrap_or_default() as i32, + }, + ) + }) + .collect::>() +} + +pub fn collect_chunk_modules( + chunk_by_ukey: &ChunkByUkey, + module_ukeys: &FxDashMap, + chunk_graph: &ChunkGraph, + module_graph: &ModuleGraph, +) -> Vec { + chunk_by_ukey + .keys() + .par_bridge() + .map(|chunk_id| { + let modules = chunk_graph + .get_ordered_chunk_modules_identifier(chunk_id) + .iter() + .flat_map(|mid| { + let mut res = vec![]; + if let Some(ukey) = module_ukeys.get(mid) { + res.push(*ukey); + } + if let Some(concatenated_module) = module_graph + .module_by_identifier(mid) + .and_then(|m| m.as_concatenated_module()) + { + res.extend( + concatenated_module + .get_modules() + .iter() + .filter_map(|m| module_ukeys.get(&m.id).map(|u| *u)), + ); + } + res + }) + .collect::>(); + RsdoctorChunkModules { + chunk: chunk_id.as_u32() as RsdoctorChunkUkey, + modules, + } + }) + .collect::>() +} + +pub fn collect_chunk_assets( + chunk_by_ukey: &ChunkByUkey, + rsd_assets: &HashMap, +) -> Vec { + chunk_by_ukey + .iter() + .par_bridge() + .map(|(chunk_id, chunk)| RsdoctorChunkAssets { + chunk: chunk_id.as_u32() as RsdoctorChunkUkey, + assets: chunk + .files() + .iter() + .filter_map(|file| rsd_assets.get(file).map(|asset| asset.ukey)) + .collect::>(), + }) + .collect::>() +} + +pub fn collect_entrypoint_assets( + entrypoints: &IndexMap, + rsd_assets: &HashMap, + entrypoint_ukey_map: &FxDashMap, + chunk_group_by_ukey: &ChunkGroupByUkey, + chunk_by_ukey: &ChunkByUkey, +) -> Vec { + entrypoints + .par_iter() + .filter_map(|(_, ukey)| { + let entrypoint_ukey = entrypoint_ukey_map.get(ukey)?; + let chunk_group = chunk_group_by_ukey.get(ukey)?; + Some(RsdoctorEntrypointAssets { + entrypoint: *entrypoint_ukey, + assets: chunk_group + .chunks + .iter() + .filter_map(|c| { + chunk_by_ukey.get(c).map(|c| { + c.files() + .iter() + .filter_map(|path| rsd_assets.get(path).map(|asset| asset.ukey)) + }) + }) + .flatten() + .collect::>(), + }) + }) + .collect::>() +} diff --git a/crates/rspack_plugin_rsdoctor/src/data.rs b/crates/rspack_plugin_rsdoctor/src/data.rs new file mode 100644 index 000000000000..c0277fa0c03c --- /dev/null +++ b/crates/rspack_plugin_rsdoctor/src/data.rs @@ -0,0 +1,202 @@ +use rspack_collections::Identifier; +use rspack_core::DependencyType; +use rustc_hash::FxHashSet as HashSet; + +#[derive(Debug, Default)] +pub enum ModuleKind { + #[default] + Normal, + Concatenated, +} + +impl From for String { + fn from(value: ModuleKind) -> Self { + match value { + ModuleKind::Normal => "normal".to_string(), + ModuleKind::Concatenated => "concatenated".to_string(), + } + } +} + +pub type ModuleUkey = i32; +pub type DependencyUkey = i32; +pub type ChunkUkey = i32; +pub type AssetUkey = i32; +pub type EntrypointUkey = i32; +pub type ModuleGraphModuleUkey = i32; +pub type ExportInfoUkey = i32; +pub type VariableUkey = i32; +pub type SideEffectUkey = i32; + +#[derive(Debug, Default)] +pub struct RsdoctorModule { + pub ukey: ModuleUkey, + pub identifier: Identifier, + pub path: String, + pub is_entry: bool, + pub kind: ModuleKind, + pub layer: Option, + pub dependencies: HashSet, + pub imported: HashSet, + pub chunks: HashSet, + pub modules: HashSet, + pub belong_modules: HashSet, +} + +#[derive(Debug, Default)] +pub struct RsdoctorDependency { + pub ukey: DependencyUkey, + pub kind: DependencyType, + pub request: String, + pub module: ModuleUkey, + pub dependency: ModuleUkey, +} + +#[derive(Debug, Default)] +pub struct RsdoctorChunk { + pub ukey: ChunkUkey, + pub name: String, + pub initial: bool, + pub entry: bool, + pub dependencies: HashSet, + pub imported: HashSet, +} + +#[derive(Debug, Default)] +pub struct RsdoctorEntrypoint { + pub ukey: EntrypointUkey, + pub name: String, + pub chunks: HashSet, +} + +#[derive(Debug, Default)] +pub struct RsdoctorAsset { + pub ukey: AssetUkey, + pub path: String, + pub size: i32, + pub chunks: HashSet, +} + +#[derive(Debug, Default)] +pub struct RsdoctorChunkAssets { + pub chunk: ChunkUkey, + pub assets: HashSet, +} + +#[derive(Debug, Default)] +pub struct RsdoctorEntrypointAssets { + pub entrypoint: EntrypointUkey, + pub assets: HashSet, +} + +#[derive(Debug, Default)] +pub struct RsdoctorModuleGraphModule { + pub ukey: ModuleGraphModuleUkey, + pub module: ModuleUkey, + pub exports: Vec, + pub side_effects: Vec, + pub variables: Vec, + pub dynamic: bool, +} + +#[derive(Debug, Default)] +pub struct RsdoctorSideEffect { + pub ukey: SideEffectUkey, + pub name: String, + pub origin_name: Option, + pub module: ModuleUkey, + pub identifier: RsdoctorStatement, + pub is_name_space: bool, + pub from_dependency: Option, + pub exports: Vec, + pub variable: Option, +} + +#[derive(Debug, Default)] +pub struct RsdoctorVariable { + pub ukey: VariableUkey, + pub name: String, + pub module: ModuleUkey, + pub used_info: String, + pub identififer: RsdoctorStatement, + pub exported: Option, +} + +#[derive(Debug, Default)] +pub struct RsdoctorExportInfo { + pub ukey: ExportInfoUkey, + pub name: String, + pub from: Option, + pub variable: Option, + pub identifier: Option, + pub side_effects: Vec, +} + +#[derive(Debug, Default)] +pub struct RsdoctorStatement { + pub module: ModuleUkey, + pub source_position: Option, + pub transformed_position: RsdoctorSourceRange, +} + +#[derive(Debug, Default)] +pub struct RsdoctorSourceRange { + pub start: RsdoctorSourcePosition, + pub end: Option, +} + +#[derive(Debug, Default)] +pub struct RsdoctorSourcePosition { + pub line: Option, + pub column: Option, + pub index: Option, +} + +#[derive(Debug, Default)] +pub struct RsdoctorModuleGraph { + pub modules: Vec, + pub dependencies: Vec, + pub chunk_modules: Vec, +} + +#[derive(Debug, Default)] +pub struct RsdoctorAssetPatch { + pub assets: Vec, + pub chunk_assets: Vec, + pub entrypoint_assets: Vec, +} + +#[derive(Debug, Default)] +pub struct RsdoctorModuleIdsPatch { + pub module_ids: Vec, +} + +#[derive(Debug, Default)] +pub struct RsdoctorModuleSourcesPatch { + pub module_original_sources: Vec, +} + +#[derive(Debug, Default)] +pub struct RsdoctorChunkModules { + pub chunk: ChunkUkey, + pub modules: Vec, +} + +#[derive(Debug, Default)] +pub struct RsdoctorChunkGraph { + pub chunks: Vec, + pub entrypoints: Vec, +} + +#[derive(Debug, Default)] +pub struct RsdoctorModuleId { + pub module: ModuleUkey, + pub render_id: String, +} + +#[derive(Debug, Default)] +pub struct RsdoctorModuleOriginalSource { + pub module: ModuleUkey, + pub source: String, + pub size: i32, +} diff --git a/crates/rspack_plugin_rsdoctor/src/drive.rs b/crates/rspack_plugin_rsdoctor/src/drive.rs new file mode 100644 index 000000000000..54b6e7e45a9e --- /dev/null +++ b/crates/rspack_plugin_rsdoctor/src/drive.rs @@ -0,0 +1,21 @@ +use rspack_hook::define_hook; + +use crate::{ + RsdoctorAssetPatch, RsdoctorChunkGraph, RsdoctorModuleGraph, RsdoctorModuleIdsPatch, + RsdoctorModuleSourcesPatch, +}; + +define_hook!(RsdoctorPluginModuleGraph: AsyncSeriesBail(data: &mut RsdoctorModuleGraph) -> bool); +define_hook!(RsdoctorPluginChunkGraph: AsyncSeriesBail(data: &mut RsdoctorChunkGraph) -> bool); +define_hook!(RsdoctorPluginModuleIds: AsyncSeriesBail(data: &mut RsdoctorModuleIdsPatch) -> bool); +define_hook!(RsdoctorPluginModuleSources: AsyncSeriesBail(data: &mut RsdoctorModuleSourcesPatch) -> bool); +define_hook!(RsdoctorPluginAssets: AsyncSeriesBail(data: &mut RsdoctorAssetPatch) -> bool); + +#[derive(Debug, Default)] +pub struct RsdoctorPluginHooks { + pub module_graph: RsdoctorPluginModuleGraphHook, + pub chunk_graph: RsdoctorPluginChunkGraphHook, + pub module_ids: RsdoctorPluginModuleIdsHook, + pub module_sources: RsdoctorPluginModuleSourcesHook, + pub assets: RsdoctorPluginAssetsHook, +} diff --git a/crates/rspack_plugin_rsdoctor/src/lib.rs b/crates/rspack_plugin_rsdoctor/src/lib.rs new file mode 100644 index 000000000000..f2bdd2453c3e --- /dev/null +++ b/crates/rspack_plugin_rsdoctor/src/lib.rs @@ -0,0 +1,12 @@ +mod chunk_graph; +mod data; +mod drive; +mod module_graph; +mod plugin; + +pub use data::*; +pub use drive::*; +pub use plugin::{ + RsdoctorPlugin, RsdoctorPluginChunkGraphFeature, RsdoctorPluginModuleGraphFeature, + RsdoctorPluginOptions, SendAssets, SendChunkGraph, SendModuleGraph, SendModuleSources, +}; diff --git a/crates/rspack_plugin_rsdoctor/src/module_graph.rs b/crates/rspack_plugin_rsdoctor/src/module_graph.rs new file mode 100644 index 000000000000..2b9700dd7f1a --- /dev/null +++ b/crates/rspack_plugin_rsdoctor/src/module_graph.rs @@ -0,0 +1,236 @@ +use std::sync::{atomic::AtomicI32, Arc}; + +use rayon::iter::{IntoParallelRefIterator, ParallelBridge, ParallelIterator}; +use rspack_collections::{Identifier, IdentifierMap}; +use rspack_core::{ + rspack_sources::MapOptions, BoxModule, ChunkGraph, Compilation, Context, DependencyId, + DependencyType, Module, ModuleGraph, ModuleIdsArtifact, +}; +use rspack_paths::Utf8PathBuf; +use rspack_util::fx_hash::FxDashMap; +use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; + +use crate::{ + ChunkUkey, ModuleKind, ModuleUkey, RsdoctorDependency, RsdoctorModule, RsdoctorModuleId, + RsdoctorModuleOriginalSource, +}; + +pub fn collect_modules( + modules: &IdentifierMap<&BoxModule>, + module_graph: &ModuleGraph, + chunk_graph: &ChunkGraph, + context: &Context, +) -> HashMap { + let module_ukey_counter: Arc = Arc::new(AtomicI32::new(0)); + + modules + .par_iter() + .map(|(module_id, module)| { + let ukey = module_ukey_counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let depth = module_graph.get_depth(module_id); + let path = if let Some(module) = module.as_normal_module() { + module.resource_resolved_data().resource.clone() + } else if let Some(module) = module.as_concatenated_module() { + let root = module.get_root(); + if let Some(module) = module_graph + .module_by_identifier(&root) + .and_then(|m| m.as_normal_module()) + { + module.resource_resolved_data().resource.clone() + } else { + root.to_string() + } + } else { + module.readable_identifier(context).to_string() + }; + let is_concatenated = module.as_concatenated_module().is_some(); + let chunks = chunk_graph + .try_get_module_chunks(module_id) + .map(|chunks| { + chunks + .iter() + .map(|i| i.as_u32() as ChunkUkey) + .collect::>() + }) + .unwrap_or_default(); + ( + module_id.to_owned(), + RsdoctorModule { + ukey, + identifier: module.identifier(), + path, + is_entry: depth.is_some_and(|d| d == 0), + kind: if is_concatenated { + ModuleKind::Concatenated + } else { + ModuleKind::Normal + }, + layer: module.get_layer().map(|layer| layer.to_string()), + dependencies: HashSet::default(), + imported: HashSet::default(), + modules: HashSet::default(), + belong_modules: HashSet::default(), + chunks, + }, + ) + }) + .collect::>() +} + +pub fn collect_concatenated_modules( + modules: &IdentifierMap<&BoxModule>, +) -> ( + HashMap>, + HashMap>, +) { + let children_map = modules + .par_iter() + .filter_map(|(module_id, module)| { + let concatenated_module = module.as_concatenated_module()?; + Some(( + module_id.to_owned(), + concatenated_module + .get_modules() + .iter() + .map(|m| m.id) + .collect::>(), + )) + }) + .collect::>(); + + let parent_map = children_map + .iter() + .flat_map(|(parent, children)| { + children + .iter() + .map(|child| (*child, *parent)) + .collect::>() + }) + .fold( + HashMap::default(), + |mut acc: HashMap>, (child, parent)| { + acc.entry(child).or_default().insert(parent); + acc + }, + ); + + (children_map, parent_map) +} + +pub fn collect_module_original_sources( + modules: &IdentifierMap<&BoxModule>, + module_ukeys: &FxDashMap, + module_graph: &ModuleGraph, + compilation: &Compilation, +) -> Vec { + let ifs = compilation.input_filesystem.clone(); + modules + .par_iter() + .filter_map(|(module_id, module)| { + let module = if let Some(module) = module.as_concatenated_module() { + module_graph + .module_by_identifier(&module.get_root())? + .as_normal_module()? + } else { + module.as_normal_module()? + }; + let module_ukey = module_ukeys.get(module_id)?; + let resource = module.resource_resolved_data().resource.clone(); + let source = module + .original_source() + .and_then(|s| s.map(&MapOptions::default())) + .and_then(|s| { + let idx = s.sources().iter().position(|s| s.eq(&resource))?; + let source = s.sources_content().get(idx)?; + Some(RsdoctorModuleOriginalSource { + module: *module_ukey, + source: source.to_string(), + size: source.len() as i32, + }) + }) + .or_else(|| { + let resource = Utf8PathBuf::from(resource); + let buffer = ifs.read(&resource).ok()?; + let content = String::from_utf8(buffer).ok()?; + Some(RsdoctorModuleOriginalSource { + module: *module_ukey, + size: content.len() as i32, + source: content, + }) + })?; + Some(source) + }) + .collect::>() +} + +pub fn collect_module_dependencies( + modules: &IdentifierMap<&BoxModule>, + module_ukeys: &FxDashMap, + module_graph: &ModuleGraph, +) -> HashMap> { + let dependency_ukey_counter = Arc::new(AtomicI32::new(0)); + + modules + .par_iter() + .filter_map(|(module_id, _)| { + let rsd_module_ukey = module_ukeys.get(module_id)?; + let dependencies = module_graph + .get_outgoing_connections(module_id) + .filter_map(|conn| { + let dep = module_graph + .dependency_by_id(&conn.dependency_id)? + .as_module_dependency()?; + + if matches!( + dep.dependency_type(), + DependencyType::CjsSelfReference + | DependencyType::EsmExportImportedSpecifier + | DependencyType::EsmImportSpecifier + ) { + return None; + } + + let dep_module = module_graph + .module_identifier_by_dependency_id(&conn.dependency_id) + .and_then(|mid| module_ukeys.get(mid).map(|ukey| (*mid, *ukey)))?; + + let dep_ukey = dependency_ukey_counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + Some(( + dep_module.0, + ( + conn.dependency_id, + RsdoctorDependency { + ukey: dep_ukey, + kind: *dep.dependency_type(), + request: dep.user_request().into(), + module: *rsd_module_ukey, + dependency: dep_module.1, + }, + ), + )) + }) + .collect::>(); + + Some((module_id.to_owned(), dependencies)) + }) + .collect::>() +} + +pub fn collect_module_ids( + modules: &IdentifierMap<&BoxModule>, + module_ukeys: &FxDashMap, + module_ids: &ModuleIdsArtifact, +) -> Vec { + modules + .keys() + .par_bridge() + .filter_map(|module_id| { + let render_id = ChunkGraph::get_module_id(module_ids, *module_id).map(|s| s.to_string())?; + let module_ukey = module_ukeys.get(module_id)?; + Some(RsdoctorModuleId { + module: *module_ukey, + render_id, + }) + }) + .collect::>() +} diff --git a/crates/rspack_plugin_rsdoctor/src/plugin.rs b/crates/rspack_plugin_rsdoctor/src/plugin.rs new file mode 100644 index 000000000000..e44d9ff80390 --- /dev/null +++ b/crates/rspack_plugin_rsdoctor/src/plugin.rs @@ -0,0 +1,463 @@ +use std::fmt; +use std::sync::{Arc, LazyLock}; + +use async_trait::async_trait; +use futures::future::BoxFuture; +use rspack_collections::Identifier; +use rspack_core::{ + ApplyContext, ChunkGroupUkey, Compilation, CompilationAfterCodeGeneration, + CompilationAfterProcessAssets, CompilationId, CompilationModuleIds, + CompilationOptimizeChunkModules, CompilationOptimizeChunks, CompilationParams, + CompilerCompilation, CompilerOptions, Plugin, PluginContext, +}; +use rspack_error::Result; +use rspack_hook::{plugin, plugin_hook}; +use rspack_util::fx_hash::FxDashMap; +use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; + +use crate::chunk_graph::{ + collect_assets, collect_chunk_assets, collect_chunk_dependencies, collect_chunk_modules, + collect_chunks, collect_entrypoint_assets, collect_entrypoints, +}; +use crate::module_graph::{ + collect_concatenated_modules, collect_module_dependencies, collect_module_ids, + collect_module_original_sources, collect_modules, +}; +use crate::{ + EntrypointUkey, ModuleUkey, RsdoctorAssetPatch, RsdoctorChunkGraph, RsdoctorModuleGraph, + RsdoctorModuleIdsPatch, RsdoctorModuleSourcesPatch, RsdoctorPluginHooks, +}; + +pub type SendModuleGraph = + Arc BoxFuture<'static, Result<()>> + Send + Sync>; +pub type SendChunkGraph = + Arc BoxFuture<'static, Result<()>> + Send + Sync>; +pub type SendAssets = + Arc BoxFuture<'static, Result<()>> + Send + Sync>; +pub type SendModuleSources = + Arc BoxFuture<'static, Result<()>> + Send + Sync>; + +static COMPILATION_HOOKS_MAP: LazyLock>> = + LazyLock::new(Default::default); + +static MODULE_UKEY_MAP: LazyLock> = + LazyLock::new(FxDashMap::default); +static ENTRYPOINT_UKEY_MAP: LazyLock> = + LazyLock::new(FxDashMap::default); + +#[derive(Debug, Hash, PartialEq, Eq)] +pub enum RsdoctorPluginModuleGraphFeature { + ModuleGraph, + ModuleIds, + ModuleSources, +} + +impl From for RsdoctorPluginModuleGraphFeature { + fn from(value: String) -> Self { + match value.as_str() { + "graph" => RsdoctorPluginModuleGraphFeature::ModuleGraph, + "ids" => RsdoctorPluginModuleGraphFeature::ModuleIds, + "sources" => RsdoctorPluginModuleGraphFeature::ModuleSources, + _ => panic!("invalid module graph feature: {}", value), + } + } +} + +impl fmt::Display for RsdoctorPluginModuleGraphFeature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RsdoctorPluginModuleGraphFeature::ModuleGraph => write!(f, "graph"), + RsdoctorPluginModuleGraphFeature::ModuleIds => write!(f, "ids"), + RsdoctorPluginModuleGraphFeature::ModuleSources => write!(f, "sources"), + } + } +} + +#[derive(Debug, Hash, PartialEq, Eq)] +pub enum RsdoctorPluginChunkGraphFeature { + ChunkGraph, + Assets, +} + +impl From for RsdoctorPluginChunkGraphFeature { + fn from(value: String) -> Self { + match value.as_str() { + "graph" => RsdoctorPluginChunkGraphFeature::ChunkGraph, + "assets" => RsdoctorPluginChunkGraphFeature::Assets, + _ => panic!("invalid chunk graph feature: {}", value), + } + } +} + +impl fmt::Display for RsdoctorPluginChunkGraphFeature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RsdoctorPluginChunkGraphFeature::ChunkGraph => write!(f, "graph"), + RsdoctorPluginChunkGraphFeature::Assets => write!(f, "assets"), + } + } +} + +#[derive(Default, Debug)] +pub struct RsdoctorPluginOptions { + pub module_graph_features: std::collections::HashSet, + pub chunk_graph_features: std::collections::HashSet, +} + +#[plugin] +#[derive(Debug)] +pub struct RsdoctorPlugin { + pub options: RsdoctorPluginOptions, +} + +impl RsdoctorPlugin { + pub fn new(config: RsdoctorPluginOptions) -> Self { + Self::new_inner(config) + } + + pub fn has_module_graph_feature(&self, feature: RsdoctorPluginModuleGraphFeature) -> bool { + if !self.options.module_graph_features.contains(&feature) { + return false; + } + if self + .options + .module_graph_features + .contains(&RsdoctorPluginModuleGraphFeature::ModuleGraph) + { + return true; + } + panic!( + "module graph feature \"{}\" need \"graph\" to be enabled", + feature + ); + } + + pub fn has_chunk_graph_feature(&self, feature: RsdoctorPluginChunkGraphFeature) -> bool { + if !self.options.chunk_graph_features.contains(&feature) { + return false; + } + if self + .options + .chunk_graph_features + .contains(&RsdoctorPluginChunkGraphFeature::ChunkGraph) + { + return true; + } + panic!( + "chunk graph feature \"{}\" need \"graph\" to be enabled", + feature + ); + } + + pub fn get_compilation_hooks( + id: CompilationId, + ) -> dashmap::mapref::one::Ref<'static, CompilationId, Box> { + if !COMPILATION_HOOKS_MAP.contains_key(&id) { + COMPILATION_HOOKS_MAP.insert(id, Default::default()); + } + COMPILATION_HOOKS_MAP + .get(&id) + .expect("should have js plugin drive") + } + + pub fn get_compilation_hooks_mut( + compilation: &Compilation, + ) -> dashmap::mapref::one::RefMut<'_, CompilationId, Box> { + COMPILATION_HOOKS_MAP.entry(compilation.id()).or_default() + } +} + +#[plugin_hook(CompilerCompilation for RsdoctorPlugin)] +async fn compilation( + &self, + _compilation: &mut Compilation, + _params: &mut CompilationParams, +) -> Result<()> { + MODULE_UKEY_MAP.clear(); + Ok(()) +} + +#[plugin_hook(CompilationOptimizeChunks for RsdoctorPlugin, stage = 9999)] +fn optimize_chunks(&self, compilation: &mut Compilation) -> Result> { + if !self.has_chunk_graph_feature(RsdoctorPluginChunkGraphFeature::ChunkGraph) { + return Ok(None); + } + + let hooks = RsdoctorPlugin::get_compilation_hooks(compilation.id()); + + let chunk_by_ukey = &compilation.chunk_by_ukey; + let chunk_group_by_ukey = &compilation.chunk_group_by_ukey; + let chunk_graph = &compilation.chunk_graph; + let chunks = chunk_by_ukey.iter().collect::>(); + + let mut rsd_chunks = HashMap::default(); + let mut rsd_entrypoints = HashMap::default(); + + // 1. collect chunks + rsd_chunks.extend(collect_chunks(&chunks, chunk_graph, chunk_group_by_ukey)); + + // 2. collect chunk dependencies + let chunk_dependencies = + collect_chunk_dependencies(&chunks, &rsd_chunks, chunk_group_by_ukey, chunk_by_ukey); + for (chunk_id, (parents, children)) in chunk_dependencies { + if let Some(rsd_chunk) = rsd_chunks.get_mut(&chunk_id) { + rsd_chunk.imported.extend(parents); + rsd_chunk.dependencies.extend(children); + } + } + + // 3. collect entrypoints + rsd_entrypoints.extend(collect_entrypoints( + &compilation.entrypoints, + &rsd_chunks, + chunk_group_by_ukey, + )); + + for (entrypoint_ukey, entrypoint) in rsd_entrypoints.iter() { + ENTRYPOINT_UKEY_MAP.insert(*entrypoint_ukey, entrypoint.ukey); + } + + tokio::spawn(async move { + match hooks + .chunk_graph + .call(&mut RsdoctorChunkGraph { + chunks: rsd_chunks.into_values().collect::>(), + entrypoints: rsd_entrypoints.into_values().collect::>(), + }) + .await + { + Ok(_) => {} + Err(e) => panic!("rsdoctor send chunk graph failed: {}", e), + }; + }); + + Ok(None) +} + +#[plugin_hook(CompilationOptimizeChunkModules for RsdoctorPlugin, stage = 9999)] +async fn optimize_chunk_modules(&self, compilation: &mut Compilation) -> Result> { + if !self.has_module_graph_feature(RsdoctorPluginModuleGraphFeature::ModuleGraph) { + return Ok(None); + } + + let hooks = RsdoctorPlugin::get_compilation_hooks(compilation.id()); + + let mut rsd_modules = HashMap::default(); + let mut rsd_dependencies = HashMap::default(); + + let module_graph = compilation.get_module_graph(); + let chunk_graph = &compilation.chunk_graph; + let chunk_by_ukey = &compilation.chunk_by_ukey; + let modules = module_graph.modules(); + + // 1. collect modules + rsd_modules.extend(collect_modules( + &modules, + &module_graph, + chunk_graph, + &compilation.options.context, + )); + + for (module_id, module) in rsd_modules.iter() { + MODULE_UKEY_MAP.insert(*module_id, module.ukey); + } + + // 2. collect concatenate children + let (child_map, parent_map) = collect_concatenated_modules(&modules); + for (module_id, children) in child_map { + if let Some(rsd_module) = rsd_modules.get_mut(&module_id) { + rsd_module.modules.extend( + children + .iter() + .filter_map(|i| MODULE_UKEY_MAP.get(i).map(|u| *u)) + .collect::>(), + ); + } + } + + // 3. collect concatenate parents + for (module_id, parents) in parent_map { + if let Some(rsd_module) = rsd_modules.get_mut(&module_id) { + rsd_module.belong_modules.extend( + parents + .iter() + .filter_map(|i| MODULE_UKEY_MAP.get(i).map(|u| *u)) + .collect::>(), + ); + } + } + + // 4. collect module dependencies + let dependency_infos = collect_module_dependencies(&modules, &MODULE_UKEY_MAP, &module_graph); + for (origin_module_id, dependencies) in dependency_infos { + for (dep_module_id, (dep_id, dependency)) in dependencies { + if let Some(rsd_module) = rsd_modules.get_mut(&dep_module_id) { + rsd_module.imported.insert(dependency.module); + } + if let Some(rsd_module) = rsd_modules.get_mut(&origin_module_id) { + rsd_module.dependencies.insert(dependency.ukey); + } + rsd_dependencies.insert(dep_id, dependency); + } + } + + // 5. collect chunk modules + let chunk_modules = + collect_chunk_modules(chunk_by_ukey, &MODULE_UKEY_MAP, chunk_graph, &module_graph); + + tokio::spawn(async move { + match hooks + .module_graph + .call(&mut RsdoctorModuleGraph { + modules: rsd_modules.into_values().collect::>(), + dependencies: rsd_dependencies.into_values().collect::>(), + chunk_modules, + }) + .await + { + Ok(_) => {} + Err(e) => panic!("rsdoctor send module graph failed: {}", e), + }; + }); + + Ok(None) +} + +#[plugin_hook(CompilationModuleIds for RsdoctorPlugin, stage = 9999)] +fn module_ids(&self, compilation: &mut Compilation) -> Result<()> { + if !self.has_module_graph_feature(RsdoctorPluginModuleGraphFeature::ModuleIds) { + return Ok(()); + } + + let hooks = RsdoctorPlugin::get_compilation_hooks(compilation.id()); + let module_graph = compilation.get_module_graph(); + let modules = module_graph.modules(); + let rsd_module_ids = + collect_module_ids(&modules, &MODULE_UKEY_MAP, &compilation.module_ids_artifact); + + tokio::spawn(async move { + match hooks + .module_ids + .call(&mut RsdoctorModuleIdsPatch { + module_ids: rsd_module_ids, + }) + .await + { + Ok(_) => {} + Err(e) => panic!("rsdoctor send module ids failed: {}", e), + }; + }); + + Ok(()) +} + +#[plugin_hook(CompilationAfterCodeGeneration for RsdoctorPlugin, stage = 9999)] +fn after_code_generation(&self, compilation: &mut Compilation) -> Result<()> { + if !self.has_module_graph_feature(RsdoctorPluginModuleGraphFeature::ModuleSources) { + return Ok(()); + } + + let hooks = RsdoctorPlugin::get_compilation_hooks(compilation.id()); + let module_graph = compilation.get_module_graph(); + let modules = module_graph.modules(); + let rsd_module_original_sources = + collect_module_original_sources(&modules, &MODULE_UKEY_MAP, &module_graph, compilation); + + tokio::spawn(async move { + match hooks + .module_sources + .call(&mut RsdoctorModuleSourcesPatch { + module_original_sources: rsd_module_original_sources, + }) + .await + { + Ok(_) => {} + Err(e) => panic!("rsdoctor send module sources failed: {}", e), + }; + }); + Ok(()) +} + +#[plugin_hook(CompilationAfterProcessAssets for RsdoctorPlugin, stage = 9999)] +async fn after_process_asssets(&self, compilation: &mut Compilation) -> Result<()> { + if !self.has_chunk_graph_feature(RsdoctorPluginChunkGraphFeature::Assets) { + return Ok(()); + } + + let hooks = RsdoctorPlugin::get_compilation_hooks(compilation.id()); + + let chunk_by_ukey = &compilation.chunk_by_ukey; + let chunk_group_by_ukey = &compilation.chunk_group_by_ukey; + let rsd_assets = collect_assets(compilation.assets(), chunk_by_ukey); + let rsd_chunk_assets = collect_chunk_assets(chunk_by_ukey, &rsd_assets); + let rsd_entrypoint_assets = collect_entrypoint_assets( + &compilation.entrypoints, + &rsd_assets, + &ENTRYPOINT_UKEY_MAP, + chunk_group_by_ukey, + chunk_by_ukey, + ); + + tokio::spawn(async move { + match hooks + .assets + .call(&mut RsdoctorAssetPatch { + assets: rsd_assets.into_values().collect::>(), + chunk_assets: rsd_chunk_assets, + entrypoint_assets: rsd_entrypoint_assets, + }) + .await + { + Ok(_) => {} + Err(e) => panic!("rsdoctor send assets failed: {}", e), + }; + }); + + Ok(()) +} + +#[async_trait] +impl Plugin for RsdoctorPlugin { + fn name(&self) -> &'static str { + "rsdoctor" + } + + fn apply(&self, ctx: PluginContext<&mut ApplyContext>, _options: &CompilerOptions) -> Result<()> { + ctx + .context + .compiler_hooks + .compilation + .tap(compilation::new(self)); + ctx + .context + .compilation_hooks + .optimize_chunk_modules + .tap(optimize_chunk_modules::new(self)); + + ctx + .context + .compilation_hooks + .optimize_chunks + .tap(optimize_chunks::new(self)); + + ctx + .context + .compilation_hooks + .module_ids + .tap(module_ids::new(self)); + + ctx + .context + .compilation_hooks + .after_code_generation + .tap(after_code_generation::new(self)); + + ctx + .context + .compilation_hooks + .after_process_assets + .tap(after_process_asssets::new(self)); + + Ok(()) + } +} diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/a.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/a.js new file mode 100644 index 000000000000..f49fbabaacba --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/a.js @@ -0,0 +1,7 @@ +import { a } from "./shared.js"; + +it('should run well', async () => { + expect(a).toBe(1) + const c = await import("./c").then(m => m.c); + expect(c).toBe(3); +}) diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/b.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/b.js new file mode 100644 index 000000000000..825bd19dee3a --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/b.js @@ -0,0 +1,7 @@ +import { b } from './shared.js' + +it('should run well', async () => { + expect(b).toBe(2) + const d = await import("./d").then(m => m.d); + expect(d).toBe(4); +}) diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/c.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/c.js new file mode 100644 index 000000000000..10779b123fc2 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/c.js @@ -0,0 +1 @@ +export const c = 3; \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/d.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/d.js new file mode 100644 index 000000000000..be523836d6e0 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/d.js @@ -0,0 +1 @@ +export const d = 4; \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/rspack.config.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/rspack.config.js new file mode 100644 index 000000000000..7047fa480ef5 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/rspack.config.js @@ -0,0 +1,95 @@ +const { + experiments: { RsdoctorPlugin } +} = require("@rspack/core"); +const fs = require("fs"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + entry: { + a: "./a.js", + b: "./b.js" + }, + output: { + filename: "[name].js" + }, + optimization: { + chunkIds: "named", + moduleIds: "named" + }, + plugins: [ + new RsdoctorPlugin({ + moduleGraphFeatures: false, + chunkGraphFeatures: ["graph", "assets"] + }), + { + apply(compiler) { + compiler.hooks.compilation.tap("TestPlugin::Assets", compilation => { + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + hooks.assets.tap("TestPlugin::Assets", data => { + const { assets } = data; + expect(assets.length).toBe(4); + const assetsInfo = assets.map(a => ({ + size: a.size, + path: a.path + })); + assetsInfo.sort((a, b) => (a.path > b.path ? 1 : -1)); + expect(assetsInfo).toMatchInlineSnapshot(` + Array [ + Object { + path: a.js, + size: 4763, + }, + Object { + path: b.js, + size: 4763, + }, + Object { + path: c_js.js, + size: 294, + }, + Object { + path: d_js.js, + size: 294, + }, + ] + `); + }); + }); + } + }, + { + apply(compiler) { + compiler.hooks.compilation.tap("TestPlugin::ChunkAssets", compilation => { + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + let chunks = []; + hooks.chunkGraph.tap("TestPlugin::ChunkAssets", data => { + chunks = data.chunks; + }); + hooks.assets.tap("TestPlugin::Assets", data => { + const { chunkAssets } = data; + for (const chunk of chunks) { + expect(chunkAssets.find(a => a.chunk === chunk.ukey).assets.length).toBe(1); + } + }); + }); + } + }, + { + apply(compiler) { + compiler.hooks.compilation.tap("TestPlugin::EntrypointAssets", compilation => { + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + let entrypoints = []; + hooks.chunkGraph.tap("TestPlugin::EntrypointAssets", data => { + entrypoints = data.entrypoints; + }); + hooks.assets.tap("TestPlugin::Assets", data => { + const { entrypointAssets } = data; + for (const ep of entrypointAssets) { + expect(entrypointAssets.find(a => a.chunk === ep.ukey).assets.length).toBe(1); + } + }); + }); + } + } + ] +}; diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/shared.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/shared.js new file mode 100644 index 000000000000..72ab60e17a25 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/shared.js @@ -0,0 +1,2 @@ +export const a = 1; +export const b = 2; diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/test.config.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/test.config.js new file mode 100644 index 000000000000..11702dd689e5 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/test.config.js @@ -0,0 +1,7 @@ +/** @type {import("../../../../dist").TDiffCaseConfig} */ +module.exports = { + files: [ + 'a.js', + 'b.js', + ], +}; diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/a.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/a.js new file mode 100644 index 000000000000..f49fbabaacba --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/a.js @@ -0,0 +1,7 @@ +import { a } from "./shared.js"; + +it('should run well', async () => { + expect(a).toBe(1) + const c = await import("./c").then(m => m.c); + expect(c).toBe(3); +}) diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/b.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/b.js new file mode 100644 index 000000000000..825bd19dee3a --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/b.js @@ -0,0 +1,7 @@ +import { b } from './shared.js' + +it('should run well', async () => { + expect(b).toBe(2) + const d = await import("./d").then(m => m.d); + expect(d).toBe(4); +}) diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/c.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/c.js new file mode 100644 index 000000000000..10779b123fc2 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/c.js @@ -0,0 +1 @@ +export const c = 3; \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/d.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/d.js new file mode 100644 index 000000000000..be523836d6e0 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/d.js @@ -0,0 +1 @@ +export const d = 4; \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/rspack.config.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/rspack.config.js new file mode 100644 index 000000000000..c31399db7df7 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/rspack.config.js @@ -0,0 +1,59 @@ +const { experiments: { RsdoctorPlugin } } = require("@rspack/core"); +const fs = require("fs"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + entry: { + a: './a.js', + b: './b.js', + }, + output: { + filename: '[name].js' + }, + plugins: [ + new RsdoctorPlugin({ + moduleGraphFeatures: false, + chunkGraphFeatures: ["graph"] + }), + { + apply(compiler) { + compiler.hooks.compilation.tap("TestPlugin::Chunks", (compilation) => { + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + hooks.chunkGraph.tap("TestPlugin::Chunks", (data) => { + const { chunks } = data; + expect(chunks.length).toBe(4); + expect(chunks.filter(c => c.entry).length).toBe(2); + expect(chunks.filter(c => c.initial).length).toBe(2); + + const entryA = chunks.find(c => c.name === 'a'); + const entryB = chunks.find(c => c.name === 'b'); + + expect(entryA.dependencies.length).toBe(1); + expect(entryB.dependencies.length).toBe(1); + for (const chunk of chunks) { + if (!chunk.name) { + expect(chunk.imported.length).toBe(1); + } + } + }); + }); + } + }, + { + apply(compiler) { + compiler.hooks.compilation.tap("TestPlugin::Entrypoints", (compilation) => { + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + hooks.chunkGraph.tap("TestPlugin::Entrypoints", (data) => { + const { entrypoints } = data; + expect(entrypoints.length).toBe(2); + + const entrypointA = entrypoints.find(e => e.name === 'a'); + const entrypointB = entrypoints.find(e => e.name === 'b'); + expect(entrypointA.chunks.length).toBe(1); + expect(entrypointB.chunks.length).toBe(1); + }); + }); + } + }, + ] +}; diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/shared.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/shared.js new file mode 100644 index 000000000000..72ab60e17a25 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/shared.js @@ -0,0 +1,2 @@ +export const a = 1; +export const b = 2; diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/test.config.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/test.config.js new file mode 100644 index 000000000000..11702dd689e5 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/chunkGraph/test.config.js @@ -0,0 +1,7 @@ +/** @type {import("../../../../dist").TDiffCaseConfig} */ +module.exports = { + files: [ + 'a.js', + 'b.js', + ], +}; diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/index.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/index.js new file mode 100644 index 000000000000..4fbaf1ed21e8 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/index.js @@ -0,0 +1,6 @@ +import { Out } from "./lib/a" + +it("should generate correct export for dynamic reexports (dynamic cjs)", () => { + expect(Out).toBe(42) +}) + diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/a.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/a.js new file mode 100644 index 000000000000..5d371e8cce9f --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/a.js @@ -0,0 +1 @@ +export * from "./b" \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/b.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/b.js new file mode 100644 index 000000000000..d720ff79c201 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/b.js @@ -0,0 +1 @@ +export * from "./c" \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/c.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/c.js new file mode 100644 index 000000000000..f79696d7c5a7 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/lib/c.js @@ -0,0 +1,2 @@ +let e = exports; +e.Out = 42; diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/rspack.config.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/rspack.config.js new file mode 100644 index 000000000000..18c66e876145 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleGraph/rspack.config.js @@ -0,0 +1,69 @@ +const { experiments: { RsdoctorPlugin } } = require("@rspack/core"); +const path = require("path"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + optimization: { + concatenateModules: true, + }, + plugins: [ + new RsdoctorPlugin({ + moduleGraphFeatures: ["graph"], + chunkGraphFeatures: false + }), + { + apply(compiler) { + compiler.hooks.compilation.tap("TestPlugin::Modules", (compilation) => { + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + hooks.moduleGraph.tap("TestPlugin::Modules", (moduleGraph) => { + const { modules } = moduleGraph; + expect(modules.length).toBe(5); + + const concatenateModules = modules.filter(module => module.kind === "concatenated"); + const normalModules = modules.filter(module => module.kind === "normal"); + expect(concatenateModules.length).toBe(1); + expect(normalModules.length).toBe(4); + + const entryModule = modules.find(module => module.isEntry && module.kind === "concatenated"); + expect(entryModule.chunks.length).toBe(1); + expect(entryModule.modules.length).toBe(3); + expect(entryModule.dependencies.length).toBe(1); + expect(entryModule.path).toBe(path.join(__dirname, './index.js')); + }); + }); + } + }, + { + apply(compiler) { + compiler.hooks.compilation.tap("TestPlugin::Dependencies", (compilation) => { + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + hooks.moduleGraph.tap("TestPlugin::Dependencies", (moduleGraph) => { + const { dependencies } = moduleGraph; + const deps = dependencies.map(dep => ({ + request: dep.request, + kind: dep.kind + })); + deps.sort((a, b) => a.request > b.request ? 1 : -1); + expect(deps).toEqual([ + { request: "./b", "kind": "esm export" }, + { request: "./c", "kind": "esm export" }, + { request: "./lib/a", "kind": "esm import" } + ]); + }); + }); + } + }, + { + apply(compiler) { + compiler.hooks.compilation.tap("TestPlugin::ChunkModules", (compilation) => { + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + hooks.moduleGraph.tap("TestPlugin::ChunkModules", (moduleGraph) => { + const { chunkModules } = moduleGraph; + expect(chunkModules.length).toBe(1); + expect(chunkModules[0].modules.length).toBe(5); + }); + }); + } + } + ] +}; diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/index.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/index.js new file mode 100644 index 000000000000..4fbaf1ed21e8 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/index.js @@ -0,0 +1,6 @@ +import { Out } from "./lib/a" + +it("should generate correct export for dynamic reexports (dynamic cjs)", () => { + expect(Out).toBe(42) +}) + diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/a.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/a.js new file mode 100644 index 000000000000..5d371e8cce9f --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/a.js @@ -0,0 +1 @@ +export * from "./b" \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/b.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/b.js new file mode 100644 index 000000000000..d720ff79c201 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/b.js @@ -0,0 +1 @@ +export * from "./c" \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/c.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/c.js new file mode 100644 index 000000000000..f79696d7c5a7 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/lib/c.js @@ -0,0 +1,2 @@ +let e = exports; +e.Out = 42; diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/rspack.config.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/rspack.config.js new file mode 100644 index 000000000000..b764e561f410 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleIds/rspack.config.js @@ -0,0 +1,25 @@ +const { experiments: { RsdoctorPlugin } } = require("@rspack/core"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + optimization: { + concatenateModules: true, + }, + plugins: [ + new RsdoctorPlugin({ + moduleGraphFeatures: ["graph", "ids"], + chunkGraphFeatures: false + }), + { + apply(compiler) { + compiler.hooks.compilation.tap("TestPlugin::ModuleIds", (compilation) => { + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + hooks.moduleIds.tap("TestPlugin::ModuleIds", (data) => { + const { moduleIds } = data; + expect(moduleIds.length).toBe(2); + }); + }); + } + }, + ] +}; diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/index.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/index.js new file mode 100644 index 000000000000..4fbaf1ed21e8 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/index.js @@ -0,0 +1,6 @@ +import { Out } from "./lib/a" + +it("should generate correct export for dynamic reexports (dynamic cjs)", () => { + expect(Out).toBe(42) +}) + diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/a.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/a.js new file mode 100644 index 000000000000..5d371e8cce9f --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/a.js @@ -0,0 +1 @@ +export * from "./b" \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/b.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/b.js new file mode 100644 index 000000000000..d720ff79c201 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/b.js @@ -0,0 +1 @@ +export * from "./c" \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/c.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/c.js new file mode 100644 index 000000000000..f79696d7c5a7 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/lib/c.js @@ -0,0 +1,2 @@ +let e = exports; +e.Out = 42; diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/rspack.config.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/rspack.config.js new file mode 100644 index 000000000000..86c67a4d158f --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/moduleSources/rspack.config.js @@ -0,0 +1,34 @@ +const { experiments: { RsdoctorPlugin } } = require("@rspack/core"); +const fs = require("fs"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + optimization: { + concatenateModules: true, + }, + plugins: [ + new RsdoctorPlugin({ + moduleGraphFeatures: ["graph", "sources"], + chunkGraphFeatures: false + }), + { + apply(compiler) { + compiler.hooks.compilation.tap("TestPlugin::ModuleIds", (compilation) => { + let modules = []; + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + hooks.moduleGraph.tap("TestPlugin::ModuleIds", (data) => { + modules = data.modules; + }); + hooks.moduleSources.tap("TestPlugin::ModuleIds", (data) => { + const { moduleOriginalSources } = data; + expect(moduleOriginalSources.length).toBe(5); + for (const module of modules) { + const moduleSource = moduleOriginalSources.find(s => s.module === module.ukey); + expect(moduleSource.source).toBe(fs.readFileSync(module.path, "utf-8")); + } + }); + }); + } + }, + ] +}; diff --git a/packages/rspack/etc/core.api.md b/packages/rspack/etc/core.api.md index 45d55c530858..6bd918d8e6ac 100644 --- a/packages/rspack/etc/core.api.md +++ b/packages/rspack/etc/core.api.md @@ -51,6 +51,11 @@ import { JsLoaderItem } from '@rspack/binding'; import type { JsModule } from '@rspack/binding'; import type { JsModuleGraph } from '@rspack/binding'; import type { JsModuleGraphConnection } from '@rspack/binding'; +import { JsRsdoctorAssetPatch } from '@rspack/binding'; +import { JsRsdoctorChunkGraph } from '@rspack/binding'; +import { JsRsdoctorModuleGraph } from '@rspack/binding'; +import { JsRsdoctorModuleIdsPatch } from '@rspack/binding'; +import { JsRsdoctorModuleSourcesPatch } from '@rspack/binding'; import { JsRuntimeModule } from '@rspack/binding'; import type { JsStats } from '@rspack/binding'; import type { JsStatsCompilation } from '@rspack/binding'; @@ -1992,6 +1997,8 @@ interface Experiments_2 { }; // (undocumented) RemoveDuplicateModulesPlugin: typeof RemoveDuplicateModulesPlugin; + // (undocumented) + RsdoctorPlugin: typeof RsdoctorPlugin; } // @public (undocumented) @@ -5065,6 +5072,51 @@ type ResourceDataWithData = ResourceData & { data?: Record; }; +// @public (undocumented) +const RsdoctorPlugin: typeof RsdoctorPluginImpl & { + getHooks: (compilation: Compilation) => RsdoctorPluginHooks; + getCompilationHooks: (compilation: Compilation) => RsdoctorPluginHooks; +}; + +// @public (undocumented) +export namespace RsdoctorPluginData { + export type { JsRsdoctorAsset as RsdoctorAsset, JsRsdoctorChunkGraph as RsdoctorChunkGraph, JsRsdoctorModuleGraph as RsdoctorModuleGraph, JsRsdoctorChunk as RsdoctorChunk, JsRsdoctorModule as RsdoctorModule, JsRsdoctorSideEffect as RsdoctorSideEffect, JsRsdoctorExportInfo as RsdoctorExportInfo, JsRsdoctorVariable as RsdoctorVariable, JsRsdoctorDependency as RsdoctorDependency, JsRsdoctorEntrypoint as RsdoctorEntrypoint, JsRsdoctorStatement as RsdoctorStatement, JsRsdoctorSourceRange as RsdoctorSourceRange, JsRsdoctorSourcePosition as RsdoctorSourcePosition, JsRsdoctorModuleGraphModule as RsdoctorModuleGraphModule, JsRsdoctorModuleIdsPatch as RsdoctorModuleIdsPatch, JsRsdoctorModuleOriginalSource as RsdoctorModuleOriginalSource, JsRsdoctorAssetPatch as RsdoctorAssetPatch, JsRsdoctorChunkAssets as RsdoctorChunkAssets, JsRsdoctorEntrypointAssets as RsdoctorEntrypointAssets, JsRsdoctorChunkModules as RsdoctorChunkModules, JsRsdoctorModuleSourcesPatch as RsdoctorModuleSourcesPatch }; +} + +// @public (undocumented) +export type RsdoctorPluginHooks = { + moduleGraph: liteTapable.AsyncSeriesBailHook<[ + JsRsdoctorModuleGraph + ], false | void>; + chunkGraph: liteTapable.AsyncSeriesBailHook<[ + JsRsdoctorChunkGraph + ], false | void>; + moduleIds: liteTapable.AsyncSeriesBailHook<[ + JsRsdoctorModuleIdsPatch + ], false | void>; + moduleSources: liteTapable.AsyncSeriesBailHook<[ + JsRsdoctorModuleSourcesPatch + ], false | void>; + assets: liteTapable.AsyncSeriesBailHook<[JsRsdoctorAssetPatch], false | void>; +}; + +// @public (undocumented) +const RsdoctorPluginImpl: { + new (c?: RsdoctorPluginOptions | undefined): { + name: BuiltinPluginName; + _args: [c?: RsdoctorPluginOptions | undefined]; + affectedHooks: "done" | "make" | "environment" | "compile" | "emit" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "compilation" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "failed" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | "additionalPass" | undefined; + raw(compiler: Compiler): BuiltinPlugin; + apply(compiler: Compiler): void; + }; +}; + +// @public (undocumented) +type RsdoctorPluginOptions = { + moduleGraphFeatures?: boolean | Array<"graph" | "ids" | "sources">; + chunkGraphFeatures?: boolean | Array<"graph" | "assets">; +}; + // @public (undocumented) type Rspack = typeof rspack_2 & typeof rspackExports & { rspack: Rspack; @@ -5217,6 +5269,8 @@ declare namespace rspackExports { SharedObject, SharePluginOptions, sharing, + RsdoctorPluginData, + RsdoctorPluginHooks, HtmlRspackPluginOptions, SwcJsMinimizerRspackPluginOptions, LightningCssMinimizerRspackPluginOptions, diff --git a/packages/rspack/rspack b/packages/rspack/rspack new file mode 120000 index 000000000000..88355f2c0efc --- /dev/null +++ b/packages/rspack/rspack @@ -0,0 +1 @@ +/Users/bytedance/rspack-dev/rspack/packages/rspack \ No newline at end of file diff --git a/packages/rspack/scripts/check-documentation-coverage.ts b/packages/rspack/scripts/check-documentation-coverage.ts index 87555de188c6..5d0eb345f14c 100644 --- a/packages/rspack/scripts/check-documentation-coverage.ts +++ b/packages/rspack/scripts/check-documentation-coverage.ts @@ -108,7 +108,7 @@ function checkPluginsDocumentationCoverage() { const implementedPlugins = getImplementedPlugins(); const documentedPlugins = getDocumentedPlugins(); - const excludedPlugins = ["OriginEntryPlugin"]; + const excludedPlugins = ["OriginEntryPlugin", "RsdoctorPlugin"]; const undocumentedPlugins = Array.from(implementedPlugins).filter( plugin => diff --git a/packages/rspack/src/Compiler.ts b/packages/rspack/src/Compiler.ts index 7c5e34a83077..12812ad09c61 100644 --- a/packages/rspack/src/Compiler.ts +++ b/packages/rspack/src/Compiler.ts @@ -19,7 +19,7 @@ import { __from_binding_runtime_globals, __to_binding_runtime_globals } from "./RuntimeGlobals"; -import { JsLoaderRspackPlugin } from "./builtin-plugin"; +import { createRsdoctorPluginHooksRegisters, JsLoaderRspackPlugin } from "./builtin-plugin"; import type { Chunk } from "./Chunk"; import { Compilation } from "./Compilation"; @@ -765,7 +765,7 @@ class Compiler { this.#compilation = undefined; // ensure thisCompilation must call this.hooks.thisCompilation.intercept({ - call: () => {} + call: () => { } }); } @@ -849,7 +849,8 @@ class Compiler { createTap, createMapTap ), - ...createHtmlPluginHooksRegisters(getCompiler, createTap, createMapTap) + ...createHtmlPluginHooksRegisters(getCompiler, createTap, createMapTap), + ...createRsdoctorPluginHooksRegisters(getCompiler, createTap, createMapTap) }; } diff --git a/packages/rspack/src/builtin-plugin/RsdoctorPlugin.ts b/packages/rspack/src/builtin-plugin/RsdoctorPlugin.ts new file mode 100644 index 000000000000..fe76c7d6f15e --- /dev/null +++ b/packages/rspack/src/builtin-plugin/RsdoctorPlugin.ts @@ -0,0 +1,228 @@ +import { + BuiltinPluginName, + RegisterJsTapKind, + type JsRsdoctorAsset, + type JsRsdoctorAssetPatch, + type JsRsdoctorChunk, + type JsRsdoctorChunkAssets, + type JsRsdoctorChunkGraph, + type JsRsdoctorChunkModules, + type JsRsdoctorDependency, + type JsRsdoctorEntrypoint, + type JsRsdoctorEntrypointAssets, + type JsRsdoctorExportInfo, + type JsRsdoctorModule, + type JsRsdoctorModuleGraph, + type JsRsdoctorModuleGraphModule, + type JsRsdoctorModuleIdsPatch, + type JsRsdoctorModuleOriginalSource, + type JsRsdoctorModuleSourcesPatch, + type JsRsdoctorSideEffect, + type JsRsdoctorSourcePosition, + type JsRsdoctorSourceRange, + type JsRsdoctorStatement, + type JsRsdoctorVariable, + type RawRsdoctorPluginOptions +} from "@rspack/binding"; +import * as liteTapable from "@rspack/lite-tapable"; +import { z } from "zod"; +import { Compilation } from "../Compilation"; +import type { Compiler } from "../Compiler"; +import { validate } from "../util/validate"; +import { create } from "./base"; +import { CreatePartialRegisters } from "../taps/types"; + +export declare namespace RsdoctorPluginData { + export type { + JsRsdoctorAsset as RsdoctorAsset, + JsRsdoctorChunkGraph as RsdoctorChunkGraph, + JsRsdoctorModuleGraph as RsdoctorModuleGraph, + JsRsdoctorChunk as RsdoctorChunk, + JsRsdoctorModule as RsdoctorModule, + JsRsdoctorSideEffect as RsdoctorSideEffect, + JsRsdoctorExportInfo as RsdoctorExportInfo, + JsRsdoctorVariable as RsdoctorVariable, + JsRsdoctorDependency as RsdoctorDependency, + JsRsdoctorEntrypoint as RsdoctorEntrypoint, + JsRsdoctorStatement as RsdoctorStatement, + JsRsdoctorSourceRange as RsdoctorSourceRange, + JsRsdoctorSourcePosition as RsdoctorSourcePosition, + JsRsdoctorModuleGraphModule as RsdoctorModuleGraphModule, + JsRsdoctorModuleIdsPatch as RsdoctorModuleIdsPatch, + JsRsdoctorModuleOriginalSource as RsdoctorModuleOriginalSource, + JsRsdoctorAssetPatch as RsdoctorAssetPatch, + JsRsdoctorChunkAssets as RsdoctorChunkAssets, + JsRsdoctorEntrypointAssets as RsdoctorEntrypointAssets, + JsRsdoctorChunkModules as RsdoctorChunkModules, + JsRsdoctorModuleSourcesPatch as RsdoctorModuleSourcesPatch + }; +} + +export type RsdoctorPluginOptions = { + moduleGraphFeatures?: boolean | Array<"graph" | "ids" | "sources">; + chunkGraphFeatures?: boolean | Array<"graph" | "assets">; +}; +const rsdoctorPluginSchema = z.strictObject({ + moduleGraphFeatures: z + .union([z.boolean(), z.array(z.enum(["graph", "ids", "sources"]))]) + .optional(), + chunkGraphFeatures: z + .union([z.boolean(), z.array(z.enum(["graph", "assets"]))]) + .optional() +}) satisfies z.ZodType; + +const RsdoctorPluginImpl = create( + BuiltinPluginName.RsdoctorPlugin, + function ( + this: Compiler, + c: RsdoctorPluginOptions = { + moduleGraphFeatures: true, + chunkGraphFeatures: true + } + ): RawRsdoctorPluginOptions { + validate(c, rsdoctorPluginSchema); + return { + moduleGraphFeatures: c.moduleGraphFeatures ?? true, + chunkGraphFeatures: c.chunkGraphFeatures ?? true + }; + } +); + +export type RsdoctorPluginHooks = { + moduleGraph: liteTapable.AsyncSeriesBailHook< + [JsRsdoctorModuleGraph], + false | void + >; + chunkGraph: liteTapable.AsyncSeriesBailHook< + [JsRsdoctorChunkGraph], + false | void + >; + moduleIds: liteTapable.AsyncSeriesBailHook< + [JsRsdoctorModuleIdsPatch], + false | void + >; + moduleSources: liteTapable.AsyncSeriesBailHook< + [JsRsdoctorModuleSourcesPatch], + false | void + >; + assets: liteTapable.AsyncSeriesBailHook<[JsRsdoctorAssetPatch], false | void>; +}; + +const compilationHooksMap: WeakMap = + new WeakMap(); + +const RsdoctorPlugin = RsdoctorPluginImpl as typeof RsdoctorPluginImpl & { + /** + * @deprecated Use `getCompilationHooks` instead. + */ + getHooks: (compilation: Compilation) => RsdoctorPluginHooks; + getCompilationHooks: (compilation: Compilation) => RsdoctorPluginHooks; +}; + +RsdoctorPlugin.getHooks = RsdoctorPlugin.getCompilationHooks = ( + compilation: Compilation +) => { + if (!(compilation instanceof Compilation)) { + throw new TypeError( + "The 'compilation' argument must be an instance of Compilation" + ); + } + let hooks = compilationHooksMap.get(compilation); + if (hooks === undefined) { + hooks = { + moduleGraph: new liteTapable.AsyncSeriesBailHook< + [JsRsdoctorModuleGraph], + false | void + >(["moduleGraph"]), + chunkGraph: new liteTapable.AsyncSeriesBailHook< + [JsRsdoctorChunkGraph], + false | void + >(["chunkGraph"]), + moduleIds: new liteTapable.AsyncSeriesBailHook< + [JsRsdoctorModuleIdsPatch], + false | void + >(["moduleIdsPatch"]), + moduleSources: new liteTapable.AsyncSeriesBailHook< + [JsRsdoctorModuleSourcesPatch], + false | void + >(["moduleSourcesPatch"]), + assets: new liteTapable.AsyncSeriesBailHook< + [JsRsdoctorAssetPatch], + false | void + >(["assetPatch"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; +}; + +export const createRsdoctorPluginHooksRegisters: CreatePartialRegisters<`RsdoctorPlugin`> = ( + getCompiler, + createTap, + createMapTap +) => { + return { + registerRsdoctorPluginModuleGraphTaps: createTap( + RegisterJsTapKind.RsdoctorPluginModuleGraph, + function () { + return RsdoctorPlugin.getCompilationHooks(getCompiler().__internal__get_compilation()!) + .moduleGraph; + }, + function (queried) { + return async function (data: JsRsdoctorModuleGraph) { + return await queried.promise(data); + }; + } + ), + registerRsdoctorPluginChunkGraphTaps: createTap( + RegisterJsTapKind.RsdoctorPluginChunkGraph, + function () { + return RsdoctorPlugin.getCompilationHooks(getCompiler().__internal__get_compilation()!) + .chunkGraph; + }, + function (queried) { + return async function (data: JsRsdoctorChunkGraph) { + return await queried.promise(data); + }; + } + ), + registerRsdoctorPluginModuleIdsTaps: createTap( + RegisterJsTapKind.RsdoctorPluginModuleIds, + function () { + return RsdoctorPlugin.getCompilationHooks(getCompiler().__internal__get_compilation()!) + .moduleIds; + }, + function (queried) { + return async function (data: JsRsdoctorModuleIdsPatch) { + return await queried.promise(data); + }; + } + ), + registerRsdoctorPluginModuleSourcesTaps: createTap( + RegisterJsTapKind.RsdoctorPluginModuleSources, + function () { + return RsdoctorPlugin.getCompilationHooks(getCompiler().__internal__get_compilation()!) + .moduleSources; + }, + function (queried) { + return async function (data: JsRsdoctorModuleSourcesPatch) { + return await queried.promise(data); + }; + } + ), + registerRsdoctorPluginAssetsTaps: createTap( + RegisterJsTapKind.RsdoctorPluginAssets, + function () { + return RsdoctorPlugin.getCompilationHooks(getCompiler().__internal__get_compilation()!) + .assets; + }, + function (queried) { + return async function (data: JsRsdoctorAssetPatch) { + return await queried.promise(data); + }; + } + ) + } +} + +export { RsdoctorPlugin }; \ No newline at end of file diff --git a/packages/rspack/src/builtin-plugin/index.ts b/packages/rspack/src/builtin-plugin/index.ts index c69ef211f8e1..6750f76083ba 100644 --- a/packages/rspack/src/builtin-plugin/index.ts +++ b/packages/rspack/src/builtin-plugin/index.ts @@ -71,3 +71,4 @@ export * from "./ContextReplacementPlugin"; export * from "./LibManifestPlugin"; export * from "./DllEntryPlugin"; export * from "./DllReferenceAgencyPlugin"; +export * from "./RsdoctorPlugin"; diff --git a/packages/rspack/src/exports.ts b/packages/rspack/src/exports.ts index 9ca4bbacab42..3b61451aa940 100644 --- a/packages/rspack/src/exports.ts +++ b/packages/rspack/src/exports.ts @@ -180,6 +180,7 @@ import { LimitChunkCountPlugin } from "./builtin-plugin"; import { RuntimeChunkPlugin } from "./builtin-plugin"; import { SplitChunksPlugin } from "./builtin-plugin"; import { RemoveDuplicateModulesPlugin } from "./builtin-plugin"; +import { RsdoctorPlugin } from "./builtin-plugin"; interface Optimize { LimitChunkCountPlugin: typeof LimitChunkCountPlugin; @@ -252,6 +253,7 @@ export const sharing = { }; ///// Rspack Postfixed Internal Plugins ///// +export type { RsdoctorPluginData, RsdoctorPluginHooks } from "./builtin-plugin"; export type { HtmlRspackPluginOptions } from "./builtin-plugin"; export type { SwcJsMinimizerRspackPluginOptions } from "./builtin-plugin"; export type { LightningCssMinimizerRspackPluginOptions } from "./builtin-plugin"; @@ -301,6 +303,7 @@ interface Experiments { cleanup: () => Promise; }; RemoveDuplicateModulesPlugin: typeof RemoveDuplicateModulesPlugin; + RsdoctorPlugin: typeof RsdoctorPlugin; } export const experiments: Experiments = { @@ -329,5 +332,11 @@ export const experiments: Experiments = { } } }, - RemoveDuplicateModulesPlugin + RemoveDuplicateModulesPlugin, + /** + * Note: This plugin is unstable yet + * + * @internal + */ + RsdoctorPlugin }; From 897eff1d112fd5812452d9cdc3e7e2ce33c0c526 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Tue, 21 Jan 2025 18:20:10 +0800 Subject: [PATCH 2/4] feat: add rsdoctor native plugin --- packages/rspack/etc/core.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rspack/etc/core.api.md b/packages/rspack/etc/core.api.md index 6bd918d8e6ac..6be862ee57d7 100644 --- a/packages/rspack/etc/core.api.md +++ b/packages/rspack/etc/core.api.md @@ -5105,7 +5105,7 @@ const RsdoctorPluginImpl: { new (c?: RsdoctorPluginOptions | undefined): { name: BuiltinPluginName; _args: [c?: RsdoctorPluginOptions | undefined]; - affectedHooks: "done" | "make" | "environment" | "compile" | "emit" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "compilation" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "failed" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | "additionalPass" | undefined; + affectedHooks: "done" | "environment" | "make" | "compile" | "emit" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "compilation" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "failed" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | "additionalPass" | undefined; raw(compiler: Compiler): BuiltinPlugin; apply(compiler: Compiler): void; }; From e2c2e7e0be4816ef785eb4ceba7340525db9f2b2 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Tue, 21 Jan 2025 20:17:46 +0800 Subject: [PATCH 3/4] feat: add rsdoctor native plugin --- .../rsdoctor/assets/rspack.config.js | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/rspack.config.js b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/rspack.config.js index 7047fa480ef5..aa7e5455fd33 100644 --- a/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/rspack.config.js +++ b/packages/rspack-test-tools/tests/configCases/rsdoctor/assets/rspack.config.js @@ -45,11 +45,11 @@ module.exports = { }, Object { path: c_js.js, - size: 294, + size: 279, }, Object { path: d_js.js, - size: 294, + size: 279, }, ] `); @@ -59,36 +59,46 @@ module.exports = { }, { apply(compiler) { - compiler.hooks.compilation.tap("TestPlugin::ChunkAssets", compilation => { - const hooks = RsdoctorPlugin.getCompilationHooks(compilation); - let chunks = []; - hooks.chunkGraph.tap("TestPlugin::ChunkAssets", data => { - chunks = data.chunks; - }); - hooks.assets.tap("TestPlugin::Assets", data => { - const { chunkAssets } = data; - for (const chunk of chunks) { - expect(chunkAssets.find(a => a.chunk === chunk.ukey).assets.length).toBe(1); - } - }); - }); + compiler.hooks.compilation.tap( + "TestPlugin::ChunkAssets", + compilation => { + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + let chunks = []; + hooks.chunkGraph.tap("TestPlugin::ChunkAssets", data => { + chunks = data.chunks; + }); + hooks.assets.tap("TestPlugin::Assets", data => { + const { chunkAssets } = data; + for (const chunk of chunks) { + expect( + chunkAssets.find(a => a.chunk === chunk.ukey).assets.length + ).toBe(1); + } + }); + } + ); } }, { apply(compiler) { - compiler.hooks.compilation.tap("TestPlugin::EntrypointAssets", compilation => { - const hooks = RsdoctorPlugin.getCompilationHooks(compilation); - let entrypoints = []; - hooks.chunkGraph.tap("TestPlugin::EntrypointAssets", data => { - entrypoints = data.entrypoints; - }); - hooks.assets.tap("TestPlugin::Assets", data => { - const { entrypointAssets } = data; - for (const ep of entrypointAssets) { - expect(entrypointAssets.find(a => a.chunk === ep.ukey).assets.length).toBe(1); - } - }); - }); + compiler.hooks.compilation.tap( + "TestPlugin::EntrypointAssets", + compilation => { + const hooks = RsdoctorPlugin.getCompilationHooks(compilation); + let entrypoints = []; + hooks.chunkGraph.tap("TestPlugin::EntrypointAssets", data => { + entrypoints = data.entrypoints; + }); + hooks.assets.tap("TestPlugin::Assets", data => { + const { entrypointAssets } = data; + for (const ep of entrypointAssets) { + expect( + entrypointAssets.find(a => a.chunk === ep.ukey).assets.length + ).toBe(1); + } + }); + } + ); } } ] From eb42d6d990cbedc606de1d2a1aa912dabd025b91 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Tue, 21 Jan 2025 20:21:10 +0800 Subject: [PATCH 4/4] feat: add rsdoctor native plugin --- packages/rspack/src/Compiler.ts | 13 ++++-- .../src/builtin-plugin/RsdoctorPlugin.ts | 45 ++++++++++--------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/packages/rspack/src/Compiler.ts b/packages/rspack/src/Compiler.ts index 12812ad09c61..7416c206c02d 100644 --- a/packages/rspack/src/Compiler.ts +++ b/packages/rspack/src/Compiler.ts @@ -19,7 +19,10 @@ import { __from_binding_runtime_globals, __to_binding_runtime_globals } from "./RuntimeGlobals"; -import { createRsdoctorPluginHooksRegisters, JsLoaderRspackPlugin } from "./builtin-plugin"; +import { + JsLoaderRspackPlugin, + createRsdoctorPluginHooksRegisters +} from "./builtin-plugin"; import type { Chunk } from "./Chunk"; import { Compilation } from "./Compilation"; @@ -765,7 +768,7 @@ class Compiler { this.#compilation = undefined; // ensure thisCompilation must call this.hooks.thisCompilation.intercept({ - call: () => { } + call: () => {} }); } @@ -850,7 +853,11 @@ class Compiler { createMapTap ), ...createHtmlPluginHooksRegisters(getCompiler, createTap, createMapTap), - ...createRsdoctorPluginHooksRegisters(getCompiler, createTap, createMapTap) + ...createRsdoctorPluginHooksRegisters( + getCompiler, + createTap, + createMapTap + ) }; } diff --git a/packages/rspack/src/builtin-plugin/RsdoctorPlugin.ts b/packages/rspack/src/builtin-plugin/RsdoctorPlugin.ts index fe76c7d6f15e..5d4d70dfad95 100644 --- a/packages/rspack/src/builtin-plugin/RsdoctorPlugin.ts +++ b/packages/rspack/src/builtin-plugin/RsdoctorPlugin.ts @@ -1,6 +1,5 @@ import { BuiltinPluginName, - RegisterJsTapKind, type JsRsdoctorAsset, type JsRsdoctorAssetPatch, type JsRsdoctorChunk, @@ -22,15 +21,16 @@ import { type JsRsdoctorSourceRange, type JsRsdoctorStatement, type JsRsdoctorVariable, - type RawRsdoctorPluginOptions + type RawRsdoctorPluginOptions, + RegisterJsTapKind } from "@rspack/binding"; import * as liteTapable from "@rspack/lite-tapable"; import { z } from "zod"; import { Compilation } from "../Compilation"; import type { Compiler } from "../Compiler"; +import type { CreatePartialRegisters } from "../taps/types"; import { validate } from "../util/validate"; import { create } from "./base"; -import { CreatePartialRegisters } from "../taps/types"; export declare namespace RsdoctorPluginData { export type { @@ -156,17 +156,16 @@ RsdoctorPlugin.getHooks = RsdoctorPlugin.getCompilationHooks = ( return hooks; }; -export const createRsdoctorPluginHooksRegisters: CreatePartialRegisters<`RsdoctorPlugin`> = ( - getCompiler, - createTap, - createMapTap -) => { +export const createRsdoctorPluginHooksRegisters: CreatePartialRegisters< + `RsdoctorPlugin` +> = (getCompiler, createTap, createMapTap) => { return { registerRsdoctorPluginModuleGraphTaps: createTap( RegisterJsTapKind.RsdoctorPluginModuleGraph, function () { - return RsdoctorPlugin.getCompilationHooks(getCompiler().__internal__get_compilation()!) - .moduleGraph; + return RsdoctorPlugin.getCompilationHooks( + getCompiler().__internal__get_compilation()! + ).moduleGraph; }, function (queried) { return async function (data: JsRsdoctorModuleGraph) { @@ -177,8 +176,9 @@ export const createRsdoctorPluginHooksRegisters: CreatePartialRegisters<`Rsdocto registerRsdoctorPluginChunkGraphTaps: createTap( RegisterJsTapKind.RsdoctorPluginChunkGraph, function () { - return RsdoctorPlugin.getCompilationHooks(getCompiler().__internal__get_compilation()!) - .chunkGraph; + return RsdoctorPlugin.getCompilationHooks( + getCompiler().__internal__get_compilation()! + ).chunkGraph; }, function (queried) { return async function (data: JsRsdoctorChunkGraph) { @@ -189,8 +189,9 @@ export const createRsdoctorPluginHooksRegisters: CreatePartialRegisters<`Rsdocto registerRsdoctorPluginModuleIdsTaps: createTap( RegisterJsTapKind.RsdoctorPluginModuleIds, function () { - return RsdoctorPlugin.getCompilationHooks(getCompiler().__internal__get_compilation()!) - .moduleIds; + return RsdoctorPlugin.getCompilationHooks( + getCompiler().__internal__get_compilation()! + ).moduleIds; }, function (queried) { return async function (data: JsRsdoctorModuleIdsPatch) { @@ -201,8 +202,9 @@ export const createRsdoctorPluginHooksRegisters: CreatePartialRegisters<`Rsdocto registerRsdoctorPluginModuleSourcesTaps: createTap( RegisterJsTapKind.RsdoctorPluginModuleSources, function () { - return RsdoctorPlugin.getCompilationHooks(getCompiler().__internal__get_compilation()!) - .moduleSources; + return RsdoctorPlugin.getCompilationHooks( + getCompiler().__internal__get_compilation()! + ).moduleSources; }, function (queried) { return async function (data: JsRsdoctorModuleSourcesPatch) { @@ -213,8 +215,9 @@ export const createRsdoctorPluginHooksRegisters: CreatePartialRegisters<`Rsdocto registerRsdoctorPluginAssetsTaps: createTap( RegisterJsTapKind.RsdoctorPluginAssets, function () { - return RsdoctorPlugin.getCompilationHooks(getCompiler().__internal__get_compilation()!) - .assets; + return RsdoctorPlugin.getCompilationHooks( + getCompiler().__internal__get_compilation()! + ).assets; }, function (queried) { return async function (data: JsRsdoctorAssetPatch) { @@ -222,7 +225,7 @@ export const createRsdoctorPluginHooksRegisters: CreatePartialRegisters<`Rsdocto }; } ) - } -} + }; +}; -export { RsdoctorPlugin }; \ No newline at end of file +export { RsdoctorPlugin };