From 2414014fe5baa5fb3f2736aa5426f05b3c2ab203 Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Fri, 24 Oct 2025 11:10:19 +0800 Subject: [PATCH 01/13] feat: treeshake share --- Cargo.lock | 1 + crates/node_binding/napi-binding.d.ts | 29 + crates/node_binding/rspack.wasi-browser.js | 61 +- crates/node_binding/rspack.wasi.cjs | 61 +- .../src/plugins/interceptor.rs | 3 +- .../src/raw_options/raw_builtins/mod.rs | 36 +- .../src/raw_options/raw_builtins/raw_mf.rs | 99 ++- crates/rspack_core/src/compilation/mod.rs | 23 +- .../src/dependency/dependency_type.rs | 6 + crates/rspack_core/src/dependency/mod.rs | 10 +- crates/rspack_core/src/lib.rs | 6 + crates/rspack_plugin_javascript/Cargo.toml | 3 +- .../esm/esm_import_specifier_dependency.rs | 79 ++- .../plugin/flag_dependency_usage_plugin.rs | 69 ++- crates/rspack_plugin_mf/src/lib.rs | 7 + crates/rspack_plugin_mf/src/manifest/data.rs | 22 +- crates/rspack_plugin_mf/src/manifest/mod.rs | 5 +- crates/rspack_plugin_mf/src/manifest/utils.rs | 1 + .../src/sharing/collect_share_entry_plugin.rs | 456 ++++++++++++++ .../src/sharing/consume_shared_plugin.rs | 30 +- crates/rspack_plugin_mf/src/sharing/mod.rs | 9 + ...ze_dependency_referenced_exports_plugin.rs | 578 ++++++++++++++++++ ...dency_referenced_exports_runtime_module.rs | 61 ++ .../src/sharing/provide_shared_module.rs | 4 + .../src/sharing/share_container_dependency.rs | 71 +++ .../share_container_entry_dependency.rs | 78 +++ .../sharing/share_container_entry_module.rs | 263 ++++++++ .../share_container_entry_module_factory.rs | 26 + .../src/sharing/share_container_plugin.rs | 107 ++++ .../sharing/share_container_runtime_module.rs | 40 ++ packages/rspack/etc/core.api.md | 143 ++++- .../ModuleFederationManifestPlugin.ts | 150 ++++- .../src/container/ModuleFederationPlugin.ts | 213 +++---- packages/rspack/src/exports.ts | 11 + .../runtime/moduleFederationDefaultRuntime.js | 24 +- .../src/sharing/CollectShareEntryPlugin.ts | 87 +++ .../rspack/src/sharing/ConsumeSharedPlugin.ts | 97 +-- .../src/sharing/IndependentSharePlugin.ts | 456 ++++++++++++++ ...timizeDependencyReferencedExportsPlugin.ts | 65 ++ .../rspack/src/sharing/ProvideSharedPlugin.ts | 68 ++- .../src/sharing/ShareContainerPlugin.ts | 115 ++++ packages/rspack/src/sharing/SharePlugin.ts | 120 ++-- .../src/sharing/TreeShakeSharePlugin.ts | 63 ++ packages/rspack/src/sharing/utils.ts | 13 + packages/rspack/src/taps/types.ts | 1 + .../collect-share-entry-plugin/index.js | 17 + .../collect-share-entry-plugin/module.js | 3 + .../node_modules/xreact/index.js | 4 + .../node_modules/xreact/package.json | 5 + .../collect-share-entry-plugin/package.json | 5 + .../rspack.config.js | 36 ++ .../sharing/reshake-share/index.js | 61 ++ .../node_modules/ui-lib-dep/index.js | 9 + .../node_modules/ui-lib-dep/package.json | 6 + .../node_modules/ui-lib/index.js | 16 + .../node_modules/ui-lib/package.json | 6 + .../sharing/reshake-share/rspack.config.js | 55 ++ .../share-container-plugin-test/index.js | 26 + .../node_modules/ui-lib-dep/index.js | 7 + .../node_modules/ui-lib-dep/package.json | 6 + .../node_modules/ui-lib/index.js | 16 + .../node_modules/ui-lib/package.json | 6 + .../rspack.config.js | 34 ++ .../sharing/treeshake-share/index.js | 73 +++ .../node_modules/ui-lib-side-effect/index.js | 12 + .../ui-lib-side-effect/package.json | 6 + .../node_modules/ui-lib/index.js | 9 + .../node_modules/ui-lib/package.json | 6 + .../node_modules/ui-lib2/index.js | 9 + .../node_modules/ui-lib2/package.json | 6 + .../sharing/treeshake-share/rspack.config.js | 50 ++ 71 files changed, 3868 insertions(+), 491 deletions(-) create mode 100644 crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_plugin.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_runtime_module.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_dependency.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_entry_dependency.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_entry_module_factory.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_plugin.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_runtime_module.rs create mode 100644 packages/rspack/src/sharing/CollectShareEntryPlugin.ts create mode 100644 packages/rspack/src/sharing/IndependentSharePlugin.ts create mode 100644 packages/rspack/src/sharing/OptimizeDependencyReferencedExportsPlugin.ts create mode 100644 packages/rspack/src/sharing/ShareContainerPlugin.ts create mode 100644 packages/rspack/src/sharing/TreeShakeSharePlugin.ts create mode 100644 tests/rspack-test/configCases/sharing/collect-share-entry-plugin/index.js create mode 100644 tests/rspack-test/configCases/sharing/collect-share-entry-plugin/module.js create mode 100644 tests/rspack-test/configCases/sharing/collect-share-entry-plugin/node_modules/xreact/index.js create mode 100644 tests/rspack-test/configCases/sharing/collect-share-entry-plugin/node_modules/xreact/package.json create mode 100644 tests/rspack-test/configCases/sharing/collect-share-entry-plugin/package.json create mode 100644 tests/rspack-test/configCases/sharing/collect-share-entry-plugin/rspack.config.js create mode 100644 tests/rspack-test/configCases/sharing/reshake-share/index.js create mode 100644 tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib-dep/index.js create mode 100644 tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib-dep/package.json create mode 100644 tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib/index.js create mode 100644 tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib/package.json create mode 100644 tests/rspack-test/configCases/sharing/reshake-share/rspack.config.js create mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/index.js create mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/index.js create mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/package.json create mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/index.js create mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/package.json create mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/rspack.config.js create mode 100644 tests/rspack-test/configCases/sharing/treeshake-share/index.js create mode 100644 tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib-side-effect/index.js create mode 100644 tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib-side-effect/package.json create mode 100644 tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib/index.js create mode 100644 tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib/package.json create mode 100644 tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib2/index.js create mode 100644 tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib2/package.json create mode 100644 tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js diff --git a/Cargo.lock b/Cargo.lock index 3277b35240c9..2685476f2a7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4452,6 +4452,7 @@ dependencies = [ "cow-utils", "either", "fast-glob", + "futures", "indexmap", "indoc", "itertools 0.14.0", diff --git a/crates/node_binding/napi-binding.d.ts b/crates/node_binding/napi-binding.d.ts index 6c2c106b8d82..e3607974ba48 100644 --- a/crates/node_binding/napi-binding.d.ts +++ b/crates/node_binding/napi-binding.d.ts @@ -540,10 +540,13 @@ export declare enum BuiltinPluginName { SplitChunksPlugin = 'SplitChunksPlugin', RemoveDuplicateModulesPlugin = 'RemoveDuplicateModulesPlugin', ShareRuntimePlugin = 'ShareRuntimePlugin', + OptimizeDependencyReferencedExportsPlugin = 'OptimizeDependencyReferencedExportsPlugin', ContainerPlugin = 'ContainerPlugin', ContainerReferencePlugin = 'ContainerReferencePlugin', ProvideSharedPlugin = 'ProvideSharedPlugin', ConsumeSharedPlugin = 'ConsumeSharedPlugin', + CollectShareEntryPlugin = 'CollectShareEntryPlugin', + ShareContainerPlugin = 'ShareContainerPlugin', ModuleFederationRuntimePlugin = 'ModuleFederationRuntimePlugin', ModuleFederationManifestPlugin = 'ModuleFederationManifestPlugin', NamedModuleIdsPlugin = 'NamedModuleIdsPlugin', @@ -1837,6 +1840,11 @@ export interface RawCircularDependencyRspackPluginOptions { onEnd?: () => void } +export interface RawCollectShareEntryPluginOptions { + consumes: Array + filename?: string +} + export interface RawConsumeOptions { key: string import?: string @@ -2584,6 +2592,19 @@ export interface RawOptimizationOptions { avoidEntryIife: boolean } +export interface RawOptimizeDependencyReferencedExportsPluginOptions { + shared: Array + injectUsedExports?: boolean + manifestFileName?: string + statsFileName?: string +} + +export interface RawOptimizeSharedConfig { + shareKey: string + treeshake: boolean + usedExports?: Array +} + export interface RawOptions { name?: string mode?: undefined | 'production' | 'development' | 'none' @@ -2820,6 +2841,14 @@ export interface RawRuntimeChunkOptions { name: string | ((entrypoint: { name: string }) => string) } +export interface RawShareContainerPluginOptions { + name: string + request: string + version: string + fileName?: string + library: JsLibraryOptions +} + export interface RawSizeLimitsPluginOptions { assetFilter?: (assetFilename: string) => boolean hints?: "error" | "warning" diff --git a/crates/node_binding/rspack.wasi-browser.js b/crates/node_binding/rspack.wasi-browser.js index e3e5c0a99d48..ee65959b37bc 100644 --- a/crates/node_binding/rspack.wasi-browser.js +++ b/crates/node_binding/rspack.wasi-browser.js @@ -63,63 +63,4 @@ const { }, }) export default __napiModule.exports -export const Assets = __napiModule.exports.Assets -export const AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock -export const Chunk = __napiModule.exports.Chunk -export const ChunkGraph = __napiModule.exports.ChunkGraph -export const ChunkGroup = __napiModule.exports.ChunkGroup -export const Chunks = __napiModule.exports.Chunks -export const CodeGenerationResult = __napiModule.exports.CodeGenerationResult -export const CodeGenerationResults = __napiModule.exports.CodeGenerationResults -export const ConcatenatedModule = __napiModule.exports.ConcatenatedModule -export const ContextModule = __napiModule.exports.ContextModule -export const Dependency = __napiModule.exports.Dependency -export const Diagnostics = __napiModule.exports.Diagnostics -export const EntryDataDto = __napiModule.exports.EntryDataDto -export const EntryDataDTO = __napiModule.exports.EntryDataDTO -export const EntryDependency = __napiModule.exports.EntryDependency -export const EntryOptionsDto = __napiModule.exports.EntryOptionsDto -export const EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO -export const ExternalModule = __napiModule.exports.ExternalModule -export const JsCompilation = __napiModule.exports.JsCompilation -export const JsCompiler = __napiModule.exports.JsCompiler -export const JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData -export const JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData -export const JsDependencies = __napiModule.exports.JsDependencies -export const JsEntries = __napiModule.exports.JsEntries -export const JsExportsInfo = __napiModule.exports.JsExportsInfo -export const JsModuleGraph = __napiModule.exports.JsModuleGraph -export const JsResolver = __napiModule.exports.JsResolver -export const JsResolverFactory = __napiModule.exports.JsResolverFactory -export const JsStats = __napiModule.exports.JsStats -export const KnownBuildInfo = __napiModule.exports.KnownBuildInfo -export const Module = __napiModule.exports.Module -export const ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection -export const NativeWatcher = __napiModule.exports.NativeWatcher -export const NativeWatchResult = __napiModule.exports.NativeWatchResult -export const NormalModule = __napiModule.exports.NormalModule -export const RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx -export const ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData -export const ResolverFactory = __napiModule.exports.ResolverFactory -export const Sources = __napiModule.exports.Sources -export const VirtualFileStore = __napiModule.exports.VirtualFileStore -export const JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore -export const async = __napiModule.exports.async -export const BuiltinPluginName = __napiModule.exports.BuiltinPluginName -export const cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace -export const EnforceExtension = __napiModule.exports.EnforceExtension -export const EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION -export const formatDiagnostic = __napiModule.exports.formatDiagnostic -export const JsLoaderState = __napiModule.exports.JsLoaderState -export const JsRspackSeverity = __napiModule.exports.JsRspackSeverity -export const loadBrowserslist = __napiModule.exports.loadBrowserslist -export const minify = __napiModule.exports.minify -export const minifySync = __napiModule.exports.minifySync -export const RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports -export const RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType -export const registerGlobalTrace = __napiModule.exports.registerGlobalTrace -export const RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind -export const sync = __napiModule.exports.sync -export const syncTraceEvent = __napiModule.exports.syncTraceEvent -export const transform = __napiModule.exports.transform -export const transformSync = __napiModule.exports.transformSync + diff --git a/crates/node_binding/rspack.wasi.cjs b/crates/node_binding/rspack.wasi.cjs index a251ce4d0d7d..1ad96db4aac4 100644 --- a/crates/node_binding/rspack.wasi.cjs +++ b/crates/node_binding/rspack.wasi.cjs @@ -108,63 +108,4 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule }, }) module.exports = __napiModule.exports -module.exports.Assets = __napiModule.exports.Assets -module.exports.AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock -module.exports.Chunk = __napiModule.exports.Chunk -module.exports.ChunkGraph = __napiModule.exports.ChunkGraph -module.exports.ChunkGroup = __napiModule.exports.ChunkGroup -module.exports.Chunks = __napiModule.exports.Chunks -module.exports.CodeGenerationResult = __napiModule.exports.CodeGenerationResult -module.exports.CodeGenerationResults = __napiModule.exports.CodeGenerationResults -module.exports.ConcatenatedModule = __napiModule.exports.ConcatenatedModule -module.exports.ContextModule = __napiModule.exports.ContextModule -module.exports.Dependency = __napiModule.exports.Dependency -module.exports.Diagnostics = __napiModule.exports.Diagnostics -module.exports.EntryDataDto = __napiModule.exports.EntryDataDto -module.exports.EntryDataDTO = __napiModule.exports.EntryDataDTO -module.exports.EntryDependency = __napiModule.exports.EntryDependency -module.exports.EntryOptionsDto = __napiModule.exports.EntryOptionsDto -module.exports.EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO -module.exports.ExternalModule = __napiModule.exports.ExternalModule -module.exports.JsCompilation = __napiModule.exports.JsCompilation -module.exports.JsCompiler = __napiModule.exports.JsCompiler -module.exports.JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData -module.exports.JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData -module.exports.JsDependencies = __napiModule.exports.JsDependencies -module.exports.JsEntries = __napiModule.exports.JsEntries -module.exports.JsExportsInfo = __napiModule.exports.JsExportsInfo -module.exports.JsModuleGraph = __napiModule.exports.JsModuleGraph -module.exports.JsResolver = __napiModule.exports.JsResolver -module.exports.JsResolverFactory = __napiModule.exports.JsResolverFactory -module.exports.JsStats = __napiModule.exports.JsStats -module.exports.KnownBuildInfo = __napiModule.exports.KnownBuildInfo -module.exports.Module = __napiModule.exports.Module -module.exports.ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection -module.exports.NativeWatcher = __napiModule.exports.NativeWatcher -module.exports.NativeWatchResult = __napiModule.exports.NativeWatchResult -module.exports.NormalModule = __napiModule.exports.NormalModule -module.exports.RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx -module.exports.ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData -module.exports.ResolverFactory = __napiModule.exports.ResolverFactory -module.exports.Sources = __napiModule.exports.Sources -module.exports.VirtualFileStore = __napiModule.exports.VirtualFileStore -module.exports.JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore -module.exports.async = __napiModule.exports.async -module.exports.BuiltinPluginName = __napiModule.exports.BuiltinPluginName -module.exports.cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace -module.exports.EnforceExtension = __napiModule.exports.EnforceExtension -module.exports.EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION -module.exports.formatDiagnostic = __napiModule.exports.formatDiagnostic -module.exports.JsLoaderState = __napiModule.exports.JsLoaderState -module.exports.JsRspackSeverity = __napiModule.exports.JsRspackSeverity -module.exports.loadBrowserslist = __napiModule.exports.loadBrowserslist -module.exports.minify = __napiModule.exports.minify -module.exports.minifySync = __napiModule.exports.minifySync -module.exports.RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports -module.exports.RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType -module.exports.registerGlobalTrace = __napiModule.exports.registerGlobalTrace -module.exports.RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind -module.exports.sync = __napiModule.exports.sync -module.exports.syncTraceEvent = __napiModule.exports.syncTraceEvent -module.exports.transform = __napiModule.exports.transform -module.exports.transformSync = __napiModule.exports.transformSync + diff --git a/crates/rspack_binding_api/src/plugins/interceptor.rs b/crates/rspack_binding_api/src/plugins/interceptor.rs index bd3854839883..5a8c8b10b166 100644 --- a/crates/rspack_binding_api/src/plugins/interceptor.rs +++ b/crates/rspack_binding_api/src/plugins/interceptor.rs @@ -78,6 +78,7 @@ use crate::{ JsContextModuleFactoryAfterResolveDataWrapper, JsContextModuleFactoryAfterResolveResult, JsContextModuleFactoryBeforeResolveDataWrapper, JsContextModuleFactoryBeforeResolveResult, }, + dependency::DependencyWrapper, html::{ JsAfterEmitData, JsAfterTemplateExecutionData, JsAlterAssetTagGroupsData, JsAlterAssetTagsData, JsBeforeAssetTagGenerationData, JsBeforeEmitData, @@ -94,7 +95,7 @@ use crate::{ runtime::{ JsAdditionalTreeRuntimeRequirementsArg, JsAdditionalTreeRuntimeRequirementsResult, JsCreateLinkData, JsCreateScriptData, JsLinkPrefetchData, JsLinkPreloadData, JsRuntimeGlobals, - JsRuntimeRequirementInTreeArg, JsRuntimeRequirementInTreeResult, + JsRuntimeRequirementInTreeArg, JsRuntimeRequirementInTreeResult, JsRuntimeSpec, }, source::JsSourceToJs, }; diff --git a/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs b/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs index 8733f3a79aa0..dc7ede5f51b7 100644 --- a/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs +++ b/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs @@ -32,7 +32,11 @@ use napi_derive::napi; use raw_dll::{RawDllReferenceAgencyPluginOptions, RawFlagAllModulesAsUsedPluginOptions}; use raw_ids::RawOccurrenceChunkIdsPluginOptions; use raw_lightning_css_minimizer::RawLightningCssMinimizerRspackPluginOptions; -use raw_mf::{RawModuleFederationManifestPluginOptions, RawModuleFederationRuntimePluginOptions}; +use raw_mf::{ + RawCollectShareEntryPluginOptions, RawModuleFederationManifestPluginOptions, + RawModuleFederationRuntimePluginOptions, RawOptimizeDependencyReferencedExportsPluginOptions, + RawProvideOptions, +}; use raw_sri::RawSubresourceIntegrityPluginOptions; use rspack_core::{BoxPlugin, Plugin, PluginExt}; use rspack_error::{Result, ToStringResultToRspackResultExt}; @@ -75,8 +79,10 @@ use rspack_plugin_lightning_css_minimizer::LightningCssMinimizerRspackPlugin; use rspack_plugin_limit_chunk_count::LimitChunkCountPlugin; use rspack_plugin_merge_duplicate_chunks::MergeDuplicateChunksPlugin; use rspack_plugin_mf::{ - ConsumeSharedPlugin, ContainerPlugin, ContainerReferencePlugin, ModuleFederationManifestPlugin, - ModuleFederationRuntimePlugin, ProvideSharedPlugin, ShareRuntimePlugin, + CollectShareEntryPlugin, ConsumeSharedPlugin, ContainerPlugin, ContainerReferencePlugin, + ModuleFederationManifestPlugin, ModuleFederationRuntimePlugin, + OptimizeDependencyReferencedExportsPlugin, ProvideSharedPlugin, ShareContainerPlugin, + ShareRuntimePlugin, }; use rspack_plugin_module_info_header::ModuleInfoHeaderPlugin; use rspack_plugin_module_replacement::{ContextReplacementPlugin, NormalModuleReplacementPlugin}; @@ -117,7 +123,7 @@ use self::{ raw_limit_chunk_count::RawLimitChunkCountPluginOptions, raw_mf::{ RawConsumeSharedPluginOptions, RawContainerPluginOptions, RawContainerReferencePluginOptions, - RawProvideOptions, + RawShareContainerPluginOptions, }, raw_normal_replacement::RawNormalModuleReplacementPluginOptions, raw_runtime_chunk::RawRuntimeChunkOptions, @@ -167,10 +173,13 @@ pub enum BuiltinPluginName { SplitChunksPlugin, RemoveDuplicateModulesPlugin, ShareRuntimePlugin, + OptimizeDependencyReferencedExportsPlugin, ContainerPlugin, ContainerReferencePlugin, ProvideSharedPlugin, ConsumeSharedPlugin, + CollectShareEntryPlugin, + ShareContainerPlugin, ModuleFederationRuntimePlugin, ModuleFederationManifestPlugin, NamedModuleIdsPlugin, @@ -463,6 +472,13 @@ impl<'a> BuiltinPlugin<'a> { ) .boxed(), ), + BuiltinPluginName::OptimizeDependencyReferencedExportsPlugin => { + let options = + downcast_into::(self.options) + .map_err(|report| napi::Error::from_reason(report.to_string()))? + .into(); + plugins.push(OptimizeDependencyReferencedExportsPlugin::new(options).boxed()); + } BuiltinPluginName::ContainerPlugin => { plugins.push( ContainerPlugin::new( @@ -492,6 +508,18 @@ impl<'a> BuiltinPlugin<'a> { provides.sort_unstable_by_key(|(k, _)| k.to_string()); plugins.push(ProvideSharedPlugin::new(provides).boxed()) } + BuiltinPluginName::CollectShareEntryPlugin => { + let options = downcast_into::(self.options) + .map_err(|report| napi::Error::from_reason(report.to_string()))? + .into(); + plugins.push(CollectShareEntryPlugin::new(options).boxed()) + } + BuiltinPluginName::ShareContainerPlugin => { + let options = downcast_into::(self.options) + .map_err(|report| napi::Error::from_reason(report.to_string()))? + .into(); + plugins.push(ShareContainerPlugin::new(options).boxed()) + } BuiltinPluginName::ConsumeSharedPlugin => plugins.push( ConsumeSharedPlugin::new( downcast_into::(self.options) diff --git a/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs b/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs index 03adc9c05724..885293bd4687 100644 --- a/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs +++ b/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs @@ -3,10 +3,12 @@ use std::{collections::HashMap, sync::Arc}; use napi::Either; use napi_derive::napi; use rspack_plugin_mf::{ - ConsumeOptions, ConsumeSharedPluginOptions, ConsumeVersion, ContainerPluginOptions, - ContainerReferencePluginOptions, ExposeOptions, ManifestExposeOption, ManifestSharedOption, - ModuleFederationManifestPluginOptions, ModuleFederationRuntimePluginOptions, ProvideOptions, - ProvideVersion, RemoteAliasTarget, RemoteOptions, StatsBuildInfo, + CollectShareEntryPluginOptions, ConsumeOptions, ConsumeSharedPluginOptions, ConsumeVersion, + ContainerPluginOptions, ContainerReferencePluginOptions, ExposeOptions, ManifestExposeOption, + ManifestSharedOption, ModuleFederationManifestPluginOptions, + ModuleFederationRuntimePluginOptions, OptimizeDependencyReferencedExportsPluginOptions, + OptimizeSharedConfig, ProvideOptions, ProvideVersion, RemoteAliasTarget, RemoteOptions, + ShareContainerEntryOptions, ShareContainerPluginOptions, StatsBuildInfo, }; use crate::options::{ @@ -133,6 +135,51 @@ impl From for (String, ProvideOptions) { } } +#[derive(Debug)] +#[napi(object)] +pub struct RawCollectShareEntryPluginOptions { + pub consumes: Vec, + pub filename: Option, +} + +impl From for CollectShareEntryPluginOptions { + fn from(value: RawCollectShareEntryPluginOptions) -> Self { + Self { + consumes: value + .consumes + .into_iter() + .map(|provide| { + let (key, consume_options): (String, ConsumeOptions) = provide.into(); + (key, std::sync::Arc::new(consume_options)) + }) + .collect(), + filename: value.filename, + } + } +} + +#[derive(Debug)] +#[napi(object)] +pub struct RawShareContainerPluginOptions { + pub name: String, + pub request: String, + pub version: String, + pub file_name: Option, + pub library: JsLibraryOptions, +} + +impl From for ShareContainerPluginOptions { + fn from(value: RawShareContainerPluginOptions) -> Self { + ShareContainerPluginOptions { + name: value.name, + request: value.request, + version: value.version, + library: value.library.into(), + file_name: value.file_name.clone().map(Into::into), + } + } +} + #[derive(Debug)] #[napi(object)] pub struct RawConsumeSharedPluginOptions { @@ -154,6 +201,50 @@ impl From for ConsumeSharedPluginOptions { } } +#[derive(Debug)] +#[napi(object)] +pub struct RawOptimizeSharedConfig { + pub share_key: String, + pub treeshake: bool, + pub used_exports: Option>, +} + +impl From for OptimizeSharedConfig { + fn from(value: RawOptimizeSharedConfig) -> Self { + Self { + share_key: value.share_key, + treeshake: value.treeshake, + used_exports: value.used_exports.unwrap_or_default(), + } + } +} + +#[derive(Debug)] +#[napi(object)] +pub struct RawOptimizeDependencyReferencedExportsPluginOptions { + pub shared: Vec, + pub inject_used_exports: Option, + pub manifest_file_name: Option, + pub stats_file_name: Option, +} + +impl From + for OptimizeDependencyReferencedExportsPluginOptions +{ + fn from(value: RawOptimizeDependencyReferencedExportsPluginOptions) -> Self { + Self { + shared: value + .shared + .into_iter() + .map(|config| config.into()) + .collect(), + inject_used_exports: value.inject_used_exports.unwrap_or(true), + manifest_file_name: value.manifest_file_name, + stats_file_name: value.stats_file_name, + } + } +} + #[derive(Debug)] #[napi(object)] pub struct RawConsumeOptions { diff --git a/crates/rspack_core/src/compilation/mod.rs b/crates/rspack_core/src/compilation/mod.rs index b39db6bc086a..776f761c09bb 100644 --- a/crates/rspack_core/src/compilation/mod.rs +++ b/crates/rspack_core/src/compilation/mod.rs @@ -46,14 +46,14 @@ use crate::{ ChunkIdsArtifact, ChunkKind, ChunkRenderArtifact, ChunkRenderCacheArtifact, ChunkRenderResult, ChunkUkey, CodeGenerationJob, CodeGenerationResult, CodeGenerationResults, CompilationLogger, CompilationLogging, CompilerOptions, ConcatenationScope, DependenciesDiagnosticsArtifact, - DependencyCodeGeneration, DependencyTemplate, DependencyTemplateType, DependencyType, Entry, - EntryData, EntryOptions, EntryRuntime, Entrypoint, ExecuteModuleId, Filename, ImportPhase, - ImportVarMap, ImportedByDeferModulesArtifact, Logger, MemoryGCStorage, ModuleFactory, - ModuleGraph, ModuleGraphCacheArtifact, ModuleGraphMut, ModuleGraphPartial, ModuleGraphRef, - ModuleIdentifier, ModuleIdsArtifact, ModuleStaticCacheArtifact, PathData, ResolverFactory, - RuntimeGlobals, RuntimeKeyMap, RuntimeMode, RuntimeModule, RuntimeSpec, RuntimeSpecMap, - RuntimeTemplate, SharedPluginDriver, SideEffectsOptimizeArtifact, SourceType, Stats, - ValueCacheVersions, + DependencyCodeGeneration, DependencyId, DependencyTemplate, DependencyTemplateType, + DependencyType, Entry, EntryData, EntryOptions, EntryRuntime, Entrypoint, ExecuteModuleId, + ExtendedReferencedExport, Filename, ImportPhase, ImportVarMap, ImportedByDeferModulesArtifact, + Logger, MemoryGCStorage, ModuleFactory, ModuleGraph, ModuleGraphCacheArtifact, ModuleGraphMut, + ModuleGraphPartial, ModuleGraphRef, ModuleIdentifier, ModuleIdsArtifact, + ModuleStaticCacheArtifact, PathData, ResolverFactory, RuntimeGlobals, RuntimeKeyMap, RuntimeMode, + RuntimeModule, RuntimeSpec, RuntimeSpecMap, RuntimeTemplate, SharedPluginDriver, + SideEffectsOptimizeArtifact, SourceType, Stats, ValueCacheVersions, build_chunk_graph::artifact::BuildChunkGraphArtifact, compilation::build_module_graph::{ BuildModuleGraphArtifact, ModuleExecutor, UpdateParam, build_module_graph, @@ -76,6 +76,12 @@ define_hook!(CompilationExecuteModule: Series(module: &ModuleIdentifier, runtime_modules: &IdentifierSet, code_generation_results: &BindingCell, execute_module_id: &ExecuteModuleId)); define_hook!(CompilationFinishModules: Series(compilation: &mut Compilation)); define_hook!(CompilationSeal: Series(compilation: &mut Compilation)); +define_hook!(CompilationDependencyReferencedExports: Series( + compilation: &Compilation, + dependency: &DependencyId, + referenced_exports: &Option> , + runtime: Option<&RuntimeSpec> +)); define_hook!(CompilationConcatenationScope: SeriesBail(compilation: &Compilation, curr_module: ModuleIdentifier) -> ConcatenationScope); define_hook!(CompilationOptimizeDependencies: SeriesBail(compilation: &mut Compilation) -> bool); define_hook!(CompilationOptimizeModules: SeriesBail(compilation: &mut Compilation) -> bool); @@ -113,6 +119,7 @@ pub struct CompilationHooks { pub succeed_module: CompilationSucceedModuleHook, pub execute_module: CompilationExecuteModuleHook, pub finish_modules: CompilationFinishModulesHook, + pub dependency_referenced_exports: CompilationDependencyReferencedExportsHook, pub seal: CompilationSealHook, pub optimize_dependencies: CompilationOptimizeDependenciesHook, pub optimize_modules: CompilationOptimizeModulesHook, diff --git a/crates/rspack_core/src/dependency/dependency_type.rs b/crates/rspack_core/src/dependency/dependency_type.rs index 26d53d4941bf..6aee44ed4220 100644 --- a/crates/rspack_core/src/dependency/dependency_type.rs +++ b/crates/rspack_core/src/dependency/dependency_type.rs @@ -95,6 +95,10 @@ pub enum DependencyType { ContainerExposed, /// container entry, ContainerEntry, + /// share container entry + ShareContainerEntry, + /// share container fallback + ShareContainerFallback, /// remote to external, RemoteToExternal, /// fallback @@ -179,6 +183,8 @@ impl DependencyType { DependencyType::ImportMetaContext => "import.meta context", DependencyType::ContainerExposed => "container exposed", DependencyType::ContainerEntry => "container entry", + DependencyType::ShareContainerEntry => "share container entry", + DependencyType::ShareContainerFallback => "share container fallback", DependencyType::DllEntry => "dll entry", DependencyType::RemoteToExternal => "remote to external", DependencyType::RemoteToFallback => "fallback", diff --git a/crates/rspack_core/src/dependency/mod.rs b/crates/rspack_core/src/dependency/mod.rs index 0b4f649d05eb..d06ae800db13 100644 --- a/crates/rspack_core/src/dependency/mod.rs +++ b/crates/rspack_core/src/dependency/mod.rs @@ -40,10 +40,16 @@ pub use static_exports_dependency::{StaticExportsDependency, StaticExportsSpec}; use swc_core::ecma::atoms::Atom; use crate::{ - ConnectionState, EvaluatedInlinableValue, ModuleGraph, ModuleGraphCacheArtifact, - ModuleGraphConnection, ModuleIdentifier, RuntimeSpec, + ConnectionState, EvaluatedInlinableValue, ExtendedReferencedExport, ModuleGraph, + ModuleGraphCacheArtifact, ModuleGraphConnection, ModuleIdentifier, RuntimeSpec, }; +#[derive(Debug, Clone)] +pub enum ProcessModuleReferencedExports { + Map(FxHashMap), + ExtendRef(Vec), +} + #[derive(Debug, Default)] pub struct ExportSpec { pub name: Atom, diff --git a/crates/rspack_core/src/lib.rs b/crates/rspack_core/src/lib.rs index 7b66d7311427..e222db26422a 100644 --- a/crates/rspack_core/src/lib.rs +++ b/crates/rspack_core/src/lib.rs @@ -123,6 +123,7 @@ pub enum SourceType { Remote, ShareInit, ConsumeShared, + ShareContainerShared, Custom(#[cacheable(with=AsPreset)] Ustr), #[default] Unknown, @@ -142,6 +143,7 @@ impl std::fmt::Display for SourceType { SourceType::Remote => write!(f, "remote"), SourceType::ShareInit => write!(f, "share-init"), SourceType::ConsumeShared => write!(f, "consume-shared"), + SourceType::ShareContainerShared => write!(f, "share-container-shared"), SourceType::Unknown => write!(f, "unknown"), SourceType::CssImport => write!(f, "css-import"), SourceType::Custom(source_type) => f.write_str(source_type), @@ -161,6 +163,7 @@ impl From<&str> for SourceType { "remote" => Self::Remote, "share-init" => Self::ShareInit, "consume-shared" => Self::ConsumeShared, + "share-container-shared" => Self::ShareContainerShared, "unknown" => Self::Unknown, "css-import" => Self::CssImport, other => SourceType::Custom(other.into()), @@ -176,6 +179,7 @@ impl From<&ModuleType> for SourceType { ModuleType::WasmSync | ModuleType::WasmAsync => Self::Wasm, ModuleType::Asset | ModuleType::AssetInline | ModuleType::AssetResource => Self::Asset, ModuleType::ConsumeShared => Self::ConsumeShared, + ModuleType::ShareContainerShared => Self::ShareContainerShared, _ => Self::Unknown, } } @@ -202,6 +206,7 @@ pub enum ModuleType { Fallback, ProvideShared, ConsumeShared, + ShareContainerShared, SelfReference, Custom(#[cacheable(with=AsPreset)] Ustr), } @@ -270,6 +275,7 @@ impl ModuleType { ModuleType::Fallback => "fallback-module", ModuleType::ProvideShared => "provide-module", ModuleType::ConsumeShared => "consume-shared-module", + ModuleType::ShareContainerShared => "share-container-shared-module", ModuleType::SelfReference => "self-reference-module", ModuleType::Custom(custom) => custom.as_str(), diff --git a/crates/rspack_plugin_javascript/Cargo.toml b/crates/rspack_plugin_javascript/Cargo.toml index 0f1197865f19..bd0d65911a7c 100644 --- a/crates/rspack_plugin_javascript/Cargo.toml +++ b/crates/rspack_plugin_javascript/Cargo.toml @@ -13,6 +13,7 @@ bitflags = { workspace = true } cow-utils = { workspace = true } either = { workspace = true } fast-glob = { workspace = true } +futures = { workspace = true } indexmap = { workspace = true } indoc = { workspace = true } itertools = { workspace = true } @@ -61,5 +62,5 @@ winnow = { workspace = true } ignored = ["tracing"] [lints.rust.unexpected_cfgs] -level = "warn" check-cfg = ['cfg(allocative)'] +level = "warn" diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/esm_import_specifier_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/esm_import_specifier_dependency.rs index da1012f92600..88d553f052ae 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/esm_import_specifier_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/esm_import_specifier_dependency.rs @@ -107,6 +107,45 @@ impl ESMImportSpecifierDependency { .unwrap_or_else(|| self.ids.as_slice()) } + pub fn get_esm_import_specifier_referenced_exports( + &self, + module_graph: &ModuleGraph, + exports_type: Option, + ) -> Vec { + let mut ids = self.get_ids(module_graph); + if ids.is_empty() { + return self.get_referenced_exports_in_destructuring(None); + } + + let mut namespace_object_as_context = self.namespace_object_as_context; + if let Some(id) = ids.first() + && id == "default" + { + match exports_type { + Some(ExportsType::DefaultOnly) | Some(ExportsType::DefaultWithNamed) => { + if ids.len() == 1 { + return self.get_referenced_exports_in_destructuring(None); + } + ids = &ids[1..]; + namespace_object_as_context = true; + } + Some(ExportsType::Dynamic) => { + return create_exports_object_referenced(); + } + _ => {} + } + } + + if self.call && !self.direct_import && (namespace_object_as_context || ids.len() > 1) { + if ids.len() == 1 { + return create_exports_object_referenced(); + } + // remove last one + ids = &ids[..ids.len() - 1]; + } + self.get_referenced_exports_in_destructuring(Some(ids)) + } + pub fn get_referenced_exports_in_destructuring( &self, ids: Option<&[Atom]>, @@ -236,44 +275,28 @@ impl Dependency for ESMImportSpecifierDependency { module_graph_cache: &ModuleGraphCacheArtifact, _runtime: Option<&RuntimeSpec>, ) -> Vec { - let mut ids = self.get_ids(module_graph); - // namespace import + let ids = self.get_ids(module_graph); if ids.is_empty() { return self.get_referenced_exports_in_destructuring(None); } - let mut namespace_object_as_context = self.namespace_object_as_context; - if let Some(id) = ids.first() + let exports_type = if let Some(id) = ids.first() && id == "default" { let parent_module = module_graph .get_parent_module(&self.id) .expect("should have parent module"); - let exports_type = - get_exports_type(module_graph, module_graph_cache, &self.id, parent_module); - match exports_type { - ExportsType::DefaultOnly | ExportsType::DefaultWithNamed => { - if ids.len() == 1 { - return self.get_referenced_exports_in_destructuring(None); - } - ids = &ids[1..]; - namespace_object_as_context = true; - } - ExportsType::Dynamic => { - return create_exports_object_referenced(); - } - _ => {} - } - } + Some(get_exports_type( + module_graph, + module_graph_cache, + &self.id, + parent_module, + )) + } else { + None + }; - if self.call && !self.direct_import && (namespace_object_as_context || ids.len() > 1) { - if ids.len() == 1 { - return create_exports_object_referenced(); - } - // remove last one - ids = &ids[..ids.len() - 1]; - } - self.get_referenced_exports_in_destructuring(Some(ids)) + self.get_esm_import_specifier_referenced_exports(module_graph, exports_type) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_javascript/src/plugin/flag_dependency_usage_plugin.rs b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_usage_plugin.rs index 284d555d9a47..894b2ca930f0 100644 --- a/crates/rspack_plugin_javascript/src/plugin/flag_dependency_usage_plugin.rs +++ b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_usage_plugin.rs @@ -45,7 +45,7 @@ impl<'a> FlagDependencyUsagePluginProxy<'a> { } } - fn apply(&mut self) { + async fn apply(&mut self) { let mut module_graph = self.compilation.get_module_graph_mut(); module_graph.active_all_exports_info(); module_graph.reset_all_exports_info_used(); @@ -95,20 +95,19 @@ impl<'a> FlagDependencyUsagePluginProxy<'a> { self.compilation.module_graph_cache_artifact.freeze(); // collect referenced exports from modules by calling `dependency.get_referenced_exports` - // and also added referenced modules to the queue for further processing - let batch_res = batch - .into_par_iter() - .map(|(block_id, runtime, force_side_effects)| { - let (referenced_exports, module_tasks) = - self.process_module(block_id, runtime.as_ref(), force_side_effects, self.global); - ( - runtime, - force_side_effects, - referenced_exports, - module_tasks, - ) - }) - .collect::>(); + // and also added referenced modules to queue for further processing + let mut batch_res = vec![]; + for (block_id, runtime, force_side_effects) in batch { + let (referenced_exports, module_tasks) = self + .process_module(block_id, runtime.as_ref(), force_side_effects, self.global) + .await; + batch_res.push(( + runtime, + force_side_effects, + referenced_exports, + module_tasks, + )); + } let mut nested_tasks = vec![]; let mut non_nested_tasks: IdentifierMap> = IdentifierMap::default(); @@ -235,7 +234,7 @@ impl<'a> FlagDependencyUsagePluginProxy<'a> { } } - fn process_module( + async fn process_module( &self, block_id: ModuleOrAsyncDependenciesBlock, runtime: Option<&RuntimeSpec>, @@ -259,19 +258,39 @@ impl<'a> FlagDependencyUsagePluginProxy<'a> { for (dep_id, module_id) in dependencies.into_iter() { let old_referenced_exports = map.remove(&module_id); - let Some(referenced_exports) = get_dependency_referenced_exports( + + let referenced_exports_result = get_dependency_referenced_exports( dep_id, &self.compilation.get_module_graph(), &self.compilation.module_graph_cache_artifact, runtime, - ) else { - continue; - }; + ); - if let Some(new_referenced_exports) = - merge_referenced_exports(old_referenced_exports, referenced_exports) - { - map.insert(module_id, new_referenced_exports); + // 直接使用 await 调用异步钩子 + self + .compilation + .plugin_driver + .compilation_hooks + .dependency_referenced_exports + .call( + &*self.compilation, + &dep_id, + &referenced_exports_result, + runtime, + ) + .await; + + match referenced_exports_result { + Some(mut referenced_exports) => { + if let Some(new_referenced_exports) = + merge_referenced_exports(old_referenced_exports, referenced_exports) + { + map.insert(module_id, new_referenced_exports); + } + } + None => { + continue; + } } } @@ -490,7 +509,7 @@ async fn optimize_dependencies(&self, compilation: &mut Compilation) -> Result, @@ -16,7 +16,7 @@ pub struct AssetsSplit { pub r#async: Vec, } -#[derive(Debug, Serialize, Clone, Default)] +#[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct StatsBuildInfo { #[serde(rename = "buildVersion")] pub build_version: String, @@ -24,7 +24,7 @@ pub struct StatsBuildInfo { pub build_name: Option, } -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct StatsExpose { pub path: String, pub id: String, @@ -35,7 +35,7 @@ pub struct StatsExpose { pub assets: StatsAssetsGroup, } -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct StatsShared { pub id: String, pub name: String, @@ -48,9 +48,11 @@ pub struct StatsShared { pub assets: StatsAssetsGroup, #[serde(default)] pub usedIn: Vec, + #[serde(default)] + pub usedExports: Vec, } -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct StatsRemote { pub alias: String, pub consumingFederationContainerName: String, @@ -62,7 +64,7 @@ pub struct StatsRemote { pub usedIn: Vec, } -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct BasicStatsMetaData { pub name: String, pub globalName: String, @@ -76,7 +78,7 @@ pub struct BasicStatsMetaData { pub r#type: Option, } -#[derive(Debug, Serialize, Clone, Default)] +#[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct RemoteEntryMeta { #[serde(default)] pub name: String, @@ -86,7 +88,7 @@ pub struct RemoteEntryMeta { pub r#type: String, } -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct StatsRoot { pub id: String, pub name: String, diff --git a/crates/rspack_plugin_mf/src/manifest/mod.rs b/crates/rspack_plugin_mf/src/manifest/mod.rs index 17cb95e13ecd..4b5883f568c4 100644 --- a/crates/rspack_plugin_mf/src/manifest/mod.rs +++ b/crates/rspack_plugin_mf/src/manifest/mod.rs @@ -11,11 +11,11 @@ use asset::{ collect_assets_for_module, collect_assets_from_chunk, collect_usage_files_for_module, empty_assets_group, module_source_path, normalize_assets_group, }; -pub use data::StatsBuildInfo; use data::{ BasicStatsMetaData, ManifestExpose, ManifestRemote, ManifestRoot, ManifestShared, - RemoteEntryMeta, StatsAssetsGroup, StatsExpose, StatsRemote, StatsRoot, StatsShared, + RemoteEntryMeta, StatsAssetsGroup, StatsExpose, StatsRemote, StatsShared, }; +pub use data::{StatsBuildInfo, StatsRoot}; pub use options::{ ManifestExposeOption, ManifestSharedOption, ModuleFederationManifestPluginOptions, RemoteAliasTarget, @@ -169,6 +169,7 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { singleton: shared.singleton, assets: StatsAssetsGroup::default(), usedIn: Vec::new(), + usedExports: Vec::new(), }) .collect::>(); let remote_list = self diff --git a/crates/rspack_plugin_mf/src/manifest/utils.rs b/crates/rspack_plugin_mf/src/manifest/utils.rs index ec2e5d2c219e..ec7ab97c95ba 100644 --- a/crates/rspack_plugin_mf/src/manifest/utils.rs +++ b/crates/rspack_plugin_mf/src/manifest/utils.rs @@ -46,6 +46,7 @@ pub fn ensure_shared_entry<'a>( singleton: None, assets: super::data::StatsAssetsGroup::default(), usedIn: Vec::new(), + usedExports: Vec::new(), }) } diff --git a/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs b/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs new file mode 100644 index 000000000000..954a0a267f94 --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs @@ -0,0 +1,456 @@ +use std::{ + collections::hash_map::Entry, + path::{Path, PathBuf}, + sync::{Arc, LazyLock, OnceLock}, +}; + +use camino::Utf8Path; +use regex::Regex; +use rspack_core::{ + BoxModule, Compilation, CompilationAsset, CompilationProcessAssets, CompilerCompilation, + CompilerThisCompilation, Context, DependencyCategory, DependencyType, ModuleFactoryCreateData, + NormalModuleFactoryFactorize, Plugin, ResolveOptionsWithDependencyType, ResolveResult, Resolver, + rspack_sources::{RawStringSource, SourceExt}, +}; +use rspack_error::{Diagnostic, Result, error}; +use rspack_fs::ReadableFileSystem; +use rspack_hook::{plugin, plugin_hook}; +use rustc_hash::{FxHashMap, FxHashSet}; +use serde::Serialize; +use tokio::sync::RwLock; + +use super::consume_shared_plugin::{ + ABSOLUTE_REQUEST, ConsumeOptions, ConsumeVersion, MatchedConsumes, PACKAGE_NAME, + RELATIVE_REQUEST, get_description_file, get_required_version_from_description_file, + resolve_matched_configs, +}; + +const DEFAULT_FILENAME: &str = "collect-share-entries.json"; + +#[derive(Debug, Clone)] +struct CollectShareEntryMeta { + request: String, + share_key: String, + share_scope: String, + is_prefix: bool, + consume: Arc, +} + +#[derive(Debug, Clone, Default)] +struct CollectShareEntryRecord { + share_scope: String, + requests: FxHashSet, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct CollectedShareRequest { + request: String, + version: String, +} + +// 直接输出共享模块映射,移除 shared 层级 +type CollectShareEntryAsset<'a> = FxHashMap<&'a str, CollectShareEntryAssetItem<'a>>; + +#[derive(Debug, Serialize)] +struct CollectShareEntryAssetItem<'a> { + #[serde(rename = "shareScope")] + share_scope: &'a str, + requests: &'a [[String; 2]], +} + +#[derive(Debug)] +pub struct CollectShareEntryPluginOptions { + pub consumes: Vec<(String, Arc)>, + pub filename: Option, +} + +#[plugin] +#[derive(Debug)] +pub struct CollectShareEntryPlugin { + options: CollectShareEntryPluginOptions, + resolver: OnceLock>, + compiler_context: OnceLock, + matched_consumes: OnceLock>, + resolved_entries: RwLock>, +} + +impl CollectShareEntryPlugin { + pub fn new(options: CollectShareEntryPluginOptions) -> Self { + // let consumes: Vec = options + // .consumes + // .into_iter() + // .map(|(request, consume)| { + // let consume = consume.clone(); + // let share_key = consume.share_key.clone(); + // let share_scope = consume.share_scope.clone(); + // let is_prefix = request.ends_with('/'); + // CollectShareEntryMeta { + // request, + // share_key, + // share_scope, + // is_prefix, + // consume, + // } + // }) + // .collect(); + + Self::new_inner( + options, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ) + } + + /// 根据模块请求路径推断版本信息 + /// 例如:../../../.eden-mono/temp/node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/index.js + /// 会找到 react-dom 的 package.json 并读取 version 字段 + async fn infer_version(&self, request: &str) -> Option { + // 将请求路径转换为 Path + let path = Path::new(request); + + // 查找包含 node_modules 的路径段 + let mut node_modules_found = false; + let mut package_path = None; + + for component in path.components() { + let comp_str = component.as_os_str().to_string_lossy(); + if comp_str == "node_modules" { + node_modules_found = true; + continue; + } + + if node_modules_found { + // 下一个组件应该是包名 + package_path = Some(comp_str.to_string()); + break; + } + } + + if let Some(package_name) = package_path { + // 构建 package.json 的完整路径 + let mut package_json_path = PathBuf::new(); + let mut found_node_modules = false; + + for component in path.components() { + let comp_str = component.as_os_str().to_string_lossy(); + package_json_path.push(comp_str.as_ref()); + + if comp_str == "node_modules" { + found_node_modules = true; + // 添加包名目录 + package_json_path.push(&package_name); + // 添加 package.json + package_json_path.push("package.json"); + break; + } + } + + if found_node_modules && package_json_path.exists() { + // 尝试读取 package.json + if let Ok(content) = std::fs::read_to_string(&package_json_path) { + if let Ok(json) = serde_json::from_str::(&content) { + // 读取 version 字段 + if let Some(version) = json.get("version").and_then(|v| v.as_str()) { + return Some(version.to_string()); + } + } + } + } + } + + None + } + + fn init_context(&self, compilation: &Compilation) { + self + .compiler_context + .set(compilation.options.context.clone()) + .expect("failed to set compiler context"); + } + + fn get_context(&self) -> Context { + self + .compiler_context + .get() + .expect("init_context first") + .clone() + } + + fn init_resolver(&self, compilation: &Compilation) { + self + .resolver + .set( + compilation + .resolver_factory + .get(ResolveOptionsWithDependencyType { + resolve_options: None, + resolve_to_context: false, + dependency_category: DependencyCategory::Esm, + }), + ) + .expect("failed to set resolver for multiple times"); + } + + fn get_resolver(&self) -> Arc { + self.resolver.get().expect("init_resolver first").clone() + } + + async fn init_matched_consumes(&self, compilation: &mut Compilation, resolver: Arc) { + let config = resolve_matched_configs(compilation, resolver, &self.options.consumes).await; + self + .matched_consumes + .set(Arc::new(config)) + .expect("failed to set matched consumes"); + } + + fn get_matched_consumes(&self) -> Arc { + self + .matched_consumes + .get() + .expect("init_matched_consumes first") + .clone() + } + + async fn record_entry( + &self, + context: &Context, + request: &str, + config: Arc, + mut add_diagnostic: impl FnMut(Diagnostic), + ) { + let direct_fallback = matches!(&config.import, Some(i) if RELATIVE_REQUEST.is_match(i) | ABSOLUTE_REQUEST.is_match(i)); + let import_resolved = match &config.import { + None => None, + Some(import) => { + let resolver = self.get_resolver(); + resolver + .resolve( + if direct_fallback { + self.get_context() + } else { + context.clone() + } + .as_ref(), + import, + ) + .await + .map_err(|_e| { + add_diagnostic(Diagnostic::error( + "ModuleNotFoundError".into(), + format!("resolving fallback for shared module {request}"), + )) + }) + .ok() + } + } + .and_then(|i| match i { + ResolveResult::Resource(r) => Some(r.path.as_str().to_string()), + ResolveResult::Ignored => None, + }); + + // 首先尝试从 import_resolved 路径推断版本 + let version = if let Some(ref resolved_path) = import_resolved { + if let Some(inferred) = self.infer_version(resolved_path).await { + Some(ConsumeVersion::Version(inferred)) + } else { + // 如果推断失败,直接返回 None,不记录这个条目 + None + } + } else { + // 如果没有 resolved 路径,也直接返回 None + None + }; + + // 如果无法获取版本信息,直接结束方法 + let version = match version { + Some(v) => v, + None => return, // 直接结束 record_entry 方法 + }; + + let share_key = config.share_key.clone(); + let share_scope = config.share_scope.clone(); + let mut resolved_entries = self.resolved_entries.write().await; + match resolved_entries.entry(share_key) { + Entry::Occupied(mut entry) => { + let record = entry.get_mut(); + record.share_scope = share_scope; + record.requests.insert(CollectedShareRequest { + request: import_resolved + .clone() + .unwrap_or_else(|| request.to_string()), + version: version.to_string(), + }); + } + Entry::Vacant(entry) => { + let mut requests = FxHashSet::default(); + requests.insert(CollectedShareRequest { + request: import_resolved + .clone() + .unwrap_or_else(|| request.to_string()), + version: version.to_string(), + }); + entry.insert(CollectShareEntryRecord { + share_scope, + requests, + }); + } + } + } +} + +#[plugin_hook(CompilerThisCompilation for CollectShareEntryPlugin)] +async fn this_compilation( + &self, + compilation: &mut Compilation, + _params: &mut rspack_core::CompilationParams, +) -> Result<()> { + if self.compiler_context.get().is_none() { + self.init_context(compilation); + } + if self.resolver.get().is_none() { + self.init_resolver(compilation); + } + if self.matched_consumes.get().is_none() { + self + .init_matched_consumes(compilation, self.get_resolver()) + .await; + } + Ok(()) +} + +#[plugin_hook(CompilationProcessAssets for CollectShareEntryPlugin)] +async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { + let entries = self.resolved_entries.read().await; + + let mut shared: FxHashMap<&str, CollectShareEntryAssetItem<'_>> = FxHashMap::default(); + let mut ordered_requests: FxHashMap<&str, Vec<[String; 2]>> = FxHashMap::default(); + + for (share_key, record) in entries.iter() { + if record.requests.is_empty() { + continue; + } + let mut requests: Vec<[String; 2]> = record + .requests + .iter() + .map(|item| [item.request.clone(), item.version.clone()]) + .collect(); + requests.sort_by(|a, b| a[0].cmp(&b[0]).then(a[1].cmp(&b[1]))); + ordered_requests.insert(share_key.as_str(), requests); + } + + for (share_key, record) in entries.iter() { + if record.requests.is_empty() { + continue; + } + let requests = ordered_requests + .get(share_key.as_str()) + .map(|v| v.as_slice()) + .unwrap_or(&[]); + shared.insert( + share_key.as_str(), + CollectShareEntryAssetItem { + share_scope: record.share_scope.as_str(), + requests, + }, + ); + } + + let json = serde_json::to_string_pretty(&shared) + .expect("CollectShareEntryPlugin: failed to serialize share entries"); + + // 获取文件名,如果不存在则使用默认值 + let filename = self + .options + .filename + .as_ref() + .map(|f| f.clone()) + .unwrap_or_else(|| DEFAULT_FILENAME.to_string()); + + compilation.emit_asset( + filename, + CompilationAsset::new( + Some(RawStringSource::from(json).boxed()), + Default::default(), + ), + ); + Ok(()) +} + +#[plugin_hook(NormalModuleFactoryFactorize for CollectShareEntryPlugin)] +async fn factorize(&self, data: &mut ModuleFactoryCreateData) -> Result> { + let dep = data.dependencies[0] + .as_module_dependency() + .expect("should be module dependency"); + if matches!( + dep.dependency_type(), + DependencyType::ConsumeSharedFallback | DependencyType::ProvideModuleForShared + ) { + return Ok(None); + } + let request = dep.request(); + + // 直接复用 consume_shared_plugin 的匹配逻辑 + let consumes = self.get_matched_consumes(); + + // 1. 精确匹配 - 使用 unresolved + if let Some(matched) = consumes.unresolved.get(request) { + self + .record_entry(&data.context, request, matched.clone(), |d| { + data.diagnostics.push(d) + }) + .await; + return Ok(None); + } + + // 2. 前缀匹配 - 使用 prefixed + for (prefix, options) in &consumes.prefixed { + if request.starts_with(prefix) { + let remainder = &request[prefix.len()..]; + self + .record_entry( + &data.context, + request, + Arc::new(ConsumeOptions { + import: options.import.as_ref().map(|i| i.to_owned() + remainder), + import_resolved: options.import_resolved.clone(), + share_key: options.share_key.clone() + remainder, + share_scope: options.share_scope.clone(), + required_version: options.required_version.clone(), + package_name: options.package_name.clone(), + strict_version: options.strict_version, + singleton: options.singleton, + eager: options.eager, + }), + |d| data.diagnostics.push(d), + ) + .await; + return Ok(None); + } + } + + Ok(None) +} + +impl Plugin for CollectShareEntryPlugin { + fn name(&self) -> &'static str { + "rspack.CollectShareEntryPlugin" + } + + fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> { + ctx + .compiler_hooks + .this_compilation + .tap(this_compilation::new(self)); + ctx + .normal_module_factory_hooks + .factorize + .tap(factorize::new(self)); + ctx + .compilation_hooks + .process_assets + .tap(process_assets::new(self)); + Ok(()) + } +} diff --git a/crates/rspack_plugin_mf/src/sharing/consume_shared_plugin.rs b/crates/rspack_plugin_mf/src/sharing/consume_shared_plugin.rs index f95c99e4370a..ae62aa99d916 100644 --- a/crates/rspack_plugin_mf/src/sharing/consume_shared_plugin.rs +++ b/crates/rspack_plugin_mf/src/sharing/consume_shared_plugin.rs @@ -54,21 +54,21 @@ impl fmt::Display for ConsumeVersion { } } -static RELATIVE_REQUEST: LazyLock = +pub static RELATIVE_REQUEST: LazyLock = LazyLock::new(|| Regex::new(r"^\.\.?(\/|$)").expect("Invalid regex")); -static ABSOLUTE_REQUEST: LazyLock = +pub static ABSOLUTE_REQUEST: LazyLock = LazyLock::new(|| Regex::new(r"^(\/|[A-Za-z]:\\|\\\\)").expect("Invalid regex")); -static PACKAGE_NAME: LazyLock = +pub static PACKAGE_NAME: LazyLock = LazyLock::new(|| Regex::new(r"^((?:@[^\\/]+[\\/])?[^\\/]+)").expect("Invalid regex")); #[derive(Debug)] -struct MatchedConsumes { - resolved: FxHashMap>, - unresolved: FxHashMap>, - prefixed: FxHashMap>, +pub struct MatchedConsumes { + pub resolved: FxHashMap>, + pub unresolved: FxHashMap>, + pub prefixed: FxHashMap>, } -async fn resolve_matched_configs( +pub async fn resolve_matched_configs( compilation: &mut Compilation, resolver: Arc, configs: &[(String, Arc)], @@ -104,7 +104,7 @@ async fn resolve_matched_configs( } } -async fn get_description_file( +pub async fn get_description_file( fs: Arc, mut dir: &Utf8Path, satisfies_description_file_data: Option) -> bool>, @@ -137,7 +137,7 @@ async fn get_description_file( } } -fn get_required_version_from_description_file( +pub fn get_required_version_from_description_file( data: serde_json::Value, package_name: &str, ) -> Option { @@ -404,12 +404,15 @@ async fn factorize(&self, data: &mut ModuleFactoryCreateData) -> Result Result> { if matches!( data.dependencies[0].dependency_type(), - DependencyType::ConsumeSharedFallback | DependencyType::ProvideModuleForShared + DependencyType::ConsumeSharedFallback + | DependencyType::ProvideModuleForShared + | DependencyType::ShareContainerFallback ) { return Ok(None); } let resource = create_data.resource_resolve_data.resource(); let consumes = self.get_matched_consumes(); + if let Some(options) = consumes.resolved.get(resource) { let module = self .create_consume_shared_module(&data.context, resource, options.clone(), |d| { diff --git a/crates/rspack_plugin_mf/src/sharing/mod.rs b/crates/rspack_plugin_mf/src/sharing/mod.rs index a2f9e246e08b..2d2dd63c2ebf 100644 --- a/crates/rspack_plugin_mf/src/sharing/mod.rs +++ b/crates/rspack_plugin_mf/src/sharing/mod.rs @@ -1,11 +1,20 @@ +pub mod collect_share_entry_plugin; pub mod consume_shared_fallback_dependency; pub mod consume_shared_module; pub mod consume_shared_plugin; pub mod consume_shared_runtime_module; +pub mod optimize_dependency_referenced_exports_plugin; +pub mod optimize_dependency_referenced_exports_runtime_module; pub mod provide_for_shared_dependency; pub mod provide_shared_dependency; pub mod provide_shared_module; pub mod provide_shared_module_factory; pub mod provide_shared_plugin; +pub mod share_container_dependency; +pub mod share_container_entry_dependency; +pub mod share_container_entry_module; +pub mod share_container_entry_module_factory; +pub mod share_container_plugin; +pub mod share_container_runtime_module; pub mod share_runtime_module; pub mod share_runtime_plugin; diff --git a/crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_plugin.rs b/crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_plugin.rs new file mode 100644 index 000000000000..ee59a0fdacaf --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_plugin.rs @@ -0,0 +1,578 @@ +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + sync::{Arc, RwLock}, +}; + +use rspack_collections::Identifiable; +use rspack_core::{ + AsyncDependenciesBlockIdentifier, ChunkUkey, Compilation, + CompilationAdditionalTreeRuntimeRequirements, CompilationDependencyReferencedExports, + CompilationOptimizeDependencies, CompilationProcessAssets, DependenciesBlock, DependencyId, + DependencyType, ExportsType, ExtendedReferencedExport, Module, ModuleGraph, ModuleIdentifier, + NormalModule, Plugin, RuntimeGlobals, RuntimeModuleExt, RuntimeSpec, + rspack_sources::{RawStringSource, SourceExt, SourceValue}, +}; +use rspack_error::Result; +use rspack_hook::{plugin, plugin_hook}; +use rspack_plugin_javascript::dependency::ESMImportSpecifierDependency; +use rspack_util::atom::Atom; +use rustc_hash::{FxHashMap, FxHashSet}; + +use super::{ + consume_shared_module::ConsumeSharedModule, + optimize_dependency_referenced_exports_runtime_module::OptimizeDependencyReferencedExportsRuntimeModule, + provide_shared_module::ProvideSharedModule, + share_container_entry_module::ShareContainerEntryModule, +}; +use crate::manifest::StatsRoot; +#[derive(Debug, Clone)] +pub struct OptimizeSharedConfig { + pub share_key: String, + pub treeshake: bool, + pub used_exports: Vec, +} + +#[derive(Debug, Clone)] +pub struct OptimizeDependencyReferencedExportsPluginOptions { + pub shared: Vec, + pub inject_used_exports: bool, + pub stats_file_name: Option, + pub manifest_file_name: Option, +} + +#[derive(Debug, Clone)] +struct SharedEntryData { + used_exports: Vec, +} + +#[plugin] +#[derive(Debug, Clone)] +pub struct OptimizeDependencyReferencedExportsPlugin { + shared_map: FxHashMap, + shared_referenced_exports: Arc>>>>, + inject_used_exports: bool, + stats_file_name: Option, + manifest_file_name: Option, +} + +impl OptimizeDependencyReferencedExportsPlugin { + pub fn new(options: OptimizeDependencyReferencedExportsPluginOptions) -> Self { + let mut shared_map = FxHashMap::default(); + let inject_used_exports = options.inject_used_exports.clone(); + for config in options.shared.into_iter().filter(|c| c.treeshake) { + let atoms = config + .used_exports + .into_iter() + .map(Atom::from) + .collect::>(); + shared_map.insert( + config.share_key, + SharedEntryData { + used_exports: atoms, + }, + ); + } + + let shared_referenced_exports = Arc::new(RwLock::new(FxHashMap::< + String, + FxHashMap>, + >::default())); + + Self::new_inner( + shared_map, + shared_referenced_exports, + inject_used_exports, + options.stats_file_name, + options.manifest_file_name, + ) + } + + fn apply_custom_exports(&self) { + let mut shared_referenced_exports = self + .shared_referenced_exports + .write() + .expect("lock poisoned"); + for (share_key, shared_entry_data) in &self.shared_map { + if let Some(runtime_map) = shared_referenced_exports.get_mut(share_key) { + for export_set in runtime_map.values_mut() { + for used_export in &shared_entry_data.used_exports { + export_set.insert(used_export.to_string()); + } + } + } + } + } +} + +fn collect_processed_modules( + module_graph: &ModuleGraph<'_>, + module_blocks: &[AsyncDependenciesBlockIdentifier], + module_deps: &[DependencyId], + out: &mut Vec, +) { + for dep_id in module_deps { + if let Some(target_id) = module_graph.module_identifier_by_dependency_id(dep_id) { + out.push(*target_id); + } + } + + for block_id in module_blocks { + if let Some(block) = module_graph.block_by_id(block_id) { + for dep_id in block.get_dependencies() { + if let Some(target_id) = module_graph.module_identifier_by_dependency_id(dep_id) { + out.push(*target_id); + } + } + } + } +} + +#[plugin_hook( + CompilationOptimizeDependencies for OptimizeDependencyReferencedExportsPlugin, + stage = 1 +)] +async fn optimize_dependencies(&self, compilation: &mut Compilation) -> Result> { + let module_ids: Vec<_> = { + let module_graph = compilation.get_module_graph(); + module_graph + .modules() + .into_iter() + .map(|(id, _)| id) + .collect() + }; + self.apply_custom_exports(); + + for module_id in module_ids { + let (share_key, modules_to_process) = match { + let module_graph = compilation.get_module_graph(); + let module = module_graph.module_by_identifier(&module_id); + module.and_then(|module| { + let module_type = module.module_type(); + if !matches!( + module_type, + rspack_core::ModuleType::ConsumeShared + | rspack_core::ModuleType::ProvideShared + | rspack_core::ModuleType::ShareContainerShared + ) { + return None; + } + + let mut modules_to_process = Vec::new(); + let share_key = match module_type { + rspack_core::ModuleType::ConsumeShared => { + let consume_shared_module = module.as_any().downcast_ref::()?; + // Use the readable_identifier to extract the share key + // The share key is part of the identifier string in format "consume shared module ({share_scope}) {share_key}@..." + let identifier = + consume_shared_module.readable_identifier(&rspack_core::Context::default()); + let identifier_str = identifier.to_string(); + let parts: Vec<&str> = identifier_str.split(") ").collect(); + if parts.len() < 2 { + return None; + } + let share_key_part = parts[1]; + let share_key_end = share_key_part.find('@').unwrap_or(share_key_part.len()); + let sk: String = share_key_part[..share_key_end].to_string(); + collect_processed_modules( + &module_graph, + consume_shared_module.get_blocks(), + consume_shared_module.get_dependencies(), + &mut modules_to_process, + ); + sk + } + rspack_core::ModuleType::ProvideShared => { + let provide_shared_module = module.as_any().downcast_ref::()?; + let sk = provide_shared_module.share_key().to_string(); + collect_processed_modules( + &module_graph, + provide_shared_module.get_blocks(), + provide_shared_module.get_dependencies(), + &mut modules_to_process, + ); + sk + } + rspack_core::ModuleType::ShareContainerShared => { + let share_container_entry_module = module + .as_any() + .downcast_ref::()?; + // Use the identifier to extract the share key + // The identifier is in format "share container entry {name}@{version}" + let identifier = share_container_entry_module.identifier().to_string(); + let parts: Vec<&str> = identifier.split(' ').collect(); + if parts.len() < 3 { + return None; + } + let name_part = parts[3]; + let name_end = name_part.find('@').unwrap_or(name_part.len()); + let sk = name_part[..name_end].to_string(); + collect_processed_modules( + &module_graph, + share_container_entry_module.get_blocks(), + share_container_entry_module.get_dependencies(), + &mut modules_to_process, + ); + sk + } + _ => return None, + }; + Some((share_key, modules_to_process)) + }) + } { + Some(result) => result, + None => continue, + }; + + if share_key.is_empty() { + continue; + } + + // Get the runtime referenced exports for this share key + let runtime_reference_exports = { + self + .shared_referenced_exports + .read() + .expect("lock poisoned") + .get(&share_key) + .cloned() + }; + + // Check if this share key is in our shared map and has treeshake enabled + if !self.shared_map.contains_key(&share_key) { + continue; + } + + if let Some(runtime_reference_exports) = runtime_reference_exports { + if runtime_reference_exports.is_empty() { + continue; + } + + let real_shared_identifier = { + let module_graph = compilation.get_module_graph(); + module_graph.modules().into_iter().find_map(|(id, module)| { + module + .as_any() + .downcast_ref::() + .filter(|normal| normal.raw_request() == share_key) + .map(|_| id) + }) + }; + + // Check if the real shared module is side effect free + if let Some(real_shared_identifier) = real_shared_identifier { + let is_side_effect_free = { + let module_graph = compilation.get_module_graph(); + module_graph + .module_by_identifier(&real_shared_identifier) + .and_then(|module| module.factory_meta().and_then(|meta| meta.side_effect_free)) + .unwrap_or(false) + }; + + if !is_side_effect_free { + continue; + } + + let mut module_graph_mut = compilation.get_module_graph_mut(); + module_graph_mut.active_all_exports_info(); + // mark used for collected modules + for module_id in &modules_to_process { + let exports_info = module_graph_mut.get_exports_info(module_id); + let exports_info_data = exports_info.as_data_mut(&mut module_graph_mut); + + for (_runtime, exports) in runtime_reference_exports.iter() { + for export_name in exports { + let export_atom = Atom::from(export_name.as_str()); + if let Some(export_info) = exports_info_data.named_exports_mut(&export_atom) { + // let runtime_spec = RuntimeSpec::from_iter([runtime.clone().into()]); + // let status = + // export_info.set_used(rspack_core::UsageState::Used, Some(&runtime_spec)); + // set used by runtime when set_owned_used_in_unknown_way remove + export_info.set_used(rspack_core::UsageState::Used, None); + } + } + } + } + + // find if can update real share module + let exports_info = module_graph_mut.get_exports_info(&real_shared_identifier); + let exports_info_data = exports_info.as_data_mut(&mut module_graph_mut); + let can_update_module_used_stage = { + let exports_view = exports_info_data.exports(); + runtime_reference_exports.iter().all(|(runtime_name, _)| { + let runtime_spec = RuntimeSpec::from_iter([runtime_name.clone().into()]); + exports_view.iter().all(|(name, export_info)| { + let used = export_info.get_used(Some(&runtime_spec)); + // if unknown module is all we mark, means we have known all module exports info , and can shake + if used != rspack_core::UsageState::Unknown { + runtime_reference_exports + .values() + .any(|exports| exports.contains(&name.to_string())) + } else { + true + } + }) + }) + }; + if can_update_module_used_stage { + // mark used exports for dependencies and blocks dependencies + // create a helper closure to update dependency's export info + // set used by runtime when set_owned_used_in_unknown_way remove + for export_info in exports_info_data.exports_mut().values_mut() { + export_info.set_used_conditionally( + Box::new(|used| *used == rspack_core::UsageState::Unknown), + rspack_core::UsageState::Unused, + None, + ); + export_info.set_can_mangle_provide(Some(false)); + export_info.set_can_mangle_use(Some(false)); + } + exports_info_data + .other_exports_info_mut() + .set_used_conditionally( + Box::new(|used| *used == rspack_core::UsageState::Unknown), + rspack_core::UsageState::Unused, + None, + ); + } + } + } + } + + Ok(None) +} + +#[plugin_hook(CompilationProcessAssets for OptimizeDependencyReferencedExportsPlugin)] +async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { + let file_names = vec![ + self.stats_file_name.clone(), + self.manifest_file_name.clone(), + ]; + for file_name in file_names { + if let Some(file_name) = &file_name { + if let Some(file) = compilation.assets().get(file_name) { + if let Some(source) = file.get_source() { + if let SourceValue::String(content) = source.source() { + if let Ok(mut stats_root) = serde_json::from_str::(&content) { + let shared_referenced_exports = self + .shared_referenced_exports + .read() + .expect("lock poisoned"); + + for shared in &mut stats_root.shared { + if let Some(runtime_map) = shared_referenced_exports.get(&shared.name) { + let mut used_exports_set = HashSet::new(); + + for (_runtime, exports) in runtime_map { + if !exports.is_empty() { + for export in exports { + used_exports_set.insert(export.clone()); + } + } + } + + shared.usedExports = used_exports_set.into_iter().collect::>(); + } + } + + let updated_content = serde_json::to_string_pretty(&stats_root) + .map_err(|e| rspack_error::error!("Failed to serialize stats root: {}", e))?; + + compilation.update_asset(file_name, |_, info| { + Ok((RawStringSource::from(updated_content).boxed(), info)) + })?; + } + } + } + } + } + } + + let assets = compilation.assets(); + if let Some(manifest_file_name) = &self.manifest_file_name { + let manifest = assets.get(manifest_file_name); + if manifest.is_none() { + return Ok(()); + } + } + if let Some(stats_file_name) = &self.stats_file_name { + let stats = assets.get(stats_file_name); + if stats.is_none() { + return Ok(()); + } + } + + // let manifest_name = self. + // let stats = compilation.assets().get() + Ok(()) +} + +#[plugin_hook( + CompilationAdditionalTreeRuntimeRequirements for OptimizeDependencyReferencedExportsPlugin +)] +async fn additional_tree_runtime_requirements( + &self, + compilation: &mut Compilation, + chunk_ukey: &ChunkUkey, + runtime_requirements: &mut RuntimeGlobals, +) -> Result<()> { + if self.shared_map.is_empty() { + return Ok(()); + } + + runtime_requirements.insert(RuntimeGlobals::RUNTIME_ID); + compilation.add_runtime_module( + chunk_ukey, + OptimizeDependencyReferencedExportsRuntimeModule::new( + self + .shared_referenced_exports + .read() + .expect("lock poisoned") + .iter() + .map(|(share_key, runtime_map)| { + ( + share_key.clone(), + runtime_map + .iter() + .map(|(runtime, exports)| { + (runtime.clone(), exports.iter().cloned().collect::>()) + }) + .collect::>(), + ) + }) + .collect::>() + .into(), + ) + .boxed(), + )?; + + Ok(()) +} + +#[plugin_hook(CompilationDependencyReferencedExports for OptimizeDependencyReferencedExportsPlugin)] +async fn dependency_referenced_exports( + &self, + compilation: &Compilation, + dependency_id: &DependencyId, + referenced_exports: &Option>, + runtime: Option<&RuntimeSpec>, +) -> Result<()> { + let module_graph = compilation.get_module_graph(); + + if referenced_exports.is_none() { + return Ok(()); + } + let Some(exports) = referenced_exports else { + return Ok(()); + }; + + let Some(dependency) = module_graph.dependency_by_id(dependency_id) else { + return Ok(()); + }; + + let Some(module_dependency) = dependency.as_module_dependency() else { + return Ok(()); + }; + + let share_key = module_dependency.request(); + + // Check if dependency type is EsmImportSpecifier and share_key is in shared_map + if !self.shared_map.contains_key(share_key) { + return Ok(()); + } + let mut final_exports = exports.clone(); + if final_exports.is_empty() { + if dependency.dependency_type() == &DependencyType::EsmImportSpecifier { + if let Some(esm_dep) = dependency + .as_any() + .downcast_ref::() + { + let ids = esm_dep.get_ids(&module_graph); + if ids.is_empty() { + return Ok(()); + } + final_exports = esm_dep.get_esm_import_specifier_referenced_exports( + &module_graph, + Some(ExportsType::DefaultWithNamed), + ); + } + } + } + if final_exports.is_empty() { + return Ok(()); + } + + // Process each referenced export + if let Some(runtime) = runtime { + if self.shared_map.contains_key(share_key) { + let mut shared_referenced_exports = self + .shared_referenced_exports + .write() + .expect("lock poisoned"); + let runtime_map: &mut HashMap< + String, + std::collections::HashSet, + rustc_hash::FxBuildHasher, + > = shared_referenced_exports + .entry(share_key.to_string()) + .or_insert_with(|| FxHashMap::default()); + + let export_set = runtime_map + .entry(runtime.as_str().to_string()) + .or_insert_with(|| FxHashSet::default()); + + for referenced_export in &final_exports { + match referenced_export { + ExtendedReferencedExport::Array(exports_array) => { + for export in exports_array { + export_set.insert(export.to_string()); + } + } + ExtendedReferencedExport::Export(referenced) => { + if referenced.name.is_empty() { + continue; + } + let flattened = referenced + .name + .iter() + .map(|atom| atom.to_string()) + .collect::>() + .join("."); + export_set.insert(flattened); + } + } + } + } + } + Ok(()) +} + +impl Plugin for OptimizeDependencyReferencedExportsPlugin { + fn name(&self) -> &'static str { + "rspack.sharing.OptimizeDependencyReferencedExportsPlugin" + } + + fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> { + if self.shared_map.is_empty() { + return Ok(()); + } + ctx + .compilation_hooks + .dependency_referenced_exports + .tap(dependency_referenced_exports::new(self)); + ctx + .compilation_hooks + .optimize_dependencies + .tap(optimize_dependencies::new(self)); + ctx + .compilation_hooks + .process_assets + .tap(process_assets::new(self)); + if self.inject_used_exports { + ctx + .compilation_hooks + .additional_tree_runtime_requirements + .tap(additional_tree_runtime_requirements::new(self)); + } + Ok(()) + } +} diff --git a/crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_runtime_module.rs b/crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_runtime_module.rs new file mode 100644 index 000000000000..68c2fb94cff4 --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_runtime_module.rs @@ -0,0 +1,61 @@ +use std::{collections::BTreeMap, sync::Arc}; + +use async_trait::async_trait; +use rspack_collections::Identifier; +use rspack_core::{ + ChunkUkey, Compilation, RuntimeGlobals, RuntimeModule, RuntimeModuleStage, impl_runtime_module, +}; +use rspack_error::{Result, error}; + +#[impl_runtime_module] +#[derive(Debug)] +pub struct OptimizeDependencyReferencedExportsRuntimeModule { + id: Identifier, + chunk: Option, + shared_used_exports: Arc>>>, +} + +impl OptimizeDependencyReferencedExportsRuntimeModule { + pub fn new(shared_used_exports: Arc>>>) -> Self { + Self::with_default( + Identifier::from("module_federation/shared_used_exports"), + None, + shared_used_exports, + ) + } +} + +#[async_trait] +impl RuntimeModule for OptimizeDependencyReferencedExportsRuntimeModule { + fn name(&self) -> Identifier { + self.id + } + + fn attach(&mut self, chunk: ChunkUkey) { + self.chunk = Some(chunk); + } + + fn stage(&self) -> RuntimeModuleStage { + RuntimeModuleStage::Attach + } + + async fn generate(&self, _compilation: &Compilation) -> Result { + if self.shared_used_exports.is_empty() { + return Ok(String::new()); + } + let federation_global = format!("{}.federation", RuntimeGlobals::REQUIRE); + let used_exports_json = serde_json::to_string(&*self.shared_used_exports).map_err(|err| { + error!( + "OptimizeDependencyReferencedExportsRuntimeModule: failed to serialize used exports: {err}" + ) + })?; + Ok(format!( + r#" +if(!{federation_global}){{return;}} +{federation_global}.usedExports = {used_exports_json}; +"#, + federation_global = federation_global, + used_exports_json = used_exports_json + )) + } +} diff --git a/crates/rspack_plugin_mf/src/sharing/provide_shared_module.rs b/crates/rspack_plugin_mf/src/sharing/provide_shared_module.rs index 888afd1f2301..7a0110f31b42 100644 --- a/crates/rspack_plugin_mf/src/sharing/provide_shared_module.rs +++ b/crates/rspack_plugin_mf/src/sharing/provide_shared_module.rs @@ -84,6 +84,10 @@ impl ProvideSharedModule { source_map_kind: SourceMapKind::empty(), } } + + pub fn share_key(&self) -> &str { + &self.name + } } impl Identifiable for ProvideSharedModule { diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_dependency.rs b/crates/rspack_plugin_mf/src/sharing/share_container_dependency.rs new file mode 100644 index 000000000000..bd52ccdee416 --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/share_container_dependency.rs @@ -0,0 +1,71 @@ +use rspack_cacheable::{cacheable, cacheable_dyn}; +use rspack_core::{ + AsContextDependency, AsDependencyCodeGeneration, Dependency, DependencyCategory, DependencyId, + DependencyType, FactorizeInfo, ModuleDependency, +}; + +#[cacheable] +#[derive(Debug, Clone)] +pub struct ShareContainerDependency { + id: DependencyId, + request: String, + resource_identifier: String, + factorize_info: FactorizeInfo, +} + +impl ShareContainerDependency { + pub fn new(request: String) -> Self { + let resource_identifier = format!("share-container-fallback:{}", request); + Self { + id: DependencyId::new(), + request, + resource_identifier, + factorize_info: Default::default(), + } + } +} + +#[cacheable_dyn] +impl Dependency for ShareContainerDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::ShareContainerFallback + } + + fn resource_identifier(&self) -> Option<&str> { + Some(&self.resource_identifier) + } + + fn could_affect_referencing_module(&self) -> rspack_core::AffectType { + rspack_core::AffectType::True + } +} + +#[cacheable_dyn] +impl ModuleDependency for ShareContainerDependency { + fn request(&self) -> &str { + &self.request + } + + fn user_request(&self) -> &str { + &self.request + } + + fn factorize_info(&self) -> &FactorizeInfo { + &self.factorize_info + } + + fn factorize_info_mut(&mut self) -> &mut FactorizeInfo { + &mut self.factorize_info + } +} + +impl AsContextDependency for ShareContainerDependency {} +impl AsDependencyCodeGeneration for ShareContainerDependency {} diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_entry_dependency.rs b/crates/rspack_plugin_mf/src/sharing/share_container_entry_dependency.rs new file mode 100644 index 000000000000..d14f778a8805 --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/share_container_entry_dependency.rs @@ -0,0 +1,78 @@ +use rspack_cacheable::{cacheable, cacheable_dyn}; +use rspack_core::{ + AsContextDependency, AsDependencyCodeGeneration, Dependency, DependencyCategory, DependencyId, + DependencyType, FactorizeInfo, ModuleDependency, +}; +use serde::Serialize; + +#[cacheable] +#[derive(Debug, Clone)] +pub struct ShareContainerEntryDependency { + id: DependencyId, + pub name: String, + pub request: String, + pub version: String, + resource_identifier: String, + factorize_info: FactorizeInfo, +} + +#[cacheable] +#[derive(Debug, Clone, Serialize)] +pub struct ShareContainerEntryOptions { + pub request: String, +} + +impl ShareContainerEntryDependency { + pub fn new(name: String, request: String, version: String) -> Self { + let resource_identifier = format!("share-container-entry-{}", &name); + Self { + id: DependencyId::new(), + name, + request, + version, + resource_identifier, + factorize_info: Default::default(), + } + } +} + +#[cacheable_dyn] +impl Dependency for ShareContainerEntryDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::ShareContainerEntry + } + + fn resource_identifier(&self) -> Option<&str> { + Some(&self.resource_identifier) + } + + fn could_affect_referencing_module(&self) -> rspack_core::AffectType { + rspack_core::AffectType::Transitive + } +} + +#[cacheable_dyn] +impl ModuleDependency for ShareContainerEntryDependency { + fn request(&self) -> &str { + &self.resource_identifier + } + + fn factorize_info(&self) -> &FactorizeInfo { + &self.factorize_info + } + + fn factorize_info_mut(&mut self) -> &mut FactorizeInfo { + &mut self.factorize_info + } +} + +impl AsContextDependency for ShareContainerEntryDependency {} +impl AsDependencyCodeGeneration for ShareContainerEntryDependency {} diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs b/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs new file mode 100644 index 000000000000..aee5d4e4ef96 --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs @@ -0,0 +1,263 @@ +use std::borrow::Cow; + +use async_trait::async_trait; +use rspack_cacheable::{cacheable, cacheable_dyn}; +use rspack_collections::{Identifiable, Identifier}; +use rspack_core::{ + AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, + BuildMeta, BuildMetaExportsType, BuildResult, CodeGenerationResult, Compilation, + ConcatenationScope, Context, DependenciesBlock, DependencyId, FactoryMeta, LibIdentOptions, + Module, ModuleDependency, ModuleGraph, ModuleIdentifier, ModuleType, RuntimeGlobals, RuntimeSpec, + SourceType, StaticExportsDependency, StaticExportsSpec, basic_function, impl_module_meta_info, + module_raw, module_update_hash, returning_function, + rspack_sources::{BoxSource, RawStringSource, SourceExt}, +}; +use rspack_error::{Result, impl_empty_diagnosable_trait}; +use rspack_hash::{RspackHash, RspackHashDigest}; +use rspack_util::source_map::{ModuleSourceMapConfig, SourceMapKind}; +use rustc_hash::FxHashSet; + +use super::share_container_dependency::ShareContainerDependency; + +#[cacheable] +#[derive(Debug)] +pub struct ShareContainerEntryModule { + blocks: Vec, + dependencies: Vec, + identifier: ModuleIdentifier, + lib_ident: String, + name: String, + request: String, + version: String, + factory_meta: Option, + build_info: BuildInfo, + build_meta: BuildMeta, + source_map_kind: SourceMapKind, +} + +impl ShareContainerEntryModule { + pub fn new(name: String, request: String, version: String) -> Self { + let lib_ident = format!("webpack/share/container/{}", &name); + Self { + blocks: Vec::new(), + dependencies: Vec::new(), + identifier: ModuleIdentifier::from(format!("share container entry {}@{}", &name, &version,)), + lib_ident, + name, + request, + version, + factory_meta: None, + build_info: BuildInfo { + strict: true, + top_level_declarations: Some(FxHashSet::default()), + ..Default::default() + }, + build_meta: BuildMeta { + exports_type: BuildMetaExportsType::Namespace, + ..Default::default() + }, + source_map_kind: SourceMapKind::empty(), + } + } +} + +impl Identifiable for ShareContainerEntryModule { + fn identifier(&self) -> Identifier { + self.identifier + } +} + +impl DependenciesBlock for ShareContainerEntryModule { + fn add_block_id(&mut self, block: AsyncDependenciesBlockIdentifier) { + self.blocks.push(block) + } + + fn get_blocks(&self) -> &[AsyncDependenciesBlockIdentifier] { + &self.blocks + } + + fn add_dependency_id(&mut self, dependency: DependencyId) { + self.dependencies.push(dependency) + } + + fn remove_dependency_id(&mut self, dependency: DependencyId) { + self.dependencies.retain(|d| d != &dependency) + } + + fn get_dependencies(&self) -> &[DependencyId] { + &self.dependencies + } +} + +#[cacheable_dyn] +#[async_trait] +impl Module for ShareContainerEntryModule { + impl_module_meta_info!(); + + fn size(&self, _source_type: Option<&SourceType>, _compilation: Option<&Compilation>) -> f64 { + 42.0 + } + + fn module_type(&self) -> &ModuleType { + &ModuleType::ShareContainerShared + } + + fn source_types(&self, _module_graph: &ModuleGraph) -> &[SourceType] { + &[SourceType::JavaScript, SourceType::Expose] + } + + fn source(&self) -> Option<&BoxSource> { + None + } + + fn readable_identifier(&self, _context: &Context) -> Cow<'_, str> { + "share container entry".into() + } + + fn lib_ident(&self, _options: LibIdentOptions) -> Option> { + Some(self.lib_ident.as_str().into()) + } + + async fn build( + &mut self, + _build_context: BuildContext, + _: Option<&Compilation>, + ) -> Result { + let mut dependencies: Vec = Vec::new(); + + dependencies.push(Box::new(StaticExportsDependency::new( + StaticExportsSpec::Array(vec!["get".into(), "init".into()]), + false, + ))); + dependencies.push(Box::new(ShareContainerDependency::new(self.name.clone()))); + + Ok(BuildResult { + dependencies, + blocks: Vec::>::new(), + ..Default::default() + }) + } + + async fn code_generation( + &self, + compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + _: Option, + ) -> Result { + let mut code_generation_result = CodeGenerationResult::default(); + code_generation_result + .runtime_requirements + .insert(RuntimeGlobals::DEFINE_PROPERTY_GETTERS); + code_generation_result + .runtime_requirements + .insert(RuntimeGlobals::EXPORTS); + code_generation_result + .runtime_requirements + .insert(RuntimeGlobals::REQUIRE); + + let module_graph = compilation.get_module_graph(); + let mut factory = String::new(); + for dependency_id in self.get_dependencies() { + let dependency = module_graph + .dependency_by_id(dependency_id) + .expect("share container dependency should exist"); + if let Some(dependency) = dependency.downcast_ref::() { + let module_expr = module_raw( + compilation, + &mut code_generation_result.runtime_requirements, + dependency_id, + dependency.user_request(), + false, + ); + factory = returning_function(&compilation.options.output.environment, &module_expr, ""); + } + } + + let federation_global = format!("{}.federation", RuntimeGlobals::REQUIRE); + + // 使用 returning_function 生成 installInitialConsumes 函数 + let install_initial_consumes_call = format!( + r#"localBundlerRuntime.installInitialConsumes({{ + installedModules: localInstalledModules, + initialConsumes: __webpack_require__.consumesLoadingData.initialConsumes, + moduleToHandlerMapping: __webpack_require__.federation.consumesLoadingModuleToHandlerMapping, + webpackRequire: __webpack_require__, + asyncLoad: true + }})"# + ); + let install_initial_consumes_fn = returning_function( + &compilation.options.output.environment, + &install_initial_consumes_call, + "", + ); + + // 使用 basic_function 创建 initShareContainer 函数,支持多语句函数体 + let init_body = format!( + r#" + var installedModules = {{}}; + {federation_global}.instance = mfInstance; + {federation_global}.bundlerRuntime = bundlerRuntime; + + // 将参数保存到局部变量,避免闭包问题 + var localBundlerRuntime = bundlerRuntime; + var localInstalledModules = installedModules; + + if(!__webpack_require__.consumesLoadingData){{return; }} + {federation_global}.installInitialConsumes = {install_initial_consumes_fn}; + + return {federation_global}.installInitialConsumes(); + "#, + federation_global = federation_global, + install_initial_consumes_fn = install_initial_consumes_fn + ); + let init_share_container_fn = basic_function( + &compilation.options.output.environment, + "mfInstance, bundlerRuntime", + &init_body, + ); + + // Generate the final source string + let source = format!( + r#" + __webpack_require__.federation = {{ instance: undefined,bundlerRuntime: undefined }} + var factory = {factory}; + var initShareContainer = {init_share_container_fn}; +{runtime}(exports, {{ + get: function() {{ return factory;}}, + init: function() {{ return initShareContainer;}} +}}); +"#, + runtime = RuntimeGlobals::DEFINE_PROPERTY_GETTERS, + factory = factory, + init_share_container_fn = init_share_container_fn + ); + + // Update the code generation result with the generated source + code_generation_result = + code_generation_result.with_javascript(RawStringSource::from(source).boxed()); + code_generation_result.add(SourceType::Expose, RawStringSource::from_static("").boxed()); + Ok(code_generation_result) + } + + async fn get_runtime_hash( + &self, + compilation: &Compilation, + runtime: Option<&RuntimeSpec>, + ) -> Result { + let mut hasher = RspackHash::from(&compilation.options.output); + module_update_hash(self, &mut hasher, compilation, runtime); + Ok(hasher.digest(&compilation.options.output.hash_digest)) + } +} + +impl_empty_diagnosable_trait!(ShareContainerEntryModule); + +impl ModuleSourceMapConfig for ShareContainerEntryModule { + fn get_source_map_kind(&self) -> &SourceMapKind { + &self.source_map_kind + } + + fn set_source_map_kind(&mut self, source_map: SourceMapKind) { + self.source_map_kind = source_map; + } +} diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_entry_module_factory.rs b/crates/rspack_plugin_mf/src/sharing/share_container_entry_module_factory.rs new file mode 100644 index 000000000000..d9939660f2aa --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/share_container_entry_module_factory.rs @@ -0,0 +1,26 @@ +use async_trait::async_trait; +use rspack_core::{ModuleExt, ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult}; +use rspack_error::Result; + +use super::{ + share_container_entry_dependency::ShareContainerEntryDependency, + share_container_entry_module::ShareContainerEntryModule, +}; + +#[derive(Debug)] +pub struct ShareContainerEntryModuleFactory; + +#[async_trait] +impl ModuleFactory for ShareContainerEntryModuleFactory { + async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result { + let dep = data.dependencies[0] + .downcast_ref::() + .expect( + "dependency of ShareContainerEntryModuleFactory should be ShareContainerEntryDependency", + ); + Ok(ModuleFactoryResult::new_with_module( + ShareContainerEntryModule::new(dep.name.clone(), dep.request.clone(), dep.version.clone()) + .boxed(), + )) + } +} diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_plugin.rs b/crates/rspack_plugin_mf/src/sharing/share_container_plugin.rs new file mode 100644 index 000000000000..7868172a9bf1 --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/share_container_plugin.rs @@ -0,0 +1,107 @@ +use std::sync::Arc; + +use rspack_core::{ + ChunkUkey, Compilation, CompilationAdditionalTreeRuntimeRequirements, CompilationParams, + CompilerCompilation, CompilerMake, DependencyType, Filename, LibraryOptions, Plugin, + RuntimeGlobals, RuntimeModuleExt, +}; +use rspack_error::Result; +use rspack_hook::{plugin, plugin_hook}; + +use super::{ + share_container_entry_dependency::ShareContainerEntryDependency, + share_container_entry_module_factory::ShareContainerEntryModuleFactory, +}; +use crate::sharing::share_container_runtime_module::ShareContainerRuntimeModule; + +#[derive(Debug)] +pub struct ShareContainerPluginOptions { + pub name: String, + pub request: String, + pub version: String, + pub file_name: Option, + pub library: LibraryOptions, +} + +#[plugin] +#[derive(Debug)] +pub struct ShareContainerPlugin { + options: ShareContainerPluginOptions, +} + +impl ShareContainerPlugin { + pub fn new(options: ShareContainerPluginOptions) -> Self { + Self::new_inner(options) + } +} + +#[plugin_hook(CompilerCompilation for ShareContainerPlugin)] +async fn compilation( + &self, + compilation: &mut Compilation, + params: &mut CompilationParams, +) -> Result<()> { + compilation.set_dependency_factory( + DependencyType::ShareContainerEntry, + Arc::new(ShareContainerEntryModuleFactory), + ); + compilation.set_dependency_factory( + DependencyType::ShareContainerFallback, + params.normal_module_factory.clone(), + ); + Ok(()) +} + +#[plugin_hook(CompilerMake for ShareContainerPlugin)] +async fn make(&self, compilation: &mut Compilation) -> Result<()> { + let dep = ShareContainerEntryDependency::new( + self.options.name.clone(), + self.options.request.clone(), + self.options.version.clone(), + ); + + compilation + .add_entry( + Box::new(dep), + rspack_core::EntryOptions { + name: Some(self.options.name.clone()), + filename: self.options.file_name.clone(), + library: Some(self.options.library.clone()), + ..Default::default() + }, + ) + .await?; + Ok(()) +} + +#[plugin_hook(CompilationAdditionalTreeRuntimeRequirements for ShareContainerPlugin)] +async fn additional_tree_runtime_requirements( + &self, + compilation: &mut Compilation, + chunk_ukey: &ChunkUkey, + _runtime_requirements: &mut RuntimeGlobals, +) -> Result<()> { + let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey); + if let Some(name) = chunk.name() + && name == self.options.name + { + compilation.add_runtime_module(chunk_ukey, ShareContainerRuntimeModule::new().boxed())?; + } + Ok(()) +} + +impl Plugin for ShareContainerPlugin { + fn name(&self) -> &'static str { + "rspack.ShareContainerPlugin" + } + + fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> { + ctx.compiler_hooks.compilation.tap(compilation::new(self)); + ctx.compiler_hooks.make.tap(make::new(self)); + ctx + .compilation_hooks + .additional_tree_runtime_requirements + .tap(additional_tree_runtime_requirements::new(self)); + Ok(()) + } +} diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_runtime_module.rs b/crates/rspack_plugin_mf/src/sharing/share_container_runtime_module.rs new file mode 100644 index 000000000000..09c88b12441d --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/share_container_runtime_module.rs @@ -0,0 +1,40 @@ +use rspack_collections::Identifier; +use rspack_core::{ChunkUkey, Compilation, RuntimeModule, RuntimeModuleStage, impl_runtime_module}; + +#[impl_runtime_module] +#[derive(Debug)] +pub struct ShareContainerRuntimeModule { + id: Identifier, + chunk: Option, +} + +impl ShareContainerRuntimeModule { + pub fn new() -> Self { + Self::with_default( + Identifier::from("webpack/runtime/share_container_federation"), + None, + ) + } +} + +#[async_trait::async_trait] +impl RuntimeModule for ShareContainerRuntimeModule { + fn name(&self) -> Identifier { + self.id + } + + async fn generate(&self, _compilation: &Compilation) -> rspack_error::Result { + Ok( + "__webpack_require__.federation = { instance: undefined,bundlerRuntime: undefined };" + .to_string(), + ) + } + + fn attach(&mut self, chunk: ChunkUkey) { + self.chunk = Some(chunk); + } + + fn stage(&self) -> RuntimeModuleStage { + RuntimeModuleStage::Attach + } +} diff --git a/packages/rspack/etc/core.api.md b/packages/rspack/etc/core.api.md index f1f8543281d0..228b72044bec 100644 --- a/packages/rspack/etc/core.api.md +++ b/packages/rspack/etc/core.api.md @@ -73,6 +73,7 @@ import { RawProvideOptions } from '@rspack/binding'; import { RawRslibPluginOptions } from '@rspack/binding'; import { RawRstestPluginOptions } from '@rspack/binding'; import { RawRuntimeChunkOptions } from '@rspack/binding'; +import { RawShareContainerPluginOptions } from '@rspack/binding'; import { RawSubresourceIntegrityPluginOptions } from '@rspack/binding'; import { readFileSync } from 'fs'; import { ReadStream as ReadStream_2 } from 'fs'; @@ -808,6 +809,29 @@ type CodeValue = RecursiveArrayOrRecord; // @public (undocumented) type CodeValuePrimitive = null | undefined | RegExp | Function | string | number | boolean | bigint; +// @public (undocumented) +class CollectShareEntryPlugin extends RspackBuiltinPlugin { + constructor(options: CollectShareEntryPluginOptions); + // (undocumented) + apply(compiler: Compiler): void; + // (undocumented) + getData(): ShareRequestsMap; + // (undocumented) + getFilename(): string; + // (undocumented) + name: BuiltinPluginName; + // (undocumented) + raw(): BuiltinPlugin; + // (undocumented) + sharedOptions: NormalizedSharedOptions; +} + +// @public (undocumented) +export type CollectShareEntryPluginOptions = { + sharedOptions: NormalizedSharedOptions; + shareScope?: string; +}; + // @public (undocumented) type CollectTypeScriptInfoOptions = { typeExports?: boolean; @@ -3412,6 +3436,18 @@ type IntermediateFileSystemExtras = { close: (arg0: number, arg1: (arg0: null | NodeJS.ErrnoException) => void) => void; }; +// @public (undocumented) +type InternalManifestPluginOptions = { + name?: string; + globalName?: string; + filePath?: string; + disableAssetsAnalyze?: boolean; + fileName?: string; + remoteAliasMap?: RemoteAliasMap; + exposes?: ManifestExposeOption[]; + shared?: ManifestSharedOption[]; +}; + // @public (undocumented) interface Invalid extends Node_4, HasSpan { // (undocumented) @@ -4819,16 +4855,7 @@ type ModuleDeclaration = ImportDeclaration | ExportDeclaration | ExportNamedDecl type ModuleExportName = Identifier | StringLiteral; // @public (undocumented) -type ModuleFederationManifestPluginOptions = { - name?: string; - globalName?: string; - filePath?: string; - disableAssetsAnalyze?: boolean; - fileName?: string; - remoteAliasMap?: RemoteAliasMap; - exposes?: ManifestExposeOption[]; - shared?: ManifestSharedOption[]; -}; +type ModuleFederationManifestPluginOptions = boolean | Pick; // @public (undocumented) class ModuleFederationPlugin { @@ -4842,7 +4869,13 @@ export interface ModuleFederationPluginOptions extends Omit; + independentShareDir?: string; + // (undocumented) + independentShareFilePath?: string; + // (undocumented) + injectUsedExports?: boolean; + // (undocumented) + manifest?: ModuleFederationManifestPluginOptions; // (undocumented) runtimePlugins?: RuntimePlugins; // (undocumented) @@ -5235,6 +5268,9 @@ export type NoParseOption = NoParseOptionSingle | NoParseOptionSingle[]; // @public (undocumented) type NoParseOptionSingle = string | RegExp | ((request: string) => boolean); +// @public (undocumented) +type NormalizedSharedOptions = [string, SharedConfig][]; + // @public (undocumented) type NormalizedStatsOptions = KnownNormalizedStatsOptions & Omit & Record; @@ -5455,6 +5491,15 @@ interface Optimize { // @public (undocumented) export const optimize: Optimize; +// @public (undocumented) +class OptimizeDependencyReferencedExportsPlugin extends RspackBuiltinPlugin { + constructor(sharedOptions: [string, SharedConfig][], injectUsedExports?: boolean, manifestOptions?: ModuleFederationManifestPluginOptions); + // (undocumented) + name: BuiltinPluginName; + // (undocumented) + raw(): BuiltinPlugin | undefined; +} + // @public (undocumented) interface OptimizerConfig { // (undocumented) @@ -6612,6 +6657,7 @@ declare namespace rspackExports { RemotesItems, RemotesObject, container, + CollectShareEntryPluginOptions, ConsumeSharedPluginOptions, Consumes, ConsumesConfig, @@ -6622,11 +6668,13 @@ declare namespace rspackExports { ProvidesConfig, ProvidesItem, ProvidesObject, + ShareContainerPluginOptions, Shared, SharedConfig, SharedItem, SharedObject, SharePluginOptions, + TreeshakeSharePluginOptions, sharing, LightningcssFeatureOptions, LightningcssLoaderOptions, @@ -7333,6 +7381,37 @@ interface SetterProperty extends PropBase, HasSpan { type: "SetterProperty"; } +// @public (undocumented) +class ShareContainerPlugin extends RspackBuiltinPlugin { + constructor(options: ShareContainerPluginOptions); + // (undocumented) + apply(compiler: Compiler): void; + // (undocumented) + filename: string; + // (undocumented) + getData(): string[]; + // (undocumented) + _globalName: string; + // (undocumented) + name: BuiltinPluginName; + // (undocumented) + _options: RawShareContainerPluginOptions; + // (undocumented) + raw(compiler: Compiler): BuiltinPlugin; + // (undocumented) + _shareName: string; +} + +// @public (undocumented) +export type ShareContainerPluginOptions = { + mfName: string; + shareName: string; + version: string; + request: string; + library?: LibraryOptions; + independentShareFileName?: string; +}; + // @public (undocumented) export type Shared = (SharedItem | SharedObject)[] | SharedObject; @@ -7347,6 +7426,9 @@ export type SharedConfig = { singleton?: boolean; strictVersion?: boolean; version?: false | string; + treeshake?: boolean; + usedExports?: string[]; + independentShareFileName?: string; }; // @public (undocumented) @@ -7408,6 +7490,8 @@ class SharePlugin { }; }[]; // (undocumented) + _sharedOptions: NormalizedSharedOptions; + // (undocumented) _shareScope: string | undefined; } @@ -7418,9 +7502,19 @@ export type SharePluginOptions = { enhanced: boolean; }; +// @public (undocumented) +type ShareRequestsMap = Record; + // @public (undocumented) export const sharing: { ProvideSharedPlugin: typeof ProvideSharedPlugin; + CollectShareEntryPlugin: typeof CollectShareEntryPlugin; + TreeshakeSharePlugin: typeof TreeshakeSharePlugin; + ShareContainerPlugin: typeof ShareContainerPlugin; + OptimizeDependencyReferencedExportsPlugin: typeof OptimizeDependencyReferencedExportsPlugin; ConsumeSharedPlugin: typeof ConsumeSharedPlugin; SharePlugin: typeof SharePlugin; }; @@ -8404,6 +8498,33 @@ interface TransformConfig { // @public (undocumented) function transformSync(source: string, options?: Options): TransformOutput; +// @public (undocumented) +class TreeshakeSharePlugin { + constructor(options: TreeshakeSharePluginOptions); + // (undocumented) + apply(compiler: Compiler): void; + // (undocumented) + mfConfig: ModuleFederationPluginOptions; + // (undocumented) + name: string; + // (undocumented) + outputDir: string; + // (undocumented) + plugins?: Plugins; + // (undocumented) + reshake?: boolean; +} + +// @public (undocumented) +export interface TreeshakeSharePluginOptions { + // (undocumented) + mfConfig: ModuleFederationPluginOptions; + // (undocumented) + plugins?: Plugins; + // (undocumented) + reshake?: boolean; +} + // @public (undocumented) type TruePlusMinus = true | "+" | "-"; diff --git a/packages/rspack/src/container/ModuleFederationManifestPlugin.ts b/packages/rspack/src/container/ModuleFederationManifestPlugin.ts index c26bc37909bb..4d82dde0498a 100644 --- a/packages/rspack/src/container/ModuleFederationManifestPlugin.ts +++ b/packages/rspack/src/container/ModuleFederationManifestPlugin.ts @@ -10,6 +10,13 @@ import { RspackBuiltinPlugin } from "../builtin-plugin/base"; import type { Compiler } from "../Compiler"; +import type { SharedConfig } from "../sharing/SharePlugin"; +import { isRequiredVersion } from "../sharing/utils"; +import { + getRemoteInfos, + type ModuleFederationPluginOptions +} from "./ModuleFederationPlugin"; +import { parseOptions } from "./options"; const MANIFEST_FILE_NAME = "mf-manifest.json"; const STATS_FILE_NAME = "mf-stats.json"; @@ -88,7 +95,7 @@ export type ManifestSharedOption = { singleton?: boolean; }; -export type ModuleFederationManifestPluginOptions = { +type InternalManifestPluginOptions = { name?: string; globalName?: string; filePath?: string; @@ -99,7 +106,16 @@ export type ModuleFederationManifestPluginOptions = { shared?: ManifestSharedOption[]; }; -function getFileName(manifestOptions: ModuleFederationManifestPluginOptions): { +export type ModuleFederationManifestPluginOptions = + | boolean + | Pick< + InternalManifestPluginOptions, + "disableAssetsAnalyze" | "filePath" | "fileName" + >; + +export function getFileName( + manifestOptions: ModuleFederationManifestPluginOptions +): { statsFileName: string; manifestFileName: string; } { @@ -135,16 +151,140 @@ function getFileName(manifestOptions: ModuleFederationManifestPluginOptions): { }; } +function resolveLibraryGlobalName( + library: ModuleFederationPluginOptions["library"] +): string | undefined { + if (!library) { + return undefined; + } + const libName = library.name; + if (!libName) { + return undefined; + } + if (typeof libName === "string") { + return libName; + } + if (Array.isArray(libName)) { + return libName[0]; + } + if (typeof libName === "object") { + return libName.root?.[0] ?? libName.amd ?? libName.commonjs ?? undefined; + } + return undefined; +} + +function collectManifestExposes( + exposes: ModuleFederationPluginOptions["exposes"] +): ManifestExposeOption[] | undefined { + if (!exposes) return undefined; + type NormalizedExpose = { import: string[]; name?: string }; + type ExposesConfigInput = { import: string | string[]; name?: string }; + const parsed = parseOptions( + exposes, + value => ({ + import: Array.isArray(value) ? value : [value], + name: undefined + }), + value => ({ + import: Array.isArray(value.import) ? value.import : [value.import], + name: value.name ?? undefined + }) + ); + const result = parsed.map(([exposeKey, info]) => { + const exposeName = info.name ?? exposeKey.replace(/^\.\//, ""); + return { + path: exposeKey, + name: exposeName + }; + }); + return result.length > 0 ? result : undefined; +} + +function collectManifestShared( + shared: ModuleFederationPluginOptions["shared"] +): ManifestSharedOption[] | undefined { + if (!shared) return undefined; + const parsed = parseOptions( + shared, + (item, key) => { + if (typeof item !== "string") { + throw new Error("Unexpected array in shared"); + } + return item === key || !isRequiredVersion(item) + ? { import: item } + : { import: key, requiredVersion: item }; + }, + item => item + ); + const result = parsed.map(([key, config]) => { + const name = config.shareKey || key; + const version = + typeof config.version === "string" ? config.version : undefined; + const requiredVersion = + typeof config.requiredVersion === "string" + ? config.requiredVersion + : undefined; + return { + name, + version, + requiredVersion, + singleton: config.singleton + }; + }); + return result.length > 0 ? result : undefined; +} + +function normalizeManifestOptions(mfConfig: ModuleFederationPluginOptions) { + const manifestOptions: InternalManifestPluginOptions = + mfConfig.manifest === true ? {} : { ...mfConfig.manifest }; + const containerName = mfConfig.name; + const globalName = + resolveLibraryGlobalName(mfConfig.library) ?? containerName; + const remoteAliasMap: RemoteAliasMap = Object.entries( + getRemoteInfos(mfConfig) + ).reduce((sum, cur) => { + if (cur[1].length > 1) { + // no support multiple remotes + return sum; + } + const remoteInfo = cur[1][0]; + const { entry, alias, name } = remoteInfo; + if (entry && name) { + sum[alias] = { + name, + entry + }; + } + return sum; + }, {}); + + const manifestExposes = collectManifestExposes(mfConfig.exposes); + if (manifestOptions.exposes === undefined && manifestExposes) { + manifestOptions.exposes = manifestExposes; + } + const manifestShared = collectManifestShared(mfConfig.shared); + if (manifestOptions.shared === undefined && manifestShared) { + manifestOptions.shared = manifestShared; + } + + return { + ...manifestOptions, + remoteAliasMap, + globalName, + name: containerName + }; +} + /** * JS-side post-processing plugin: reads mf-manifest.json and mf-stats.json, executes additionalData callback and merges/overwrites manifest. * To avoid cross-NAPI callback complexity, this plugin runs at the afterProcessAssets stage to ensure Rust-side MfManifestPlugin has already output its artifacts. */ export class ModuleFederationManifestPlugin extends RspackBuiltinPlugin { name = BuiltinPluginName.ModuleFederationManifestPlugin; - private opts: ModuleFederationManifestPluginOptions; - constructor(opts: ModuleFederationManifestPluginOptions) { + private opts: InternalManifestPluginOptions; + constructor(opts: ModuleFederationPluginOptions) { super(); - this.opts = opts; + this.opts = normalizeManifestOptions(opts); } raw(compiler: Compiler): BuiltinPlugin { diff --git a/packages/rspack/src/container/ModuleFederationPlugin.ts b/packages/rspack/src/container/ModuleFederationPlugin.ts index 505e56bd5b58..e116b713b891 100644 --- a/packages/rspack/src/container/ModuleFederationPlugin.ts +++ b/packages/rspack/src/container/ModuleFederationPlugin.ts @@ -1,13 +1,14 @@ import type { Compiler } from "../Compiler"; import type { ExternalsType } from "../config"; +import { + IndependentSharePlugin, + type ShareFallback +} from "../sharing/IndependentSharePlugin"; import type { SharedConfig } from "../sharing/SharePlugin"; import { isRequiredVersion } from "../sharing/utils"; import { - type ManifestExposeOption, - type ManifestSharedOption, ModuleFederationManifestPlugin, - type ModuleFederationManifestPluginOptions, - type RemoteAliasMap + type ModuleFederationManifestPluginOptions } from "./ModuleFederationManifestPlugin"; import type { ModuleFederationPluginV1Options } from "./ModuleFederationPluginV1"; import { ModuleFederationRuntimePlugin } from "./ModuleFederationRuntimePlugin"; @@ -20,19 +21,20 @@ export interface ModuleFederationPluginOptions runtimePlugins?: RuntimePlugins; implementation?: string; shareStrategy?: "version-first" | "loaded-first"; - manifest?: - | boolean - | Omit< - ModuleFederationManifestPluginOptions, - "remoteAliasMap" | "globalName" | "name" | "exposes" | "shared" - >; + injectUsedExports?: boolean; + independentShareDir?: string; + independentShareFilePath?: string; + manifest?: ModuleFederationManifestPluginOptions; } export type RuntimePlugins = string[] | [string, Record][]; export class ModuleFederationPlugin { + private _independentSharePlugin?: IndependentSharePlugin; + constructor(private _options: ModuleFederationPluginOptions) {} apply(compiler: Compiler) { + const { name, shared } = this._options; const { webpack } = compiler; const paths = getPaths(this._options); compiler.options.resolve.alias = { @@ -41,13 +43,39 @@ export class ModuleFederationPlugin { ...compiler.options.resolve.alias }; - // Generate the runtime entry content - const entryRuntime = getDefaultEntryRuntime(paths, this._options, compiler); + const sharedOptions = getSharedOptions(this._options); + const treeshakeEntries = sharedOptions.filter( + ([, config]) => config.treeshake + ); + if (treeshakeEntries.length > 0) { + this._independentSharePlugin = new IndependentSharePlugin({ + name, + shared: shared || {}, + outputFilePath: this._options.independentShareFilePath + }); + this._independentSharePlugin.apply(compiler); + } - // Pass only the entry runtime to the Rust-side plugin - new ModuleFederationRuntimePlugin({ - entryRuntime - }).apply(compiler); + let runtimePluginApplied = false; + compiler.hooks.beforeRun.tapPromise( + { + name: "ModuleFederationPlugin", + stage: 100 + }, + async () => { + if (runtimePluginApplied) return; + runtimePluginApplied = true; + const entryRuntime = getDefaultEntryRuntime( + paths, + this._options, + compiler, + this._independentSharePlugin?.buildAssets || {} + ); + new ModuleFederationRuntimePlugin({ + entryRuntime + }).apply(compiler); + } + ); new webpack.container.ModuleFederationPluginV1({ ...this._options, @@ -55,46 +83,7 @@ export class ModuleFederationPlugin { }).apply(compiler); if (this._options.manifest) { - const manifestOptions: ModuleFederationManifestPluginOptions = - this._options.manifest === true ? {} : { ...this._options.manifest }; - const containerName = manifestOptions.name ?? this._options.name; - const globalName = - manifestOptions.globalName ?? - resolveLibraryGlobalName(this._options.library) ?? - containerName; - const remoteAliasMap: RemoteAliasMap = Object.entries( - getRemoteInfos(this._options) - ).reduce((sum, cur) => { - if (cur[1].length > 1) { - // no support multiple remotes - return sum; - } - const remoteInfo = cur[1][0]; - const { entry, alias, name } = remoteInfo; - if (entry && name) { - sum[alias] = { - name, - entry - }; - } - return sum; - }, {}); - - const manifestExposes = collectManifestExposes(this._options.exposes); - if (manifestOptions.exposes === undefined && manifestExposes) { - manifestOptions.exposes = manifestExposes; - } - const manifestShared = collectManifestShared(this._options.shared); - if (manifestOptions.shared === undefined && manifestShared) { - manifestOptions.shared = manifestShared; - } - - new ModuleFederationManifestPlugin({ - ...manifestOptions, - name: containerName, - globalName, - remoteAliasMap - }).apply(compiler); + new ModuleFederationManifestPlugin(this._options).apply(compiler); } } } @@ -115,90 +104,9 @@ interface RemoteInfo { type RemoteInfos = Record; -function collectManifestExposes( - exposes: ModuleFederationPluginOptions["exposes"] -): ManifestExposeOption[] | undefined { - if (!exposes) return undefined; - type NormalizedExpose = { import: string[]; name?: string }; - type ExposesConfigInput = { import: string | string[]; name?: string }; - const parsed = parseOptions( - exposes, - (value, key) => ({ - import: Array.isArray(value) ? value : [value], - name: undefined - }), - value => ({ - import: Array.isArray(value.import) ? value.import : [value.import], - name: value.name ?? undefined - }) - ); - const result = parsed.map(([exposeKey, info]) => { - const exposeName = info.name ?? exposeKey.replace(/^\.\//, ""); - return { - path: exposeKey, - name: exposeName - }; - }); - return result.length > 0 ? result : undefined; -} - -function collectManifestShared( - shared: ModuleFederationPluginOptions["shared"] -): ManifestSharedOption[] | undefined { - if (!shared) return undefined; - const parsed = parseOptions( - shared, - (item, key) => { - if (typeof item !== "string") { - throw new Error("Unexpected array in shared"); - } - return item === key || !isRequiredVersion(item) - ? { import: item } - : { import: key, requiredVersion: item }; - }, - item => item - ); - const result = parsed.map(([key, config]) => { - const name = config.shareKey || key; - const version = - typeof config.version === "string" ? config.version : undefined; - const requiredVersion = - typeof config.requiredVersion === "string" - ? config.requiredVersion - : undefined; - return { - name, - version, - requiredVersion, - singleton: config.singleton - }; - }); - return result.length > 0 ? result : undefined; -} - -function resolveLibraryGlobalName( - library: ModuleFederationPluginOptions["library"] -): string | undefined { - if (!library) { - return undefined; - } - const libName = library.name; - if (!libName) { - return undefined; - } - if (typeof libName === "string") { - return libName; - } - if (Array.isArray(libName)) { - return libName[0]; - } - if (typeof libName === "object") { - return libName.root?.[0] ?? libName.amd ?? libName.commonjs ?? undefined; - } - return undefined; -} - -function getRemoteInfos(options: ModuleFederationPluginOptions): RemoteInfos { +export function getRemoteInfos( + options: ModuleFederationPluginOptions +): RemoteInfos { if (!options.remotes) { return {}; } @@ -281,6 +189,24 @@ function getRuntimePlugins(options: ModuleFederationPluginOptions) { return options.runtimePlugins ?? []; } +function getSharedOptions( + options: ModuleFederationPluginOptions +): [string, SharedConfig][] { + if (!options.shared) return []; + return parseOptions( + options.shared, + (item, key) => { + if (typeof item !== "string") { + throw new Error("Unexpected array in shared"); + } + return item === key || !isRequiredVersion(item) + ? { import: item } + : { import: key, requiredVersion: item }; + }, + item => item + ); +} + function getPaths(options: ModuleFederationPluginOptions): RuntimePaths { if (IS_BROWSER) { return { @@ -307,10 +233,12 @@ function getPaths(options: ModuleFederationPluginOptions): RuntimePaths { }; } +// 注入 fallback function getDefaultEntryRuntime( paths: RuntimePaths, options: ModuleFederationPluginOptions, - compiler: Compiler + compiler: Compiler, + treeshakeShareFallbacks: ShareFallback ) { const runtimePlugins = getRuntimePlugins(options); const remoteInfos = getRemoteInfos(options); @@ -345,6 +273,9 @@ function getDefaultEntryRuntime( `const __module_federation_share_strategy__ = ${JSON.stringify( options.shareStrategy ?? "version-first" )}`, + `const __module_federation_share_fallbacks__ = ${JSON.stringify( + treeshakeShareFallbacks + )}`, IS_BROWSER ? MF_RUNTIME_CODE : compiler.webpack.Template.getFunctionContent( diff --git a/packages/rspack/src/exports.ts b/packages/rspack/src/exports.ts index 40fef1ca250e..2bcb5a35e67e 100644 --- a/packages/rspack/src/exports.ts +++ b/packages/rspack/src/exports.ts @@ -267,10 +267,15 @@ export const container = { ModuleFederationPluginV1 }; +import { CollectShareEntryPlugin } from "./sharing/CollectShareEntryPlugin"; import { ConsumeSharedPlugin } from "./sharing/ConsumeSharedPlugin"; +import { OptimizeDependencyReferencedExportsPlugin } from "./sharing/OptimizeDependencyReferencedExportsPlugin"; import { ProvideSharedPlugin } from "./sharing/ProvideSharedPlugin"; +import { ShareContainerPlugin } from "./sharing/ShareContainerPlugin"; import { SharePlugin } from "./sharing/SharePlugin"; +import { TreeshakeSharePlugin } from "./sharing/TreeShakeSharePlugin"; +export type { CollectShareEntryPluginOptions } from "./sharing/CollectShareEntryPlugin"; export type { ConsumeSharedPluginOptions, Consumes, @@ -285,6 +290,7 @@ export type { ProvidesItem, ProvidesObject } from "./sharing/ProvideSharedPlugin"; +export type { ShareContainerPluginOptions } from "./sharing/ShareContainerPlugin"; export type { Shared, SharedConfig, @@ -292,8 +298,13 @@ export type { SharedObject, SharePluginOptions } from "./sharing/SharePlugin"; +export type { TreeshakeSharePluginOptions } from "./sharing/TreeShakeSharePlugin"; export const sharing = { ProvideSharedPlugin, + CollectShareEntryPlugin, + TreeshakeSharePlugin, + ShareContainerPlugin, + OptimizeDependencyReferencedExportsPlugin, ConsumeSharedPlugin, SharePlugin }; diff --git a/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js b/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js index a19796cf01ab..679f249780c0 100644 --- a/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js +++ b/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js @@ -3,7 +3,8 @@ var __module_federation_bundler_runtime__, __module_federation_runtime_plugins__, __module_federation_remote_infos__, __module_federation_container_name__, - __module_federation_share_strategy__; + __module_federation_share_strategy__, + __module_federation_share_fallbacks__; module.exports = function () { if ( (__webpack_require__.initializeSharingData || @@ -46,6 +47,8 @@ module.exports = function () { const containerShareScope = __webpack_require__.initializeExposesData?.shareScope; + const shareFallbacks = __module_federation_share_fallbacks__ || {}; + for (const key in __module_federation_bundler_runtime__) { __webpack_require__.federation[key] = __module_federation_bundler_runtime__[key]; @@ -127,6 +130,18 @@ module.exports = function () { shareConfig, get: factory }; + const fallbackEntry = shareFallbacks[name]; + if (fallbackEntry) { + const fallbackUrl = + (__webpack_require__.p || "") + fallbackEntry[1]; + options.fallbackName = fallbackEntry[0]; + shareConfig.fallback = fallbackUrl; + options.fallback = function () { + return __webpack_require__.federation.importExternal( + fallbackUrl + ); + }; + } if (shared[name]) { shared[name].push(options); } else { @@ -187,6 +202,13 @@ module.exports = function () { "webpackRequire", () => __webpack_require__ ); + if (Object.keys(shareFallbacks).length) { + early( + __webpack_require__.federation.bundlerRuntimeOptions, + "sharedEntries", + () => shareFallbacks + ); + } merge( __webpack_require__.federation.bundlerRuntimeOptions.remotes, "idToRemoteMap", diff --git a/packages/rspack/src/sharing/CollectShareEntryPlugin.ts b/packages/rspack/src/sharing/CollectShareEntryPlugin.ts new file mode 100644 index 000000000000..246422c50253 --- /dev/null +++ b/packages/rspack/src/sharing/CollectShareEntryPlugin.ts @@ -0,0 +1,87 @@ +import { + type BuiltinPlugin, + BuiltinPluginName, + type RawCollectShareEntryPluginOptions +} from "@rspack/binding"; +import { + createBuiltinPlugin, + RspackBuiltinPlugin +} from "../builtin-plugin/base"; +import type { Compiler } from "../Compiler"; +import { normalizeConsumeShareOptions } from "./ConsumeSharedPlugin"; +import { + createConsumeShareOptions, + type NormalizedSharedOptions +} from "./SharePlugin"; + +export type CollectShareEntryPluginOptions = { + sharedOptions: NormalizedSharedOptions; + shareScope?: string; +}; + +export type ShareRequestsMap = Record< + string, + { + shareScope: string; + requests: [string, string][]; + } +>; + +const SHARE_ENTRY_ASSET = "collect-share-entries.json"; +export class CollectShareEntryPlugin extends RspackBuiltinPlugin { + name = BuiltinPluginName.CollectShareEntryPlugin; + sharedOptions: NormalizedSharedOptions; + private _collectedEntries: ShareRequestsMap; + + constructor(options: CollectShareEntryPluginOptions) { + super(); + const { sharedOptions } = options; + + this.sharedOptions = sharedOptions; + this._collectedEntries = {}; + } + + getData() { + return this._collectedEntries; + } + + getFilename() { + return SHARE_ENTRY_ASSET; + } + + apply(compiler: Compiler) { + super.apply(compiler); + + compiler.hooks.thisCompilation.tap("Collect share entry", compilation => { + compilation.hooks.processAssets.tapPromise( + { + name: "CollectShareEntry", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE + }, + async () => { + const filename = this.getFilename(); + const asset = compilation.getAsset(filename); + if (!asset) { + throw new Error(`Can not get ${filename}`); + } + this._collectedEntries = JSON.parse(asset.source.source().toString()); + compilation.deleteAsset(filename); + } + ); + }); + } + + raw(): BuiltinPlugin { + const consumeShareOptions = createConsumeShareOptions(this.sharedOptions); + const normalizedConsumeShareOptions = + normalizeConsumeShareOptions(consumeShareOptions); + const rawOptions: RawCollectShareEntryPluginOptions = { + consumes: normalizedConsumeShareOptions.map(([key, v]) => ({ + key, + ...v + })), + filename: this.getFilename() + }; + return createBuiltinPlugin(this.name, rawOptions); + } +} diff --git a/packages/rspack/src/sharing/ConsumeSharedPlugin.ts b/packages/rspack/src/sharing/ConsumeSharedPlugin.ts index fe5e24105797..eea6ffdcb671 100644 --- a/packages/rspack/src/sharing/ConsumeSharedPlugin.ts +++ b/packages/rspack/src/sharing/ConsumeSharedPlugin.ts @@ -33,6 +33,57 @@ export type ConsumesConfig = { strictVersion?: boolean; }; +export function normalizeConsumeShareOptions( + consumes: Consumes, + shareScope?: string +) { + return parseOptions( + consumes, + (item, key) => { + if (Array.isArray(item)) throw new Error("Unexpected array in options"); + const result = + item === key || !isRequiredVersion(item) + ? // item is a request/key + { + import: key, + shareScope: shareScope || "default", + shareKey: key, + requiredVersion: undefined, + packageName: undefined, + strictVersion: false, + singleton: false, + eager: false + } + : // key is a request/key + // item is a version + { + import: key, + shareScope: shareScope || "default", + shareKey: key, + requiredVersion: item, + strictVersion: true, + packageName: undefined, + singleton: false, + eager: false + }; + return result; + }, + (item, key) => ({ + import: item.import === false ? undefined : item.import || key, + shareScope: item.shareScope || shareScope || "default", + shareKey: item.shareKey || key, + requiredVersion: item.requiredVersion, + strictVersion: + typeof item.strictVersion === "boolean" + ? item.strictVersion + : item.import !== false && !item.singleton, + packageName: item.packageName, + singleton: !!item.singleton, + eager: !!item.eager + }) + ); +} + export class ConsumeSharedPlugin extends RspackBuiltinPlugin { name = BuiltinPluginName.ConsumeSharedPlugin; _options; @@ -40,51 +91,9 @@ export class ConsumeSharedPlugin extends RspackBuiltinPlugin { constructor(options: ConsumeSharedPluginOptions) { super(); this._options = { - consumes: parseOptions( + consumes: normalizeConsumeShareOptions( options.consumes, - (item, key) => { - if (Array.isArray(item)) - throw new Error("Unexpected array in options"); - const result = - item === key || !isRequiredVersion(item) - ? // item is a request/key - { - import: key, - shareScope: options.shareScope || "default", - shareKey: key, - requiredVersion: undefined, - packageName: undefined, - strictVersion: false, - singleton: false, - eager: false - } - : // key is a request/key - // item is a version - { - import: key, - shareScope: options.shareScope || "default", - shareKey: key, - requiredVersion: item, - strictVersion: true, - packageName: undefined, - singleton: false, - eager: false - }; - return result; - }, - (item, key) => ({ - import: item.import === false ? undefined : item.import || key, - shareScope: item.shareScope || options.shareScope || "default", - shareKey: item.shareKey || key, - requiredVersion: item.requiredVersion, - strictVersion: - typeof item.strictVersion === "boolean" - ? item.strictVersion - : item.import !== false && !item.singleton, - packageName: item.packageName, - singleton: !!item.singleton, - eager: !!item.eager - }) + options.shareScope ), enhanced: options.enhanced ?? false }; diff --git a/packages/rspack/src/sharing/IndependentSharePlugin.ts b/packages/rspack/src/sharing/IndependentSharePlugin.ts new file mode 100644 index 000000000000..c9791b579b46 --- /dev/null +++ b/packages/rspack/src/sharing/IndependentSharePlugin.ts @@ -0,0 +1,456 @@ +import { basename, join, resolve } from "node:path"; + +import type { Compiler } from "../Compiler"; +import type { LibraryOptions, Plugins, RspackOptions } from "../config"; +import { + getFileName, + type ModuleFederationManifestPluginOptions +} from "../container/ModuleFederationManifestPlugin"; +import { parseOptions } from "../container/options"; +import { + CollectShareEntryPlugin, + type ShareRequestsMap +} from "./CollectShareEntryPlugin"; +import { ConsumeSharedPlugin } from "./ConsumeSharedPlugin"; +import { OptimizeDependencyReferencedExportsPlugin } from "./OptimizeDependencyReferencedExportsPlugin"; +import { + ShareContainerPlugin, + type ShareContainerPluginOptions +} from "./ShareContainerPlugin"; +import type { Shared, SharedConfig } from "./SharePlugin"; +import { encodeName, isRequiredVersion } from "./utils"; + +const VIRTUAL_ENTRY = "./virtual-entry.js"; +const VIRTUAL_ENTRY_NAME = "virtual-entry"; + +export type MakeRequired = Required> & + Omit; + +const filterPlugin = (plugin: Plugins[0]) => { + if (!plugin) { + return true; + } + const pluginName = plugin["name"] || plugin["constructor"]?.name; + if (!pluginName) { + return true; + } + return ![ + "TreeshakeSharePlugin", + "IndependentSharePlugin", + "ModuleFederationPlugin", + "OptimizeDependencyReferencedExportsPlugin", + "HtmlWebpackPlugin" + ].includes(pluginName); +}; + +export interface IndependentSharePluginOptions { + name: string; + shared: Shared; + library?: LibraryOptions; + outputDir?: string; + outputFilePath?: string; + plugins?: Plugins; + treeshake?: boolean; + manifest?: ModuleFederationManifestPluginOptions; + injectUsedExports?: boolean; +} + +// { react: [ [ react/19.0.0/index.js , 19.0.0, react_global_name ] ] } +export type ShareFallback = Record; + +class VirtualEntryPlugin { + sharedOptions: [string, SharedConfig][]; + constructor(sharedOptions: [string, SharedConfig][]) { + this.sharedOptions = sharedOptions; + } + createEntry() { + const { sharedOptions } = this; + const entryContent = sharedOptions.reduce((acc, cur, index) => { + return `${acc}import shared_${index} from '${cur[0]}';\n`; + }, ""); + return entryContent; + } + + static entry() { + return { + [VIRTUAL_ENTRY_NAME]: VIRTUAL_ENTRY + }; + } + + apply(compiler: Compiler) { + new compiler.rspack.experiments.VirtualModulesPlugin({ + [VIRTUAL_ENTRY]: this.createEntry() + }).apply(compiler); + + compiler.hooks.thisCompilation.tap( + "RemoveVirtualEntryAsset", + compilation => { + compilation.hooks.processAssets.tapPromise( + { + name: "RemoveVirtualEntryAsset", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE + }, + async () => { + try { + const chunk = compilation.namedChunks.get(VIRTUAL_ENTRY_NAME); + + chunk?.files.forEach(f => { + compilation.deleteAsset(f); + }); + } catch (_e) { + console.error("Failed to remove virtual entry file!"); + } + } + ); + } + ); + } +} + +export class IndependentSharePlugin { + mfName: string; + shared: Shared; + library?: LibraryOptions; + sharedOptions: [string, SharedConfig][]; + outputDir: string; + outputFilePath?: string; + plugins: Plugins; + compilers: Map = new Map(); + treeshake?: boolean; + manifest?: ModuleFederationManifestPluginOptions; + buildAssets: ShareFallback = {}; + injectUsedExports?: boolean; + + name = "IndependentSharePlugin"; + constructor(options: IndependentSharePluginOptions) { + const { + outputDir, + outputFilePath, + plugins, + treeshake, + shared, + name, + manifest, + injectUsedExports, + library + } = options; + this.shared = shared; + this.mfName = name; + this.outputDir = outputFilePath ? "" : outputDir || "independent-packages"; + this.outputFilePath = outputFilePath; + this.plugins = plugins || []; + this.treeshake = treeshake; + this.manifest = manifest; + this.injectUsedExports = injectUsedExports ?? true; + this.library = library; + this.sharedOptions = parseOptions( + shared, + (item, key) => { + if (typeof item !== "string") + throw new Error( + `Unexpected array in shared configuration for key "${key}"` + ); + const config: SharedConfig = + item === key || !isRequiredVersion(item) + ? { + import: item + } + : { + import: key, + requiredVersion: item + }; + + return config; + }, + item => { + return item; + } + ); + } + + apply(compiler: Compiler) { + compiler.hooks.beforeRun.tapAsync( + "IndependentSharePlugin", + async (compiler, callback) => { + await this.createIndependentCompilers(compiler); + callback(); + } + ); + + // clean hooks + compiler.hooks.shutdown.tapAsync("IndependentSharePlugin", callback => { + this.cleanup(); + console.log("cleanup"); + callback(); + }); + + // inject buildAssets to stats + compiler.hooks.compilation.tap("IndependentSharePlugin", compilation => { + compilation.hooks.processAssets.tapPromise( + { + name: "injectBuildAssets", + stage: (compilation.constructor as any) + .PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER + }, + async () => { + if (!this.manifest) { + return; + } + const { statsFileName } = getFileName(this.manifest); + const stats = compilation.getAsset(statsFileName); + if (!stats) { + return; + } + const statsContent = JSON.parse(stats.source.source().toString()) as { + shared: Array<{ + name: string; + version: string; + fallback?: string; + fallbackName?: string; + }>; + }; + + const { shared } = statsContent; + Object.entries(this.buildAssets).forEach(([key, item]) => { + const targetShared = shared.find(s => s.name === key); + if (!targetShared) { + return; + } + item.forEach(([entry, version, globalName]) => { + if (version === targetShared.version) { + targetShared.fallback = entry; + targetShared.fallbackName = globalName; + } + }); + }); + + compilation.updateAsset( + statsFileName, + new compiler.webpack.sources.RawSource(JSON.stringify(statsContent)) + ); + } + ); + }); + } + + private async createIndependentCompilers(parentCompiler: Compiler) { + const { sharedOptions, buildAssets } = this; + console.log("🚀 Start creating a standalone compiler..."); + + const parentOutputDir = parentCompiler.options.output.path + ? basename(parentCompiler.options.output.path) + : ""; + // collect share requests for each shareName and then build share container + const shareRequestsMap: ShareRequestsMap = + await this.createIndependentCompiler(parentCompiler, parentOutputDir); + + await Promise.all( + sharedOptions.map(async ([shareName, shareConfig]) => { + if (!shareConfig.treeshake || shareConfig.import === false) { + return; + } + const shareRequests = shareRequestsMap[shareName].requests; + await Promise.all( + shareRequests.map(async ([request, version]) => { + const sharedConfig = sharedOptions.find( + ([name]) => name === shareName + )?.[1]; + const [shareFileName, globalName] = + await this.createIndependentCompiler( + parentCompiler, + parentOutputDir, + { + shareRequestsMap, + currentShare: { + shareName, + version, + request, + independentShareFileName: + sharedConfig?.independentShareFileName + } + } + ); + if (typeof shareFileName === "string") { + buildAssets[shareName] ||= []; + buildAssets[shareName].push([shareFileName, version, globalName]); + } + }) + ); + }) + ); + + console.log("✅ All independent packages have been compiled successfully"); + } + + private async createIndependentCompiler( + parentCompiler: Compiler, + parentOutputDir: string, + extraOptions?: { + currentShare: Omit; + shareRequestsMap: ShareRequestsMap; + } + ) { + const { + mfName, + plugins, + outputDir, + outputFilePath, + sharedOptions, + treeshake, + library + } = this; + const outputDirWithShareName = + outputFilePath || + join(outputDir, encodeName(extraOptions?.currentShare?.shareName || "")); + + const parentConfig = parentCompiler.options; + + const finalPlugins = []; + const rspack = parentCompiler.rspack; + let extraPlugin: CollectShareEntryPlugin | ShareContainerPlugin; + if (!extraOptions) { + extraPlugin = new CollectShareEntryPlugin({ + sharedOptions, + shareScope: "default" + }); + } else { + extraPlugin = new ShareContainerPlugin({ + mfName, + library, + ...extraOptions.currentShare + }); + } + (parentConfig.plugins || []).forEach(plugin => { + if ( + plugin !== undefined && + typeof plugin !== "string" && + filterPlugin(plugin) + ) { + finalPlugins.push(plugin); + } + }); + plugins.forEach(plugin => { + finalPlugins.push(plugin); + }); + finalPlugins.push(extraPlugin); + + finalPlugins.push( + new ConsumeSharedPlugin({ + consumes: sharedOptions + .filter( + ([key, options]) => + extraOptions?.currentShare.shareName !== (options.shareKey || key) + ) + .map(([key, options]) => ({ + [key]: { + import: false, + shareKey: options.shareKey || key, + shareScope: options.shareScope, + requiredVersion: options.requiredVersion, + strictVersion: options.strictVersion, + singleton: options.singleton, + packageName: options.packageName, + eager: options.eager + } + })), + enhanced: true + }) + ); + + if (treeshake) { + finalPlugins.push( + new OptimizeDependencyReferencedExportsPlugin( + sharedOptions, + this.injectUsedExports + ) + ); + } + finalPlugins.push( + new VirtualEntryPlugin(sharedOptions) + // new rspack.experiments.VirtualModulesPlugin({ + // [VIRTUAL_ENTRY]: this.createEntry() + // }) + ); + const fullOutputDir = resolve( + parentCompiler.outputPath, + outputDirWithShareName + ); + const compilerConfig: RspackOptions = { + ...parentConfig, + mode: parentConfig.mode || "development", + + entry: VirtualEntryPlugin.entry, + + output: { + path: fullOutputDir, + clean: true, + publicPath: parentConfig.output?.publicPath || "auto" + }, + + plugins: finalPlugins, + + optimization: { + ...parentConfig.optimization, + splitChunks: false + } + }; + + const compiler = rspack.rspack(compilerConfig); + + compiler.inputFileSystem = parentCompiler.inputFileSystem; + compiler.outputFileSystem = parentCompiler.outputFileSystem; + compiler.intermediateFileSystem = parentCompiler.intermediateFileSystem; + + const { currentShare } = extraOptions || {}; + currentShare && + this.compilers.set( + `${currentShare.shareName}@${currentShare.version}`, + compiler + ); + + return new Promise((resolve, reject) => { + compiler.run((err: any, stats: any) => { + if (err || stats?.hasErrors()) { + const target = currentShare ? currentShare.shareName : "收集依赖"; + console.error( + `❌ ${target} 编译失败:`, + err || + stats + .toJson() + .errors.map((e: Error) => e.message) + .join("\n") + ); + reject(err || new Error(`${target} 编译失败`)); + return; + } + + currentShare && + console.log(`✅ 独立包 ${currentShare.shareName} 编译成功`); + + if (stats) { + currentShare && console.log(`📊 ${currentShare.shareName} 编译统计:`); + console.log( + stats.toString({ + colors: true, + chunks: false, + modules: false + }) + ); + } + + resolve(extraPlugin.getData()); + }); + }); + } + + private cleanup() { + this.compilers.forEach(compiler => { + if (compiler.watching) { + compiler.watching.close(() => { + console.log("👋 编译器已关闭"); + }); + } + }); + this.compilers.clear(); + } +} diff --git a/packages/rspack/src/sharing/OptimizeDependencyReferencedExportsPlugin.ts b/packages/rspack/src/sharing/OptimizeDependencyReferencedExportsPlugin.ts new file mode 100644 index 000000000000..c432c771e662 --- /dev/null +++ b/packages/rspack/src/sharing/OptimizeDependencyReferencedExportsPlugin.ts @@ -0,0 +1,65 @@ +import type { + BuiltinPlugin, + RawOptimizeDependencyReferencedExportsPluginOptions +} from "@rspack/binding"; +import { BuiltinPluginName } from "@rspack/binding"; + +import { + createBuiltinPlugin, + RspackBuiltinPlugin +} from "../builtin-plugin/base"; +import { + getFileName, + type ModuleFederationManifestPluginOptions +} from "../container/ModuleFederationManifestPlugin"; +import type { SharedConfig } from "./SharePlugin"; + +type OptimizeSharedConfig = { + shareKey: string; + treeshake: boolean; + usedExports?: string[]; +}; + +export class OptimizeDependencyReferencedExportsPlugin extends RspackBuiltinPlugin { + name = BuiltinPluginName.OptimizeDependencyReferencedExportsPlugin; + private sharedOptions: [string, SharedConfig][]; + private injectUsedExports: boolean; + private manifestOptions: ModuleFederationManifestPluginOptions; + + constructor( + sharedOptions: [string, SharedConfig][], + injectUsedExports?: boolean, + manifestOptions?: ModuleFederationManifestPluginOptions + ) { + super(); + this.sharedOptions = sharedOptions; + this.injectUsedExports = injectUsedExports ?? true; + this.manifestOptions = manifestOptions ?? {}; + } + + private buildOptions(): RawOptimizeDependencyReferencedExportsPluginOptions { + const shared: OptimizeSharedConfig[] = this.sharedOptions.map( + ([shareKey, config]) => ({ + shareKey, + treeshake: !!config.treeshake, + usedExports: config.usedExports + }) + ); + const { manifestFileName, statsFileName } = getFileName( + this.manifestOptions + ); + return { + shared, + injectUsedExports: this.injectUsedExports, + manifestFileName, + statsFileName + }; + } + + raw(): BuiltinPlugin | undefined { + if (!this.sharedOptions.length) { + return; + } + return createBuiltinPlugin(this.name, this.buildOptions()); + } +} diff --git a/packages/rspack/src/sharing/ProvideSharedPlugin.ts b/packages/rspack/src/sharing/ProvideSharedPlugin.ts index e3c048de42ed..a35152b564f6 100644 --- a/packages/rspack/src/sharing/ProvideSharedPlugin.ts +++ b/packages/rspack/src/sharing/ProvideSharedPlugin.ts @@ -39,6 +39,42 @@ type ProvidesEnhancedExtraConfig = { requiredVersion?: false | string; }; +export function normalizeProvideShareOptions( + options: Provides, + shareScope?: string, + enhanced?: boolean +) { + return parseOptions( + options, + item => { + if (Array.isArray(item)) throw new Error("Unexpected array of provides"); + return { + shareKey: item, + version: undefined, + shareScope: shareScope || "default", + eager: false + }; + }, + item => { + const raw = { + shareKey: item.shareKey, + version: item.version, + shareScope: item.shareScope || shareScope || "default", + eager: !!item.eager + }; + if (enhanced) { + const enhancedItem: ProvidesConfig = item; + return { + ...raw, + singleton: enhancedItem.singleton, + requiredVersion: enhancedItem.requiredVersion, + strictVersion: enhancedItem.strictVersion + }; + } + return raw; + } + ); +} export class ProvideSharedPlugin< Enhanced extends boolean = false > extends RspackBuiltinPlugin { @@ -48,36 +84,10 @@ export class ProvideSharedPlugin< constructor(options: ProvideSharedPluginOptions) { super(); - this._provides = parseOptions( + this._provides = normalizeProvideShareOptions( options.provides, - item => { - if (Array.isArray(item)) - throw new Error("Unexpected array of provides"); - return { - shareKey: item, - version: undefined, - shareScope: options.shareScope || "default", - eager: false - }; - }, - item => { - const raw = { - shareKey: item.shareKey, - version: item.version, - shareScope: item.shareScope || options.shareScope || "default", - eager: !!item.eager - }; - if (options.enhanced) { - const enhancedItem: ProvidesConfig = item; - return { - ...raw, - singleton: enhancedItem.singleton, - requiredVersion: enhancedItem.requiredVersion, - strictVersion: enhancedItem.strictVersion - }; - } - return raw; - } + options.shareScope, + options.enhanced ); this._enhanced = options.enhanced; } diff --git a/packages/rspack/src/sharing/ShareContainerPlugin.ts b/packages/rspack/src/sharing/ShareContainerPlugin.ts new file mode 100644 index 000000000000..fa48a2eb4769 --- /dev/null +++ b/packages/rspack/src/sharing/ShareContainerPlugin.ts @@ -0,0 +1,115 @@ +import { + type BuiltinPlugin, + BuiltinPluginName, + type RawShareContainerPluginOptions +} from "@rspack/binding"; +import { + createBuiltinPlugin, + RspackBuiltinPlugin +} from "../builtin-plugin/base"; +import type { Compilation } from "../Compilation"; +import type { Compiler } from "../Compiler"; +import type { LibraryOptions } from "../config"; +import { encodeName } from "./utils"; + +export type ShareContainerPluginOptions = { + mfName: string; + shareName: string; + version: string; + request: string; + library?: LibraryOptions; + independentShareFileName?: string; +}; + +function assert(condition: any, msg: string): asserts condition { + if (!condition) { + throw new Error(msg); + } +} + +const HOT_UPDATE_SUFFIX = ".hot-update"; + +export class ShareContainerPlugin extends RspackBuiltinPlugin { + name = BuiltinPluginName.ShareContainerPlugin; + filename = ""; + _options: RawShareContainerPluginOptions; + _shareName: string; + _globalName: string; + + constructor(options: ShareContainerPluginOptions) { + super(); + const { shareName, library, request, independentShareFileName } = options; + const version = options.version || "0.0.0"; + this._globalName = encodeName(`${options.mfName}_${shareName}_${version}`); + const fileName = independentShareFileName || `${version}/share-entry.js`; + this._shareName = shareName; + this._options = { + name: shareName, + request: request, + library: (library + ? { ...library, name: this._globalName } + : undefined) || { + type: "var", + name: this._globalName + }, + version, + fileName + }; + } + getData() { + return [this.name, this._globalName]; + } + + raw(compiler: Compiler): BuiltinPlugin { + const { library } = this._options; + if (!compiler.options.output.enabledLibraryTypes!.includes(library.type)) { + compiler.options.output.enabledLibraryTypes!.push(library.type); + } + return createBuiltinPlugin(this.name, this._options); + } + + apply(compiler: Compiler) { + super.apply(compiler); + const shareName = this._shareName; + compiler.hooks.thisCompilation.tap( + this.name, + (compilation: Compilation) => { + compilation.hooks.processAssets.tapPromise( + { + name: "getShareContainerFile" + }, + async () => { + const remoteEntryPoint = compilation.entrypoints.get(shareName); + assert( + remoteEntryPoint, + `Can not get shared ${shareName} entryPoint!` + ); + const remoteEntryNameChunk = compilation.namedChunks.get(shareName); + assert( + remoteEntryNameChunk, + `Can not get shared ${shareName} chunk!` + ); + + const files = Array.from( + remoteEntryNameChunk.files as Iterable + ).filter( + (f: string) => + !f.includes(HOT_UPDATE_SUFFIX) && !f.endsWith(".css") + ); + assert( + files.length > 0, + `no files found for shared ${shareName} chunk` + ); + assert( + files.length === 1, + `shared ${shareName} chunk should not have multiple files!, current files: ${files.join( + "," + )}` + ); + this.filename = files[0]; + } + ); + } + ); + } +} diff --git a/packages/rspack/src/sharing/SharePlugin.ts b/packages/rspack/src/sharing/SharePlugin.ts index 571ebf8bf194..4df525b155e9 100644 --- a/packages/rspack/src/sharing/SharePlugin.ts +++ b/packages/rspack/src/sharing/SharePlugin.ts @@ -1,6 +1,7 @@ import type { Compiler } from "../Compiler"; import { parseOptions } from "../container/options"; import { ConsumeSharedPlugin } from "./ConsumeSharedPlugin"; +import { OptimizeDependencyReferencedExportsPlugin } from "./OptimizeDependencyReferencedExportsPlugin"; import { ProvideSharedPlugin } from "./ProvideSharedPlugin"; import { isRequiredVersion } from "./utils"; @@ -24,62 +25,86 @@ export type SharedConfig = { singleton?: boolean; strictVersion?: boolean; version?: false | string; + treeshake?: boolean; + usedExports?: string[]; + independentShareFileName?: string; }; +export type NormalizedSharedOptions = [string, SharedConfig][]; + +export function normalizeSharedOptions( + shared: Shared +): NormalizedSharedOptions { + return parseOptions( + shared, + (item, key) => { + if (typeof item !== "string") + throw new Error("Unexpected array in shared"); + const config: SharedConfig = + item === key || !isRequiredVersion(item) + ? { + import: item + } + : { + import: key, + requiredVersion: item + }; + return config; + }, + item => item + ); +} + +export function createProvideShareOptions( + normalizedSharedOptions: NormalizedSharedOptions +) { + return normalizedSharedOptions + .filter(([, options]) => options.import !== false) + .map(([key, options]) => ({ + [options.import || key]: { + shareKey: options.shareKey || key, + shareScope: options.shareScope, + version: options.version, + eager: options.eager, + singleton: options.singleton, + requiredVersion: options.requiredVersion, + strictVersion: options.strictVersion + } + })); +} + +export function createConsumeShareOptions( + normalizedSharedOptions: NormalizedSharedOptions +) { + return normalizedSharedOptions.map(([key, options]) => ({ + [key]: { + import: options.import, + shareKey: options.shareKey || key, + shareScope: options.shareScope, + requiredVersion: options.requiredVersion, + strictVersion: options.strictVersion, + singleton: options.singleton, + packageName: options.packageName, + eager: options.eager + } + })); +} export class SharePlugin { _shareScope; _consumes; _provides; _enhanced; + _sharedOptions; constructor(options: SharePluginOptions) { - const sharedOptions = parseOptions( - options.shared, - (item, key) => { - if (typeof item !== "string") - throw new Error("Unexpected array in shared"); - const config: SharedConfig = - item === key || !isRequiredVersion(item) - ? { - import: item - } - : { - import: key, - requiredVersion: item - }; - return config; - }, - item => item - ); - const consumes = sharedOptions.map(([key, options]) => ({ - [key]: { - import: options.import, - shareKey: options.shareKey || key, - shareScope: options.shareScope, - requiredVersion: options.requiredVersion, - strictVersion: options.strictVersion, - singleton: options.singleton, - packageName: options.packageName, - eager: options.eager - } - })); - const provides = sharedOptions - .filter(([, options]) => options.import !== false) - .map(([key, options]) => ({ - [options.import || key]: { - shareKey: options.shareKey || key, - shareScope: options.shareScope, - version: options.version, - eager: options.eager, - singleton: options.singleton, - requiredVersion: options.requiredVersion, - strictVersion: options.strictVersion - } - })); + const sharedOptions = normalizeSharedOptions(options.shared); + const consumes = createConsumeShareOptions(sharedOptions); + const provides = createProvideShareOptions(sharedOptions); this._shareScope = options.shareScope; this._consumes = consumes; this._provides = provides; this._enhanced = options.enhanced ?? false; + this._sharedOptions = sharedOptions; } apply(compiler: Compiler) { @@ -93,5 +118,14 @@ export class SharePlugin { provides: this._provides, enhanced: this._enhanced }).apply(compiler); + + const treeshakeOptions = this._sharedOptions.filter( + ([, config]) => config.treeshake + ); + if (treeshakeOptions.length > 0) { + new OptimizeDependencyReferencedExportsPlugin(treeshakeOptions).apply( + compiler + ); + } } } diff --git a/packages/rspack/src/sharing/TreeShakeSharePlugin.ts b/packages/rspack/src/sharing/TreeShakeSharePlugin.ts new file mode 100644 index 000000000000..f9778fe5cd21 --- /dev/null +++ b/packages/rspack/src/sharing/TreeShakeSharePlugin.ts @@ -0,0 +1,63 @@ +import type { Compiler } from "../Compiler"; +import type { Plugins } from "../config"; +import type { ModuleFederationPluginOptions } from "../container/ModuleFederationPlugin"; +import { IndependentSharePlugin } from "./IndependentSharePlugin"; +import { OptimizeDependencyReferencedExportsPlugin } from "./OptimizeDependencyReferencedExportsPlugin"; +import { normalizeSharedOptions } from "./SharePlugin"; + +export interface TreeshakeSharePluginOptions { + mfConfig: ModuleFederationPluginOptions; + plugins?: Plugins; + reshake?: boolean; +} + +export class TreeshakeSharePlugin { + mfConfig: ModuleFederationPluginOptions; + outputDir: string; + plugins?: Plugins; + reshake?: boolean; + + name = "TreeshakeSharePlugin"; + constructor(options: TreeshakeSharePluginOptions) { + const { mfConfig, plugins, reshake } = options; + this.mfConfig = mfConfig; + this.outputDir = mfConfig.independentShareDir || "independent-packages"; + this.plugins = plugins; + this.reshake = Boolean(reshake); + } + + apply(compiler: Compiler) { + const { mfConfig, outputDir, plugins, reshake } = this; + const { name, shared, library } = mfConfig; + if (!shared) { + return; + } + const sharedOptions = normalizeSharedOptions(shared); + if (!sharedOptions.length) { + return; + } + + if (!reshake) { + new OptimizeDependencyReferencedExportsPlugin( + sharedOptions, + mfConfig.injectUsedExports, + mfConfig.manifest + ).apply(compiler); + } + + if ( + sharedOptions.some( + ([_, config]) => config.treeshake && config.import !== false + ) + ) { + new IndependentSharePlugin({ + name: name, + shared: shared, + outputDir, + plugins, + treeshake: reshake, + library + }).apply(compiler); + } + } +} diff --git a/packages/rspack/src/sharing/utils.ts b/packages/rspack/src/sharing/utils.ts index 3a89dbd573f3..83d5d62e145d 100644 --- a/packages/rspack/src/sharing/utils.ts +++ b/packages/rspack/src/sharing/utils.ts @@ -3,3 +3,16 @@ const VERSION_PATTERN_REGEXP = /^([\d^=v<>~]|[*xX]$)/; export function isRequiredVersion(str: string) { return VERSION_PATTERN_REGEXP.test(str); } + +export const encodeName = function ( + name: string, + prefix = "", + withExt = false +): string { + const ext = withExt ? ".js" : ""; + return `${prefix}${name + .replace(/@/g, "scope_") + .replace(/-/g, "_") + .replace(/\//g, "__") + .replace(/\./g, "")}${ext}`; +}; diff --git a/packages/rspack/src/taps/types.ts b/packages/rspack/src/taps/types.ts index 2e914497d233..aa56f3cb73fb 100644 --- a/packages/rspack/src/taps/types.ts +++ b/packages/rspack/src/taps/types.ts @@ -19,6 +19,7 @@ type RegisterTapKeys< T, L extends string > = T extends keyof binding.RegisterJsTaps ? (T extends L ? T : never) : never; + type PartialRegisters = { [K in RegisterTapKeys< keyof binding.RegisterJsTaps, diff --git a/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/index.js b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/index.js new file mode 100644 index 000000000000..d3ae599c684e --- /dev/null +++ b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/index.js @@ -0,0 +1,17 @@ +const fs = __non_webpack_require__("fs"); +const path = __non_webpack_require__("path"); + +it("should emit collect share entry asset with expected requests", async () => { + await import('./module'); + const assetPath = path.join(__dirname, "collect-share-entries.json"); + expect(fs.existsSync(assetPath)).toBe(true); + + const content = JSON.parse(fs.readFileSync(assetPath, "utf-8")); + expect(content.shared).toBeDefined(); + + const collectInfo = content.shared["xreact"]; + expect(collectInfo).toBeDefined(); + expect(collectInfo.shareScope).toBe("default"); + expect(collectInfo.requests[0][0]).toContain("sharing/collect-share-entry-plugin/node_modules/xreact/index.js"); + expect(collectInfo.requests[0][1]).toEqual("1.0.0"); +}); diff --git a/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/module.js b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/module.js new file mode 100644 index 000000000000..70a7cb73256d --- /dev/null +++ b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/module.js @@ -0,0 +1,3 @@ +import "xreact"; + +export default "collect-share-entry-plugin"; diff --git a/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/node_modules/xreact/index.js b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/node_modules/xreact/index.js new file mode 100644 index 000000000000..ecafa5007699 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/node_modules/xreact/index.js @@ -0,0 +1,4 @@ +module.exports = { + default: "react-mock", + version: "1.0.0" +}; diff --git a/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/node_modules/xreact/package.json b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/node_modules/xreact/package.json new file mode 100644 index 000000000000..2b5f633e943a --- /dev/null +++ b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/node_modules/xreact/package.json @@ -0,0 +1,5 @@ +{ + "name": "xreact", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/package.json b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/package.json new file mode 100644 index 000000000000..2f0551fad629 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "xreact": "1.2.3" + } +} diff --git a/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/rspack.config.js b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/rspack.config.js new file mode 100644 index 000000000000..2163d1f17105 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/rspack.config.js @@ -0,0 +1,36 @@ + +const { sharing } = require("@rspack/core"); + +const { CollectShareEntryPlugin, SharePlugin } = sharing; + +const sharedOptions = [ + [ + "xreact", + { + import: "xreact", + shareKey: "xreact", + shareScope: "default", + version: "1.0.0" + } + ] +]; + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + plugins: [ + new SharePlugin({ + shareScope: "default", + shared: { + xreact: { + import: "xreact", + shareKey: "xreact", + shareScope: "default", + version: "1.0.0" + } + }, + }), + new CollectShareEntryPlugin({ + sharedOptions + }) + ] +}; diff --git a/tests/rspack-test/configCases/sharing/reshake-share/index.js b/tests/rspack-test/configCases/sharing/reshake-share/index.js new file mode 100644 index 000000000000..d1143f0d5890 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/reshake-share/index.js @@ -0,0 +1,61 @@ +const fs = __non_webpack_require__("fs"); +const path = __non_webpack_require__("path"); + +const independentShareDir = path.join( + __dirname, + "independent-packages" +); + +const customPluginAssetPath = path.join( + independentShareDir, + "apply-plugin.json" +); + +const uiLibShareContainerPath = path.join( + independentShareDir, + "ui_lib/1.0.0", + "share-entry.js" +); + +const uiLibDepShareContainerPath = path.join( + independentShareDir, + "ui_lib_dep/1.0.0", + "share-entry.js" +); + + +it("should build independent share file", () => { + expect(fs.existsSync(uiLibShareContainerPath)).toBe(true); + expect(fs.existsSync(uiLibDepShareContainerPath)).toBe(true); + expect(fs.existsSync(customPluginAssetPath)).toBe(true); +}); + +it("reshake share container should only have specify usedExports", async () => { + const uiLibDepShareContainerModule = __non_webpack_require__(uiLibDepShareContainerPath).reshake_share_ui_lib_dep_100; + await uiLibDepShareContainerModule.init({},{ + installInitialConsumes: async ()=>{ + return 'call init' + } + }); + const shareModules = await uiLibDepShareContainerModule.get(); + expect(shareModules.Message).toBe('Message'); + expect(shareModules.Text).not.toBeDefined(); +}); + + +it("correct handle share dep while reshake", async () => { + const uiLibShareContainerModule = __non_webpack_require__(uiLibShareContainerPath).reshake_share_ui_lib_100; + await uiLibShareContainerModule.init({},{ + installInitialConsumes: async ({webpackRequire})=>{ + webpackRequire.m['webpack/sharing/consume/default/ui-lib-dep'] = (m)=>{ + m.exports = { + Message: 'Message', + } + } + return 'call init' + } + }); + const shareModules = await uiLibShareContainerModule.get(); + expect(shareModules.Badge).toBe('Badge'); + expect(shareModules.MessagePro).toBe('MessagePro'); +}); diff --git a/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib-dep/index.js b/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib-dep/index.js new file mode 100644 index 000000000000..c6939e2c6206 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib-dep/index.js @@ -0,0 +1,9 @@ +export const Message = 'Message'; +export const Spin = 'Spin' +export const Text = 'Text' + +export default { + Message, + Spin, + Text +} diff --git a/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib-dep/package.json b/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib-dep/package.json new file mode 100644 index 000000000000..436a74b6795a --- /dev/null +++ b/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib-dep/package.json @@ -0,0 +1,6 @@ +{ + "name": "ui-lib-dep", + "main": "./index.js", + "version": "1.0.0", + "sideEffects": false +} diff --git a/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib/index.js b/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib/index.js new file mode 100644 index 000000000000..c158056185a4 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib/index.js @@ -0,0 +1,16 @@ +import {Message,Spin} from 'ui-lib-dep'; + +export const Button = 'Button'; +export const List = 'List' +export const Badge = 'Badge' + +export const MessagePro = `${Message}Pro`; +export const SpinPro = `${Spin}Pro`; + +export default { + Button, + List, + Badge, + MessagePro, + SpinPro +} diff --git a/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib/package.json b/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib/package.json new file mode 100644 index 000000000000..90f9db2691b3 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/reshake-share/node_modules/ui-lib/package.json @@ -0,0 +1,6 @@ +{ + "name": "ui-lib", + "main": "./index.js", + "version": "1.0.0", + "sideEffects": false +} diff --git a/tests/rspack-test/configCases/sharing/reshake-share/rspack.config.js b/tests/rspack-test/configCases/sharing/reshake-share/rspack.config.js new file mode 100644 index 000000000000..95a93ba1d84c --- /dev/null +++ b/tests/rspack-test/configCases/sharing/reshake-share/rspack.config.js @@ -0,0 +1,55 @@ +const { TreeshakeSharePlugin } = require("@rspack/core").sharing; + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + // entry:'./index.js', + optimization: { + // minimize:false, + chunkIds: "named", + moduleIds: "named" + }, + output: { + chunkFilename: "[id].js" + }, + plugins: [ + new TreeshakeSharePlugin({ + reshake: true, + plugins: [ + { + apply(compiler) { + compiler.hooks.thisCompilation.tap('applyPlugins', (compilation) => { + compilation.hooks.processAssets.tapPromise( + { + name: 'applyPlugins', + }, + async () => { + compilation.emitAsset('apply-plugin.json', new compilation.compiler.rspack.sources.RawSource(JSON.stringify({ + reshake: true + }))) + }) + }) + } + } + ], + mfConfig: { + name: 'reshake_share', + library: { + type: 'commonjs2', + }, + shared: { + 'ui-lib': { + treeshake: true, + requiredVersion: '*', + usedExports:['Badge','MessagePro'] + }, + 'ui-lib-dep': { + treeshake: true, + requiredVersion: '*', + usedExports:['Message'] + } + }, + + } + }) + ] +}; diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/index.js b/tests/rspack-test/configCases/sharing/share-container-plugin-test/index.js new file mode 100644 index 000000000000..b0eb3cf43ecf --- /dev/null +++ b/tests/rspack-test/configCases/sharing/share-container-plugin-test/index.js @@ -0,0 +1,26 @@ +import uiLib from 'ui-lib'; + +const fs = __non_webpack_require__("fs"); +const path = __non_webpack_require__("path"); + +it("should generate share container bundle with expected content", async () => { + const bundlePath = path.join( + __dirname, + "1.0.0", + "share-entry.js" + ); + const shareContainer = __non_webpack_require__(bundlePath).ui_lib; + expect(Object.getOwnPropertyNames(shareContainer).sort()).toEqual(['get','init']); + __webpack_require__.consumesLoadingData = { + initialConsumes: { + } + } + const response = await shareContainer.init({},{ + installInitialConsumes: async ()=>{ + return 'call init' + } + }); + expect(response).toBe('call init'); + const shareModules = await shareContainer.get(); + expect(['Button', 'Badge', 'List', 'MessagePro', 'SpinPro'].every(m=>Boolean(shareModules[m]))); +}); diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/index.js b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/index.js new file mode 100644 index 000000000000..bf1ed03efa91 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/index.js @@ -0,0 +1,7 @@ +export const Message = 'Message'; +export const Spin = 'Spin' + +export default { + Message, + Spin +} diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/package.json b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/package.json new file mode 100644 index 000000000000..436a74b6795a --- /dev/null +++ b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/package.json @@ -0,0 +1,6 @@ +{ + "name": "ui-lib-dep", + "main": "./index.js", + "version": "1.0.0", + "sideEffects": false +} diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/index.js b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/index.js new file mode 100644 index 000000000000..c158056185a4 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/index.js @@ -0,0 +1,16 @@ +import {Message,Spin} from 'ui-lib-dep'; + +export const Button = 'Button'; +export const List = 'List' +export const Badge = 'Badge' + +export const MessagePro = `${Message}Pro`; +export const SpinPro = `${Spin}Pro`; + +export default { + Button, + List, + Badge, + MessagePro, + SpinPro +} diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/package.json b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/package.json new file mode 100644 index 000000000000..90f9db2691b3 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/package.json @@ -0,0 +1,6 @@ +{ + "name": "ui-lib", + "main": "./index.js", + "version": "1.0.0", + "sideEffects": false +} diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/rspack.config.js b/tests/rspack-test/configCases/sharing/share-container-plugin-test/rspack.config.js new file mode 100644 index 000000000000..c2223ff34cd1 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/share-container-plugin-test/rspack.config.js @@ -0,0 +1,34 @@ +const path = require("path"); + +const { sharing } = require("@rspack/core"); + +const { ShareContainerPlugin,ConsumeSharedPlugin } = sharing; + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + plugins: [ + new ShareContainerPlugin({ + library: { + name:'ui_lib', + type:'commonjs2' + }, + mfName: "host", + shareName: "ui-lib", + version: "1.0.0", + request: path.resolve(__dirname, "node_modules/ui-lib/index.js") + }), + + new ConsumeSharedPlugin({ + consumes: [ + ['ui-lib', { + import: 'ui-lib', + shareScope: 'ui-lib', + }], + ['ui-lib-dep', { + import: 'ui-lib-dep', + shareScope: 'ui-lib-dep', + }], + ] + }) + ] +}; diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/index.js b/tests/rspack-test/configCases/sharing/treeshake-share/index.js new file mode 100644 index 000000000000..39dd2f080766 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/treeshake-share/index.js @@ -0,0 +1,73 @@ +const fs = __non_webpack_require__("fs"); +const path = __non_webpack_require__("path"); + +it("should treeshake ui-lib correctly", async () => { + const { Button } = await import("ui-lib"); + expect(Button).toEqual('Button'); + + const bundlePath = path.join( + __dirname, + "node_modules_ui-lib_index_js.js" + ); + const bundleContent = fs.readFileSync(bundlePath, "utf-8"); + expect(bundleContent).toContain("Button"); + expect(bundleContent).not.toContain("List"); +}); + +it("should treeshake ui-lib2 correctly", async () => { + const uiLib2 = await import("ui-lib2"); + expect(uiLib2.List).toEqual('List'); + + const bundlePath = path.join( + __dirname, + "node_modules_ui-lib2_index_js.js" + ); + const bundleContent = fs.readFileSync(bundlePath, "utf-8"); + expect(bundleContent).toContain("List"); + expect(bundleContent).not.toContain("Button"); + expect(bundleContent).not.toContain("Badge"); +}); + +it("should not treeshake ui-lib-side-effect if not set sideEffect:false ", async () => { + const uiLibSideEffect = await import("ui-lib-side-effect"); + expect(uiLibSideEffect.List).toEqual('List'); + + const bundlePath = path.join( + __dirname, + "node_modules_ui-lib-side-effect_index_js.js" + ); + const bundleContent = fs.readFileSync(bundlePath, "utf-8"); + expect(bundleContent).toContain("List"); + expect(bundleContent).toContain("Button"); + expect(bundleContent).toContain("Badge"); +}); + +it("should inject usedExports into entry chunk by default", async () => { + expect(__webpack_require__.federation.usedExports['ui-lib']['main'].sort()).toEqual([ 'Badge', 'Button' ]) +}); + +it("should inject usedExports into manifest and stats if enable manifest", async () => { + const { Button } = await import("ui-lib"); + expect(Button).toEqual('Button'); + + const manifestPath = path.join( + __dirname, + "mf-manifest.json" + ); + const manifestContent = JSON.parse(fs.readFileSync(manifestPath, "utf-8")); + expect(JSON.stringify(manifestContent.shared.find(s=>s.name === 'ui-lib').usedExports.sort())).toEqual(JSON.stringify([ + "Badge", + "Button" + ])); + + const statsPath = path.join( + __dirname, + "mf-stats.json" + ); + const statsContent = JSON.parse(fs.readFileSync(statsPath, "utf-8")); + expect(JSON.stringify(statsContent.shared.find(s=>s.name === 'ui-lib').usedExports.sort())).toEqual(JSON.stringify([ + "Badge", + "Button" + ])); +}); + diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib-side-effect/index.js b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib-side-effect/index.js new file mode 100644 index 000000000000..0568c23344ad --- /dev/null +++ b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib-side-effect/index.js @@ -0,0 +1,12 @@ +export const Button = 'Button'; +export const List = 'List' +export const Badge = 'Badge' + +globalThis.Button = Button; +globalThis.List = List; +globalThis.Badge = Badge; +export default { + Button, + List, + Badge +} diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib-side-effect/package.json b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib-side-effect/package.json new file mode 100644 index 000000000000..08f52aa758de --- /dev/null +++ b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib-side-effect/package.json @@ -0,0 +1,6 @@ +{ + "name": "ui-lib-side-effect", + "main": "./index.js", + "version": "1.0.0", + "sideEffects": true +} diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib/index.js b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib/index.js new file mode 100644 index 000000000000..9dd1824aaaad --- /dev/null +++ b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib/index.js @@ -0,0 +1,9 @@ +export const Button = 'Button'; +export const List = 'List' +export const Badge = 'Badge' + +export default { + Button, + List, + Badge +} diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib/package.json b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib/package.json new file mode 100644 index 000000000000..90f9db2691b3 --- /dev/null +++ b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib/package.json @@ -0,0 +1,6 @@ +{ + "name": "ui-lib", + "main": "./index.js", + "version": "1.0.0", + "sideEffects": false +} diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib2/index.js b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib2/index.js new file mode 100644 index 000000000000..9dd1824aaaad --- /dev/null +++ b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib2/index.js @@ -0,0 +1,9 @@ +export const Button = 'Button'; +export const List = 'List' +export const Badge = 'Badge' + +export default { + Button, + List, + Badge +} diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib2/package.json b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib2/package.json new file mode 100644 index 000000000000..1299fb07efbe --- /dev/null +++ b/tests/rspack-test/configCases/sharing/treeshake-share/node_modules/ui-lib2/package.json @@ -0,0 +1,6 @@ +{ + "name": "ui-lib2", + "main": "./index.js", + "version": "1.0.0", + "sideEffects": false +} diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js b/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js new file mode 100644 index 000000000000..3baee7941e7e --- /dev/null +++ b/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js @@ -0,0 +1,50 @@ +const path = require("path"); + +const { container ,sharing} = require("@rspack/core"); + +const {OptimizeDependencyReferencedExportsPlugin } = sharing; +const { ModuleFederationPlugin } = container; + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + optimization:{ + minimize: true, + chunkIds:'named', + moduleIds: 'named' + }, + output: { + chunkFilename: "[id].js" + }, + entry: { + main: "./index.js" + }, + plugins: [ + new ModuleFederationPlugin({ + name:'treeshake_share', + manifest: true, + shared: { + 'ui-lib': { + requiredVersion:'*' + }, + 'ui-lib2': { + requiredVersion:'*' + }, + 'ui-lib-side-effect': { + requiredVersion:'*' + } + } + }), + new OptimizeDependencyReferencedExportsPlugin([ + ['ui-lib',{ + treeshake:true, + usedExports: ['Badge'] + }], + ['ui-lib2',{ + treeshake:true, + }], + ['ui-lib-side-effect',{ + treeshake:true, + }] + ]) + ] +}; From eebe90a4dd600ced56ade26aa561d55c2a26cedf Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Thu, 20 Nov 2025 16:46:47 +0800 Subject: [PATCH 02/13] chore: add missed file --- crates/node_binding/rspack.wasi-browser.js | 61 +++++++++++++++++++++- crates/node_binding/rspack.wasi.cjs | 61 +++++++++++++++++++++- 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/crates/node_binding/rspack.wasi-browser.js b/crates/node_binding/rspack.wasi-browser.js index ee65959b37bc..e3e5c0a99d48 100644 --- a/crates/node_binding/rspack.wasi-browser.js +++ b/crates/node_binding/rspack.wasi-browser.js @@ -63,4 +63,63 @@ const { }, }) export default __napiModule.exports - +export const Assets = __napiModule.exports.Assets +export const AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock +export const Chunk = __napiModule.exports.Chunk +export const ChunkGraph = __napiModule.exports.ChunkGraph +export const ChunkGroup = __napiModule.exports.ChunkGroup +export const Chunks = __napiModule.exports.Chunks +export const CodeGenerationResult = __napiModule.exports.CodeGenerationResult +export const CodeGenerationResults = __napiModule.exports.CodeGenerationResults +export const ConcatenatedModule = __napiModule.exports.ConcatenatedModule +export const ContextModule = __napiModule.exports.ContextModule +export const Dependency = __napiModule.exports.Dependency +export const Diagnostics = __napiModule.exports.Diagnostics +export const EntryDataDto = __napiModule.exports.EntryDataDto +export const EntryDataDTO = __napiModule.exports.EntryDataDTO +export const EntryDependency = __napiModule.exports.EntryDependency +export const EntryOptionsDto = __napiModule.exports.EntryOptionsDto +export const EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO +export const ExternalModule = __napiModule.exports.ExternalModule +export const JsCompilation = __napiModule.exports.JsCompilation +export const JsCompiler = __napiModule.exports.JsCompiler +export const JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData +export const JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData +export const JsDependencies = __napiModule.exports.JsDependencies +export const JsEntries = __napiModule.exports.JsEntries +export const JsExportsInfo = __napiModule.exports.JsExportsInfo +export const JsModuleGraph = __napiModule.exports.JsModuleGraph +export const JsResolver = __napiModule.exports.JsResolver +export const JsResolverFactory = __napiModule.exports.JsResolverFactory +export const JsStats = __napiModule.exports.JsStats +export const KnownBuildInfo = __napiModule.exports.KnownBuildInfo +export const Module = __napiModule.exports.Module +export const ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection +export const NativeWatcher = __napiModule.exports.NativeWatcher +export const NativeWatchResult = __napiModule.exports.NativeWatchResult +export const NormalModule = __napiModule.exports.NormalModule +export const RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx +export const ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData +export const ResolverFactory = __napiModule.exports.ResolverFactory +export const Sources = __napiModule.exports.Sources +export const VirtualFileStore = __napiModule.exports.VirtualFileStore +export const JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore +export const async = __napiModule.exports.async +export const BuiltinPluginName = __napiModule.exports.BuiltinPluginName +export const cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace +export const EnforceExtension = __napiModule.exports.EnforceExtension +export const EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION +export const formatDiagnostic = __napiModule.exports.formatDiagnostic +export const JsLoaderState = __napiModule.exports.JsLoaderState +export const JsRspackSeverity = __napiModule.exports.JsRspackSeverity +export const loadBrowserslist = __napiModule.exports.loadBrowserslist +export const minify = __napiModule.exports.minify +export const minifySync = __napiModule.exports.minifySync +export const RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports +export const RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType +export const registerGlobalTrace = __napiModule.exports.registerGlobalTrace +export const RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind +export const sync = __napiModule.exports.sync +export const syncTraceEvent = __napiModule.exports.syncTraceEvent +export const transform = __napiModule.exports.transform +export const transformSync = __napiModule.exports.transformSync diff --git a/crates/node_binding/rspack.wasi.cjs b/crates/node_binding/rspack.wasi.cjs index 1ad96db4aac4..a251ce4d0d7d 100644 --- a/crates/node_binding/rspack.wasi.cjs +++ b/crates/node_binding/rspack.wasi.cjs @@ -108,4 +108,63 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule }, }) module.exports = __napiModule.exports - +module.exports.Assets = __napiModule.exports.Assets +module.exports.AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock +module.exports.Chunk = __napiModule.exports.Chunk +module.exports.ChunkGraph = __napiModule.exports.ChunkGraph +module.exports.ChunkGroup = __napiModule.exports.ChunkGroup +module.exports.Chunks = __napiModule.exports.Chunks +module.exports.CodeGenerationResult = __napiModule.exports.CodeGenerationResult +module.exports.CodeGenerationResults = __napiModule.exports.CodeGenerationResults +module.exports.ConcatenatedModule = __napiModule.exports.ConcatenatedModule +module.exports.ContextModule = __napiModule.exports.ContextModule +module.exports.Dependency = __napiModule.exports.Dependency +module.exports.Diagnostics = __napiModule.exports.Diagnostics +module.exports.EntryDataDto = __napiModule.exports.EntryDataDto +module.exports.EntryDataDTO = __napiModule.exports.EntryDataDTO +module.exports.EntryDependency = __napiModule.exports.EntryDependency +module.exports.EntryOptionsDto = __napiModule.exports.EntryOptionsDto +module.exports.EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO +module.exports.ExternalModule = __napiModule.exports.ExternalModule +module.exports.JsCompilation = __napiModule.exports.JsCompilation +module.exports.JsCompiler = __napiModule.exports.JsCompiler +module.exports.JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData +module.exports.JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData +module.exports.JsDependencies = __napiModule.exports.JsDependencies +module.exports.JsEntries = __napiModule.exports.JsEntries +module.exports.JsExportsInfo = __napiModule.exports.JsExportsInfo +module.exports.JsModuleGraph = __napiModule.exports.JsModuleGraph +module.exports.JsResolver = __napiModule.exports.JsResolver +module.exports.JsResolverFactory = __napiModule.exports.JsResolverFactory +module.exports.JsStats = __napiModule.exports.JsStats +module.exports.KnownBuildInfo = __napiModule.exports.KnownBuildInfo +module.exports.Module = __napiModule.exports.Module +module.exports.ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection +module.exports.NativeWatcher = __napiModule.exports.NativeWatcher +module.exports.NativeWatchResult = __napiModule.exports.NativeWatchResult +module.exports.NormalModule = __napiModule.exports.NormalModule +module.exports.RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx +module.exports.ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData +module.exports.ResolverFactory = __napiModule.exports.ResolverFactory +module.exports.Sources = __napiModule.exports.Sources +module.exports.VirtualFileStore = __napiModule.exports.VirtualFileStore +module.exports.JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore +module.exports.async = __napiModule.exports.async +module.exports.BuiltinPluginName = __napiModule.exports.BuiltinPluginName +module.exports.cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace +module.exports.EnforceExtension = __napiModule.exports.EnforceExtension +module.exports.EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION +module.exports.formatDiagnostic = __napiModule.exports.formatDiagnostic +module.exports.JsLoaderState = __napiModule.exports.JsLoaderState +module.exports.JsRspackSeverity = __napiModule.exports.JsRspackSeverity +module.exports.loadBrowserslist = __napiModule.exports.loadBrowserslist +module.exports.minify = __napiModule.exports.minify +module.exports.minifySync = __napiModule.exports.minifySync +module.exports.RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports +module.exports.RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType +module.exports.registerGlobalTrace = __napiModule.exports.registerGlobalTrace +module.exports.RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind +module.exports.sync = __napiModule.exports.sync +module.exports.syncTraceEvent = __napiModule.exports.syncTraceEvent +module.exports.transform = __napiModule.exports.transform +module.exports.transformSync = __napiModule.exports.transformSync From 6540f44543be52db54f977b01e392ef14797a9bb Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Fri, 21 Nov 2025 18:36:14 +0800 Subject: [PATCH 03/13] chore: simplify collect shared logic --- .../src/sharing/collect_share_entry_plugin.rs | 116 ++++++++++++++---- .../src/sharing/IndependentSharePlugin.ts | 2 +- 2 files changed, 92 insertions(+), 26 deletions(-) diff --git a/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs b/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs index 954a0a267f94..120ffc4c684b 100644 --- a/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs +++ b/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs @@ -8,8 +8,9 @@ use camino::Utf8Path; use regex::Regex; use rspack_core::{ BoxModule, Compilation, CompilationAsset, CompilationProcessAssets, CompilerCompilation, - CompilerThisCompilation, Context, DependencyCategory, DependencyType, ModuleFactoryCreateData, - NormalModuleFactoryFactorize, Plugin, ResolveOptionsWithDependencyType, ResolveResult, Resolver, + CompilerThisCompilation, Context, DependenciesBlock, DependencyCategory, DependencyType, Module, + ModuleFactoryCreateData, NormalModuleFactoryFactorize, Plugin, ResolveOptionsWithDependencyType, + ResolveResult, Resolver, rspack_sources::{RawStringSource, SourceExt}, }; use rspack_error::{Diagnostic, Result, error}; @@ -322,37 +323,102 @@ async fn this_compilation( #[plugin_hook(CompilationProcessAssets for CollectShareEntryPlugin)] async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { - let entries = self.resolved_entries.read().await; + dbg!("process_assets"); + // 遍历图中的 ConsumeSharedModule,收集其 fallback 映射到的真实模块地址 + let module_graph = compilation.get_module_graph(); + let mut ordered_requests: FxHashMap> = FxHashMap::default(); + let mut share_scopes: FxHashMap = FxHashMap::default(); + + for (id, module) in module_graph.modules().into_iter() { + let module_type = module.module_type(); + dbg!(&module_type); + if !matches!(module_type, rspack_core::ModuleType::ConsumeShared) { + continue; + } - let mut shared: FxHashMap<&str, CollectShareEntryAssetItem<'_>> = FxHashMap::default(); - let mut ordered_requests: FxHashMap<&str, Vec<[String; 2]>> = FxHashMap::default(); + if let Some(consume) = module + .as_any() + .downcast_ref::() + { + // 从 readable_identifier 中解析 share_scope 与 share_key + let ident = consume.readable_identifier(&Context::default()).to_string(); + dbg!(&ident); + // 形如: "consume shared module ({scope}) {share_key}@..." + let (scope, key) = { + let mut scope = String::new(); + let mut key = String::new(); + if let Some(start) = ident.find("(") + && let Some(end) = ident.find(")") + && end > start + { + scope = ident[start + 1..end].to_string(); + } + if let Some(pos) = ident.find(") ") { + let rest = &ident[pos + 2..]; + let at = rest.find('@').unwrap_or(rest.len()); + key = rest[..at].to_string(); + } + (scope, key) + }; - for (share_key, record) in entries.iter() { - if record.requests.is_empty() { - continue; + if key.is_empty() { + continue; + } + + // 收集该 consume 模块的依赖与异步块中的依赖对应的真实模块 + let mut target_modules = Vec::new(); + for dep_id in consume.get_dependencies() { + if let Some(target_id) = module_graph.module_identifier_by_dependency_id(dep_id) { + target_modules.push(*target_id); + } + } + for block_id in consume.get_blocks() { + if let Some(block) = module_graph.block_by_id(block_id) { + for dep_id in block.get_dependencies() { + if let Some(target_id) = module_graph.module_identifier_by_dependency_id(dep_id) { + target_modules.push(*target_id); + } + } + } + } + + // 将真实模块的资源路径加入到映射集合,并推断版本 + let mut reqs = ordered_requests.remove(&key).unwrap_or_default(); + for target_id in target_modules { + if let Some(target) = module_graph.module_by_identifier(&target_id) { + if let Some(normal) = target.as_any().downcast_ref::() { + let resource = normal.resource_resolved_data().resource().to_string(); + let version = self + .infer_version(&resource) + .await + .unwrap_or_else(|| "".to_string()); + let pair = [resource, version]; + if !reqs.iter().any(|p| p[0] == pair[0] && p[1] == pair[1]) { + reqs.push(pair); + } + } + } + } + reqs.sort_by(|a, b| a[0].cmp(&b[0]).then(a[1].cmp(&b[1]))); + ordered_requests.insert(key.clone(), reqs); + if !scope.is_empty() { + share_scopes.insert(key.clone(), scope); + } } - let mut requests: Vec<[String; 2]> = record - .requests - .iter() - .map(|item| [item.request.clone(), item.version.clone()]) - .collect(); - requests.sort_by(|a, b| a[0].cmp(&b[0]).then(a[1].cmp(&b[1]))); - ordered_requests.insert(share_key.as_str(), requests); } - for (share_key, record) in entries.iter() { - if record.requests.is_empty() { - continue; - } - let requests = ordered_requests - .get(share_key.as_str()) - .map(|v| v.as_slice()) - .unwrap_or(&[]); + // 生成资产内容 + let mut shared: FxHashMap<&str, CollectShareEntryAssetItem<'_>> = FxHashMap::default(); + for (share_key, requests) in ordered_requests.iter() { + let scope = share_scopes + .get(share_key) + .map(|s| s.as_str()) + .unwrap_or(""); shared.insert( share_key.as_str(), CollectShareEntryAssetItem { - share_scope: record.share_scope.as_str(), - requests, + share_scope: scope, + requests: requests.as_slice(), }, ); } diff --git a/packages/rspack/src/sharing/IndependentSharePlugin.ts b/packages/rspack/src/sharing/IndependentSharePlugin.ts index c9791b579b46..7fbc8acec93f 100644 --- a/packages/rspack/src/sharing/IndependentSharePlugin.ts +++ b/packages/rspack/src/sharing/IndependentSharePlugin.ts @@ -343,7 +343,7 @@ export class IndependentSharePlugin { ) .map(([key, options]) => ({ [key]: { - import: false, + import: extraOptions ? options.import : false, shareKey: options.shareKey || key, shareScope: options.shareScope, requiredVersion: options.requiredVersion, From b37d5a7647d8490295fe2670424f6ad33fea3220 Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Mon, 24 Nov 2025 11:24:37 +0800 Subject: [PATCH 04/13] fix: only set import:false when re-build share entry --- .../src/sharing/collect_share_entry_plugin.rs | 7 +------ packages/rspack/src/sharing/IndependentSharePlugin.ts | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs b/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs index 120ffc4c684b..54939ddcfd15 100644 --- a/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs +++ b/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs @@ -323,15 +323,13 @@ async fn this_compilation( #[plugin_hook(CompilationProcessAssets for CollectShareEntryPlugin)] async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { - dbg!("process_assets"); // 遍历图中的 ConsumeSharedModule,收集其 fallback 映射到的真实模块地址 let module_graph = compilation.get_module_graph(); let mut ordered_requests: FxHashMap> = FxHashMap::default(); let mut share_scopes: FxHashMap = FxHashMap::default(); - for (id, module) in module_graph.modules().into_iter() { + for (_id, module) in module_graph.modules().into_iter() { let module_type = module.module_type(); - dbg!(&module_type); if !matches!(module_type, rspack_core::ModuleType::ConsumeShared) { continue; } @@ -342,7 +340,6 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { { // 从 readable_identifier 中解析 share_scope 与 share_key let ident = consume.readable_identifier(&Context::default()).to_string(); - dbg!(&ident); // 形如: "consume shared module ({scope}) {share_key}@..." let (scope, key) = { let mut scope = String::new(); @@ -360,11 +357,9 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { } (scope, key) }; - if key.is_empty() { continue; } - // 收集该 consume 模块的依赖与异步块中的依赖对应的真实模块 let mut target_modules = Vec::new(); for dep_id in consume.get_dependencies() { diff --git a/packages/rspack/src/sharing/IndependentSharePlugin.ts b/packages/rspack/src/sharing/IndependentSharePlugin.ts index 7fbc8acec93f..45517036c2bb 100644 --- a/packages/rspack/src/sharing/IndependentSharePlugin.ts +++ b/packages/rspack/src/sharing/IndependentSharePlugin.ts @@ -343,7 +343,7 @@ export class IndependentSharePlugin { ) .map(([key, options]) => ({ [key]: { - import: extraOptions ? options.import : false, + import: !extraOptions ? options.import : false, shareKey: options.shareKey || key, shareScope: options.shareScope, requiredVersion: options.requiredVersion, From 8085944d8784f08dd01f55e2067c02bfc9f3bbe2 Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Mon, 24 Nov 2025 11:37:55 +0800 Subject: [PATCH 05/13] chore: fix lint --- packages/rspack/src/sharing/IndependentSharePlugin.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/rspack/src/sharing/IndependentSharePlugin.ts b/packages/rspack/src/sharing/IndependentSharePlugin.ts index 45517036c2bb..42c38a0825a5 100644 --- a/packages/rspack/src/sharing/IndependentSharePlugin.ts +++ b/packages/rspack/src/sharing/IndependentSharePlugin.ts @@ -30,7 +30,7 @@ const filterPlugin = (plugin: Plugins[0]) => { if (!plugin) { return true; } - const pluginName = plugin["name"] || plugin["constructor"]?.name; + const pluginName = plugin.name || plugin.constructor?.name; if (!pluginName) { return true; } @@ -169,11 +169,10 @@ export class IndependentSharePlugin { } apply(compiler: Compiler) { - compiler.hooks.beforeRun.tapAsync( + compiler.hooks.beforeRun.tapPromise( "IndependentSharePlugin", - async (compiler, callback) => { + async compiler => { await this.createIndependentCompilers(compiler); - callback(); } ); @@ -202,12 +201,12 @@ export class IndependentSharePlugin { return; } const statsContent = JSON.parse(stats.source.source().toString()) as { - shared: Array<{ + shared: { name: string; version: string; fallback?: string; fallbackName?: string; - }>; + }[]; }; const { shared } = statsContent; From 6f0e9250680655ed8e7a2d61835aa2a9d191c2cc Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Mon, 24 Nov 2025 18:46:58 +0800 Subject: [PATCH 06/13] chore: fix ut --- crates/node_binding/napi-binding.d.ts | 18 +-- .../src/raw_options/raw_builtins/mod.rs | 30 ++-- .../src/raw_options/raw_builtins/raw_mf.rs | 18 ++- crates/rspack_plugin_mf/src/lib.rs | 9 +- crates/rspack_plugin_mf/src/manifest/mod.rs | 2 +- ...ugin.rs => collect_shared_entry_plugin.rs} | 135 +++++++----------- crates/rspack_plugin_mf/src/sharing/mod.rs | 6 +- .../sharing/share_container_entry_module.rs | 6 +- ...> shared_used_exports_optimizer_plugin.rs} | 29 ++-- ..._used_exports_optimizer_runtime_module.rs} | 6 +- packages/rspack/etc/core.api.md | 90 ++---------- .../src/container/ModuleFederationPlugin.ts | 20 ++- packages/rspack/src/exports.ts | 14 +- ...yPlugin.ts => CollectSharedEntryPlugin.ts} | 17 +-- ...rePlugin.ts => IndependentSharedPlugin.ts} | 94 ++++++------ packages/rspack/src/sharing/SharePlugin.ts | 10 -- ...ts => SharedUsedExportsOptimizerPlugin.ts} | 8 +- ...harePlugin.ts => TreeShakeSharedPlugin.ts} | 27 ++-- .../collect-share-entry-plugin/index.js | 7 +- .../rspack.config.js | 45 +++--- .../sharing/reshake-share/rspack.config.js | 4 +- .../share-container-plugin-test/index.js | 26 ---- .../node_modules/ui-lib-dep/index.js | 7 - .../node_modules/ui-lib-dep/package.json | 6 - .../node_modules/ui-lib/index.js | 16 --- .../node_modules/ui-lib/package.json | 6 - .../rspack.config.js | 34 ----- .../sharing/treeshake-share/rspack.config.js | 29 ++-- .../webpack/tree-shake-shared-plugin.mdx | 46 ++++++ .../webpack/tree-shake-shared-plugin.mdx | 46 ++++++ 30 files changed, 342 insertions(+), 469 deletions(-) rename crates/rspack_plugin_mf/src/sharing/{collect_share_entry_plugin.rs => collect_shared_entry_plugin.rs} (76%) rename crates/rspack_plugin_mf/src/sharing/{optimize_dependency_referenced_exports_plugin.rs => shared_used_exports_optimizer_plugin.rs} (94%) rename crates/rspack_plugin_mf/src/sharing/{optimize_dependency_referenced_exports_runtime_module.rs => shared_used_exports_optimizer_runtime_module.rs} (89%) rename packages/rspack/src/sharing/{CollectShareEntryPlugin.ts => CollectSharedEntryPlugin.ts} (78%) rename packages/rspack/src/sharing/{IndependentSharePlugin.ts => IndependentSharedPlugin.ts} (84%) rename packages/rspack/src/sharing/{OptimizeDependencyReferencedExportsPlugin.ts => SharedUsedExportsOptimizerPlugin.ts} (83%) rename packages/rspack/src/sharing/{TreeShakeSharePlugin.ts => TreeShakeSharedPlugin.ts} (64%) delete mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/index.js delete mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/index.js delete mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/package.json delete mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/index.js delete mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/package.json delete mode 100644 tests/rspack-test/configCases/sharing/share-container-plugin-test/rspack.config.js create mode 100644 website/docs/en/plugins/webpack/tree-shake-shared-plugin.mdx create mode 100644 website/docs/zh/plugins/webpack/tree-shake-shared-plugin.mdx diff --git a/crates/node_binding/napi-binding.d.ts b/crates/node_binding/napi-binding.d.ts index e3607974ba48..7440ecbb5c35 100644 --- a/crates/node_binding/napi-binding.d.ts +++ b/crates/node_binding/napi-binding.d.ts @@ -540,12 +540,12 @@ export declare enum BuiltinPluginName { SplitChunksPlugin = 'SplitChunksPlugin', RemoveDuplicateModulesPlugin = 'RemoveDuplicateModulesPlugin', ShareRuntimePlugin = 'ShareRuntimePlugin', - OptimizeDependencyReferencedExportsPlugin = 'OptimizeDependencyReferencedExportsPlugin', + SharedUsedExportsOptimizerPlugin = 'SharedUsedExportsOptimizerPlugin', ContainerPlugin = 'ContainerPlugin', ContainerReferencePlugin = 'ContainerReferencePlugin', ProvideSharedPlugin = 'ProvideSharedPlugin', ConsumeSharedPlugin = 'ConsumeSharedPlugin', - CollectShareEntryPlugin = 'CollectShareEntryPlugin', + CollectSharedEntryPlugin = 'CollectSharedEntryPlugin', ShareContainerPlugin = 'ShareContainerPlugin', ModuleFederationRuntimePlugin = 'ModuleFederationRuntimePlugin', ModuleFederationManifestPlugin = 'ModuleFederationManifestPlugin', @@ -2592,13 +2592,6 @@ export interface RawOptimizationOptions { avoidEntryIife: boolean } -export interface RawOptimizeDependencyReferencedExportsPluginOptions { - shared: Array - injectUsedExports?: boolean - manifestFileName?: string - statsFileName?: string -} - export interface RawOptimizeSharedConfig { shareKey: string treeshake: boolean @@ -2849,6 +2842,13 @@ export interface RawShareContainerPluginOptions { library: JsLibraryOptions } +export interface RawSharedUsedExportsOptimizerPluginOptions { + shared: Array + injectUsedExports?: boolean + manifestFileName?: string + statsFileName?: string +} + export interface RawSizeLimitsPluginOptions { assetFilter?: (assetFilename: string) => boolean hints?: "error" | "warning" diff --git a/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs b/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs index dc7ede5f51b7..56a7674d6e37 100644 --- a/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs +++ b/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs @@ -34,8 +34,8 @@ use raw_ids::RawOccurrenceChunkIdsPluginOptions; use raw_lightning_css_minimizer::RawLightningCssMinimizerRspackPluginOptions; use raw_mf::{ RawCollectShareEntryPluginOptions, RawModuleFederationManifestPluginOptions, - RawModuleFederationRuntimePluginOptions, RawOptimizeDependencyReferencedExportsPluginOptions, - RawProvideOptions, + RawModuleFederationRuntimePluginOptions, RawProvideOptions, + RawSharedUsedExportsOptimizerPluginOptions, }; use raw_sri::RawSubresourceIntegrityPluginOptions; use rspack_core::{BoxPlugin, Plugin, PluginExt}; @@ -79,10 +79,9 @@ use rspack_plugin_lightning_css_minimizer::LightningCssMinimizerRspackPlugin; use rspack_plugin_limit_chunk_count::LimitChunkCountPlugin; use rspack_plugin_merge_duplicate_chunks::MergeDuplicateChunksPlugin; use rspack_plugin_mf::{ - CollectShareEntryPlugin, ConsumeSharedPlugin, ContainerPlugin, ContainerReferencePlugin, - ModuleFederationManifestPlugin, ModuleFederationRuntimePlugin, - OptimizeDependencyReferencedExportsPlugin, ProvideSharedPlugin, ShareContainerPlugin, - ShareRuntimePlugin, + CollectSharedEntryPlugin, ConsumeSharedPlugin, ContainerPlugin, ContainerReferencePlugin, + ModuleFederationManifestPlugin, ModuleFederationRuntimePlugin, ProvideSharedPlugin, + ShareContainerPlugin, ShareRuntimePlugin, SharedUsedExportsOptimizerPlugin, }; use rspack_plugin_module_info_header::ModuleInfoHeaderPlugin; use rspack_plugin_module_replacement::{ContextReplacementPlugin, NormalModuleReplacementPlugin}; @@ -173,12 +172,12 @@ pub enum BuiltinPluginName { SplitChunksPlugin, RemoveDuplicateModulesPlugin, ShareRuntimePlugin, - OptimizeDependencyReferencedExportsPlugin, + SharedUsedExportsOptimizerPlugin, ContainerPlugin, ContainerReferencePlugin, ProvideSharedPlugin, ConsumeSharedPlugin, - CollectShareEntryPlugin, + CollectSharedEntryPlugin, ShareContainerPlugin, ModuleFederationRuntimePlugin, ModuleFederationManifestPlugin, @@ -472,12 +471,11 @@ impl<'a> BuiltinPlugin<'a> { ) .boxed(), ), - BuiltinPluginName::OptimizeDependencyReferencedExportsPlugin => { - let options = - downcast_into::(self.options) - .map_err(|report| napi::Error::from_reason(report.to_string()))? - .into(); - plugins.push(OptimizeDependencyReferencedExportsPlugin::new(options).boxed()); + BuiltinPluginName::SharedUsedExportsOptimizerPlugin => { + let options = downcast_into::(self.options) + .map_err(|report| napi::Error::from_reason(report.to_string()))? + .into(); + plugins.push(SharedUsedExportsOptimizerPlugin::new(options).boxed()); } BuiltinPluginName::ContainerPlugin => { plugins.push( @@ -508,11 +506,11 @@ impl<'a> BuiltinPlugin<'a> { provides.sort_unstable_by_key(|(k, _)| k.to_string()); plugins.push(ProvideSharedPlugin::new(provides).boxed()) } - BuiltinPluginName::CollectShareEntryPlugin => { + BuiltinPluginName::CollectSharedEntryPlugin => { let options = downcast_into::(self.options) .map_err(|report| napi::Error::from_reason(report.to_string()))? .into(); - plugins.push(CollectShareEntryPlugin::new(options).boxed()) + plugins.push(CollectSharedEntryPlugin::new(options).boxed()) } BuiltinPluginName::ShareContainerPlugin => { let options = downcast_into::(self.options) diff --git a/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs b/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs index 885293bd4687..be001382ff7c 100644 --- a/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs +++ b/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs @@ -3,12 +3,12 @@ use std::{collections::HashMap, sync::Arc}; use napi::Either; use napi_derive::napi; use rspack_plugin_mf::{ - CollectShareEntryPluginOptions, ConsumeOptions, ConsumeSharedPluginOptions, ConsumeVersion, + CollectSharedEntryPluginOptions, ConsumeOptions, ConsumeSharedPluginOptions, ConsumeVersion, ContainerPluginOptions, ContainerReferencePluginOptions, ExposeOptions, ManifestExposeOption, ManifestSharedOption, ModuleFederationManifestPluginOptions, - ModuleFederationRuntimePluginOptions, OptimizeDependencyReferencedExportsPluginOptions, - OptimizeSharedConfig, ProvideOptions, ProvideVersion, RemoteAliasTarget, RemoteOptions, - ShareContainerEntryOptions, ShareContainerPluginOptions, StatsBuildInfo, + ModuleFederationRuntimePluginOptions, OptimizeSharedConfig, ProvideOptions, ProvideVersion, + RemoteAliasTarget, RemoteOptions, ShareContainerEntryOptions, ShareContainerPluginOptions, + SharedUsedExportsOptimizerPluginOptions, StatsBuildInfo, }; use crate::options::{ @@ -142,7 +142,7 @@ pub struct RawCollectShareEntryPluginOptions { pub filename: Option, } -impl From for CollectShareEntryPluginOptions { +impl From for CollectSharedEntryPluginOptions { fn from(value: RawCollectShareEntryPluginOptions) -> Self { Self { consumes: value @@ -221,17 +221,15 @@ impl From for OptimizeSharedConfig { #[derive(Debug)] #[napi(object)] -pub struct RawOptimizeDependencyReferencedExportsPluginOptions { +pub struct RawSharedUsedExportsOptimizerPluginOptions { pub shared: Vec, pub inject_used_exports: Option, pub manifest_file_name: Option, pub stats_file_name: Option, } -impl From - for OptimizeDependencyReferencedExportsPluginOptions -{ - fn from(value: RawOptimizeDependencyReferencedExportsPluginOptions) -> Self { +impl From for SharedUsedExportsOptimizerPluginOptions { + fn from(value: RawSharedUsedExportsOptimizerPluginOptions) -> Self { Self { shared: value .shared diff --git a/crates/rspack_plugin_mf/src/lib.rs b/crates/rspack_plugin_mf/src/lib.rs index d6c70a112524..169ab1e9535b 100644 --- a/crates/rspack_plugin_mf/src/lib.rs +++ b/crates/rspack_plugin_mf/src/lib.rs @@ -16,15 +16,11 @@ pub use manifest::{ ModuleFederationManifestPluginOptions, RemoteAliasTarget, StatsBuildInfo, }; pub use sharing::{ - collect_share_entry_plugin::{CollectShareEntryPlugin, CollectShareEntryPluginOptions}, + collect_shared_entry_plugin::{CollectSharedEntryPlugin, CollectSharedEntryPluginOptions}, consume_shared_module::ConsumeSharedModule, consume_shared_plugin::{ ConsumeOptions, ConsumeSharedPlugin, ConsumeSharedPluginOptions, ConsumeVersion, }, - optimize_dependency_referenced_exports_plugin::{ - OptimizeDependencyReferencedExportsPlugin, OptimizeDependencyReferencedExportsPluginOptions, - OptimizeSharedConfig, - }, provide_shared_module::ProvideSharedModule, provide_shared_plugin::{ProvideOptions, ProvideSharedPlugin, ProvideVersion}, share_container_entry_dependency::ShareContainerEntryOptions, @@ -33,6 +29,9 @@ pub use sharing::{ CodeGenerationDataShareInit, DataInitStage, ShareInitData, ShareRuntimeModule, }, share_runtime_plugin::ShareRuntimePlugin, + shared_used_exports_optimizer_plugin::{ + OptimizeSharedConfig, SharedUsedExportsOptimizerPlugin, SharedUsedExportsOptimizerPluginOptions, + }, }; mod utils { diff --git a/crates/rspack_plugin_mf/src/manifest/mod.rs b/crates/rspack_plugin_mf/src/manifest/mod.rs index 4b5883f568c4..3214cc0606fd 100644 --- a/crates/rspack_plugin_mf/src/manifest/mod.rs +++ b/crates/rspack_plugin_mf/src/manifest/mod.rs @@ -84,7 +84,7 @@ fn get_remote_entry_name(compilation: &Compilation, container_name: &str) -> Opt } None } -#[plugin_hook(CompilationProcessAssets for ModuleFederationManifestPlugin)] +#[plugin_hook(CompilationProcessAssets for ModuleFederationManifestPlugin, stage = 0)] async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { // Prepare entrypoint names let entry_point_names: HashSet = compilation diff --git a/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs b/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs similarity index 76% rename from crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs rename to crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs index 54939ddcfd15..cd9c7f7995a0 100644 --- a/crates/rspack_plugin_mf/src/sharing/collect_share_entry_plugin.rs +++ b/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs @@ -1,44 +1,30 @@ use std::{ collections::hash_map::Entry, path::{Path, PathBuf}, - sync::{Arc, LazyLock, OnceLock}, + sync::{Arc, OnceLock}, }; -use camino::Utf8Path; -use regex::Regex; use rspack_core::{ - BoxModule, Compilation, CompilationAsset, CompilationProcessAssets, CompilerCompilation, - CompilerThisCompilation, Context, DependenciesBlock, DependencyCategory, DependencyType, Module, - ModuleFactoryCreateData, NormalModuleFactoryFactorize, Plugin, ResolveOptionsWithDependencyType, - ResolveResult, Resolver, + BoxModule, Compilation, CompilationAsset, CompilationProcessAssets, CompilerThisCompilation, + Context, DependenciesBlock, DependencyCategory, DependencyType, Module, ModuleFactoryCreateData, + NormalModuleFactoryFactorize, Plugin, ResolveOptionsWithDependencyType, ResolveResult, Resolver, rspack_sources::{RawStringSource, SourceExt}, }; -use rspack_error::{Diagnostic, Result, error}; -use rspack_fs::ReadableFileSystem; +use rspack_error::{Diagnostic, Result}; use rspack_hook::{plugin, plugin_hook}; use rustc_hash::{FxHashMap, FxHashSet}; use serde::Serialize; use tokio::sync::RwLock; use super::consume_shared_plugin::{ - ABSOLUTE_REQUEST, ConsumeOptions, ConsumeVersion, MatchedConsumes, PACKAGE_NAME, - RELATIVE_REQUEST, get_description_file, get_required_version_from_description_file, + ABSOLUTE_REQUEST, ConsumeOptions, ConsumeVersion, MatchedConsumes, RELATIVE_REQUEST, resolve_matched_configs, }; -const DEFAULT_FILENAME: &str = "collect-share-entries.json"; - -#[derive(Debug, Clone)] -struct CollectShareEntryMeta { - request: String, - share_key: String, - share_scope: String, - is_prefix: bool, - consume: Arc, -} +const DEFAULT_FILENAME: &str = "collect-shared-entries.json"; #[derive(Debug, Clone, Default)] -struct CollectShareEntryRecord { +struct CollectSharedEntryRecord { share_scope: String, requests: FxHashSet, } @@ -49,52 +35,31 @@ struct CollectedShareRequest { version: String, } -// 直接输出共享模块映射,移除 shared 层级 -type CollectShareEntryAsset<'a> = FxHashMap<&'a str, CollectShareEntryAssetItem<'a>>; - #[derive(Debug, Serialize)] -struct CollectShareEntryAssetItem<'a> { +struct CollectSharedEntryAssetItem<'a> { #[serde(rename = "shareScope")] share_scope: &'a str, requests: &'a [[String; 2]], } #[derive(Debug)] -pub struct CollectShareEntryPluginOptions { +pub struct CollectSharedEntryPluginOptions { pub consumes: Vec<(String, Arc)>, pub filename: Option, } #[plugin] #[derive(Debug)] -pub struct CollectShareEntryPlugin { - options: CollectShareEntryPluginOptions, +pub struct CollectSharedEntryPlugin { + options: CollectSharedEntryPluginOptions, resolver: OnceLock>, compiler_context: OnceLock, matched_consumes: OnceLock>, - resolved_entries: RwLock>, + resolved_entries: RwLock>, } -impl CollectShareEntryPlugin { - pub fn new(options: CollectShareEntryPluginOptions) -> Self { - // let consumes: Vec = options - // .consumes - // .into_iter() - // .map(|(request, consume)| { - // let consume = consume.clone(); - // let share_key = consume.share_key.clone(); - // let share_scope = consume.share_scope.clone(); - // let is_prefix = request.ends_with('/'); - // CollectShareEntryMeta { - // request, - // share_key, - // share_scope, - // is_prefix, - // consume, - // } - // }) - // .collect(); - +impl CollectSharedEntryPlugin { + pub fn new(options: CollectSharedEntryPluginOptions) -> Self { Self::new_inner( options, Default::default(), @@ -104,14 +69,14 @@ impl CollectShareEntryPlugin { ) } - /// 根据模块请求路径推断版本信息 - /// 例如:../../../.eden-mono/temp/node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/index.js - /// 会找到 react-dom 的 package.json 并读取 version 字段 + /// Infer package version from a module request path + /// Example: ../../../.eden-mono/temp/node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/index.js + /// It locates react-dom's package.json and reads the version field async fn infer_version(&self, request: &str) -> Option { - // 将请求路径转换为 Path + // Convert request string to Path let path = Path::new(request); - // 查找包含 node_modules 的路径段 + // Find the node_modules segment let mut node_modules_found = false; let mut package_path = None; @@ -123,14 +88,14 @@ impl CollectShareEntryPlugin { } if node_modules_found { - // 下一个组件应该是包名 + // The next component should be the package name package_path = Some(comp_str.to_string()); break; } } if let Some(package_name) = package_path { - // 构建 package.json 的完整路径 + // Build the full path to package.json let mut package_json_path = PathBuf::new(); let mut found_node_modules = false; @@ -140,19 +105,19 @@ impl CollectShareEntryPlugin { if comp_str == "node_modules" { found_node_modules = true; - // 添加包名目录 + // Append package name directory package_json_path.push(&package_name); - // 添加 package.json + // Append package.json package_json_path.push("package.json"); break; } } if found_node_modules && package_json_path.exists() { - // 尝试读取 package.json + // Try reading package.json if let Ok(content) = std::fs::read_to_string(&package_json_path) { if let Ok(json) = serde_json::from_str::(&content) { - // 读取 version 字段 + // Read the version field if let Some(version) = json.get("version").and_then(|v| v.as_str()) { return Some(version.to_string()); } @@ -251,23 +216,23 @@ impl CollectShareEntryPlugin { ResolveResult::Ignored => None, }); - // 首先尝试从 import_resolved 路径推断版本 + // First try to infer version from the import_resolved path let version = if let Some(ref resolved_path) = import_resolved { if let Some(inferred) = self.infer_version(resolved_path).await { Some(ConsumeVersion::Version(inferred)) } else { - // 如果推断失败,直接返回 None,不记录这个条目 + // If inference fails, return None and skip this entry None } } else { - // 如果没有 resolved 路径,也直接返回 None + // If there is no resolved path, also return None None }; - // 如果无法获取版本信息,直接结束方法 + // If no version info can be obtained, exit early let version = match version { Some(v) => v, - None => return, // 直接结束 record_entry 方法 + None => return, // Early return from record_entry }; let share_key = config.share_key.clone(); @@ -292,7 +257,7 @@ impl CollectShareEntryPlugin { .unwrap_or_else(|| request.to_string()), version: version.to_string(), }); - entry.insert(CollectShareEntryRecord { + entry.insert(CollectSharedEntryRecord { share_scope, requests, }); @@ -301,7 +266,7 @@ impl CollectShareEntryPlugin { } } -#[plugin_hook(CompilerThisCompilation for CollectShareEntryPlugin)] +#[plugin_hook(CompilerThisCompilation for CollectSharedEntryPlugin)] async fn this_compilation( &self, compilation: &mut Compilation, @@ -321,9 +286,9 @@ async fn this_compilation( Ok(()) } -#[plugin_hook(CompilationProcessAssets for CollectShareEntryPlugin)] +#[plugin_hook(CompilationProcessAssets for CollectSharedEntryPlugin)] async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { - // 遍历图中的 ConsumeSharedModule,收集其 fallback 映射到的真实模块地址 + // Traverse ConsumeSharedModule in the graph and collect real resolved module paths from fallback let module_graph = compilation.get_module_graph(); let mut ordered_requests: FxHashMap> = FxHashMap::default(); let mut share_scopes: FxHashMap = FxHashMap::default(); @@ -338,9 +303,9 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { .as_any() .downcast_ref::() { - // 从 readable_identifier 中解析 share_scope 与 share_key + // Parse share_scope and share_key from readable_identifier let ident = consume.readable_identifier(&Context::default()).to_string(); - // 形如: "consume shared module ({scope}) {share_key}@..." + // Format: "consume shared module ({scope}) {share_key}@..." let (scope, key) = { let mut scope = String::new(); let mut key = String::new(); @@ -360,7 +325,7 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { if key.is_empty() { continue; } - // 收集该 consume 模块的依赖与异步块中的依赖对应的真实模块 + // Collect target modules from dependencies and async blocks let mut target_modules = Vec::new(); for dep_id in consume.get_dependencies() { if let Some(target_id) = module_graph.module_identifier_by_dependency_id(dep_id) { @@ -377,7 +342,7 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { } } - // 将真实模块的资源路径加入到映射集合,并推断版本 + // Add real module resource paths to the map and infer version let mut reqs = ordered_requests.remove(&key).unwrap_or_default(); for target_id in target_modules { if let Some(target) = module_graph.module_by_identifier(&target_id) { @@ -402,8 +367,8 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { } } - // 生成资产内容 - let mut shared: FxHashMap<&str, CollectShareEntryAssetItem<'_>> = FxHashMap::default(); + // Build asset content + let mut shared: FxHashMap<&str, CollectSharedEntryAssetItem<'_>> = FxHashMap::default(); for (share_key, requests) in ordered_requests.iter() { let scope = share_scopes .get(share_key) @@ -411,7 +376,7 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { .unwrap_or(""); shared.insert( share_key.as_str(), - CollectShareEntryAssetItem { + CollectSharedEntryAssetItem { share_scope: scope, requests: requests.as_slice(), }, @@ -419,9 +384,9 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { } let json = serde_json::to_string_pretty(&shared) - .expect("CollectShareEntryPlugin: failed to serialize share entries"); + .expect("CollectSharedEntryPlugin: failed to serialize share entries"); - // 获取文件名,如果不存在则使用默认值 + // Get filename, or use default when absent let filename = self .options .filename @@ -439,7 +404,7 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { Ok(()) } -#[plugin_hook(NormalModuleFactoryFactorize for CollectShareEntryPlugin)] +#[plugin_hook(NormalModuleFactoryFactorize for CollectSharedEntryPlugin)] async fn factorize(&self, data: &mut ModuleFactoryCreateData) -> Result> { let dep = data.dependencies[0] .as_module_dependency() @@ -452,10 +417,10 @@ async fn factorize(&self, data: &mut ModuleFactoryCreateData) -> Result Result Result &'static str { - "rspack.CollectShareEntryPlugin" + "rspack.CollectSharedEntryPlugin" } fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> { diff --git a/crates/rspack_plugin_mf/src/sharing/mod.rs b/crates/rspack_plugin_mf/src/sharing/mod.rs index 2d2dd63c2ebf..38747ea9fada 100644 --- a/crates/rspack_plugin_mf/src/sharing/mod.rs +++ b/crates/rspack_plugin_mf/src/sharing/mod.rs @@ -1,10 +1,8 @@ -pub mod collect_share_entry_plugin; +pub mod collect_shared_entry_plugin; pub mod consume_shared_fallback_dependency; pub mod consume_shared_module; pub mod consume_shared_plugin; pub mod consume_shared_runtime_module; -pub mod optimize_dependency_referenced_exports_plugin; -pub mod optimize_dependency_referenced_exports_runtime_module; pub mod provide_for_shared_dependency; pub mod provide_shared_dependency; pub mod provide_shared_module; @@ -18,3 +16,5 @@ pub mod share_container_plugin; pub mod share_container_runtime_module; pub mod share_runtime_module; pub mod share_runtime_plugin; +pub mod shared_used_exports_optimizer_plugin; +pub mod shared_used_exports_optimizer_runtime_module; diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs b/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs index aee5d4e4ef96..cc8ba2c38136 100644 --- a/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs +++ b/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs @@ -175,7 +175,7 @@ impl Module for ShareContainerEntryModule { let federation_global = format!("{}.federation", RuntimeGlobals::REQUIRE); - // 使用 returning_function 生成 installInitialConsumes 函数 + // Generate installInitialConsumes function using returning_function let install_initial_consumes_call = format!( r#"localBundlerRuntime.installInitialConsumes({{ installedModules: localInstalledModules, @@ -191,14 +191,14 @@ impl Module for ShareContainerEntryModule { "", ); - // 使用 basic_function 创建 initShareContainer 函数,支持多语句函数体 + // Create initShareContainer function using basic_function, supporting multi-statement body let init_body = format!( r#" var installedModules = {{}}; {federation_global}.instance = mfInstance; {federation_global}.bundlerRuntime = bundlerRuntime; - // 将参数保存到局部变量,避免闭包问题 + // Save parameters to local variables to avoid closure issues var localBundlerRuntime = bundlerRuntime; var localInstalledModules = installedModules; diff --git a/crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_plugin.rs b/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_plugin.rs similarity index 94% rename from crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_plugin.rs rename to crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_plugin.rs index ee59a0fdacaf..9d3ffba0cd4a 100644 --- a/crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_plugin.rs +++ b/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_plugin.rs @@ -19,10 +19,9 @@ use rspack_util::atom::Atom; use rustc_hash::{FxHashMap, FxHashSet}; use super::{ - consume_shared_module::ConsumeSharedModule, - optimize_dependency_referenced_exports_runtime_module::OptimizeDependencyReferencedExportsRuntimeModule, - provide_shared_module::ProvideSharedModule, + consume_shared_module::ConsumeSharedModule, provide_shared_module::ProvideSharedModule, share_container_entry_module::ShareContainerEntryModule, + shared_used_exports_optimizer_runtime_module::SharedUsedExportsOptimizerRuntimeModule, }; use crate::manifest::StatsRoot; #[derive(Debug, Clone)] @@ -33,7 +32,7 @@ pub struct OptimizeSharedConfig { } #[derive(Debug, Clone)] -pub struct OptimizeDependencyReferencedExportsPluginOptions { +pub struct SharedUsedExportsOptimizerPluginOptions { pub shared: Vec, pub inject_used_exports: bool, pub stats_file_name: Option, @@ -47,7 +46,7 @@ struct SharedEntryData { #[plugin] #[derive(Debug, Clone)] -pub struct OptimizeDependencyReferencedExportsPlugin { +pub struct SharedUsedExportsOptimizerPlugin { shared_map: FxHashMap, shared_referenced_exports: Arc>>>>, inject_used_exports: bool, @@ -55,8 +54,8 @@ pub struct OptimizeDependencyReferencedExportsPlugin { manifest_file_name: Option, } -impl OptimizeDependencyReferencedExportsPlugin { - pub fn new(options: OptimizeDependencyReferencedExportsPluginOptions) -> Self { +impl SharedUsedExportsOptimizerPlugin { + pub fn new(options: SharedUsedExportsOptimizerPluginOptions) -> Self { let mut shared_map = FxHashMap::default(); let inject_used_exports = options.inject_used_exports.clone(); for config in options.shared.into_iter().filter(|c| c.treeshake) { @@ -128,7 +127,7 @@ fn collect_processed_modules( } #[plugin_hook( - CompilationOptimizeDependencies for OptimizeDependencyReferencedExportsPlugin, + CompilationOptimizeDependencies for SharedUsedExportsOptimizerPlugin, stage = 1 )] async fn optimize_dependencies(&self, compilation: &mut Compilation) -> Result> { @@ -341,18 +340,20 @@ async fn optimize_dependencies(&self, compilation: &mut Compilation) -> Result Result<()> { let file_names = vec![ self.stats_file_name.clone(), self.manifest_file_name.clone(), ]; + dbg!(&file_names); for file_name in file_names { if let Some(file_name) = &file_name { if let Some(file) = compilation.assets().get(file_name) { if let Some(source) = file.get_source() { if let SourceValue::String(content) = source.source() { if let Ok(mut stats_root) = serde_json::from_str::(&content) { + dbg!(&content); let shared_referenced_exports = self .shared_referenced_exports .read() @@ -407,7 +408,7 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { } #[plugin_hook( - CompilationAdditionalTreeRuntimeRequirements for OptimizeDependencyReferencedExportsPlugin + CompilationAdditionalTreeRuntimeRequirements for SharedUsedExportsOptimizerPlugin )] async fn additional_tree_runtime_requirements( &self, @@ -422,7 +423,7 @@ async fn additional_tree_runtime_requirements( runtime_requirements.insert(RuntimeGlobals::RUNTIME_ID); compilation.add_runtime_module( chunk_ukey, - OptimizeDependencyReferencedExportsRuntimeModule::new( + SharedUsedExportsOptimizerRuntimeModule::new( self .shared_referenced_exports .read() @@ -448,7 +449,7 @@ async fn additional_tree_runtime_requirements( Ok(()) } -#[plugin_hook(CompilationDependencyReferencedExports for OptimizeDependencyReferencedExportsPlugin)] +#[plugin_hook(CompilationDependencyReferencedExports for SharedUsedExportsOptimizerPlugin)] async fn dependency_referenced_exports( &self, compilation: &Compilation, @@ -546,9 +547,9 @@ async fn dependency_referenced_exports( Ok(()) } -impl Plugin for OptimizeDependencyReferencedExportsPlugin { +impl Plugin for SharedUsedExportsOptimizerPlugin { fn name(&self) -> &'static str { - "rspack.sharing.OptimizeDependencyReferencedExportsPlugin" + "rspack.sharing.SharedUsedExportsOptimizerPlugin" } fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> { diff --git a/crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_runtime_module.rs b/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_runtime_module.rs similarity index 89% rename from crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_runtime_module.rs rename to crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_runtime_module.rs index 68c2fb94cff4..3f6ed05f6f4f 100644 --- a/crates/rspack_plugin_mf/src/sharing/optimize_dependency_referenced_exports_runtime_module.rs +++ b/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_runtime_module.rs @@ -9,13 +9,13 @@ use rspack_error::{Result, error}; #[impl_runtime_module] #[derive(Debug)] -pub struct OptimizeDependencyReferencedExportsRuntimeModule { +pub struct SharedUsedExportsOptimizerRuntimeModule { id: Identifier, chunk: Option, shared_used_exports: Arc>>>, } -impl OptimizeDependencyReferencedExportsRuntimeModule { +impl SharedUsedExportsOptimizerRuntimeModule { pub fn new(shared_used_exports: Arc>>>) -> Self { Self::with_default( Identifier::from("module_federation/shared_used_exports"), @@ -26,7 +26,7 @@ impl OptimizeDependencyReferencedExportsRuntimeModule { } #[async_trait] -impl RuntimeModule for OptimizeDependencyReferencedExportsRuntimeModule { +impl RuntimeModule for SharedUsedExportsOptimizerRuntimeModule { fn name(&self) -> Identifier { self.id } diff --git a/packages/rspack/etc/core.api.md b/packages/rspack/etc/core.api.md index 228b72044bec..af2d39761125 100644 --- a/packages/rspack/etc/core.api.md +++ b/packages/rspack/etc/core.api.md @@ -73,7 +73,6 @@ import { RawProvideOptions } from '@rspack/binding'; import { RawRslibPluginOptions } from '@rspack/binding'; import { RawRstestPluginOptions } from '@rspack/binding'; import { RawRuntimeChunkOptions } from '@rspack/binding'; -import { RawShareContainerPluginOptions } from '@rspack/binding'; import { RawSubresourceIntegrityPluginOptions } from '@rspack/binding'; import { readFileSync } from 'fs'; import { ReadStream as ReadStream_2 } from 'fs'; @@ -809,29 +808,6 @@ type CodeValue = RecursiveArrayOrRecord; // @public (undocumented) type CodeValuePrimitive = null | undefined | RegExp | Function | string | number | boolean | bigint; -// @public (undocumented) -class CollectShareEntryPlugin extends RspackBuiltinPlugin { - constructor(options: CollectShareEntryPluginOptions); - // (undocumented) - apply(compiler: Compiler): void; - // (undocumented) - getData(): ShareRequestsMap; - // (undocumented) - getFilename(): string; - // (undocumented) - name: BuiltinPluginName; - // (undocumented) - raw(): BuiltinPlugin; - // (undocumented) - sharedOptions: NormalizedSharedOptions; -} - -// @public (undocumented) -export type CollectShareEntryPluginOptions = { - sharedOptions: NormalizedSharedOptions; - shareScope?: string; -}; - // @public (undocumented) type CollectTypeScriptInfoOptions = { typeExports?: boolean; @@ -5491,15 +5467,6 @@ interface Optimize { // @public (undocumented) export const optimize: Optimize; -// @public (undocumented) -class OptimizeDependencyReferencedExportsPlugin extends RspackBuiltinPlugin { - constructor(sharedOptions: [string, SharedConfig][], injectUsedExports?: boolean, manifestOptions?: ModuleFederationManifestPluginOptions); - // (undocumented) - name: BuiltinPluginName; - // (undocumented) - raw(): BuiltinPlugin | undefined; -} - // @public (undocumented) interface OptimizerConfig { // (undocumented) @@ -6657,7 +6624,6 @@ declare namespace rspackExports { RemotesItems, RemotesObject, container, - CollectShareEntryPluginOptions, ConsumeSharedPluginOptions, Consumes, ConsumesConfig, @@ -6668,13 +6634,12 @@ declare namespace rspackExports { ProvidesConfig, ProvidesItem, ProvidesObject, - ShareContainerPluginOptions, Shared, SharedConfig, SharedItem, SharedObject, SharePluginOptions, - TreeshakeSharePluginOptions, + TreeshakeSharedPluginOptions, sharing, LightningcssFeatureOptions, LightningcssLoaderOptions, @@ -7381,37 +7346,6 @@ interface SetterProperty extends PropBase, HasSpan { type: "SetterProperty"; } -// @public (undocumented) -class ShareContainerPlugin extends RspackBuiltinPlugin { - constructor(options: ShareContainerPluginOptions); - // (undocumented) - apply(compiler: Compiler): void; - // (undocumented) - filename: string; - // (undocumented) - getData(): string[]; - // (undocumented) - _globalName: string; - // (undocumented) - name: BuiltinPluginName; - // (undocumented) - _options: RawShareContainerPluginOptions; - // (undocumented) - raw(compiler: Compiler): BuiltinPlugin; - // (undocumented) - _shareName: string; -} - -// @public (undocumented) -export type ShareContainerPluginOptions = { - mfName: string; - shareName: string; - version: string; - request: string; - library?: LibraryOptions; - independentShareFileName?: string; -}; - // @public (undocumented) export type Shared = (SharedItem | SharedObject)[] | SharedObject; @@ -7457,6 +7391,9 @@ type SharedOptimizationSplitChunksCacheGroup = { automaticNameDelimiter?: string; }; +// @public (undocumented) +type ShareFallback = Record; + // @public (undocumented) class SharePlugin { constructor(options: SharePluginOptions); @@ -7502,19 +7439,10 @@ export type SharePluginOptions = { enhanced: boolean; }; -// @public (undocumented) -type ShareRequestsMap = Record; - // @public (undocumented) export const sharing: { ProvideSharedPlugin: typeof ProvideSharedPlugin; - CollectShareEntryPlugin: typeof CollectShareEntryPlugin; - TreeshakeSharePlugin: typeof TreeshakeSharePlugin; - ShareContainerPlugin: typeof ShareContainerPlugin; - OptimizeDependencyReferencedExportsPlugin: typeof OptimizeDependencyReferencedExportsPlugin; + TreeShakeSharedPlugin: typeof TreeShakeSharedPlugin; ConsumeSharedPlugin: typeof ConsumeSharedPlugin; SharePlugin: typeof SharePlugin; }; @@ -8499,11 +8427,13 @@ interface TransformConfig { function transformSync(source: string, options?: Options): TransformOutput; // @public (undocumented) -class TreeshakeSharePlugin { - constructor(options: TreeshakeSharePluginOptions); +class TreeShakeSharedPlugin { + constructor(options: TreeshakeSharedPluginOptions); // (undocumented) apply(compiler: Compiler): void; // (undocumented) + get buildAssets(): ShareFallback; + // (undocumented) mfConfig: ModuleFederationPluginOptions; // (undocumented) name: string; @@ -8516,7 +8446,7 @@ class TreeshakeSharePlugin { } // @public (undocumented) -export interface TreeshakeSharePluginOptions { +export interface TreeshakeSharedPluginOptions { // (undocumented) mfConfig: ModuleFederationPluginOptions; // (undocumented) diff --git a/packages/rspack/src/container/ModuleFederationPlugin.ts b/packages/rspack/src/container/ModuleFederationPlugin.ts index e116b713b891..e7caa39382b4 100644 --- a/packages/rspack/src/container/ModuleFederationPlugin.ts +++ b/packages/rspack/src/container/ModuleFederationPlugin.ts @@ -1,10 +1,8 @@ import type { Compiler } from "../Compiler"; import type { ExternalsType } from "../config"; -import { - IndependentSharePlugin, - type ShareFallback -} from "../sharing/IndependentSharePlugin"; +import type { ShareFallback } from "../sharing/IndependentSharedPlugin"; import type { SharedConfig } from "../sharing/SharePlugin"; +import { TreeShakeSharedPlugin } from "../sharing/TreeShakeSharedPlugin"; import { isRequiredVersion } from "../sharing/utils"; import { ModuleFederationManifestPlugin, @@ -29,12 +27,11 @@ export interface ModuleFederationPluginOptions export type RuntimePlugins = string[] | [string, Record][]; export class ModuleFederationPlugin { - private _independentSharePlugin?: IndependentSharePlugin; + private _treeShakeSharedPlugin?: TreeShakeSharedPlugin; constructor(private _options: ModuleFederationPluginOptions) {} apply(compiler: Compiler) { - const { name, shared } = this._options; const { webpack } = compiler; const paths = getPaths(this._options); compiler.options.resolve.alias = { @@ -48,12 +45,11 @@ export class ModuleFederationPlugin { ([, config]) => config.treeshake ); if (treeshakeEntries.length > 0) { - this._independentSharePlugin = new IndependentSharePlugin({ - name, - shared: shared || {}, - outputFilePath: this._options.independentShareFilePath + this._treeShakeSharedPlugin = new TreeShakeSharedPlugin({ + mfConfig: this._options, + reshake: false }); - this._independentSharePlugin.apply(compiler); + this._treeShakeSharedPlugin.apply(compiler); } let runtimePluginApplied = false; @@ -69,7 +65,7 @@ export class ModuleFederationPlugin { paths, this._options, compiler, - this._independentSharePlugin?.buildAssets || {} + this._treeShakeSharedPlugin?.buildAssets || {} ); new ModuleFederationRuntimePlugin({ entryRuntime diff --git a/packages/rspack/src/exports.ts b/packages/rspack/src/exports.ts index 2bcb5a35e67e..52cee9868820 100644 --- a/packages/rspack/src/exports.ts +++ b/packages/rspack/src/exports.ts @@ -267,15 +267,11 @@ export const container = { ModuleFederationPluginV1 }; -import { CollectShareEntryPlugin } from "./sharing/CollectShareEntryPlugin"; import { ConsumeSharedPlugin } from "./sharing/ConsumeSharedPlugin"; -import { OptimizeDependencyReferencedExportsPlugin } from "./sharing/OptimizeDependencyReferencedExportsPlugin"; import { ProvideSharedPlugin } from "./sharing/ProvideSharedPlugin"; -import { ShareContainerPlugin } from "./sharing/ShareContainerPlugin"; import { SharePlugin } from "./sharing/SharePlugin"; -import { TreeshakeSharePlugin } from "./sharing/TreeShakeSharePlugin"; +import { TreeShakeSharedPlugin } from "./sharing/TreeShakeSharedPlugin"; -export type { CollectShareEntryPluginOptions } from "./sharing/CollectShareEntryPlugin"; export type { ConsumeSharedPluginOptions, Consumes, @@ -290,7 +286,6 @@ export type { ProvidesItem, ProvidesObject } from "./sharing/ProvideSharedPlugin"; -export type { ShareContainerPluginOptions } from "./sharing/ShareContainerPlugin"; export type { Shared, SharedConfig, @@ -298,13 +293,10 @@ export type { SharedObject, SharePluginOptions } from "./sharing/SharePlugin"; -export type { TreeshakeSharePluginOptions } from "./sharing/TreeShakeSharePlugin"; +export type { TreeshakeSharedPluginOptions } from "./sharing/TreeShakeSharedPlugin"; export const sharing = { ProvideSharedPlugin, - CollectShareEntryPlugin, - TreeshakeSharePlugin, - ShareContainerPlugin, - OptimizeDependencyReferencedExportsPlugin, + TreeShakeSharedPlugin, ConsumeSharedPlugin, SharePlugin }; diff --git a/packages/rspack/src/sharing/CollectShareEntryPlugin.ts b/packages/rspack/src/sharing/CollectSharedEntryPlugin.ts similarity index 78% rename from packages/rspack/src/sharing/CollectShareEntryPlugin.ts rename to packages/rspack/src/sharing/CollectSharedEntryPlugin.ts index 246422c50253..037daae54430 100644 --- a/packages/rspack/src/sharing/CollectShareEntryPlugin.ts +++ b/packages/rspack/src/sharing/CollectSharedEntryPlugin.ts @@ -14,7 +14,7 @@ import { type NormalizedSharedOptions } from "./SharePlugin"; -export type CollectShareEntryPluginOptions = { +export type CollectSharedEntryPluginOptions = { sharedOptions: NormalizedSharedOptions; shareScope?: string; }; @@ -27,13 +27,13 @@ export type ShareRequestsMap = Record< } >; -const SHARE_ENTRY_ASSET = "collect-share-entries.json"; -export class CollectShareEntryPlugin extends RspackBuiltinPlugin { - name = BuiltinPluginName.CollectShareEntryPlugin; +const SHARE_ENTRY_ASSET = "collect-shared-entries.json"; +export class CollectSharedEntryPlugin extends RspackBuiltinPlugin { + name = BuiltinPluginName.CollectSharedEntryPlugin; sharedOptions: NormalizedSharedOptions; private _collectedEntries: ShareRequestsMap; - constructor(options: CollectShareEntryPluginOptions) { + constructor(options: CollectSharedEntryPluginOptions) { super(); const { sharedOptions } = options; @@ -52,11 +52,12 @@ export class CollectShareEntryPlugin extends RspackBuiltinPlugin { apply(compiler: Compiler) { super.apply(compiler); - compiler.hooks.thisCompilation.tap("Collect share entry", compilation => { + compiler.hooks.thisCompilation.tap("Collect shared entry", compilation => { compilation.hooks.processAssets.tapPromise( { - name: "CollectShareEntry", - stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE + name: "CollectSharedEntry", + stage: + compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE }, async () => { const filename = this.getFilename(); diff --git a/packages/rspack/src/sharing/IndependentSharePlugin.ts b/packages/rspack/src/sharing/IndependentSharedPlugin.ts similarity index 84% rename from packages/rspack/src/sharing/IndependentSharePlugin.ts rename to packages/rspack/src/sharing/IndependentSharedPlugin.ts index 42c38a0825a5..0e60d9b1f81d 100644 --- a/packages/rspack/src/sharing/IndependentSharePlugin.ts +++ b/packages/rspack/src/sharing/IndependentSharedPlugin.ts @@ -8,15 +8,15 @@ import { } from "../container/ModuleFederationManifestPlugin"; import { parseOptions } from "../container/options"; import { - CollectShareEntryPlugin, + CollectSharedEntryPlugin, type ShareRequestsMap -} from "./CollectShareEntryPlugin"; +} from "./CollectSharedEntryPlugin"; import { ConsumeSharedPlugin } from "./ConsumeSharedPlugin"; -import { OptimizeDependencyReferencedExportsPlugin } from "./OptimizeDependencyReferencedExportsPlugin"; import { ShareContainerPlugin, type ShareContainerPluginOptions } from "./ShareContainerPlugin"; +import { SharedUsedExportsOptimizerPlugin } from "./SharedUsedExportsOptimizerPlugin"; import type { Shared, SharedConfig } from "./SharePlugin"; import { encodeName, isRequiredVersion } from "./utils"; @@ -35,10 +35,10 @@ const filterPlugin = (plugin: Plugins[0]) => { return true; } return ![ - "TreeshakeSharePlugin", - "IndependentSharePlugin", + "TreeShakeSharedPlugin", + "IndependentSharedPlugin", "ModuleFederationPlugin", - "OptimizeDependencyReferencedExportsPlugin", + "SharedUsedExportsOptimizerPlugin", "HtmlWebpackPlugin" ].includes(pluginName); }; @@ -107,7 +107,7 @@ class VirtualEntryPlugin { } } -export class IndependentSharePlugin { +export class IndependentSharedPlugin { mfName: string; shared: Shared; library?: LibraryOptions; @@ -121,7 +121,7 @@ export class IndependentSharePlugin { buildAssets: ShareFallback = {}; injectUsedExports?: boolean; - name = "IndependentSharePlugin"; + name = "IndependentSharedPlugin"; constructor(options: IndependentSharePluginOptions) { const { outputDir, @@ -170,21 +170,20 @@ export class IndependentSharePlugin { apply(compiler: Compiler) { compiler.hooks.beforeRun.tapPromise( - "IndependentSharePlugin", + "IndependentSharedPlugin", async compiler => { await this.createIndependentCompilers(compiler); } ); // clean hooks - compiler.hooks.shutdown.tapAsync("IndependentSharePlugin", callback => { + compiler.hooks.shutdown.tapAsync("IndependentSharedPlugin", callback => { this.cleanup(); - console.log("cleanup"); callback(); }); // inject buildAssets to stats - compiler.hooks.compilation.tap("IndependentSharePlugin", compilation => { + compiler.hooks.compilation.tap("IndependentSharedPlugin", compilation => { compilation.hooks.processAssets.tapPromise( { name: "injectBuildAssets", @@ -195,38 +194,49 @@ export class IndependentSharePlugin { if (!this.manifest) { return; } - const { statsFileName } = getFileName(this.manifest); - const stats = compilation.getAsset(statsFileName); - if (!stats) { - return; - } - const statsContent = JSON.parse(stats.source.source().toString()) as { - shared: { - name: string; - version: string; - fallback?: string; - fallbackName?: string; - }[]; - }; - - const { shared } = statsContent; - Object.entries(this.buildAssets).forEach(([key, item]) => { - const targetShared = shared.find(s => s.name === key); - if (!targetShared) { + const { statsFileName, manifestFileName } = getFileName( + this.manifest + ); + const injectBuildAssetsIntoStatsOrManifest = (filename: string) => { + const stats = compilation.getAsset(filename); + if (!stats) { return; } - item.forEach(([entry, version, globalName]) => { - if (version === targetShared.version) { - targetShared.fallback = entry; - targetShared.fallbackName = globalName; + const statsContent = JSON.parse( + stats.source.source().toString() + ) as { + shared: { + name: string; + version: string; + fallback?: string; + fallbackName?: string; + }[]; + }; + + const { shared } = statsContent; + Object.entries(this.buildAssets).forEach(([key, item]) => { + const targetShared = shared.find(s => s.name === key); + if (!targetShared) { + return; } + item.forEach(([entry, version, globalName]) => { + if (version === targetShared.version) { + targetShared.fallback = entry; + targetShared.fallbackName = globalName; + } + }); }); - }); - compilation.updateAsset( - statsFileName, - new compiler.webpack.sources.RawSource(JSON.stringify(statsContent)) - ); + compilation.updateAsset( + filename, + new compiler.webpack.sources.RawSource( + JSON.stringify(statsContent) + ) + ); + }; + + injectBuildAssetsIntoStatsOrManifest(statsFileName); + injectBuildAssetsIntoStatsOrManifest(manifestFileName); } ); }); @@ -306,9 +316,9 @@ export class IndependentSharePlugin { const finalPlugins = []; const rspack = parentCompiler.rspack; - let extraPlugin: CollectShareEntryPlugin | ShareContainerPlugin; + let extraPlugin: CollectSharedEntryPlugin | ShareContainerPlugin; if (!extraOptions) { - extraPlugin = new CollectShareEntryPlugin({ + extraPlugin = new CollectSharedEntryPlugin({ sharedOptions, shareScope: "default" }); @@ -358,7 +368,7 @@ export class IndependentSharePlugin { if (treeshake) { finalPlugins.push( - new OptimizeDependencyReferencedExportsPlugin( + new SharedUsedExportsOptimizerPlugin( sharedOptions, this.injectUsedExports ) diff --git a/packages/rspack/src/sharing/SharePlugin.ts b/packages/rspack/src/sharing/SharePlugin.ts index 4df525b155e9..1737d45e553a 100644 --- a/packages/rspack/src/sharing/SharePlugin.ts +++ b/packages/rspack/src/sharing/SharePlugin.ts @@ -1,7 +1,6 @@ import type { Compiler } from "../Compiler"; import { parseOptions } from "../container/options"; import { ConsumeSharedPlugin } from "./ConsumeSharedPlugin"; -import { OptimizeDependencyReferencedExportsPlugin } from "./OptimizeDependencyReferencedExportsPlugin"; import { ProvideSharedPlugin } from "./ProvideSharedPlugin"; import { isRequiredVersion } from "./utils"; @@ -118,14 +117,5 @@ export class SharePlugin { provides: this._provides, enhanced: this._enhanced }).apply(compiler); - - const treeshakeOptions = this._sharedOptions.filter( - ([, config]) => config.treeshake - ); - if (treeshakeOptions.length > 0) { - new OptimizeDependencyReferencedExportsPlugin(treeshakeOptions).apply( - compiler - ); - } } } diff --git a/packages/rspack/src/sharing/OptimizeDependencyReferencedExportsPlugin.ts b/packages/rspack/src/sharing/SharedUsedExportsOptimizerPlugin.ts similarity index 83% rename from packages/rspack/src/sharing/OptimizeDependencyReferencedExportsPlugin.ts rename to packages/rspack/src/sharing/SharedUsedExportsOptimizerPlugin.ts index c432c771e662..2c6b77869280 100644 --- a/packages/rspack/src/sharing/OptimizeDependencyReferencedExportsPlugin.ts +++ b/packages/rspack/src/sharing/SharedUsedExportsOptimizerPlugin.ts @@ -1,6 +1,6 @@ import type { BuiltinPlugin, - RawOptimizeDependencyReferencedExportsPluginOptions + RawSharedUsedExportsOptimizerPluginOptions } from "@rspack/binding"; import { BuiltinPluginName } from "@rspack/binding"; @@ -20,8 +20,8 @@ type OptimizeSharedConfig = { usedExports?: string[]; }; -export class OptimizeDependencyReferencedExportsPlugin extends RspackBuiltinPlugin { - name = BuiltinPluginName.OptimizeDependencyReferencedExportsPlugin; +export class SharedUsedExportsOptimizerPlugin extends RspackBuiltinPlugin { + name = BuiltinPluginName.SharedUsedExportsOptimizerPlugin; private sharedOptions: [string, SharedConfig][]; private injectUsedExports: boolean; private manifestOptions: ModuleFederationManifestPluginOptions; @@ -37,7 +37,7 @@ export class OptimizeDependencyReferencedExportsPlugin extends RspackBuiltinPlug this.manifestOptions = manifestOptions ?? {}; } - private buildOptions(): RawOptimizeDependencyReferencedExportsPluginOptions { + private buildOptions(): RawSharedUsedExportsOptimizerPluginOptions { const shared: OptimizeSharedConfig[] = this.sharedOptions.map( ([shareKey, config]) => ({ shareKey, diff --git a/packages/rspack/src/sharing/TreeShakeSharePlugin.ts b/packages/rspack/src/sharing/TreeShakeSharedPlugin.ts similarity index 64% rename from packages/rspack/src/sharing/TreeShakeSharePlugin.ts rename to packages/rspack/src/sharing/TreeShakeSharedPlugin.ts index f9778fe5cd21..2b737a1ed517 100644 --- a/packages/rspack/src/sharing/TreeShakeSharePlugin.ts +++ b/packages/rspack/src/sharing/TreeShakeSharedPlugin.ts @@ -1,24 +1,25 @@ import type { Compiler } from "../Compiler"; import type { Plugins } from "../config"; import type { ModuleFederationPluginOptions } from "../container/ModuleFederationPlugin"; -import { IndependentSharePlugin } from "./IndependentSharePlugin"; -import { OptimizeDependencyReferencedExportsPlugin } from "./OptimizeDependencyReferencedExportsPlugin"; +import { IndependentSharedPlugin } from "./IndependentSharedPlugin"; +import { SharedUsedExportsOptimizerPlugin } from "./SharedUsedExportsOptimizerPlugin"; import { normalizeSharedOptions } from "./SharePlugin"; -export interface TreeshakeSharePluginOptions { +export interface TreeshakeSharedPluginOptions { mfConfig: ModuleFederationPluginOptions; plugins?: Plugins; reshake?: boolean; } -export class TreeshakeSharePlugin { +export class TreeShakeSharedPlugin { mfConfig: ModuleFederationPluginOptions; outputDir: string; plugins?: Plugins; reshake?: boolean; + private _independentSharePlugin?: IndependentSharedPlugin; - name = "TreeshakeSharePlugin"; - constructor(options: TreeshakeSharePluginOptions) { + name = "TreeShakeSharedPlugin"; + constructor(options: TreeshakeSharedPluginOptions) { const { mfConfig, plugins, reshake } = options; this.mfConfig = mfConfig; this.outputDir = mfConfig.independentShareDir || "independent-packages"; @@ -38,7 +39,7 @@ export class TreeshakeSharePlugin { } if (!reshake) { - new OptimizeDependencyReferencedExportsPlugin( + new SharedUsedExportsOptimizerPlugin( sharedOptions, mfConfig.injectUsedExports, mfConfig.manifest @@ -50,14 +51,20 @@ export class TreeshakeSharePlugin { ([_, config]) => config.treeshake && config.import !== false ) ) { - new IndependentSharePlugin({ + this._independentSharePlugin = new IndependentSharedPlugin({ name: name, shared: shared, outputDir, plugins, treeshake: reshake, - library - }).apply(compiler); + library, + manifest: mfConfig.manifest + }); + this._independentSharePlugin.apply(compiler); } } + + get buildAssets() { + return this._independentSharePlugin?.buildAssets || {}; + } } diff --git a/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/index.js b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/index.js index d3ae599c684e..1831496118e5 100644 --- a/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/index.js +++ b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/index.js @@ -3,13 +3,10 @@ const path = __non_webpack_require__("path"); it("should emit collect share entry asset with expected requests", async () => { await import('./module'); - const assetPath = path.join(__dirname, "collect-share-entries.json"); - expect(fs.existsSync(assetPath)).toBe(true); - + const assetPath = path.join(__dirname, "independent-packages/collect-shared-entries-copy.json"); const content = JSON.parse(fs.readFileSync(assetPath, "utf-8")); - expect(content.shared).toBeDefined(); - const collectInfo = content.shared["xreact"]; + const collectInfo = content.xreact; expect(collectInfo).toBeDefined(); expect(collectInfo.shareScope).toBe("default"); expect(collectInfo.requests[0][0]).toContain("sharing/collect-share-entry-plugin/node_modules/xreact/index.js"); diff --git a/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/rspack.config.js b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/rspack.config.js index 2163d1f17105..c59903ca11af 100644 --- a/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/rspack.config.js +++ b/tests/rspack-test/configCases/sharing/collect-share-entry-plugin/rspack.config.js @@ -1,36 +1,39 @@ -const { sharing } = require("@rspack/core"); - -const { CollectShareEntryPlugin, SharePlugin } = sharing; - -const sharedOptions = [ - [ - "xreact", - { - import: "xreact", - shareKey: "xreact", - shareScope: "default", - version: "1.0.0" - } - ] -]; +const { container,sources } = require("@rspack/core"); /** @type {import("@rspack/core").Configuration} */ module.exports = { plugins: [ - new SharePlugin({ - shareScope: "default", + new container.ModuleFederationPlugin({ shared: { xreact: { import: "xreact", shareKey: "xreact", shareScope: "default", - version: "1.0.0" + version: "1.0.0", + treeshake: true } }, }), - new CollectShareEntryPlugin({ - sharedOptions - }) + { + apply(compiler) { + compiler.hooks.thisCompilation.tap("CollectSharedEntryPlugin", (compilation) => { + compilation.hooks.processAssets.tapPromise( + { + name: "emitCollectSharedEntry", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE + }, + async () => { + const filename = 'collect-shared-entries.json'; + const asset = compilation.getAsset(filename); + if (!asset) { + return; + } + compilation.emitAsset('collect-shared-entries-copy.json', new sources.RawSource(asset.source.source().toString())); + } + ); + }); + } + } ] }; diff --git a/tests/rspack-test/configCases/sharing/reshake-share/rspack.config.js b/tests/rspack-test/configCases/sharing/reshake-share/rspack.config.js index 95a93ba1d84c..47d774a07270 100644 --- a/tests/rspack-test/configCases/sharing/reshake-share/rspack.config.js +++ b/tests/rspack-test/configCases/sharing/reshake-share/rspack.config.js @@ -1,4 +1,4 @@ -const { TreeshakeSharePlugin } = require("@rspack/core").sharing; +const { TreeShakeSharedPlugin } = require("@rspack/core").sharing; /** @type {import("@rspack/core").Configuration} */ module.exports = { @@ -12,7 +12,7 @@ module.exports = { chunkFilename: "[id].js" }, plugins: [ - new TreeshakeSharePlugin({ + new TreeShakeSharedPlugin({ reshake: true, plugins: [ { diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/index.js b/tests/rspack-test/configCases/sharing/share-container-plugin-test/index.js deleted file mode 100644 index b0eb3cf43ecf..000000000000 --- a/tests/rspack-test/configCases/sharing/share-container-plugin-test/index.js +++ /dev/null @@ -1,26 +0,0 @@ -import uiLib from 'ui-lib'; - -const fs = __non_webpack_require__("fs"); -const path = __non_webpack_require__("path"); - -it("should generate share container bundle with expected content", async () => { - const bundlePath = path.join( - __dirname, - "1.0.0", - "share-entry.js" - ); - const shareContainer = __non_webpack_require__(bundlePath).ui_lib; - expect(Object.getOwnPropertyNames(shareContainer).sort()).toEqual(['get','init']); - __webpack_require__.consumesLoadingData = { - initialConsumes: { - } - } - const response = await shareContainer.init({},{ - installInitialConsumes: async ()=>{ - return 'call init' - } - }); - expect(response).toBe('call init'); - const shareModules = await shareContainer.get(); - expect(['Button', 'Badge', 'List', 'MessagePro', 'SpinPro'].every(m=>Boolean(shareModules[m]))); -}); diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/index.js b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/index.js deleted file mode 100644 index bf1ed03efa91..000000000000 --- a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/index.js +++ /dev/null @@ -1,7 +0,0 @@ -export const Message = 'Message'; -export const Spin = 'Spin' - -export default { - Message, - Spin -} diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/package.json b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/package.json deleted file mode 100644 index 436a74b6795a..000000000000 --- a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib-dep/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "ui-lib-dep", - "main": "./index.js", - "version": "1.0.0", - "sideEffects": false -} diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/index.js b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/index.js deleted file mode 100644 index c158056185a4..000000000000 --- a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import {Message,Spin} from 'ui-lib-dep'; - -export const Button = 'Button'; -export const List = 'List' -export const Badge = 'Badge' - -export const MessagePro = `${Message}Pro`; -export const SpinPro = `${Spin}Pro`; - -export default { - Button, - List, - Badge, - MessagePro, - SpinPro -} diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/package.json b/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/package.json deleted file mode 100644 index 90f9db2691b3..000000000000 --- a/tests/rspack-test/configCases/sharing/share-container-plugin-test/node_modules/ui-lib/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "ui-lib", - "main": "./index.js", - "version": "1.0.0", - "sideEffects": false -} diff --git a/tests/rspack-test/configCases/sharing/share-container-plugin-test/rspack.config.js b/tests/rspack-test/configCases/sharing/share-container-plugin-test/rspack.config.js deleted file mode 100644 index c2223ff34cd1..000000000000 --- a/tests/rspack-test/configCases/sharing/share-container-plugin-test/rspack.config.js +++ /dev/null @@ -1,34 +0,0 @@ -const path = require("path"); - -const { sharing } = require("@rspack/core"); - -const { ShareContainerPlugin,ConsumeSharedPlugin } = sharing; - -/** @type {import("@rspack/core").Configuration} */ -module.exports = { - plugins: [ - new ShareContainerPlugin({ - library: { - name:'ui_lib', - type:'commonjs2' - }, - mfName: "host", - shareName: "ui-lib", - version: "1.0.0", - request: path.resolve(__dirname, "node_modules/ui-lib/index.js") - }), - - new ConsumeSharedPlugin({ - consumes: [ - ['ui-lib', { - import: 'ui-lib', - shareScope: 'ui-lib', - }], - ['ui-lib-dep', { - import: 'ui-lib-dep', - shareScope: 'ui-lib-dep', - }], - ] - }) - ] -}; diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js b/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js index 3baee7941e7e..c732f861da66 100644 --- a/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js +++ b/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js @@ -1,8 +1,5 @@ -const path = require("path"); +const { container } = require("@rspack/core"); -const { container ,sharing} = require("@rspack/core"); - -const {OptimizeDependencyReferencedExportsPlugin } = sharing; const { ModuleFederationPlugin } = container; /** @type {import("@rspack/core").Configuration} */ @@ -24,27 +21,19 @@ module.exports = { manifest: true, shared: { 'ui-lib': { - requiredVersion:'*' + requiredVersion:'*', + treeshake:true, + usedExports: ['Badge'] }, 'ui-lib2': { - requiredVersion:'*' + requiredVersion:'*', + treeshake:true, }, 'ui-lib-side-effect': { - requiredVersion:'*' + requiredVersion:'*', + treeshake:true, } } - }), - new OptimizeDependencyReferencedExportsPlugin([ - ['ui-lib',{ - treeshake:true, - usedExports: ['Badge'] - }], - ['ui-lib2',{ - treeshake:true, - }], - ['ui-lib-side-effect',{ - treeshake:true, - }] - ]) + }) ] }; diff --git a/website/docs/en/plugins/webpack/tree-shake-shared-plugin.mdx b/website/docs/en/plugins/webpack/tree-shake-shared-plugin.mdx new file mode 100644 index 000000000000..1658ca551418 --- /dev/null +++ b/website/docs/en/plugins/webpack/tree-shake-shared-plugin.mdx @@ -0,0 +1,46 @@ +import { Stability, ApiMeta } from '@components/ApiMeta'; + +# TreeShakeSharedPlugin + + + +**Overview** +- Performs on-demand builds and export optimization for `shared` dependencies in Module Federation. +- When `reshake` is false, enables `SharedUsedExportsOptimizerPlugin` to inject runtime used-exports. +- If any shared entry has `treeshake: true` and `import !== false`, uses `IndependentSharedPlugin` to build independent artifacts per shared package and write back to stats/manifest. + +**Options** +- `mfConfig`: `ModuleFederationPluginOptions` with `name`, `shared`, `library`, `manifest`, `injectUsedExports`. +- `plugins`: extra plugins reused in independent builds. +- `reshake`: whether to perform additional tree-shaking during independent builds. + +**Usage** +```js +// rspack.config.js +const { TreeShakeSharedPlugin } = require('@rspack/core'); + +module.exports = { + plugins: [ + new TreeShakeSharedPlugin({ + mfConfig: { + name: 'app', + shared: { + 'lodash-es': { treeshake: true }, + react: { treeshake: true, requiredVersion: '18.3.1' } + }, + library: { type: 'var', name: 'App' }, + manifest: { statsFileName: 'stats.json', manifestFileName: 'manifest.json' }, + injectUsedExports: true + }, + plugins: [], + reshake: false + }) + ] +}; +``` + +**Behavior** +- Normalizes `shared` into `[shareName, SharedConfig][]`. +- Registers `SharedUsedExportsOptimizerPlugin` when `reshake` is `false` to inject used exports from stats. +- Triggers independent compilation for `treeshake` entries and writes produced assets back into stats/manifest. + diff --git a/website/docs/zh/plugins/webpack/tree-shake-shared-plugin.mdx b/website/docs/zh/plugins/webpack/tree-shake-shared-plugin.mdx new file mode 100644 index 000000000000..09682462c2e4 --- /dev/null +++ b/website/docs/zh/plugins/webpack/tree-shake-shared-plugin.mdx @@ -0,0 +1,46 @@ +import { Stability, ApiMeta } from '@components/ApiMeta'; + +# TreeShakeSharedPlugin + + + +**概览** +- 基于模块联邦配置对 shared 依赖进行按需构建与导出优化。 +- 非 `reshake` 模式下,启用 `SharedUsedExportsOptimizerPlugin` 注入运行时已用导出信息。 +- 当 shared 项存在 `treeshake: true` 且 `import !== false` 时,使用 `IndependentSharedPlugin` 为每个共享包生成独立产物并回填到清单。 + +**选项** +- `mfConfig`:`ModuleFederationPluginOptions`,传入模块联邦的 name、shared、library、manifest、injectUsedExports。 +- `plugins`:额外插件列表,可在独立编译中复用。 +- `reshake`:是否在独立编译阶段执行二次摇树优化。 + +**使用示例** +```js +// rspack.config.js +const { TreeShakeSharedPlugin } = require('@rspack/core'); + +module.exports = { + plugins: [ + new TreeShakeSharedPlugin({ + mfConfig: { + name: 'app', + shared: { + 'lodash-es': { treeshake: true }, + react: { treeshake: true, requiredVersion: '18.3.1' } + }, + library: { type: 'var', name: 'App' }, + manifest: { statsFileName: 'stats.json', manifestFileName: 'manifest.json' }, + injectUsedExports: true + }, + plugins: [], + reshake: false + }) + ] +}; +``` + +**行为说明** +- 读取 `shared` 配置后标准化为 `[shareName, SharedConfig][]`。 +- 当 `reshake` 为 `false` 时,注册 `SharedUsedExportsOptimizerPlugin`,基于 stats 清单注入已用导出集合。 +- 对 `treeshake` 的共享包触发独立编译,产出回写到 stats/manifest 中的 fallback 字段。 + From 0961bab15a252466008651ff2247b28c59ddb27d Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Tue, 25 Nov 2025 10:53:32 +0800 Subject: [PATCH 07/13] fix: collect shared request from named_condition --- .../sharing/collect_shared_entry_plugin.rs | 85 ++++++++++--------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs b/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs index cd9c7f7995a0..6fe98a2b5c06 100644 --- a/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs +++ b/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs @@ -4,6 +4,7 @@ use std::{ sync::{Arc, OnceLock}, }; +use regex::Regex; use rspack_core::{ BoxModule, Compilation, CompilationAsset, CompilationProcessAssets, CompilerThisCompilation, Context, DependenciesBlock, DependencyCategory, DependencyType, Module, ModuleFactoryCreateData, @@ -73,54 +74,56 @@ impl CollectSharedEntryPlugin { /// Example: ../../../.eden-mono/temp/node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/index.js /// It locates react-dom's package.json and reads the version field async fn infer_version(&self, request: &str) -> Option { - // Convert request string to Path - let path = Path::new(request); + // 1) Try pnpm store path pattern: .pnpm/@_ + let pnpm_re = Regex::new(r"/\\.pnpm/[^/]*@([^/_]+)").ok(); + if let Some(re) = pnpm_re { + if let Some(caps) = re.captures(request) { + if let Some(m) = caps.get(1) { + return Some(m.as_str().to_string()); + } + } + } - // Find the node_modules segment - let mut node_modules_found = false; - let mut package_path = None; + // 2) Fallback: walk to node_modules/[/...] and read package.json + let path = Path::new(request); + let mut package_json_path = PathBuf::new(); + let mut found_node_modules = false; + let mut need_two_segments = false; + let mut captured = false; for component in path.components() { let comp_str = component.as_os_str().to_string_lossy(); - if comp_str == "node_modules" { - node_modules_found = true; + package_json_path.push(comp_str.as_ref()); + if !found_node_modules && comp_str == "node_modules" { + found_node_modules = true; continue; } - - if node_modules_found { - // The next component should be the package name - package_path = Some(comp_str.to_string()); - break; - } - } - - if let Some(package_name) = package_path { - // Build the full path to package.json - let mut package_json_path = PathBuf::new(); - let mut found_node_modules = false; - - for component in path.components() { - let comp_str = component.as_os_str().to_string_lossy(); - package_json_path.push(comp_str.as_ref()); - - if comp_str == "node_modules" { - found_node_modules = true; - // Append package name directory - package_json_path.push(&package_name); - // Append package.json - package_json_path.push("package.json"); - break; + if found_node_modules && !captured { + if comp_str.starts_with('@') { + // scoped package: need scope + name + need_two_segments = true; + continue; + } else { + if need_two_segments { + // this is the name after scope + package_json_path.push("package.json"); + captured = true; + break; + } else { + // unscoped package name is this segment + package_json_path.push("package.json"); + captured = true; + break; + } } } + } - if found_node_modules && package_json_path.exists() { - // Try reading package.json - if let Ok(content) = std::fs::read_to_string(&package_json_path) { - if let Ok(json) = serde_json::from_str::(&content) { - // Read the version field - if let Some(version) = json.get("version").and_then(|v| v.as_str()) { - return Some(version.to_string()); - } + if captured && package_json_path.exists() { + if let Ok(content) = std::fs::read_to_string(&package_json_path) { + if let Ok(json) = serde_json::from_str::(&content) { + if let Some(version) = json.get("version").and_then(|v| v.as_str()) { + return Some(version.to_string()); } } } @@ -346,8 +349,8 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { let mut reqs = ordered_requests.remove(&key).unwrap_or_default(); for target_id in target_modules { if let Some(target) = module_graph.module_by_identifier(&target_id) { - if let Some(normal) = target.as_any().downcast_ref::() { - let resource = normal.resource_resolved_data().resource().to_string(); + if let Some(name) = target.name_for_condition() { + let resource: String = name.into(); let version = self .infer_version(&resource) .await From 65bcd7f036d33d9afb6b3b13556b8c7c2bc10766 Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Tue, 25 Nov 2025 16:41:30 +0800 Subject: [PATCH 08/13] chore: delete useless assets --- crates/node_binding/rspack.wasi-browser.js | 61 +------- crates/node_binding/rspack.wasi.cjs | 61 +------- .../sharing/collect_shared_entry_plugin.rs | 138 +++++------------- .../src/sharing/CollectSharedEntryPlugin.ts | 15 +- 4 files changed, 49 insertions(+), 226 deletions(-) diff --git a/crates/node_binding/rspack.wasi-browser.js b/crates/node_binding/rspack.wasi-browser.js index e3e5c0a99d48..ee65959b37bc 100644 --- a/crates/node_binding/rspack.wasi-browser.js +++ b/crates/node_binding/rspack.wasi-browser.js @@ -63,63 +63,4 @@ const { }, }) export default __napiModule.exports -export const Assets = __napiModule.exports.Assets -export const AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock -export const Chunk = __napiModule.exports.Chunk -export const ChunkGraph = __napiModule.exports.ChunkGraph -export const ChunkGroup = __napiModule.exports.ChunkGroup -export const Chunks = __napiModule.exports.Chunks -export const CodeGenerationResult = __napiModule.exports.CodeGenerationResult -export const CodeGenerationResults = __napiModule.exports.CodeGenerationResults -export const ConcatenatedModule = __napiModule.exports.ConcatenatedModule -export const ContextModule = __napiModule.exports.ContextModule -export const Dependency = __napiModule.exports.Dependency -export const Diagnostics = __napiModule.exports.Diagnostics -export const EntryDataDto = __napiModule.exports.EntryDataDto -export const EntryDataDTO = __napiModule.exports.EntryDataDTO -export const EntryDependency = __napiModule.exports.EntryDependency -export const EntryOptionsDto = __napiModule.exports.EntryOptionsDto -export const EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO -export const ExternalModule = __napiModule.exports.ExternalModule -export const JsCompilation = __napiModule.exports.JsCompilation -export const JsCompiler = __napiModule.exports.JsCompiler -export const JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData -export const JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData -export const JsDependencies = __napiModule.exports.JsDependencies -export const JsEntries = __napiModule.exports.JsEntries -export const JsExportsInfo = __napiModule.exports.JsExportsInfo -export const JsModuleGraph = __napiModule.exports.JsModuleGraph -export const JsResolver = __napiModule.exports.JsResolver -export const JsResolverFactory = __napiModule.exports.JsResolverFactory -export const JsStats = __napiModule.exports.JsStats -export const KnownBuildInfo = __napiModule.exports.KnownBuildInfo -export const Module = __napiModule.exports.Module -export const ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection -export const NativeWatcher = __napiModule.exports.NativeWatcher -export const NativeWatchResult = __napiModule.exports.NativeWatchResult -export const NormalModule = __napiModule.exports.NormalModule -export const RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx -export const ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData -export const ResolverFactory = __napiModule.exports.ResolverFactory -export const Sources = __napiModule.exports.Sources -export const VirtualFileStore = __napiModule.exports.VirtualFileStore -export const JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore -export const async = __napiModule.exports.async -export const BuiltinPluginName = __napiModule.exports.BuiltinPluginName -export const cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace -export const EnforceExtension = __napiModule.exports.EnforceExtension -export const EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION -export const formatDiagnostic = __napiModule.exports.formatDiagnostic -export const JsLoaderState = __napiModule.exports.JsLoaderState -export const JsRspackSeverity = __napiModule.exports.JsRspackSeverity -export const loadBrowserslist = __napiModule.exports.loadBrowserslist -export const minify = __napiModule.exports.minify -export const minifySync = __napiModule.exports.minifySync -export const RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports -export const RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType -export const registerGlobalTrace = __napiModule.exports.registerGlobalTrace -export const RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind -export const sync = __napiModule.exports.sync -export const syncTraceEvent = __napiModule.exports.syncTraceEvent -export const transform = __napiModule.exports.transform -export const transformSync = __napiModule.exports.transformSync + diff --git a/crates/node_binding/rspack.wasi.cjs b/crates/node_binding/rspack.wasi.cjs index a251ce4d0d7d..1ad96db4aac4 100644 --- a/crates/node_binding/rspack.wasi.cjs +++ b/crates/node_binding/rspack.wasi.cjs @@ -108,63 +108,4 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule }, }) module.exports = __napiModule.exports -module.exports.Assets = __napiModule.exports.Assets -module.exports.AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock -module.exports.Chunk = __napiModule.exports.Chunk -module.exports.ChunkGraph = __napiModule.exports.ChunkGraph -module.exports.ChunkGroup = __napiModule.exports.ChunkGroup -module.exports.Chunks = __napiModule.exports.Chunks -module.exports.CodeGenerationResult = __napiModule.exports.CodeGenerationResult -module.exports.CodeGenerationResults = __napiModule.exports.CodeGenerationResults -module.exports.ConcatenatedModule = __napiModule.exports.ConcatenatedModule -module.exports.ContextModule = __napiModule.exports.ContextModule -module.exports.Dependency = __napiModule.exports.Dependency -module.exports.Diagnostics = __napiModule.exports.Diagnostics -module.exports.EntryDataDto = __napiModule.exports.EntryDataDto -module.exports.EntryDataDTO = __napiModule.exports.EntryDataDTO -module.exports.EntryDependency = __napiModule.exports.EntryDependency -module.exports.EntryOptionsDto = __napiModule.exports.EntryOptionsDto -module.exports.EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO -module.exports.ExternalModule = __napiModule.exports.ExternalModule -module.exports.JsCompilation = __napiModule.exports.JsCompilation -module.exports.JsCompiler = __napiModule.exports.JsCompiler -module.exports.JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData -module.exports.JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData -module.exports.JsDependencies = __napiModule.exports.JsDependencies -module.exports.JsEntries = __napiModule.exports.JsEntries -module.exports.JsExportsInfo = __napiModule.exports.JsExportsInfo -module.exports.JsModuleGraph = __napiModule.exports.JsModuleGraph -module.exports.JsResolver = __napiModule.exports.JsResolver -module.exports.JsResolverFactory = __napiModule.exports.JsResolverFactory -module.exports.JsStats = __napiModule.exports.JsStats -module.exports.KnownBuildInfo = __napiModule.exports.KnownBuildInfo -module.exports.Module = __napiModule.exports.Module -module.exports.ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection -module.exports.NativeWatcher = __napiModule.exports.NativeWatcher -module.exports.NativeWatchResult = __napiModule.exports.NativeWatchResult -module.exports.NormalModule = __napiModule.exports.NormalModule -module.exports.RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx -module.exports.ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData -module.exports.ResolverFactory = __napiModule.exports.ResolverFactory -module.exports.Sources = __napiModule.exports.Sources -module.exports.VirtualFileStore = __napiModule.exports.VirtualFileStore -module.exports.JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore -module.exports.async = __napiModule.exports.async -module.exports.BuiltinPluginName = __napiModule.exports.BuiltinPluginName -module.exports.cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace -module.exports.EnforceExtension = __napiModule.exports.EnforceExtension -module.exports.EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION -module.exports.formatDiagnostic = __napiModule.exports.formatDiagnostic -module.exports.JsLoaderState = __napiModule.exports.JsLoaderState -module.exports.JsRspackSeverity = __napiModule.exports.JsRspackSeverity -module.exports.loadBrowserslist = __napiModule.exports.loadBrowserslist -module.exports.minify = __napiModule.exports.minify -module.exports.minifySync = __napiModule.exports.minifySync -module.exports.RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports -module.exports.RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType -module.exports.registerGlobalTrace = __napiModule.exports.registerGlobalTrace -module.exports.RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind -module.exports.sync = __napiModule.exports.sync -module.exports.syncTraceEvent = __napiModule.exports.syncTraceEvent -module.exports.transform = __napiModule.exports.transform -module.exports.transformSync = __napiModule.exports.transformSync + diff --git a/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs b/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs index 6fe98a2b5c06..905069bfb948 100644 --- a/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs +++ b/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs @@ -84,46 +84,40 @@ impl CollectSharedEntryPlugin { } } - // 2) Fallback: walk to node_modules/[/...] and read package.json + // 2) Fallback: read version from the deepest node_modules//package.json let path = Path::new(request); - let mut package_json_path = PathBuf::new(); - let mut found_node_modules = false; - let mut need_two_segments = false; - let mut captured = false; - - for component in path.components() { - let comp_str = component.as_os_str().to_string_lossy(); - package_json_path.push(comp_str.as_ref()); - if !found_node_modules && comp_str == "node_modules" { - found_node_modules = true; - continue; - } - if found_node_modules && !captured { - if comp_str.starts_with('@') { - // scoped package: need scope + name - need_two_segments = true; - continue; - } else { - if need_two_segments { - // this is the name after scope - package_json_path.push("package.json"); - captured = true; - break; - } else { - // unscoped package name is this segment - package_json_path.push("package.json"); - captured = true; - break; + let comps: Vec = path + .components() + .map(|c| c.as_os_str().to_string_lossy().to_string()) + .collect(); + if let Some(idx) = comps.iter().rposition(|c| c == "node_modules") { + let mut pkg_parts: Vec<&str> = Vec::new(); + if let Some(next) = comps.get(idx + 1) { + if next.starts_with('@') { + if let Some(next2) = comps.get(idx + 2) { + pkg_parts.push(next.as_str()); + pkg_parts.push(next2.as_str()); } + } else { + pkg_parts.push(next.as_str()); } } - } - - if captured && package_json_path.exists() { - if let Ok(content) = std::fs::read_to_string(&package_json_path) { - if let Ok(json) = serde_json::from_str::(&content) { - if let Some(version) = json.get("version").and_then(|v| v.as_str()) { - return Some(version.to_string()); + if !pkg_parts.is_empty() { + let mut package_json_path = PathBuf::new(); + for c in comps.iter().take(idx + 1) { + package_json_path.push(c); + } + for p in &pkg_parts { + package_json_path.push(p); + } + package_json_path.push("package.json"); + if package_json_path.exists() { + if let Ok(content) = std::fs::read_to_string(&package_json_path) { + if let Ok(json) = serde_json::from_str::(&content) { + if let Some(version) = json.get("version").and_then(|v| v.as_str()) { + return Some(version.to_string()); + } + } } } } @@ -355,6 +349,7 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { .infer_version(&resource) .await .unwrap_or_else(|| "".to_string()); + dbg!(&version, &resource); let pair = [resource, version]; if !reqs.iter().any(|p| p[0] == pair[0] && p[1] == pair[1]) { reqs.push(pair); @@ -407,75 +402,20 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { Ok(()) } -#[plugin_hook(NormalModuleFactoryFactorize for CollectSharedEntryPlugin)] -async fn factorize(&self, data: &mut ModuleFactoryCreateData) -> Result> { - let dep = data.dependencies[0] - .as_module_dependency() - .expect("should be module dependency"); - if matches!( - dep.dependency_type(), - DependencyType::ConsumeSharedFallback | DependencyType::ProvideModuleForShared - ) { - return Ok(None); - } - let request = dep.request(); - - // Reuse the matching logic from consume_shared_plugin - let consumes = self.get_matched_consumes(); - - // 1. Exact match - use `unresolved` - if let Some(matched) = consumes.unresolved.get(request) { - self - .record_entry(&data.context, request, matched.clone(), |d| { - data.diagnostics.push(d) - }) - .await; - return Ok(None); - } - - // 2. Prefix match - use `prefixed` - for (prefix, options) in &consumes.prefixed { - if request.starts_with(prefix) { - let remainder = &request[prefix.len()..]; - self - .record_entry( - &data.context, - request, - Arc::new(ConsumeOptions { - import: options.import.as_ref().map(|i| i.to_owned() + remainder), - import_resolved: options.import_resolved.clone(), - share_key: options.share_key.clone() + remainder, - share_scope: options.share_scope.clone(), - required_version: options.required_version.clone(), - package_name: options.package_name.clone(), - strict_version: options.strict_version, - singleton: options.singleton, - eager: options.eager, - }), - |d| data.diagnostics.push(d), - ) - .await; - return Ok(None); - } - } - - Ok(None) -} - impl Plugin for CollectSharedEntryPlugin { fn name(&self) -> &'static str { "rspack.CollectSharedEntryPlugin" } fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> { - ctx - .compiler_hooks - .this_compilation - .tap(this_compilation::new(self)); - ctx - .normal_module_factory_hooks - .factorize - .tap(factorize::new(self)); + // ctx + // .compiler_hooks + // .this_compilation + // .tap(this_compilation::new(self)); + // ctx + // .normal_module_factory_hooks + // .factorize + // .tap(factorize::new(self)); ctx .compilation_hooks .process_assets diff --git a/packages/rspack/src/sharing/CollectSharedEntryPlugin.ts b/packages/rspack/src/sharing/CollectSharedEntryPlugin.ts index 037daae54430..19f97ea92c69 100644 --- a/packages/rspack/src/sharing/CollectSharedEntryPlugin.ts +++ b/packages/rspack/src/sharing/CollectSharedEntryPlugin.ts @@ -60,13 +60,14 @@ export class CollectSharedEntryPlugin extends RspackBuiltinPlugin { compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE }, async () => { - const filename = this.getFilename(); - const asset = compilation.getAsset(filename); - if (!asset) { - throw new Error(`Can not get ${filename}`); - } - this._collectedEntries = JSON.parse(asset.source.source().toString()); - compilation.deleteAsset(filename); + compilation.getAssets().forEach(asset => { + if (asset.name === SHARE_ENTRY_ASSET) { + this._collectedEntries = JSON.parse( + asset.source.source().toString() + ); + } + compilation.deleteAsset(asset.name); + }); } ); }); From 611139bae173b69cd1b43c6b3195597eca81ad87 Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Tue, 25 Nov 2025 17:00:27 +0800 Subject: [PATCH 09/13] docs: optimize doc --- .../webpack/tree-shake-shared-plugin.mdx | 29 +++++++++---------- .../webpack/tree-shake-shared-plugin.mdx | 23 +++++++-------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/website/docs/en/plugins/webpack/tree-shake-shared-plugin.mdx b/website/docs/en/plugins/webpack/tree-shake-shared-plugin.mdx index 1658ca551418..1b16d5496b58 100644 --- a/website/docs/en/plugins/webpack/tree-shake-shared-plugin.mdx +++ b/website/docs/en/plugins/webpack/tree-shake-shared-plugin.mdx @@ -5,16 +5,17 @@ import { Stability, ApiMeta } from '@components/ApiMeta'; **Overview** -- Performs on-demand builds and export optimization for `shared` dependencies in Module Federation. -- When `reshake` is false, enables `SharedUsedExportsOptimizerPlugin` to inject runtime used-exports. -- If any shared entry has `treeshake: true` and `import !== false`, uses `IndependentSharedPlugin` to build independent artifacts per shared package and write back to stats/manifest. + +- Performs on-demand build and export optimization for `shared` dependencies based on Module Federation configuration. **Options** -- `mfConfig`: `ModuleFederationPluginOptions` with `name`, `shared`, `library`, `manifest`, `injectUsedExports`. -- `plugins`: extra plugins reused in independent builds. -- `reshake`: whether to perform additional tree-shaking during independent builds. + +- `mfConfig`: `ModuleFederationPluginOptions`, configuration passed to the Module Federation plugin. +- `plugins`: extra plugins reused during independent builds. +- `reshake`: whether to perform a second tree-shake during independent builds (typically used when the deployment platform has determined complete dependency info and triggers a fresh build to improve tree-shake accuracy for shared dependencies). **Usage** + ```js // rspack.config.js const { TreeShakeSharedPlugin } = require('@rspack/core'); @@ -22,25 +23,23 @@ const { TreeShakeSharedPlugin } = require('@rspack/core'); module.exports = { plugins: [ new TreeShakeSharedPlugin({ + reshake: true, mfConfig: { name: 'app', shared: { 'lodash-es': { treeshake: true }, - react: { treeshake: true, requiredVersion: '18.3.1' } }, library: { type: 'var', name: 'App' }, - manifest: { statsFileName: 'stats.json', manifestFileName: 'manifest.json' }, - injectUsedExports: true + manifest: true, }, plugins: [], - reshake: false - }) - ] + }), + ], }; ``` **Behavior** -- Normalizes `shared` into `[shareName, SharedConfig][]`. -- Registers `SharedUsedExportsOptimizerPlugin` when `reshake` is `false` to inject used exports from stats. -- Triggers independent compilation for `treeshake` entries and writes produced assets back into stats/manifest. +- Normalizes `shared` into `[shareName, SharedConfig][]`. +- Registers `SharedUsedExportsOptimizerPlugin` when `reshake` is `false` to inject used-exports from the stats manifest. +- Triggers independent compilation for shared entries with `treeshake: true`, and writes the produced assets back to the `stats/manifest` (fallback fields). diff --git a/website/docs/zh/plugins/webpack/tree-shake-shared-plugin.mdx b/website/docs/zh/plugins/webpack/tree-shake-shared-plugin.mdx index 09682462c2e4..eda8c1f393bc 100644 --- a/website/docs/zh/plugins/webpack/tree-shake-shared-plugin.mdx +++ b/website/docs/zh/plugins/webpack/tree-shake-shared-plugin.mdx @@ -5,16 +5,17 @@ import { Stability, ApiMeta } from '@components/ApiMeta'; **概览** -- 基于模块联邦配置对 shared 依赖进行按需构建与导出优化。 -- 非 `reshake` 模式下,启用 `SharedUsedExportsOptimizerPlugin` 注入运行时已用导出信息。 -- 当 shared 项存在 `treeshake: true` 且 `import !== false` 时,使用 `IndependentSharedPlugin` 为每个共享包生成独立产物并回填到清单。 + +基于模块联邦配置对 shared 依赖进行按需构建与导出优化。 **选项** -- `mfConfig`:`ModuleFederationPluginOptions`,传入模块联邦的 name、shared、library、manifest、injectUsedExports。 + +- `mfConfig`:`ModuleFederationPluginOptions`,传入模块联邦插件所需要的配置项。 - `plugins`:额外插件列表,可在独立编译中复用。 -- `reshake`:是否在独立编译阶段执行二次摇树优化。 +- `reshake`:是否在独立编译阶段执行二次摇树优化(二次摇树通常在部署平台确定了完整的依赖信息后重新触发的一次全新构建,提高共享依赖 treeshake 命中的准确率)。 **使用示例** + ```js // rspack.config.js const { TreeShakeSharedPlugin } = require('@rspack/core'); @@ -22,25 +23,23 @@ const { TreeShakeSharedPlugin } = require('@rspack/core'); module.exports = { plugins: [ new TreeShakeSharedPlugin({ + reshake: true, mfConfig: { name: 'app', shared: { 'lodash-es': { treeshake: true }, - react: { treeshake: true, requiredVersion: '18.3.1' } }, library: { type: 'var', name: 'App' }, - manifest: { statsFileName: 'stats.json', manifestFileName: 'manifest.json' }, - injectUsedExports: true + manifest: true, }, plugins: [], - reshake: false - }) - ] + }), + ], }; ``` **行为说明** + - 读取 `shared` 配置后标准化为 `[shareName, SharedConfig][]`。 - 当 `reshake` 为 `false` 时,注册 `SharedUsedExportsOptimizerPlugin`,基于 stats 清单注入已用导出集合。 - 对 `treeshake` 的共享包触发独立编译,产出回写到 stats/manifest 中的 fallback 字段。 - From 6b712371b66494260d5cd78d50164c0e56b99631 Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Tue, 25 Nov 2025 19:07:57 +0800 Subject: [PATCH 10/13] chore: update runtime template usage --- crates/node_binding/rspack.wasi-browser.js | 61 +++++- crates/node_binding/rspack.wasi.cjs | 61 +++++- .../sharing/collect_shared_entry_plugin.rs | 206 +----------------- .../sharing/share_container_entry_module.rs | 37 ++-- ...d_used_exports_optimizer_runtime_module.rs | 9 +- 5 files changed, 155 insertions(+), 219 deletions(-) diff --git a/crates/node_binding/rspack.wasi-browser.js b/crates/node_binding/rspack.wasi-browser.js index ee65959b37bc..e3e5c0a99d48 100644 --- a/crates/node_binding/rspack.wasi-browser.js +++ b/crates/node_binding/rspack.wasi-browser.js @@ -63,4 +63,63 @@ const { }, }) export default __napiModule.exports - +export const Assets = __napiModule.exports.Assets +export const AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock +export const Chunk = __napiModule.exports.Chunk +export const ChunkGraph = __napiModule.exports.ChunkGraph +export const ChunkGroup = __napiModule.exports.ChunkGroup +export const Chunks = __napiModule.exports.Chunks +export const CodeGenerationResult = __napiModule.exports.CodeGenerationResult +export const CodeGenerationResults = __napiModule.exports.CodeGenerationResults +export const ConcatenatedModule = __napiModule.exports.ConcatenatedModule +export const ContextModule = __napiModule.exports.ContextModule +export const Dependency = __napiModule.exports.Dependency +export const Diagnostics = __napiModule.exports.Diagnostics +export const EntryDataDto = __napiModule.exports.EntryDataDto +export const EntryDataDTO = __napiModule.exports.EntryDataDTO +export const EntryDependency = __napiModule.exports.EntryDependency +export const EntryOptionsDto = __napiModule.exports.EntryOptionsDto +export const EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO +export const ExternalModule = __napiModule.exports.ExternalModule +export const JsCompilation = __napiModule.exports.JsCompilation +export const JsCompiler = __napiModule.exports.JsCompiler +export const JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData +export const JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData +export const JsDependencies = __napiModule.exports.JsDependencies +export const JsEntries = __napiModule.exports.JsEntries +export const JsExportsInfo = __napiModule.exports.JsExportsInfo +export const JsModuleGraph = __napiModule.exports.JsModuleGraph +export const JsResolver = __napiModule.exports.JsResolver +export const JsResolverFactory = __napiModule.exports.JsResolverFactory +export const JsStats = __napiModule.exports.JsStats +export const KnownBuildInfo = __napiModule.exports.KnownBuildInfo +export const Module = __napiModule.exports.Module +export const ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection +export const NativeWatcher = __napiModule.exports.NativeWatcher +export const NativeWatchResult = __napiModule.exports.NativeWatchResult +export const NormalModule = __napiModule.exports.NormalModule +export const RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx +export const ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData +export const ResolverFactory = __napiModule.exports.ResolverFactory +export const Sources = __napiModule.exports.Sources +export const VirtualFileStore = __napiModule.exports.VirtualFileStore +export const JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore +export const async = __napiModule.exports.async +export const BuiltinPluginName = __napiModule.exports.BuiltinPluginName +export const cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace +export const EnforceExtension = __napiModule.exports.EnforceExtension +export const EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION +export const formatDiagnostic = __napiModule.exports.formatDiagnostic +export const JsLoaderState = __napiModule.exports.JsLoaderState +export const JsRspackSeverity = __napiModule.exports.JsRspackSeverity +export const loadBrowserslist = __napiModule.exports.loadBrowserslist +export const minify = __napiModule.exports.minify +export const minifySync = __napiModule.exports.minifySync +export const RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports +export const RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType +export const registerGlobalTrace = __napiModule.exports.registerGlobalTrace +export const RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind +export const sync = __napiModule.exports.sync +export const syncTraceEvent = __napiModule.exports.syncTraceEvent +export const transform = __napiModule.exports.transform +export const transformSync = __napiModule.exports.transformSync diff --git a/crates/node_binding/rspack.wasi.cjs b/crates/node_binding/rspack.wasi.cjs index 1ad96db4aac4..a251ce4d0d7d 100644 --- a/crates/node_binding/rspack.wasi.cjs +++ b/crates/node_binding/rspack.wasi.cjs @@ -108,4 +108,63 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule }, }) module.exports = __napiModule.exports - +module.exports.Assets = __napiModule.exports.Assets +module.exports.AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock +module.exports.Chunk = __napiModule.exports.Chunk +module.exports.ChunkGraph = __napiModule.exports.ChunkGraph +module.exports.ChunkGroup = __napiModule.exports.ChunkGroup +module.exports.Chunks = __napiModule.exports.Chunks +module.exports.CodeGenerationResult = __napiModule.exports.CodeGenerationResult +module.exports.CodeGenerationResults = __napiModule.exports.CodeGenerationResults +module.exports.ConcatenatedModule = __napiModule.exports.ConcatenatedModule +module.exports.ContextModule = __napiModule.exports.ContextModule +module.exports.Dependency = __napiModule.exports.Dependency +module.exports.Diagnostics = __napiModule.exports.Diagnostics +module.exports.EntryDataDto = __napiModule.exports.EntryDataDto +module.exports.EntryDataDTO = __napiModule.exports.EntryDataDTO +module.exports.EntryDependency = __napiModule.exports.EntryDependency +module.exports.EntryOptionsDto = __napiModule.exports.EntryOptionsDto +module.exports.EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO +module.exports.ExternalModule = __napiModule.exports.ExternalModule +module.exports.JsCompilation = __napiModule.exports.JsCompilation +module.exports.JsCompiler = __napiModule.exports.JsCompiler +module.exports.JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData +module.exports.JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData +module.exports.JsDependencies = __napiModule.exports.JsDependencies +module.exports.JsEntries = __napiModule.exports.JsEntries +module.exports.JsExportsInfo = __napiModule.exports.JsExportsInfo +module.exports.JsModuleGraph = __napiModule.exports.JsModuleGraph +module.exports.JsResolver = __napiModule.exports.JsResolver +module.exports.JsResolverFactory = __napiModule.exports.JsResolverFactory +module.exports.JsStats = __napiModule.exports.JsStats +module.exports.KnownBuildInfo = __napiModule.exports.KnownBuildInfo +module.exports.Module = __napiModule.exports.Module +module.exports.ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection +module.exports.NativeWatcher = __napiModule.exports.NativeWatcher +module.exports.NativeWatchResult = __napiModule.exports.NativeWatchResult +module.exports.NormalModule = __napiModule.exports.NormalModule +module.exports.RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx +module.exports.ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData +module.exports.ResolverFactory = __napiModule.exports.ResolverFactory +module.exports.Sources = __napiModule.exports.Sources +module.exports.VirtualFileStore = __napiModule.exports.VirtualFileStore +module.exports.JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore +module.exports.async = __napiModule.exports.async +module.exports.BuiltinPluginName = __napiModule.exports.BuiltinPluginName +module.exports.cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace +module.exports.EnforceExtension = __napiModule.exports.EnforceExtension +module.exports.EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION +module.exports.formatDiagnostic = __napiModule.exports.formatDiagnostic +module.exports.JsLoaderState = __napiModule.exports.JsLoaderState +module.exports.JsRspackSeverity = __napiModule.exports.JsRspackSeverity +module.exports.loadBrowserslist = __napiModule.exports.loadBrowserslist +module.exports.minify = __napiModule.exports.minify +module.exports.minifySync = __napiModule.exports.minifySync +module.exports.RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports +module.exports.RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType +module.exports.registerGlobalTrace = __napiModule.exports.registerGlobalTrace +module.exports.RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind +module.exports.sync = __napiModule.exports.sync +module.exports.syncTraceEvent = __napiModule.exports.syncTraceEvent +module.exports.transform = __napiModule.exports.transform +module.exports.transformSync = __napiModule.exports.transformSync diff --git a/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs b/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs index 905069bfb948..cad6092f4bba 100644 --- a/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs +++ b/crates/rspack_plugin_mf/src/sharing/collect_shared_entry_plugin.rs @@ -1,41 +1,23 @@ use std::{ - collections::hash_map::Entry, path::{Path, PathBuf}, - sync::{Arc, OnceLock}, + sync::Arc, }; use regex::Regex; use rspack_core::{ - BoxModule, Compilation, CompilationAsset, CompilationProcessAssets, CompilerThisCompilation, - Context, DependenciesBlock, DependencyCategory, DependencyType, Module, ModuleFactoryCreateData, - NormalModuleFactoryFactorize, Plugin, ResolveOptionsWithDependencyType, ResolveResult, Resolver, + Compilation, CompilationAsset, CompilationProcessAssets, Context, DependenciesBlock, Module, + Plugin, rspack_sources::{RawStringSource, SourceExt}, }; -use rspack_error::{Diagnostic, Result}; +use rspack_error::Result; use rspack_hook::{plugin, plugin_hook}; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap; use serde::Serialize; -use tokio::sync::RwLock; -use super::consume_shared_plugin::{ - ABSOLUTE_REQUEST, ConsumeOptions, ConsumeVersion, MatchedConsumes, RELATIVE_REQUEST, - resolve_matched_configs, -}; +use super::consume_shared_plugin::ConsumeOptions; const DEFAULT_FILENAME: &str = "collect-shared-entries.json"; -#[derive(Debug, Clone, Default)] -struct CollectSharedEntryRecord { - share_scope: String, - requests: FxHashSet, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct CollectedShareRequest { - request: String, - version: String, -} - #[derive(Debug, Serialize)] struct CollectSharedEntryAssetItem<'a> { #[serde(rename = "shareScope")] @@ -53,21 +35,11 @@ pub struct CollectSharedEntryPluginOptions { #[derive(Debug)] pub struct CollectSharedEntryPlugin { options: CollectSharedEntryPluginOptions, - resolver: OnceLock>, - compiler_context: OnceLock, - matched_consumes: OnceLock>, - resolved_entries: RwLock>, } impl CollectSharedEntryPlugin { pub fn new(options: CollectSharedEntryPluginOptions) -> Self { - Self::new_inner( - options, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ) + Self::new_inner(options) } /// Infer package version from a module request path @@ -125,162 +97,6 @@ impl CollectSharedEntryPlugin { None } - - fn init_context(&self, compilation: &Compilation) { - self - .compiler_context - .set(compilation.options.context.clone()) - .expect("failed to set compiler context"); - } - - fn get_context(&self) -> Context { - self - .compiler_context - .get() - .expect("init_context first") - .clone() - } - - fn init_resolver(&self, compilation: &Compilation) { - self - .resolver - .set( - compilation - .resolver_factory - .get(ResolveOptionsWithDependencyType { - resolve_options: None, - resolve_to_context: false, - dependency_category: DependencyCategory::Esm, - }), - ) - .expect("failed to set resolver for multiple times"); - } - - fn get_resolver(&self) -> Arc { - self.resolver.get().expect("init_resolver first").clone() - } - - async fn init_matched_consumes(&self, compilation: &mut Compilation, resolver: Arc) { - let config = resolve_matched_configs(compilation, resolver, &self.options.consumes).await; - self - .matched_consumes - .set(Arc::new(config)) - .expect("failed to set matched consumes"); - } - - fn get_matched_consumes(&self) -> Arc { - self - .matched_consumes - .get() - .expect("init_matched_consumes first") - .clone() - } - - async fn record_entry( - &self, - context: &Context, - request: &str, - config: Arc, - mut add_diagnostic: impl FnMut(Diagnostic), - ) { - let direct_fallback = matches!(&config.import, Some(i) if RELATIVE_REQUEST.is_match(i) | ABSOLUTE_REQUEST.is_match(i)); - let import_resolved = match &config.import { - None => None, - Some(import) => { - let resolver = self.get_resolver(); - resolver - .resolve( - if direct_fallback { - self.get_context() - } else { - context.clone() - } - .as_ref(), - import, - ) - .await - .map_err(|_e| { - add_diagnostic(Diagnostic::error( - "ModuleNotFoundError".into(), - format!("resolving fallback for shared module {request}"), - )) - }) - .ok() - } - } - .and_then(|i| match i { - ResolveResult::Resource(r) => Some(r.path.as_str().to_string()), - ResolveResult::Ignored => None, - }); - - // First try to infer version from the import_resolved path - let version = if let Some(ref resolved_path) = import_resolved { - if let Some(inferred) = self.infer_version(resolved_path).await { - Some(ConsumeVersion::Version(inferred)) - } else { - // If inference fails, return None and skip this entry - None - } - } else { - // If there is no resolved path, also return None - None - }; - - // If no version info can be obtained, exit early - let version = match version { - Some(v) => v, - None => return, // Early return from record_entry - }; - - let share_key = config.share_key.clone(); - let share_scope = config.share_scope.clone(); - let mut resolved_entries = self.resolved_entries.write().await; - match resolved_entries.entry(share_key) { - Entry::Occupied(mut entry) => { - let record = entry.get_mut(); - record.share_scope = share_scope; - record.requests.insert(CollectedShareRequest { - request: import_resolved - .clone() - .unwrap_or_else(|| request.to_string()), - version: version.to_string(), - }); - } - Entry::Vacant(entry) => { - let mut requests = FxHashSet::default(); - requests.insert(CollectedShareRequest { - request: import_resolved - .clone() - .unwrap_or_else(|| request.to_string()), - version: version.to_string(), - }); - entry.insert(CollectSharedEntryRecord { - share_scope, - requests, - }); - } - } - } -} - -#[plugin_hook(CompilerThisCompilation for CollectSharedEntryPlugin)] -async fn this_compilation( - &self, - compilation: &mut Compilation, - _params: &mut rspack_core::CompilationParams, -) -> Result<()> { - if self.compiler_context.get().is_none() { - self.init_context(compilation); - } - if self.resolver.get().is_none() { - self.init_resolver(compilation); - } - if self.matched_consumes.get().is_none() { - self - .init_matched_consumes(compilation, self.get_resolver()) - .await; - } - Ok(()) } #[plugin_hook(CompilationProcessAssets for CollectSharedEntryPlugin)] @@ -408,14 +224,6 @@ impl Plugin for CollectSharedEntryPlugin { } fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> { - // ctx - // .compiler_hooks - // .this_compilation - // .tap(this_compilation::new(self)); - // ctx - // .normal_module_factory_hooks - // .factorize - // .tap(factorize::new(self)); ctx .compilation_hooks .process_assets diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs b/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs index cc8ba2c38136..82a5a852e666 100644 --- a/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs +++ b/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs @@ -8,8 +8,8 @@ use rspack_core::{ BuildMeta, BuildMetaExportsType, BuildResult, CodeGenerationResult, Compilation, ConcatenationScope, Context, DependenciesBlock, DependencyId, FactoryMeta, LibIdentOptions, Module, ModuleDependency, ModuleGraph, ModuleIdentifier, ModuleType, RuntimeGlobals, RuntimeSpec, - SourceType, StaticExportsDependency, StaticExportsSpec, basic_function, impl_module_meta_info, - module_raw, module_update_hash, returning_function, + SourceType, StaticExportsDependency, StaticExportsSpec, impl_module_meta_info, + module_update_hash, rspack_sources::{BoxSource, RawStringSource, SourceExt}, }; use rspack_error::{Result, impl_empty_diagnosable_trait}; @@ -162,18 +162,25 @@ impl Module for ShareContainerEntryModule { .dependency_by_id(dependency_id) .expect("share container dependency should exist"); if let Some(dependency) = dependency.downcast_ref::() { - let module_expr = module_raw( + let module_expr = compilation.runtime_template.module_raw( compilation, &mut code_generation_result.runtime_requirements, dependency_id, dependency.user_request(), false, ); - factory = returning_function(&compilation.options.output.environment, &module_expr, ""); + factory = compilation + .runtime_template + .returning_function(&module_expr, ""); } } - let federation_global = format!("{}.federation", RuntimeGlobals::REQUIRE); + let federation_global = format!( + "{}.federation", + compilation + .runtime_template + .render_runtime_globals(&RuntimeGlobals::REQUIRE) + ); // Generate installInitialConsumes function using returning_function let install_initial_consumes_call = format!( @@ -185,11 +192,9 @@ impl Module for ShareContainerEntryModule { asyncLoad: true }})"# ); - let install_initial_consumes_fn = returning_function( - &compilation.options.output.environment, - &install_initial_consumes_call, - "", - ); + let install_initial_consumes_fn = compilation + .runtime_template + .returning_function(&install_initial_consumes_call, ""); // Create initShareContainer function using basic_function, supporting multi-statement body let init_body = format!( @@ -210,11 +215,9 @@ impl Module for ShareContainerEntryModule { federation_global = federation_global, install_initial_consumes_fn = install_initial_consumes_fn ); - let init_share_container_fn = basic_function( - &compilation.options.output.environment, - "mfInstance, bundlerRuntime", - &init_body, - ); + let init_share_container_fn = compilation + .runtime_template + .basic_function("mfInstance, bundlerRuntime", &init_body); // Generate the final source string let source = format!( @@ -227,7 +230,9 @@ impl Module for ShareContainerEntryModule { init: function() {{ return initShareContainer;}} }}); "#, - runtime = RuntimeGlobals::DEFINE_PROPERTY_GETTERS, + runtime = compilation + .runtime_template + .render_runtime_globals(&RuntimeGlobals::DEFINE_PROPERTY_GETTERS), factory = factory, init_share_container_fn = init_share_container_fn ); diff --git a/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_runtime_module.rs b/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_runtime_module.rs index 3f6ed05f6f4f..11540696a2b7 100644 --- a/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_runtime_module.rs +++ b/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_runtime_module.rs @@ -39,11 +39,16 @@ impl RuntimeModule for SharedUsedExportsOptimizerRuntimeModule { RuntimeModuleStage::Attach } - async fn generate(&self, _compilation: &Compilation) -> Result { + async fn generate(&self, compilation: &Compilation) -> Result { if self.shared_used_exports.is_empty() { return Ok(String::new()); } - let federation_global = format!("{}.federation", RuntimeGlobals::REQUIRE); + let federation_global = format!( + "{}.federation", + compilation + .runtime_template + .render_runtime_globals(&RuntimeGlobals::REQUIRE) + ); let used_exports_json = serde_json::to_string(&*self.shared_used_exports).map_err(|err| { error!( "OptimizeDependencyReferencedExportsRuntimeModule: failed to serialize used exports: {err}" From 61fe32f90d4c87ea6a8de058dd1fd57c5a3bf27e Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Thu, 27 Nov 2025 16:49:36 +0800 Subject: [PATCH 11/13] chore: change default library type as global --- crates/node_binding/rspack.wasi-browser.js | 61 +------- crates/node_binding/rspack.wasi.cjs | 61 +------- .../src/raw_options/raw_builtins/raw_mf.rs | 8 +- .../shared_used_exports_optimizer_plugin.rs | 18 --- .../rspack/src/container/ContainerPlugin.ts | 2 +- .../ModuleFederationManifestPlugin.ts | 7 + .../src/container/ModuleFederationPlugin.ts | 3 +- .../runtime/moduleFederationDefaultRuntime.js | 33 ++--- .../src/sharing/IndependentSharedPlugin.ts | 130 ++++++++---------- .../src/sharing/ShareContainerPlugin.ts | 9 +- .../SharedUsedExportsOptimizerPlugin.ts | 6 +- .../sharing/treeshake-share/index.js | 1 + .../sharing/treeshake-share/rspack.config.js | 3 +- 13 files changed, 101 insertions(+), 241 deletions(-) diff --git a/crates/node_binding/rspack.wasi-browser.js b/crates/node_binding/rspack.wasi-browser.js index e3e5c0a99d48..ee65959b37bc 100644 --- a/crates/node_binding/rspack.wasi-browser.js +++ b/crates/node_binding/rspack.wasi-browser.js @@ -63,63 +63,4 @@ const { }, }) export default __napiModule.exports -export const Assets = __napiModule.exports.Assets -export const AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock -export const Chunk = __napiModule.exports.Chunk -export const ChunkGraph = __napiModule.exports.ChunkGraph -export const ChunkGroup = __napiModule.exports.ChunkGroup -export const Chunks = __napiModule.exports.Chunks -export const CodeGenerationResult = __napiModule.exports.CodeGenerationResult -export const CodeGenerationResults = __napiModule.exports.CodeGenerationResults -export const ConcatenatedModule = __napiModule.exports.ConcatenatedModule -export const ContextModule = __napiModule.exports.ContextModule -export const Dependency = __napiModule.exports.Dependency -export const Diagnostics = __napiModule.exports.Diagnostics -export const EntryDataDto = __napiModule.exports.EntryDataDto -export const EntryDataDTO = __napiModule.exports.EntryDataDTO -export const EntryDependency = __napiModule.exports.EntryDependency -export const EntryOptionsDto = __napiModule.exports.EntryOptionsDto -export const EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO -export const ExternalModule = __napiModule.exports.ExternalModule -export const JsCompilation = __napiModule.exports.JsCompilation -export const JsCompiler = __napiModule.exports.JsCompiler -export const JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData -export const JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData -export const JsDependencies = __napiModule.exports.JsDependencies -export const JsEntries = __napiModule.exports.JsEntries -export const JsExportsInfo = __napiModule.exports.JsExportsInfo -export const JsModuleGraph = __napiModule.exports.JsModuleGraph -export const JsResolver = __napiModule.exports.JsResolver -export const JsResolverFactory = __napiModule.exports.JsResolverFactory -export const JsStats = __napiModule.exports.JsStats -export const KnownBuildInfo = __napiModule.exports.KnownBuildInfo -export const Module = __napiModule.exports.Module -export const ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection -export const NativeWatcher = __napiModule.exports.NativeWatcher -export const NativeWatchResult = __napiModule.exports.NativeWatchResult -export const NormalModule = __napiModule.exports.NormalModule -export const RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx -export const ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData -export const ResolverFactory = __napiModule.exports.ResolverFactory -export const Sources = __napiModule.exports.Sources -export const VirtualFileStore = __napiModule.exports.VirtualFileStore -export const JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore -export const async = __napiModule.exports.async -export const BuiltinPluginName = __napiModule.exports.BuiltinPluginName -export const cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace -export const EnforceExtension = __napiModule.exports.EnforceExtension -export const EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION -export const formatDiagnostic = __napiModule.exports.formatDiagnostic -export const JsLoaderState = __napiModule.exports.JsLoaderState -export const JsRspackSeverity = __napiModule.exports.JsRspackSeverity -export const loadBrowserslist = __napiModule.exports.loadBrowserslist -export const minify = __napiModule.exports.minify -export const minifySync = __napiModule.exports.minifySync -export const RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports -export const RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType -export const registerGlobalTrace = __napiModule.exports.registerGlobalTrace -export const RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind -export const sync = __napiModule.exports.sync -export const syncTraceEvent = __napiModule.exports.syncTraceEvent -export const transform = __napiModule.exports.transform -export const transformSync = __napiModule.exports.transformSync + diff --git a/crates/node_binding/rspack.wasi.cjs b/crates/node_binding/rspack.wasi.cjs index a251ce4d0d7d..1ad96db4aac4 100644 --- a/crates/node_binding/rspack.wasi.cjs +++ b/crates/node_binding/rspack.wasi.cjs @@ -108,63 +108,4 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule }, }) module.exports = __napiModule.exports -module.exports.Assets = __napiModule.exports.Assets -module.exports.AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock -module.exports.Chunk = __napiModule.exports.Chunk -module.exports.ChunkGraph = __napiModule.exports.ChunkGraph -module.exports.ChunkGroup = __napiModule.exports.ChunkGroup -module.exports.Chunks = __napiModule.exports.Chunks -module.exports.CodeGenerationResult = __napiModule.exports.CodeGenerationResult -module.exports.CodeGenerationResults = __napiModule.exports.CodeGenerationResults -module.exports.ConcatenatedModule = __napiModule.exports.ConcatenatedModule -module.exports.ContextModule = __napiModule.exports.ContextModule -module.exports.Dependency = __napiModule.exports.Dependency -module.exports.Diagnostics = __napiModule.exports.Diagnostics -module.exports.EntryDataDto = __napiModule.exports.EntryDataDto -module.exports.EntryDataDTO = __napiModule.exports.EntryDataDTO -module.exports.EntryDependency = __napiModule.exports.EntryDependency -module.exports.EntryOptionsDto = __napiModule.exports.EntryOptionsDto -module.exports.EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO -module.exports.ExternalModule = __napiModule.exports.ExternalModule -module.exports.JsCompilation = __napiModule.exports.JsCompilation -module.exports.JsCompiler = __napiModule.exports.JsCompiler -module.exports.JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData -module.exports.JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData -module.exports.JsDependencies = __napiModule.exports.JsDependencies -module.exports.JsEntries = __napiModule.exports.JsEntries -module.exports.JsExportsInfo = __napiModule.exports.JsExportsInfo -module.exports.JsModuleGraph = __napiModule.exports.JsModuleGraph -module.exports.JsResolver = __napiModule.exports.JsResolver -module.exports.JsResolverFactory = __napiModule.exports.JsResolverFactory -module.exports.JsStats = __napiModule.exports.JsStats -module.exports.KnownBuildInfo = __napiModule.exports.KnownBuildInfo -module.exports.Module = __napiModule.exports.Module -module.exports.ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection -module.exports.NativeWatcher = __napiModule.exports.NativeWatcher -module.exports.NativeWatchResult = __napiModule.exports.NativeWatchResult -module.exports.NormalModule = __napiModule.exports.NormalModule -module.exports.RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx -module.exports.ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData -module.exports.ResolverFactory = __napiModule.exports.ResolverFactory -module.exports.Sources = __napiModule.exports.Sources -module.exports.VirtualFileStore = __napiModule.exports.VirtualFileStore -module.exports.JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore -module.exports.async = __napiModule.exports.async -module.exports.BuiltinPluginName = __napiModule.exports.BuiltinPluginName -module.exports.cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace -module.exports.EnforceExtension = __napiModule.exports.EnforceExtension -module.exports.EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION -module.exports.formatDiagnostic = __napiModule.exports.formatDiagnostic -module.exports.JsLoaderState = __napiModule.exports.JsLoaderState -module.exports.JsRspackSeverity = __napiModule.exports.JsRspackSeverity -module.exports.loadBrowserslist = __napiModule.exports.loadBrowserslist -module.exports.minify = __napiModule.exports.minify -module.exports.minifySync = __napiModule.exports.minifySync -module.exports.RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports -module.exports.RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType -module.exports.registerGlobalTrace = __napiModule.exports.registerGlobalTrace -module.exports.RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind -module.exports.sync = __napiModule.exports.sync -module.exports.syncTraceEvent = __napiModule.exports.syncTraceEvent -module.exports.transform = __napiModule.exports.transform -module.exports.transformSync = __napiModule.exports.transformSync + diff --git a/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs b/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs index be001382ff7c..52e48ec1788f 100644 --- a/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs +++ b/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs @@ -237,8 +237,12 @@ impl From for SharedUsedExportsOptim .map(|config| config.into()) .collect(), inject_used_exports: value.inject_used_exports.unwrap_or(true), - manifest_file_name: value.manifest_file_name, - stats_file_name: value.stats_file_name, + manifest_file_name: value + .manifest_file_name + .and_then(|s| if s.trim().is_empty() { None } else { Some(s) }), + stats_file_name: value + .stats_file_name + .and_then(|s| if s.trim().is_empty() { None } else { Some(s) }), } } } diff --git a/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_plugin.rs b/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_plugin.rs index 9d3ffba0cd4a..f9d7f4a4e55d 100644 --- a/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_plugin.rs +++ b/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_plugin.rs @@ -346,14 +346,12 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { self.stats_file_name.clone(), self.manifest_file_name.clone(), ]; - dbg!(&file_names); for file_name in file_names { if let Some(file_name) = &file_name { if let Some(file) = compilation.assets().get(file_name) { if let Some(source) = file.get_source() { if let SourceValue::String(content) = source.source() { if let Ok(mut stats_root) = serde_json::from_str::(&content) { - dbg!(&content); let shared_referenced_exports = self .shared_referenced_exports .read() @@ -388,22 +386,6 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { } } - let assets = compilation.assets(); - if let Some(manifest_file_name) = &self.manifest_file_name { - let manifest = assets.get(manifest_file_name); - if manifest.is_none() { - return Ok(()); - } - } - if let Some(stats_file_name) = &self.stats_file_name { - let stats = assets.get(stats_file_name); - if stats.is_none() { - return Ok(()); - } - } - - // let manifest_name = self. - // let stats = compilation.assets().get() Ok(()) } diff --git a/packages/rspack/src/container/ContainerPlugin.ts b/packages/rspack/src/container/ContainerPlugin.ts index aecdf9b78d9b..b8540fe07b88 100644 --- a/packages/rspack/src/container/ContainerPlugin.ts +++ b/packages/rspack/src/container/ContainerPlugin.ts @@ -42,7 +42,7 @@ export class ContainerPlugin extends RspackBuiltinPlugin { name: options.name, shareScope: options.shareScope || "default", library: options.library || { - type: "var", + type: "global", name: options.name }, runtime: options.runtime, diff --git a/packages/rspack/src/container/ModuleFederationManifestPlugin.ts b/packages/rspack/src/container/ModuleFederationManifestPlugin.ts index 4d82dde0498a..601bf7f5eca2 100644 --- a/packages/rspack/src/container/ModuleFederationManifestPlugin.ts +++ b/packages/rspack/src/container/ModuleFederationManifestPlugin.ts @@ -120,6 +120,13 @@ export function getFileName( manifestFileName: string; } { if (!manifestOptions) { + return { + statsFileName: "", + manifestFileName: "" + }; + } + + if (typeof manifestOptions === "boolean") { return { statsFileName: STATS_FILE_NAME, manifestFileName: MANIFEST_FILE_NAME diff --git a/packages/rspack/src/container/ModuleFederationPlugin.ts b/packages/rspack/src/container/ModuleFederationPlugin.ts index e7caa39382b4..f73ef27372ab 100644 --- a/packages/rspack/src/container/ModuleFederationPlugin.ts +++ b/packages/rspack/src/container/ModuleFederationPlugin.ts @@ -229,7 +229,6 @@ function getPaths(options: ModuleFederationPluginOptions): RuntimePaths { }; } -// 注入 fallback function getDefaultEntryRuntime( paths: RuntimePaths, options: ModuleFederationPluginOptions, @@ -240,6 +239,7 @@ function getDefaultEntryRuntime( const remoteInfos = getRemoteInfos(options); const runtimePluginImports = []; const runtimePluginVars = []; + const libraryType = options.library?.type || "var"; for (let i = 0; i < runtimePlugins.length; i++) { const runtimePluginVar = `__module_federation_runtime_plugin_${i}__`; const pluginSpec = runtimePlugins[i]; @@ -272,6 +272,7 @@ function getDefaultEntryRuntime( `const __module_federation_share_fallbacks__ = ${JSON.stringify( treeshakeShareFallbacks )}`, + `const __module_federation_library_type__ = ${JSON.stringify(libraryType)}`, IS_BROWSER ? MF_RUNTIME_CODE : compiler.webpack.Template.getFunctionContent( diff --git a/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js b/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js index 679f249780c0..a8d0a27f0717 100644 --- a/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js +++ b/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js @@ -4,7 +4,8 @@ var __module_federation_bundler_runtime__, __module_federation_remote_infos__, __module_federation_container_name__, __module_federation_share_strategy__, - __module_federation_share_fallbacks__; + __module_federation_share_fallbacks__, + __module_federation_library_type__; module.exports = function () { if ( (__webpack_require__.initializeSharingData || @@ -47,13 +48,21 @@ module.exports = function () { const containerShareScope = __webpack_require__.initializeExposesData?.shareScope; - const shareFallbacks = __module_federation_share_fallbacks__ || {}; - for (const key in __module_federation_bundler_runtime__) { __webpack_require__.federation[key] = __module_federation_bundler_runtime__[key]; } + early( + __webpack_require__.federation, + "libraryType", + () => __module_federation_library_type__ + ); + early( + __webpack_require__.federation, + "sharedFallback", + () => __module_federation_share_fallbacks__ + ); early( __webpack_require__.federation, "consumesLoadingModuleToHandlerMapping", @@ -130,18 +139,6 @@ module.exports = function () { shareConfig, get: factory }; - const fallbackEntry = shareFallbacks[name]; - if (fallbackEntry) { - const fallbackUrl = - (__webpack_require__.p || "") + fallbackEntry[1]; - options.fallbackName = fallbackEntry[0]; - shareConfig.fallback = fallbackUrl; - options.fallback = function () { - return __webpack_require__.federation.importExternal( - fallbackUrl - ); - }; - } if (shared[name]) { shared[name].push(options); } else { @@ -295,9 +292,9 @@ module.exports = function () { }); __webpack_require__.federation.instance = - __webpack_require__.federation.runtime.init( - __webpack_require__.federation.initOptions - ); + __webpack_require__.federation.bundlerRuntime.init({ + webpackRequire: __webpack_require__ + }); if (__webpack_require__.consumesLoadingData?.initialConsumes) { __webpack_require__.federation.bundlerRuntime.installInitialConsumes({ diff --git a/packages/rspack/src/sharing/IndependentSharedPlugin.ts b/packages/rspack/src/sharing/IndependentSharedPlugin.ts index 0e60d9b1f81d..e0452c822d38 100644 --- a/packages/rspack/src/sharing/IndependentSharedPlugin.ts +++ b/packages/rspack/src/sharing/IndependentSharedPlugin.ts @@ -115,7 +115,6 @@ export class IndependentSharedPlugin { outputDir: string; outputFilePath?: string; plugins: Plugins; - compilers: Map = new Map(); treeshake?: boolean; manifest?: ModuleFederationManifestPluginOptions; buildAssets: ShareFallback = {}; @@ -169,6 +168,7 @@ export class IndependentSharedPlugin { } apply(compiler: Compiler) { + const { manifest } = this; compiler.hooks.beforeRun.tapPromise( "IndependentSharedPlugin", async compiler => { @@ -178,68 +178,64 @@ export class IndependentSharedPlugin { // clean hooks compiler.hooks.shutdown.tapAsync("IndependentSharedPlugin", callback => { - this.cleanup(); callback(); }); // inject buildAssets to stats - compiler.hooks.compilation.tap("IndependentSharedPlugin", compilation => { - compilation.hooks.processAssets.tapPromise( - { - name: "injectBuildAssets", - stage: (compilation.constructor as any) - .PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER - }, - async () => { - if (!this.manifest) { - return; - } - const { statsFileName, manifestFileName } = getFileName( - this.manifest - ); - const injectBuildAssetsIntoStatsOrManifest = (filename: string) => { - const stats = compilation.getAsset(filename); - if (!stats) { - return; - } - const statsContent = JSON.parse( - stats.source.source().toString() - ) as { - shared: { - name: string; - version: string; - fallback?: string; - fallbackName?: string; - }[]; - }; - - const { shared } = statsContent; - Object.entries(this.buildAssets).forEach(([key, item]) => { - const targetShared = shared.find(s => s.name === key); - if (!targetShared) { + if (manifest) { + compiler.hooks.compilation.tap("IndependentSharedPlugin", compilation => { + compilation.hooks.processAssets.tapPromise( + { + name: "injectBuildAssets", + stage: (compilation.constructor as any) + .PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER + }, + async () => { + const { statsFileName, manifestFileName } = getFileName(manifest); + const injectBuildAssetsIntoStatsOrManifest = (filename: string) => { + const stats = compilation.getAsset(filename); + if (!stats) { return; } - item.forEach(([entry, version, globalName]) => { - if (version === targetShared.version) { - targetShared.fallback = entry; - targetShared.fallbackName = globalName; + const statsContent = JSON.parse( + stats.source.source().toString() + ) as { + shared: { + name: string; + version: string; + fallback?: string; + fallbackName?: string; + }[]; + }; + + const { shared } = statsContent; + Object.entries(this.buildAssets).forEach(([key, item]) => { + const targetShared = shared.find(s => s.name === key); + if (!targetShared) { + return; } + item.forEach(([entry, version, globalName]) => { + if (version === targetShared.version) { + targetShared.fallback = entry; + targetShared.fallbackName = globalName; + } + }); }); - }); - - compilation.updateAsset( - filename, - new compiler.webpack.sources.RawSource( - JSON.stringify(statsContent) - ) - ); - }; - - injectBuildAssetsIntoStatsOrManifest(statsFileName); - injectBuildAssetsIntoStatsOrManifest(manifestFileName); - } - ); - }); + + compilation.updateAsset( + filename, + new compiler.webpack.sources.RawSource( + JSON.stringify(statsContent) + ) + ); + }; + + injectBuildAssetsIntoStatsOrManifest(statsFileName); + injectBuildAssetsIntoStatsOrManifest(manifestFileName); + } + ); + }); + } } private async createIndependentCompilers(parentCompiler: Compiler) { @@ -264,7 +260,7 @@ export class IndependentSharedPlugin { const sharedConfig = sharedOptions.find( ([name]) => name === shareName )?.[1]; - const [shareFileName, globalName] = + const [shareFileName, globalName, sharedVersion] = await this.createIndependentCompiler( parentCompiler, parentOutputDir, @@ -281,7 +277,11 @@ export class IndependentSharedPlugin { ); if (typeof shareFileName === "string") { buildAssets[shareName] ||= []; - buildAssets[shareName].push([shareFileName, version, globalName]); + buildAssets[shareName].push([ + shareFileName, + sharedVersion, + globalName + ]); } }) ); @@ -411,11 +411,6 @@ export class IndependentSharedPlugin { compiler.intermediateFileSystem = parentCompiler.intermediateFileSystem; const { currentShare } = extraOptions || {}; - currentShare && - this.compilers.set( - `${currentShare.shareName}@${currentShare.version}`, - compiler - ); return new Promise((resolve, reject) => { compiler.run((err: any, stats: any) => { @@ -451,15 +446,4 @@ export class IndependentSharedPlugin { }); }); } - - private cleanup() { - this.compilers.forEach(compiler => { - if (compiler.watching) { - compiler.watching.close(() => { - console.log("👋 编译器已关闭"); - }); - } - }); - this.compilers.clear(); - } } diff --git a/packages/rspack/src/sharing/ShareContainerPlugin.ts b/packages/rspack/src/sharing/ShareContainerPlugin.ts index fa48a2eb4769..4839a0fe8919 100644 --- a/packages/rspack/src/sharing/ShareContainerPlugin.ts +++ b/packages/rspack/src/sharing/ShareContainerPlugin.ts @@ -38,9 +38,10 @@ export class ShareContainerPlugin extends RspackBuiltinPlugin { constructor(options: ShareContainerPluginOptions) { super(); - const { shareName, library, request, independentShareFileName } = options; + const { shareName, library, request, independentShareFileName, mfName } = + options; const version = options.version || "0.0.0"; - this._globalName = encodeName(`${options.mfName}_${shareName}_${version}`); + this._globalName = encodeName(`${mfName}_${shareName}_${version}`); const fileName = independentShareFileName || `${version}/share-entry.js`; this._shareName = shareName; this._options = { @@ -49,7 +50,7 @@ export class ShareContainerPlugin extends RspackBuiltinPlugin { library: (library ? { ...library, name: this._globalName } : undefined) || { - type: "var", + type: "global", name: this._globalName }, version, @@ -57,7 +58,7 @@ export class ShareContainerPlugin extends RspackBuiltinPlugin { }; } getData() { - return [this.name, this._globalName]; + return [this._options.fileName, this._globalName, this._options.version]; } raw(compiler: Compiler): BuiltinPlugin { diff --git a/packages/rspack/src/sharing/SharedUsedExportsOptimizerPlugin.ts b/packages/rspack/src/sharing/SharedUsedExportsOptimizerPlugin.ts index 2c6b77869280..11d6f1a19793 100644 --- a/packages/rspack/src/sharing/SharedUsedExportsOptimizerPlugin.ts +++ b/packages/rspack/src/sharing/SharedUsedExportsOptimizerPlugin.ts @@ -12,7 +12,7 @@ import { getFileName, type ModuleFederationManifestPluginOptions } from "../container/ModuleFederationManifestPlugin"; -import type { SharedConfig } from "./SharePlugin"; +import type { NormalizedSharedOptions } from "./SharePlugin"; type OptimizeSharedConfig = { shareKey: string; @@ -22,12 +22,12 @@ type OptimizeSharedConfig = { export class SharedUsedExportsOptimizerPlugin extends RspackBuiltinPlugin { name = BuiltinPluginName.SharedUsedExportsOptimizerPlugin; - private sharedOptions: [string, SharedConfig][]; + private sharedOptions: NormalizedSharedOptions; private injectUsedExports: boolean; private manifestOptions: ModuleFederationManifestPluginOptions; constructor( - sharedOptions: [string, SharedConfig][], + sharedOptions: NormalizedSharedOptions, injectUsedExports?: boolean, manifestOptions?: ModuleFederationManifestPluginOptions ) { diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/index.js b/tests/rspack-test/configCases/sharing/treeshake-share/index.js index 39dd2f080766..c65b698326b3 100644 --- a/tests/rspack-test/configCases/sharing/treeshake-share/index.js +++ b/tests/rspack-test/configCases/sharing/treeshake-share/index.js @@ -71,3 +71,4 @@ it("should inject usedExports into manifest and stats if enable manifest", async ])); }); +// it("should ") diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js b/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js index c732f861da66..560a589ff4a5 100644 --- a/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js +++ b/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js @@ -4,8 +4,9 @@ const { ModuleFederationPlugin } = container; /** @type {import("@rspack/core").Configuration} */ module.exports = { + entry: './index.js', optimization:{ - minimize: true, + minimize: false, chunkIds:'named', moduleIds: 'named' }, From 4a264eb5fae4fbd850959d801833bc2df4cada18 Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Tue, 2 Dec 2025 16:48:29 +0800 Subject: [PATCH 12/13] chore: wip --- .../sharing/shared_container_dependency.rs | 71 +++++ .../shared_container_entry_dependency.rs | 78 +++++ .../sharing/shared_container_entry_module.rs | 268 ++++++++++++++++++ .../shared_container_entry_module_factory.rs | 26 ++ .../src/sharing/shared_container_plugin.rs | 107 +++++++ .../shared_container_runtime_module.rs | 40 +++ .../src/sharing/SharedContainerPlugin.ts | 116 ++++++++ 7 files changed, 706 insertions(+) create mode 100644 crates/rspack_plugin_mf/src/sharing/shared_container_dependency.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/shared_container_entry_dependency.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/shared_container_entry_module.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/shared_container_entry_module_factory.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/shared_container_plugin.rs create mode 100644 crates/rspack_plugin_mf/src/sharing/shared_container_runtime_module.rs create mode 100644 packages/rspack/src/sharing/SharedContainerPlugin.ts diff --git a/crates/rspack_plugin_mf/src/sharing/shared_container_dependency.rs b/crates/rspack_plugin_mf/src/sharing/shared_container_dependency.rs new file mode 100644 index 000000000000..81909a2bafed --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/shared_container_dependency.rs @@ -0,0 +1,71 @@ +use rspack_cacheable::{cacheable, cacheable_dyn}; +use rspack_core::{ + AsContextDependency, AsDependencyCodeGeneration, Dependency, DependencyCategory, DependencyId, + DependencyType, FactorizeInfo, ModuleDependency, +}; + +#[cacheable] +#[derive(Debug, Clone)] +pub struct SharedContainerDependency { + id: DependencyId, + request: String, + resource_identifier: String, + factorize_info: FactorizeInfo, +} + +impl SharedContainerDependency { + pub fn new(request: String) -> Self { + let resource_identifier = format!("share-container-fallback:{}", request); + Self { + id: DependencyId::new(), + request, + resource_identifier, + factorize_info: Default::default(), + } + } +} + +#[cacheable_dyn] +impl Dependency for SharedContainerDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::ShareContainerFallback + } + + fn resource_identifier(&self) -> Option<&str> { + Some(&self.resource_identifier) + } + + fn could_affect_referencing_module(&self) -> rspack_core::AffectType { + rspack_core::AffectType::True + } +} + +#[cacheable_dyn] +impl ModuleDependency for SharedContainerDependency { + fn request(&self) -> &str { + &self.request + } + + fn user_request(&self) -> &str { + &self.request + } + + fn factorize_info(&self) -> &FactorizeInfo { + &self.factorize_info + } + + fn factorize_info_mut(&mut self) -> &mut FactorizeInfo { + &mut self.factorize_info + } +} + +impl AsContextDependency for SharedContainerDependency {} +impl AsDependencyCodeGeneration for SharedContainerDependency {} diff --git a/crates/rspack_plugin_mf/src/sharing/shared_container_entry_dependency.rs b/crates/rspack_plugin_mf/src/sharing/shared_container_entry_dependency.rs new file mode 100644 index 000000000000..e4774db0e147 --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/shared_container_entry_dependency.rs @@ -0,0 +1,78 @@ +use rspack_cacheable::{cacheable, cacheable_dyn}; +use rspack_core::{ + AsContextDependency, AsDependencyCodeGeneration, Dependency, DependencyCategory, DependencyId, + DependencyType, FactorizeInfo, ModuleDependency, +}; +use serde::Serialize; + +#[cacheable] +#[derive(Debug, Clone)] +pub struct SharedContainerEntryDependency { + id: DependencyId, + pub name: String, + pub request: String, + pub version: String, + resource_identifier: String, + factorize_info: FactorizeInfo, +} + +#[cacheable] +#[derive(Debug, Clone, Serialize)] +pub struct ShareContainerEntryOptions { + pub request: String, +} + +impl SharedContainerEntryDependency { + pub fn new(name: String, request: String, version: String) -> Self { + let resource_identifier = format!("share-container-entry-{}", &name); + Self { + id: DependencyId::new(), + name, + request, + version, + resource_identifier, + factorize_info: Default::default(), + } + } +} + +#[cacheable_dyn] +impl Dependency for SharedContainerEntryDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::ShareContainerEntry + } + + fn resource_identifier(&self) -> Option<&str> { + Some(&self.resource_identifier) + } + + fn could_affect_referencing_module(&self) -> rspack_core::AffectType { + rspack_core::AffectType::Transitive + } +} + +#[cacheable_dyn] +impl ModuleDependency for SharedContainerEntryDependency { + fn request(&self) -> &str { + &self.resource_identifier + } + + fn factorize_info(&self) -> &FactorizeInfo { + &self.factorize_info + } + + fn factorize_info_mut(&mut self) -> &mut FactorizeInfo { + &mut self.factorize_info + } +} + +impl AsContextDependency for SharedContainerEntryDependency {} +impl AsDependencyCodeGeneration for SharedContainerEntryDependency {} diff --git a/crates/rspack_plugin_mf/src/sharing/shared_container_entry_module.rs b/crates/rspack_plugin_mf/src/sharing/shared_container_entry_module.rs new file mode 100644 index 000000000000..a02890de2d15 --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/shared_container_entry_module.rs @@ -0,0 +1,268 @@ +use std::borrow::Cow; + +use async_trait::async_trait; +use rspack_cacheable::{cacheable, cacheable_dyn}; +use rspack_collections::{Identifiable, Identifier}; +use rspack_core::{ + AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, + BuildMeta, BuildMetaExportsType, BuildResult, CodeGenerationResult, Compilation, + ConcatenationScope, Context, DependenciesBlock, DependencyId, FactoryMeta, LibIdentOptions, + Module, ModuleDependency, ModuleGraph, ModuleIdentifier, ModuleType, RuntimeGlobals, RuntimeSpec, + SourceType, StaticExportsDependency, StaticExportsSpec, impl_module_meta_info, + module_update_hash, + rspack_sources::{BoxSource, RawStringSource, SourceExt}, +}; +use rspack_error::{Result, impl_empty_diagnosable_trait}; +use rspack_hash::{RspackHash, RspackHashDigest}; +use rspack_util::source_map::{ModuleSourceMapConfig, SourceMapKind}; +use rustc_hash::FxHashSet; + +use super::shared_container_dependency::SharedContainerDependency; + +#[cacheable] +#[derive(Debug)] +pub struct SharedContainerEntryModule { + blocks: Vec, + dependencies: Vec, + identifier: ModuleIdentifier, + lib_ident: String, + name: String, + request: String, + version: String, + factory_meta: Option, + build_info: BuildInfo, + build_meta: BuildMeta, + source_map_kind: SourceMapKind, +} + +impl SharedContainerEntryModule { + pub fn new(name: String, request: String, version: String) -> Self { + let lib_ident = format!("webpack/share/container/{}", &name); + Self { + blocks: Vec::new(), + dependencies: Vec::new(), + identifier: ModuleIdentifier::from(format!("share container entry {}@{}", &name, &version,)), + lib_ident, + name, + request, + version, + factory_meta: None, + build_info: BuildInfo { + strict: true, + top_level_declarations: Some(FxHashSet::default()), + ..Default::default() + }, + build_meta: BuildMeta { + exports_type: BuildMetaExportsType::Namespace, + ..Default::default() + }, + source_map_kind: SourceMapKind::empty(), + } + } +} + +impl Identifiable for SharedContainerEntryModule { + fn identifier(&self) -> Identifier { + self.identifier + } +} + +impl DependenciesBlock for SharedContainerEntryModule { + fn add_block_id(&mut self, block: AsyncDependenciesBlockIdentifier) { + self.blocks.push(block) + } + + fn get_blocks(&self) -> &[AsyncDependenciesBlockIdentifier] { + &self.blocks + } + + fn add_dependency_id(&mut self, dependency: DependencyId) { + self.dependencies.push(dependency) + } + + fn remove_dependency_id(&mut self, dependency: DependencyId) { + self.dependencies.retain(|d| d != &dependency) + } + + fn get_dependencies(&self) -> &[DependencyId] { + &self.dependencies + } +} + +#[cacheable_dyn] +#[async_trait] +impl Module for SharedContainerEntryModule { + impl_module_meta_info!(); + + fn size(&self, _source_type: Option<&SourceType>, _compilation: Option<&Compilation>) -> f64 { + 42.0 + } + + fn module_type(&self) -> &ModuleType { + &ModuleType::ShareContainerShared + } + + fn source_types(&self, _module_graph: &ModuleGraph) -> &[SourceType] { + &[SourceType::JavaScript, SourceType::Expose] + } + + fn source(&self) -> Option<&BoxSource> { + None + } + + fn readable_identifier(&self, _context: &Context) -> Cow<'_, str> { + "share container entry".into() + } + + fn lib_ident(&self, _options: LibIdentOptions) -> Option> { + Some(self.lib_ident.as_str().into()) + } + + async fn build( + &mut self, + _build_context: BuildContext, + _: Option<&Compilation>, + ) -> Result { + let mut dependencies: Vec = Vec::new(); + + dependencies.push(Box::new(StaticExportsDependency::new( + StaticExportsSpec::Array(vec!["get".into(), "init".into()]), + false, + ))); + dependencies.push(Box::new(SharedContainerDependency::new(self.name.clone()))); + + Ok(BuildResult { + dependencies, + blocks: Vec::>::new(), + ..Default::default() + }) + } + + async fn code_generation( + &self, + compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + _: Option, + ) -> Result { + let mut code_generation_result = CodeGenerationResult::default(); + code_generation_result + .runtime_requirements + .insert(RuntimeGlobals::DEFINE_PROPERTY_GETTERS); + code_generation_result + .runtime_requirements + .insert(RuntimeGlobals::EXPORTS); + code_generation_result + .runtime_requirements + .insert(RuntimeGlobals::REQUIRE); + + let module_graph = compilation.get_module_graph(); + let mut factory = String::new(); + for dependency_id in self.get_dependencies() { + let dependency = module_graph + .dependency_by_id(dependency_id) + .expect("share container dependency should exist"); + if let Some(dependency) = dependency.downcast_ref::() { + let module_expr = compilation.runtime_template.module_raw( + compilation, + &mut code_generation_result.runtime_requirements, + dependency_id, + dependency.user_request(), + false, + ); + factory = compilation + .runtime_template + .returning_function(&module_expr, ""); + } + } + + let federation_global = format!( + "{}.federation", + compilation + .runtime_template + .render_runtime_globals(&RuntimeGlobals::REQUIRE) + ); + + // Generate installInitialConsumes function using returning_function + let install_initial_consumes_call = format!( + r#"localBundlerRuntime.installInitialConsumes({{ + installedModules: localInstalledModules, + initialConsumes: __webpack_require__.consumesLoadingData.initialConsumes, + moduleToHandlerMapping: __webpack_require__.federation.consumesLoadingModuleToHandlerMapping, + webpackRequire: __webpack_require__, + asyncLoad: true + }})"# + ); + let install_initial_consumes_fn = compilation + .runtime_template + .returning_function(&install_initial_consumes_call, ""); + + // Create initShareContainer function using basic_function, supporting multi-statement body + let init_body = format!( + r#" + var installedModules = {{}}; + {federation_global}.instance = mfInstance; + {federation_global}.bundlerRuntime = bundlerRuntime; + + // Save parameters to local variables to avoid closure issues + var localBundlerRuntime = bundlerRuntime; + var localInstalledModules = installedModules; + + if(!__webpack_require__.consumesLoadingData){{return; }} + {federation_global}.installInitialConsumes = {install_initial_consumes_fn}; + + return {federation_global}.installInitialConsumes(); + "#, + federation_global = federation_global, + install_initial_consumes_fn = install_initial_consumes_fn + ); + let init_share_container_fn = compilation + .runtime_template + .basic_function("mfInstance, bundlerRuntime", &init_body); + + // Generate the final source string + let source = format!( + r#" + __webpack_require__.federation = {{ instance: undefined,bundlerRuntime: undefined }} + var factory = {factory}; + var initShareContainer = {init_share_container_fn}; +{runtime}(exports, {{ + get: function() {{ return factory;}}, + init: function() {{ return initShareContainer;}} +}}); +"#, + runtime = compilation + .runtime_template + .render_runtime_globals(&RuntimeGlobals::DEFINE_PROPERTY_GETTERS), + factory = factory, + init_share_container_fn = init_share_container_fn + ); + + // Update the code generation result with the generated source + code_generation_result = + code_generation_result.with_javascript(RawStringSource::from(source).boxed()); + code_generation_result.add(SourceType::Expose, RawStringSource::from_static("").boxed()); + Ok(code_generation_result) + } + + async fn get_runtime_hash( + &self, + compilation: &Compilation, + runtime: Option<&RuntimeSpec>, + ) -> Result { + let mut hasher = RspackHash::from(&compilation.options.output); + module_update_hash(self, &mut hasher, compilation, runtime); + Ok(hasher.digest(&compilation.options.output.hash_digest)) + } +} + +impl_empty_diagnosable_trait!(SharedContainerEntryModule); + +impl ModuleSourceMapConfig for SharedContainerEntryModule { + fn get_source_map_kind(&self) -> &SourceMapKind { + &self.source_map_kind + } + + fn set_source_map_kind(&mut self, source_map: SourceMapKind) { + self.source_map_kind = source_map; + } +} diff --git a/crates/rspack_plugin_mf/src/sharing/shared_container_entry_module_factory.rs b/crates/rspack_plugin_mf/src/sharing/shared_container_entry_module_factory.rs new file mode 100644 index 000000000000..d7c9d5c6a42c --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/shared_container_entry_module_factory.rs @@ -0,0 +1,26 @@ +use async_trait::async_trait; +use rspack_core::{ModuleExt, ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult}; +use rspack_error::Result; + +use super::{ + shared_container_entry_dependency::SharedContainerEntryDependency, + shared_container_entry_module::SharedContainerEntryModule, +}; + +#[derive(Debug)] +pub struct SharedContainerEntryModuleFactory; + +#[async_trait] +impl ModuleFactory for SharedContainerEntryModuleFactory { + async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result { + let dep = data.dependencies[0] + .downcast_ref::() + .expect( + "dependency of SharedContainerEntryModuleFactory should be SharedContainerEntryDependency", + ); + Ok(ModuleFactoryResult::new_with_module( + SharedContainerEntryModule::new(dep.name.clone(), dep.request.clone(), dep.version.clone()) + .boxed(), + )) + } +} diff --git a/crates/rspack_plugin_mf/src/sharing/shared_container_plugin.rs b/crates/rspack_plugin_mf/src/sharing/shared_container_plugin.rs new file mode 100644 index 000000000000..bf4ae375f968 --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/shared_container_plugin.rs @@ -0,0 +1,107 @@ +use std::sync::Arc; + +use rspack_core::{ + ChunkUkey, Compilation, CompilationAdditionalTreeRuntimeRequirements, CompilationParams, + CompilerCompilation, CompilerMake, DependencyType, Filename, LibraryOptions, Plugin, + RuntimeGlobals, RuntimeModuleExt, +}; +use rspack_error::Result; +use rspack_hook::{plugin, plugin_hook}; + +use super::{ + shared_container_entry_dependency::SharedContainerEntryDependency, + shared_container_entry_module_factory::SharedContainerEntryModuleFactory, +}; +use crate::sharing::shared_container_runtime_module::ShareContainerRuntimeModule; + +#[derive(Debug)] +pub struct SharedContainerPluginOptions { + pub name: String, + pub request: String, + pub version: String, + pub file_name: Option, + pub library: LibraryOptions, +} + +#[plugin] +#[derive(Debug)] +pub struct SharedContainerPlugin { + options: SharedContainerPluginOptions, +} + +impl SharedContainerPlugin { + pub fn new(options: SharedContainerPluginOptions) -> Self { + Self::new_inner(options) + } +} + +#[plugin_hook(CompilerCompilation for SharedContainerPlugin)] +async fn compilation( + &self, + compilation: &mut Compilation, + params: &mut CompilationParams, +) -> Result<()> { + compilation.set_dependency_factory( + DependencyType::ShareContainerEntry, + Arc::new(SharedContainerEntryModuleFactory), + ); + compilation.set_dependency_factory( + DependencyType::ShareContainerFallback, + params.normal_module_factory.clone(), + ); + Ok(()) +} + +#[plugin_hook(CompilerMake for SharedContainerPlugin)] +async fn make(&self, compilation: &mut Compilation) -> Result<()> { + let dep = SharedContainerEntryDependency::new( + self.options.name.clone(), + self.options.request.clone(), + self.options.version.clone(), + ); + + compilation + .add_entry( + Box::new(dep), + rspack_core::EntryOptions { + name: Some(self.options.name.clone()), + filename: self.options.file_name.clone(), + library: Some(self.options.library.clone()), + ..Default::default() + }, + ) + .await?; + Ok(()) +} + +#[plugin_hook(CompilationAdditionalTreeRuntimeRequirements for SharedContainerPlugin)] +async fn additional_tree_runtime_requirements( + &self, + compilation: &mut Compilation, + chunk_ukey: &ChunkUkey, + _runtime_requirements: &mut RuntimeGlobals, +) -> Result<()> { + let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey); + if let Some(name) = chunk.name() + && name == self.options.name + { + compilation.add_runtime_module(chunk_ukey, ShareContainerRuntimeModule::new().boxed())?; + } + Ok(()) +} + +impl Plugin for SharedContainerPlugin { + fn name(&self) -> &'static str { + "rspack.SharedContainerPlugin" + } + + fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> { + ctx.compiler_hooks.compilation.tap(compilation::new(self)); + ctx.compiler_hooks.make.tap(make::new(self)); + ctx + .compilation_hooks + .additional_tree_runtime_requirements + .tap(additional_tree_runtime_requirements::new(self)); + Ok(()) + } +} diff --git a/crates/rspack_plugin_mf/src/sharing/shared_container_runtime_module.rs b/crates/rspack_plugin_mf/src/sharing/shared_container_runtime_module.rs new file mode 100644 index 000000000000..09c88b12441d --- /dev/null +++ b/crates/rspack_plugin_mf/src/sharing/shared_container_runtime_module.rs @@ -0,0 +1,40 @@ +use rspack_collections::Identifier; +use rspack_core::{ChunkUkey, Compilation, RuntimeModule, RuntimeModuleStage, impl_runtime_module}; + +#[impl_runtime_module] +#[derive(Debug)] +pub struct ShareContainerRuntimeModule { + id: Identifier, + chunk: Option, +} + +impl ShareContainerRuntimeModule { + pub fn new() -> Self { + Self::with_default( + Identifier::from("webpack/runtime/share_container_federation"), + None, + ) + } +} + +#[async_trait::async_trait] +impl RuntimeModule for ShareContainerRuntimeModule { + fn name(&self) -> Identifier { + self.id + } + + async fn generate(&self, _compilation: &Compilation) -> rspack_error::Result { + Ok( + "__webpack_require__.federation = { instance: undefined,bundlerRuntime: undefined };" + .to_string(), + ) + } + + fn attach(&mut self, chunk: ChunkUkey) { + self.chunk = Some(chunk); + } + + fn stage(&self) -> RuntimeModuleStage { + RuntimeModuleStage::Attach + } +} diff --git a/packages/rspack/src/sharing/SharedContainerPlugin.ts b/packages/rspack/src/sharing/SharedContainerPlugin.ts new file mode 100644 index 000000000000..97c87f6a86a2 --- /dev/null +++ b/packages/rspack/src/sharing/SharedContainerPlugin.ts @@ -0,0 +1,116 @@ +import { + type BuiltinPlugin, + BuiltinPluginName, + type RawSharedContainerPluginOptions +} from "@rspack/binding"; +import { + createBuiltinPlugin, + RspackBuiltinPlugin +} from "../builtin-plugin/base"; +import type { Compilation } from "../Compilation"; +import type { Compiler } from "../Compiler"; +import type { LibraryOptions } from "../config"; +import { encodeName } from "./utils"; + +export type SharedContainerPluginOptions = { + mfName: string; + shareName: string; + version: string; + request: string; + library?: LibraryOptions; + independentShareFileName?: string; +}; + +function assert(condition: any, msg: string): asserts condition { + if (!condition) { + throw new Error(msg); + } +} + +const HOT_UPDATE_SUFFIX = ".hot-update"; + +export class SharedContainerPlugin extends RspackBuiltinPlugin { + name = BuiltinPluginName.SharedContainerPlugin; + filename = ""; + _options: RawSharedContainerPluginOptions; + _shareName: string; + _globalName: string; + + constructor(options: SharedContainerPluginOptions) { + super(); + const { shareName, library, request, independentShareFileName, mfName } = + options; + const version = options.version || "0.0.0"; + this._globalName = encodeName(`${mfName}_${shareName}_${version}`); + const fileName = independentShareFileName || `${version}/share-entry.js`; + this._shareName = shareName; + this._options = { + name: shareName, + request: request, + library: (library + ? { ...library, name: this._globalName } + : undefined) || { + type: "global", + name: this._globalName + }, + version, + fileName + }; + } + getData() { + return [this._options.fileName, this._globalName, this._options.version]; + } + + raw(compiler: Compiler): BuiltinPlugin { + const { library } = this._options; + if (!compiler.options.output.enabledLibraryTypes!.includes(library.type)) { + compiler.options.output.enabledLibraryTypes!.push(library.type); + } + return createBuiltinPlugin(this.name, this._options); + } + + apply(compiler: Compiler) { + super.apply(compiler); + const shareName = this._shareName; + compiler.hooks.thisCompilation.tap( + this.name, + (compilation: Compilation) => { + compilation.hooks.processAssets.tapPromise( + { + name: "getShareContainerFile" + }, + async () => { + const remoteEntryPoint = compilation.entrypoints.get(shareName); + assert( + remoteEntryPoint, + `Can not get shared ${shareName} entryPoint!` + ); + const remoteEntryNameChunk = compilation.namedChunks.get(shareName); + assert( + remoteEntryNameChunk, + `Can not get shared ${shareName} chunk!` + ); + + const files = Array.from( + remoteEntryNameChunk.files as Iterable + ).filter( + (f: string) => + !f.includes(HOT_UPDATE_SUFFIX) && !f.endsWith(".css") + ); + assert( + files.length > 0, + `no files found for shared ${shareName} chunk` + ); + assert( + files.length === 1, + `shared ${shareName} chunk should not have multiple files!, current files: ${files.join( + "," + )}` + ); + this.filename = files[0]; + } + ); + } + ); + } +} From 7c286f3e9c39ed5e9d061c03f720c77b9d49a33a Mon Sep 17 00:00:00 2001 From: "zhanghang.heal" Date: Tue, 2 Dec 2025 16:49:00 +0800 Subject: [PATCH 13/13] chore: wip --- crates/node_binding/napi-binding.d.ts | 4 +- crates/node_binding/rspack.wasi-browser.js | 61 +++- crates/node_binding/rspack.wasi.cjs | 61 +++- .../src/raw_options/raw_builtins/mod.rs | 12 +- .../src/raw_options/raw_builtins/raw_mf.rs | 10 +- crates/rspack_plugin_mf/src/lib.rs | 4 +- crates/rspack_plugin_mf/src/sharing/mod.rs | 12 +- .../src/sharing/share_container_dependency.rs | 71 ----- .../share_container_entry_dependency.rs | 78 ----- .../sharing/share_container_entry_module.rs | 268 ------------------ .../share_container_entry_module_factory.rs | 26 -- .../src/sharing/share_container_plugin.rs | 107 ------- .../sharing/share_container_runtime_module.rs | 40 --- .../shared_used_exports_optimizer_plugin.rs | 4 +- .../src/sharing/IndependentSharedPlugin.ts | 12 +- .../src/sharing/ShareContainerPlugin.ts | 116 -------- .../src/sharing/TreeShakeSharedPlugin.ts | 15 +- .../sharing/treeshake-share/rspack.config.js | 4 - 18 files changed, 156 insertions(+), 749 deletions(-) delete mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_dependency.rs delete mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_entry_dependency.rs delete mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs delete mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_entry_module_factory.rs delete mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_plugin.rs delete mode 100644 crates/rspack_plugin_mf/src/sharing/share_container_runtime_module.rs delete mode 100644 packages/rspack/src/sharing/ShareContainerPlugin.ts diff --git a/crates/node_binding/napi-binding.d.ts b/crates/node_binding/napi-binding.d.ts index 7440ecbb5c35..3d6dea40d43a 100644 --- a/crates/node_binding/napi-binding.d.ts +++ b/crates/node_binding/napi-binding.d.ts @@ -546,7 +546,7 @@ export declare enum BuiltinPluginName { ProvideSharedPlugin = 'ProvideSharedPlugin', ConsumeSharedPlugin = 'ConsumeSharedPlugin', CollectSharedEntryPlugin = 'CollectSharedEntryPlugin', - ShareContainerPlugin = 'ShareContainerPlugin', + SharedContainerPlugin = 'SharedContainerPlugin', ModuleFederationRuntimePlugin = 'ModuleFederationRuntimePlugin', ModuleFederationManifestPlugin = 'ModuleFederationManifestPlugin', NamedModuleIdsPlugin = 'NamedModuleIdsPlugin', @@ -2834,7 +2834,7 @@ export interface RawRuntimeChunkOptions { name: string | ((entrypoint: { name: string }) => string) } -export interface RawShareContainerPluginOptions { +export interface RawSharedContainerPluginOptions { name: string request: string version: string diff --git a/crates/node_binding/rspack.wasi-browser.js b/crates/node_binding/rspack.wasi-browser.js index ee65959b37bc..e3e5c0a99d48 100644 --- a/crates/node_binding/rspack.wasi-browser.js +++ b/crates/node_binding/rspack.wasi-browser.js @@ -63,4 +63,63 @@ const { }, }) export default __napiModule.exports - +export const Assets = __napiModule.exports.Assets +export const AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock +export const Chunk = __napiModule.exports.Chunk +export const ChunkGraph = __napiModule.exports.ChunkGraph +export const ChunkGroup = __napiModule.exports.ChunkGroup +export const Chunks = __napiModule.exports.Chunks +export const CodeGenerationResult = __napiModule.exports.CodeGenerationResult +export const CodeGenerationResults = __napiModule.exports.CodeGenerationResults +export const ConcatenatedModule = __napiModule.exports.ConcatenatedModule +export const ContextModule = __napiModule.exports.ContextModule +export const Dependency = __napiModule.exports.Dependency +export const Diagnostics = __napiModule.exports.Diagnostics +export const EntryDataDto = __napiModule.exports.EntryDataDto +export const EntryDataDTO = __napiModule.exports.EntryDataDTO +export const EntryDependency = __napiModule.exports.EntryDependency +export const EntryOptionsDto = __napiModule.exports.EntryOptionsDto +export const EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO +export const ExternalModule = __napiModule.exports.ExternalModule +export const JsCompilation = __napiModule.exports.JsCompilation +export const JsCompiler = __napiModule.exports.JsCompiler +export const JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData +export const JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData +export const JsDependencies = __napiModule.exports.JsDependencies +export const JsEntries = __napiModule.exports.JsEntries +export const JsExportsInfo = __napiModule.exports.JsExportsInfo +export const JsModuleGraph = __napiModule.exports.JsModuleGraph +export const JsResolver = __napiModule.exports.JsResolver +export const JsResolverFactory = __napiModule.exports.JsResolverFactory +export const JsStats = __napiModule.exports.JsStats +export const KnownBuildInfo = __napiModule.exports.KnownBuildInfo +export const Module = __napiModule.exports.Module +export const ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection +export const NativeWatcher = __napiModule.exports.NativeWatcher +export const NativeWatchResult = __napiModule.exports.NativeWatchResult +export const NormalModule = __napiModule.exports.NormalModule +export const RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx +export const ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData +export const ResolverFactory = __napiModule.exports.ResolverFactory +export const Sources = __napiModule.exports.Sources +export const VirtualFileStore = __napiModule.exports.VirtualFileStore +export const JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore +export const async = __napiModule.exports.async +export const BuiltinPluginName = __napiModule.exports.BuiltinPluginName +export const cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace +export const EnforceExtension = __napiModule.exports.EnforceExtension +export const EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION +export const formatDiagnostic = __napiModule.exports.formatDiagnostic +export const JsLoaderState = __napiModule.exports.JsLoaderState +export const JsRspackSeverity = __napiModule.exports.JsRspackSeverity +export const loadBrowserslist = __napiModule.exports.loadBrowserslist +export const minify = __napiModule.exports.minify +export const minifySync = __napiModule.exports.minifySync +export const RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports +export const RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType +export const registerGlobalTrace = __napiModule.exports.registerGlobalTrace +export const RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind +export const sync = __napiModule.exports.sync +export const syncTraceEvent = __napiModule.exports.syncTraceEvent +export const transform = __napiModule.exports.transform +export const transformSync = __napiModule.exports.transformSync diff --git a/crates/node_binding/rspack.wasi.cjs b/crates/node_binding/rspack.wasi.cjs index 1ad96db4aac4..a251ce4d0d7d 100644 --- a/crates/node_binding/rspack.wasi.cjs +++ b/crates/node_binding/rspack.wasi.cjs @@ -108,4 +108,63 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule }, }) module.exports = __napiModule.exports - +module.exports.Assets = __napiModule.exports.Assets +module.exports.AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock +module.exports.Chunk = __napiModule.exports.Chunk +module.exports.ChunkGraph = __napiModule.exports.ChunkGraph +module.exports.ChunkGroup = __napiModule.exports.ChunkGroup +module.exports.Chunks = __napiModule.exports.Chunks +module.exports.CodeGenerationResult = __napiModule.exports.CodeGenerationResult +module.exports.CodeGenerationResults = __napiModule.exports.CodeGenerationResults +module.exports.ConcatenatedModule = __napiModule.exports.ConcatenatedModule +module.exports.ContextModule = __napiModule.exports.ContextModule +module.exports.Dependency = __napiModule.exports.Dependency +module.exports.Diagnostics = __napiModule.exports.Diagnostics +module.exports.EntryDataDto = __napiModule.exports.EntryDataDto +module.exports.EntryDataDTO = __napiModule.exports.EntryDataDTO +module.exports.EntryDependency = __napiModule.exports.EntryDependency +module.exports.EntryOptionsDto = __napiModule.exports.EntryOptionsDto +module.exports.EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO +module.exports.ExternalModule = __napiModule.exports.ExternalModule +module.exports.JsCompilation = __napiModule.exports.JsCompilation +module.exports.JsCompiler = __napiModule.exports.JsCompiler +module.exports.JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData +module.exports.JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData +module.exports.JsDependencies = __napiModule.exports.JsDependencies +module.exports.JsEntries = __napiModule.exports.JsEntries +module.exports.JsExportsInfo = __napiModule.exports.JsExportsInfo +module.exports.JsModuleGraph = __napiModule.exports.JsModuleGraph +module.exports.JsResolver = __napiModule.exports.JsResolver +module.exports.JsResolverFactory = __napiModule.exports.JsResolverFactory +module.exports.JsStats = __napiModule.exports.JsStats +module.exports.KnownBuildInfo = __napiModule.exports.KnownBuildInfo +module.exports.Module = __napiModule.exports.Module +module.exports.ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection +module.exports.NativeWatcher = __napiModule.exports.NativeWatcher +module.exports.NativeWatchResult = __napiModule.exports.NativeWatchResult +module.exports.NormalModule = __napiModule.exports.NormalModule +module.exports.RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx +module.exports.ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData +module.exports.ResolverFactory = __napiModule.exports.ResolverFactory +module.exports.Sources = __napiModule.exports.Sources +module.exports.VirtualFileStore = __napiModule.exports.VirtualFileStore +module.exports.JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore +module.exports.async = __napiModule.exports.async +module.exports.BuiltinPluginName = __napiModule.exports.BuiltinPluginName +module.exports.cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace +module.exports.EnforceExtension = __napiModule.exports.EnforceExtension +module.exports.EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION +module.exports.formatDiagnostic = __napiModule.exports.formatDiagnostic +module.exports.JsLoaderState = __napiModule.exports.JsLoaderState +module.exports.JsRspackSeverity = __napiModule.exports.JsRspackSeverity +module.exports.loadBrowserslist = __napiModule.exports.loadBrowserslist +module.exports.minify = __napiModule.exports.minify +module.exports.minifySync = __napiModule.exports.minifySync +module.exports.RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports +module.exports.RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType +module.exports.registerGlobalTrace = __napiModule.exports.registerGlobalTrace +module.exports.RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind +module.exports.sync = __napiModule.exports.sync +module.exports.syncTraceEvent = __napiModule.exports.syncTraceEvent +module.exports.transform = __napiModule.exports.transform +module.exports.transformSync = __napiModule.exports.transformSync diff --git a/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs b/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs index 56a7674d6e37..d74fe7d9acdf 100644 --- a/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs +++ b/crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs @@ -81,7 +81,7 @@ use rspack_plugin_merge_duplicate_chunks::MergeDuplicateChunksPlugin; use rspack_plugin_mf::{ CollectSharedEntryPlugin, ConsumeSharedPlugin, ContainerPlugin, ContainerReferencePlugin, ModuleFederationManifestPlugin, ModuleFederationRuntimePlugin, ProvideSharedPlugin, - ShareContainerPlugin, ShareRuntimePlugin, SharedUsedExportsOptimizerPlugin, + ShareRuntimePlugin, SharedContainerPlugin, SharedUsedExportsOptimizerPlugin, }; use rspack_plugin_module_info_header::ModuleInfoHeaderPlugin; use rspack_plugin_module_replacement::{ContextReplacementPlugin, NormalModuleReplacementPlugin}; @@ -122,7 +122,7 @@ use self::{ raw_limit_chunk_count::RawLimitChunkCountPluginOptions, raw_mf::{ RawConsumeSharedPluginOptions, RawContainerPluginOptions, RawContainerReferencePluginOptions, - RawShareContainerPluginOptions, + RawSharedContainerPluginOptions, }, raw_normal_replacement::RawNormalModuleReplacementPluginOptions, raw_runtime_chunk::RawRuntimeChunkOptions, @@ -178,7 +178,7 @@ pub enum BuiltinPluginName { ProvideSharedPlugin, ConsumeSharedPlugin, CollectSharedEntryPlugin, - ShareContainerPlugin, + SharedContainerPlugin, ModuleFederationRuntimePlugin, ModuleFederationManifestPlugin, NamedModuleIdsPlugin, @@ -512,11 +512,11 @@ impl<'a> BuiltinPlugin<'a> { .into(); plugins.push(CollectSharedEntryPlugin::new(options).boxed()) } - BuiltinPluginName::ShareContainerPlugin => { - let options = downcast_into::(self.options) + BuiltinPluginName::SharedContainerPlugin => { + let options = downcast_into::(self.options) .map_err(|report| napi::Error::from_reason(report.to_string()))? .into(); - plugins.push(ShareContainerPlugin::new(options).boxed()) + plugins.push(SharedContainerPlugin::new(options).boxed()) } BuiltinPluginName::ConsumeSharedPlugin => plugins.push( ConsumeSharedPlugin::new( diff --git a/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs b/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs index 52e48ec1788f..0e9ef22a52e7 100644 --- a/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs +++ b/crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs @@ -7,7 +7,7 @@ use rspack_plugin_mf::{ ContainerPluginOptions, ContainerReferencePluginOptions, ExposeOptions, ManifestExposeOption, ManifestSharedOption, ModuleFederationManifestPluginOptions, ModuleFederationRuntimePluginOptions, OptimizeSharedConfig, ProvideOptions, ProvideVersion, - RemoteAliasTarget, RemoteOptions, ShareContainerEntryOptions, ShareContainerPluginOptions, + RemoteAliasTarget, RemoteOptions, ShareContainerEntryOptions, SharedContainerPluginOptions, SharedUsedExportsOptimizerPluginOptions, StatsBuildInfo, }; @@ -160,7 +160,7 @@ impl From for CollectSharedEntryPluginOptions #[derive(Debug)] #[napi(object)] -pub struct RawShareContainerPluginOptions { +pub struct RawSharedContainerPluginOptions { pub name: String, pub request: String, pub version: String, @@ -168,9 +168,9 @@ pub struct RawShareContainerPluginOptions { pub library: JsLibraryOptions, } -impl From for ShareContainerPluginOptions { - fn from(value: RawShareContainerPluginOptions) -> Self { - ShareContainerPluginOptions { +impl From for SharedContainerPluginOptions { + fn from(value: RawSharedContainerPluginOptions) -> Self { + SharedContainerPluginOptions { name: value.name, request: value.request, version: value.version, diff --git a/crates/rspack_plugin_mf/src/lib.rs b/crates/rspack_plugin_mf/src/lib.rs index 169ab1e9535b..e2422e90e8bd 100644 --- a/crates/rspack_plugin_mf/src/lib.rs +++ b/crates/rspack_plugin_mf/src/lib.rs @@ -23,12 +23,12 @@ pub use sharing::{ }, provide_shared_module::ProvideSharedModule, provide_shared_plugin::{ProvideOptions, ProvideSharedPlugin, ProvideVersion}, - share_container_entry_dependency::ShareContainerEntryOptions, - share_container_plugin::{ShareContainerPlugin, ShareContainerPluginOptions}, share_runtime_module::{ CodeGenerationDataShareInit, DataInitStage, ShareInitData, ShareRuntimeModule, }, share_runtime_plugin::ShareRuntimePlugin, + shared_container_entry_dependency::ShareContainerEntryOptions, + shared_container_plugin::{SharedContainerPlugin, SharedContainerPluginOptions}, shared_used_exports_optimizer_plugin::{ OptimizeSharedConfig, SharedUsedExportsOptimizerPlugin, SharedUsedExportsOptimizerPluginOptions, }, diff --git a/crates/rspack_plugin_mf/src/sharing/mod.rs b/crates/rspack_plugin_mf/src/sharing/mod.rs index 38747ea9fada..606f650bc5f1 100644 --- a/crates/rspack_plugin_mf/src/sharing/mod.rs +++ b/crates/rspack_plugin_mf/src/sharing/mod.rs @@ -8,13 +8,13 @@ pub mod provide_shared_dependency; pub mod provide_shared_module; pub mod provide_shared_module_factory; pub mod provide_shared_plugin; -pub mod share_container_dependency; -pub mod share_container_entry_dependency; -pub mod share_container_entry_module; -pub mod share_container_entry_module_factory; -pub mod share_container_plugin; -pub mod share_container_runtime_module; pub mod share_runtime_module; pub mod share_runtime_plugin; +pub mod shared_container_dependency; +pub mod shared_container_entry_dependency; +pub mod shared_container_entry_module; +pub mod shared_container_entry_module_factory; +pub mod shared_container_plugin; +pub mod shared_container_runtime_module; pub mod shared_used_exports_optimizer_plugin; pub mod shared_used_exports_optimizer_runtime_module; diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_dependency.rs b/crates/rspack_plugin_mf/src/sharing/share_container_dependency.rs deleted file mode 100644 index bd52ccdee416..000000000000 --- a/crates/rspack_plugin_mf/src/sharing/share_container_dependency.rs +++ /dev/null @@ -1,71 +0,0 @@ -use rspack_cacheable::{cacheable, cacheable_dyn}; -use rspack_core::{ - AsContextDependency, AsDependencyCodeGeneration, Dependency, DependencyCategory, DependencyId, - DependencyType, FactorizeInfo, ModuleDependency, -}; - -#[cacheable] -#[derive(Debug, Clone)] -pub struct ShareContainerDependency { - id: DependencyId, - request: String, - resource_identifier: String, - factorize_info: FactorizeInfo, -} - -impl ShareContainerDependency { - pub fn new(request: String) -> Self { - let resource_identifier = format!("share-container-fallback:{}", request); - Self { - id: DependencyId::new(), - request, - resource_identifier, - factorize_info: Default::default(), - } - } -} - -#[cacheable_dyn] -impl Dependency for ShareContainerDependency { - fn id(&self) -> &DependencyId { - &self.id - } - - fn category(&self) -> &DependencyCategory { - &DependencyCategory::Esm - } - - fn dependency_type(&self) -> &DependencyType { - &DependencyType::ShareContainerFallback - } - - fn resource_identifier(&self) -> Option<&str> { - Some(&self.resource_identifier) - } - - fn could_affect_referencing_module(&self) -> rspack_core::AffectType { - rspack_core::AffectType::True - } -} - -#[cacheable_dyn] -impl ModuleDependency for ShareContainerDependency { - fn request(&self) -> &str { - &self.request - } - - fn user_request(&self) -> &str { - &self.request - } - - fn factorize_info(&self) -> &FactorizeInfo { - &self.factorize_info - } - - fn factorize_info_mut(&mut self) -> &mut FactorizeInfo { - &mut self.factorize_info - } -} - -impl AsContextDependency for ShareContainerDependency {} -impl AsDependencyCodeGeneration for ShareContainerDependency {} diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_entry_dependency.rs b/crates/rspack_plugin_mf/src/sharing/share_container_entry_dependency.rs deleted file mode 100644 index d14f778a8805..000000000000 --- a/crates/rspack_plugin_mf/src/sharing/share_container_entry_dependency.rs +++ /dev/null @@ -1,78 +0,0 @@ -use rspack_cacheable::{cacheable, cacheable_dyn}; -use rspack_core::{ - AsContextDependency, AsDependencyCodeGeneration, Dependency, DependencyCategory, DependencyId, - DependencyType, FactorizeInfo, ModuleDependency, -}; -use serde::Serialize; - -#[cacheable] -#[derive(Debug, Clone)] -pub struct ShareContainerEntryDependency { - id: DependencyId, - pub name: String, - pub request: String, - pub version: String, - resource_identifier: String, - factorize_info: FactorizeInfo, -} - -#[cacheable] -#[derive(Debug, Clone, Serialize)] -pub struct ShareContainerEntryOptions { - pub request: String, -} - -impl ShareContainerEntryDependency { - pub fn new(name: String, request: String, version: String) -> Self { - let resource_identifier = format!("share-container-entry-{}", &name); - Self { - id: DependencyId::new(), - name, - request, - version, - resource_identifier, - factorize_info: Default::default(), - } - } -} - -#[cacheable_dyn] -impl Dependency for ShareContainerEntryDependency { - fn id(&self) -> &DependencyId { - &self.id - } - - fn category(&self) -> &DependencyCategory { - &DependencyCategory::Esm - } - - fn dependency_type(&self) -> &DependencyType { - &DependencyType::ShareContainerEntry - } - - fn resource_identifier(&self) -> Option<&str> { - Some(&self.resource_identifier) - } - - fn could_affect_referencing_module(&self) -> rspack_core::AffectType { - rspack_core::AffectType::Transitive - } -} - -#[cacheable_dyn] -impl ModuleDependency for ShareContainerEntryDependency { - fn request(&self) -> &str { - &self.resource_identifier - } - - fn factorize_info(&self) -> &FactorizeInfo { - &self.factorize_info - } - - fn factorize_info_mut(&mut self) -> &mut FactorizeInfo { - &mut self.factorize_info - } -} - -impl AsContextDependency for ShareContainerEntryDependency {} -impl AsDependencyCodeGeneration for ShareContainerEntryDependency {} diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs b/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs deleted file mode 100644 index 82a5a852e666..000000000000 --- a/crates/rspack_plugin_mf/src/sharing/share_container_entry_module.rs +++ /dev/null @@ -1,268 +0,0 @@ -use std::borrow::Cow; - -use async_trait::async_trait; -use rspack_cacheable::{cacheable, cacheable_dyn}; -use rspack_collections::{Identifiable, Identifier}; -use rspack_core::{ - AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, - BuildMeta, BuildMetaExportsType, BuildResult, CodeGenerationResult, Compilation, - ConcatenationScope, Context, DependenciesBlock, DependencyId, FactoryMeta, LibIdentOptions, - Module, ModuleDependency, ModuleGraph, ModuleIdentifier, ModuleType, RuntimeGlobals, RuntimeSpec, - SourceType, StaticExportsDependency, StaticExportsSpec, impl_module_meta_info, - module_update_hash, - rspack_sources::{BoxSource, RawStringSource, SourceExt}, -}; -use rspack_error::{Result, impl_empty_diagnosable_trait}; -use rspack_hash::{RspackHash, RspackHashDigest}; -use rspack_util::source_map::{ModuleSourceMapConfig, SourceMapKind}; -use rustc_hash::FxHashSet; - -use super::share_container_dependency::ShareContainerDependency; - -#[cacheable] -#[derive(Debug)] -pub struct ShareContainerEntryModule { - blocks: Vec, - dependencies: Vec, - identifier: ModuleIdentifier, - lib_ident: String, - name: String, - request: String, - version: String, - factory_meta: Option, - build_info: BuildInfo, - build_meta: BuildMeta, - source_map_kind: SourceMapKind, -} - -impl ShareContainerEntryModule { - pub fn new(name: String, request: String, version: String) -> Self { - let lib_ident = format!("webpack/share/container/{}", &name); - Self { - blocks: Vec::new(), - dependencies: Vec::new(), - identifier: ModuleIdentifier::from(format!("share container entry {}@{}", &name, &version,)), - lib_ident, - name, - request, - version, - factory_meta: None, - build_info: BuildInfo { - strict: true, - top_level_declarations: Some(FxHashSet::default()), - ..Default::default() - }, - build_meta: BuildMeta { - exports_type: BuildMetaExportsType::Namespace, - ..Default::default() - }, - source_map_kind: SourceMapKind::empty(), - } - } -} - -impl Identifiable for ShareContainerEntryModule { - fn identifier(&self) -> Identifier { - self.identifier - } -} - -impl DependenciesBlock for ShareContainerEntryModule { - fn add_block_id(&mut self, block: AsyncDependenciesBlockIdentifier) { - self.blocks.push(block) - } - - fn get_blocks(&self) -> &[AsyncDependenciesBlockIdentifier] { - &self.blocks - } - - fn add_dependency_id(&mut self, dependency: DependencyId) { - self.dependencies.push(dependency) - } - - fn remove_dependency_id(&mut self, dependency: DependencyId) { - self.dependencies.retain(|d| d != &dependency) - } - - fn get_dependencies(&self) -> &[DependencyId] { - &self.dependencies - } -} - -#[cacheable_dyn] -#[async_trait] -impl Module for ShareContainerEntryModule { - impl_module_meta_info!(); - - fn size(&self, _source_type: Option<&SourceType>, _compilation: Option<&Compilation>) -> f64 { - 42.0 - } - - fn module_type(&self) -> &ModuleType { - &ModuleType::ShareContainerShared - } - - fn source_types(&self, _module_graph: &ModuleGraph) -> &[SourceType] { - &[SourceType::JavaScript, SourceType::Expose] - } - - fn source(&self) -> Option<&BoxSource> { - None - } - - fn readable_identifier(&self, _context: &Context) -> Cow<'_, str> { - "share container entry".into() - } - - fn lib_ident(&self, _options: LibIdentOptions) -> Option> { - Some(self.lib_ident.as_str().into()) - } - - async fn build( - &mut self, - _build_context: BuildContext, - _: Option<&Compilation>, - ) -> Result { - let mut dependencies: Vec = Vec::new(); - - dependencies.push(Box::new(StaticExportsDependency::new( - StaticExportsSpec::Array(vec!["get".into(), "init".into()]), - false, - ))); - dependencies.push(Box::new(ShareContainerDependency::new(self.name.clone()))); - - Ok(BuildResult { - dependencies, - blocks: Vec::>::new(), - ..Default::default() - }) - } - - async fn code_generation( - &self, - compilation: &Compilation, - _runtime: Option<&RuntimeSpec>, - _: Option, - ) -> Result { - let mut code_generation_result = CodeGenerationResult::default(); - code_generation_result - .runtime_requirements - .insert(RuntimeGlobals::DEFINE_PROPERTY_GETTERS); - code_generation_result - .runtime_requirements - .insert(RuntimeGlobals::EXPORTS); - code_generation_result - .runtime_requirements - .insert(RuntimeGlobals::REQUIRE); - - let module_graph = compilation.get_module_graph(); - let mut factory = String::new(); - for dependency_id in self.get_dependencies() { - let dependency = module_graph - .dependency_by_id(dependency_id) - .expect("share container dependency should exist"); - if let Some(dependency) = dependency.downcast_ref::() { - let module_expr = compilation.runtime_template.module_raw( - compilation, - &mut code_generation_result.runtime_requirements, - dependency_id, - dependency.user_request(), - false, - ); - factory = compilation - .runtime_template - .returning_function(&module_expr, ""); - } - } - - let federation_global = format!( - "{}.federation", - compilation - .runtime_template - .render_runtime_globals(&RuntimeGlobals::REQUIRE) - ); - - // Generate installInitialConsumes function using returning_function - let install_initial_consumes_call = format!( - r#"localBundlerRuntime.installInitialConsumes({{ - installedModules: localInstalledModules, - initialConsumes: __webpack_require__.consumesLoadingData.initialConsumes, - moduleToHandlerMapping: __webpack_require__.federation.consumesLoadingModuleToHandlerMapping, - webpackRequire: __webpack_require__, - asyncLoad: true - }})"# - ); - let install_initial_consumes_fn = compilation - .runtime_template - .returning_function(&install_initial_consumes_call, ""); - - // Create initShareContainer function using basic_function, supporting multi-statement body - let init_body = format!( - r#" - var installedModules = {{}}; - {federation_global}.instance = mfInstance; - {federation_global}.bundlerRuntime = bundlerRuntime; - - // Save parameters to local variables to avoid closure issues - var localBundlerRuntime = bundlerRuntime; - var localInstalledModules = installedModules; - - if(!__webpack_require__.consumesLoadingData){{return; }} - {federation_global}.installInitialConsumes = {install_initial_consumes_fn}; - - return {federation_global}.installInitialConsumes(); - "#, - federation_global = federation_global, - install_initial_consumes_fn = install_initial_consumes_fn - ); - let init_share_container_fn = compilation - .runtime_template - .basic_function("mfInstance, bundlerRuntime", &init_body); - - // Generate the final source string - let source = format!( - r#" - __webpack_require__.federation = {{ instance: undefined,bundlerRuntime: undefined }} - var factory = {factory}; - var initShareContainer = {init_share_container_fn}; -{runtime}(exports, {{ - get: function() {{ return factory;}}, - init: function() {{ return initShareContainer;}} -}}); -"#, - runtime = compilation - .runtime_template - .render_runtime_globals(&RuntimeGlobals::DEFINE_PROPERTY_GETTERS), - factory = factory, - init_share_container_fn = init_share_container_fn - ); - - // Update the code generation result with the generated source - code_generation_result = - code_generation_result.with_javascript(RawStringSource::from(source).boxed()); - code_generation_result.add(SourceType::Expose, RawStringSource::from_static("").boxed()); - Ok(code_generation_result) - } - - async fn get_runtime_hash( - &self, - compilation: &Compilation, - runtime: Option<&RuntimeSpec>, - ) -> Result { - let mut hasher = RspackHash::from(&compilation.options.output); - module_update_hash(self, &mut hasher, compilation, runtime); - Ok(hasher.digest(&compilation.options.output.hash_digest)) - } -} - -impl_empty_diagnosable_trait!(ShareContainerEntryModule); - -impl ModuleSourceMapConfig for ShareContainerEntryModule { - fn get_source_map_kind(&self) -> &SourceMapKind { - &self.source_map_kind - } - - fn set_source_map_kind(&mut self, source_map: SourceMapKind) { - self.source_map_kind = source_map; - } -} diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_entry_module_factory.rs b/crates/rspack_plugin_mf/src/sharing/share_container_entry_module_factory.rs deleted file mode 100644 index d9939660f2aa..000000000000 --- a/crates/rspack_plugin_mf/src/sharing/share_container_entry_module_factory.rs +++ /dev/null @@ -1,26 +0,0 @@ -use async_trait::async_trait; -use rspack_core::{ModuleExt, ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult}; -use rspack_error::Result; - -use super::{ - share_container_entry_dependency::ShareContainerEntryDependency, - share_container_entry_module::ShareContainerEntryModule, -}; - -#[derive(Debug)] -pub struct ShareContainerEntryModuleFactory; - -#[async_trait] -impl ModuleFactory for ShareContainerEntryModuleFactory { - async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result { - let dep = data.dependencies[0] - .downcast_ref::() - .expect( - "dependency of ShareContainerEntryModuleFactory should be ShareContainerEntryDependency", - ); - Ok(ModuleFactoryResult::new_with_module( - ShareContainerEntryModule::new(dep.name.clone(), dep.request.clone(), dep.version.clone()) - .boxed(), - )) - } -} diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_plugin.rs b/crates/rspack_plugin_mf/src/sharing/share_container_plugin.rs deleted file mode 100644 index 7868172a9bf1..000000000000 --- a/crates/rspack_plugin_mf/src/sharing/share_container_plugin.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::sync::Arc; - -use rspack_core::{ - ChunkUkey, Compilation, CompilationAdditionalTreeRuntimeRequirements, CompilationParams, - CompilerCompilation, CompilerMake, DependencyType, Filename, LibraryOptions, Plugin, - RuntimeGlobals, RuntimeModuleExt, -}; -use rspack_error::Result; -use rspack_hook::{plugin, plugin_hook}; - -use super::{ - share_container_entry_dependency::ShareContainerEntryDependency, - share_container_entry_module_factory::ShareContainerEntryModuleFactory, -}; -use crate::sharing::share_container_runtime_module::ShareContainerRuntimeModule; - -#[derive(Debug)] -pub struct ShareContainerPluginOptions { - pub name: String, - pub request: String, - pub version: String, - pub file_name: Option, - pub library: LibraryOptions, -} - -#[plugin] -#[derive(Debug)] -pub struct ShareContainerPlugin { - options: ShareContainerPluginOptions, -} - -impl ShareContainerPlugin { - pub fn new(options: ShareContainerPluginOptions) -> Self { - Self::new_inner(options) - } -} - -#[plugin_hook(CompilerCompilation for ShareContainerPlugin)] -async fn compilation( - &self, - compilation: &mut Compilation, - params: &mut CompilationParams, -) -> Result<()> { - compilation.set_dependency_factory( - DependencyType::ShareContainerEntry, - Arc::new(ShareContainerEntryModuleFactory), - ); - compilation.set_dependency_factory( - DependencyType::ShareContainerFallback, - params.normal_module_factory.clone(), - ); - Ok(()) -} - -#[plugin_hook(CompilerMake for ShareContainerPlugin)] -async fn make(&self, compilation: &mut Compilation) -> Result<()> { - let dep = ShareContainerEntryDependency::new( - self.options.name.clone(), - self.options.request.clone(), - self.options.version.clone(), - ); - - compilation - .add_entry( - Box::new(dep), - rspack_core::EntryOptions { - name: Some(self.options.name.clone()), - filename: self.options.file_name.clone(), - library: Some(self.options.library.clone()), - ..Default::default() - }, - ) - .await?; - Ok(()) -} - -#[plugin_hook(CompilationAdditionalTreeRuntimeRequirements for ShareContainerPlugin)] -async fn additional_tree_runtime_requirements( - &self, - compilation: &mut Compilation, - chunk_ukey: &ChunkUkey, - _runtime_requirements: &mut RuntimeGlobals, -) -> Result<()> { - let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey); - if let Some(name) = chunk.name() - && name == self.options.name - { - compilation.add_runtime_module(chunk_ukey, ShareContainerRuntimeModule::new().boxed())?; - } - Ok(()) -} - -impl Plugin for ShareContainerPlugin { - fn name(&self) -> &'static str { - "rspack.ShareContainerPlugin" - } - - fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> { - ctx.compiler_hooks.compilation.tap(compilation::new(self)); - ctx.compiler_hooks.make.tap(make::new(self)); - ctx - .compilation_hooks - .additional_tree_runtime_requirements - .tap(additional_tree_runtime_requirements::new(self)); - Ok(()) - } -} diff --git a/crates/rspack_plugin_mf/src/sharing/share_container_runtime_module.rs b/crates/rspack_plugin_mf/src/sharing/share_container_runtime_module.rs deleted file mode 100644 index 09c88b12441d..000000000000 --- a/crates/rspack_plugin_mf/src/sharing/share_container_runtime_module.rs +++ /dev/null @@ -1,40 +0,0 @@ -use rspack_collections::Identifier; -use rspack_core::{ChunkUkey, Compilation, RuntimeModule, RuntimeModuleStage, impl_runtime_module}; - -#[impl_runtime_module] -#[derive(Debug)] -pub struct ShareContainerRuntimeModule { - id: Identifier, - chunk: Option, -} - -impl ShareContainerRuntimeModule { - pub fn new() -> Self { - Self::with_default( - Identifier::from("webpack/runtime/share_container_federation"), - None, - ) - } -} - -#[async_trait::async_trait] -impl RuntimeModule for ShareContainerRuntimeModule { - fn name(&self) -> Identifier { - self.id - } - - async fn generate(&self, _compilation: &Compilation) -> rspack_error::Result { - Ok( - "__webpack_require__.federation = { instance: undefined,bundlerRuntime: undefined };" - .to_string(), - ) - } - - fn attach(&mut self, chunk: ChunkUkey) { - self.chunk = Some(chunk); - } - - fn stage(&self) -> RuntimeModuleStage { - RuntimeModuleStage::Attach - } -} diff --git a/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_plugin.rs b/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_plugin.rs index f9d7f4a4e55d..66d6c9be1fa4 100644 --- a/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_plugin.rs +++ b/crates/rspack_plugin_mf/src/sharing/shared_used_exports_optimizer_plugin.rs @@ -20,7 +20,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use super::{ consume_shared_module::ConsumeSharedModule, provide_shared_module::ProvideSharedModule, - share_container_entry_module::ShareContainerEntryModule, + shared_container_entry_module::SharedContainerEntryModule, shared_used_exports_optimizer_runtime_module::SharedUsedExportsOptimizerRuntimeModule, }; use crate::manifest::StatsRoot; @@ -194,7 +194,7 @@ async fn optimize_dependencies(&self, compilation: &mut Compilation) -> Result { let share_container_entry_module = module .as_any() - .downcast_ref::()?; + .downcast_ref::()?; // Use the identifier to extract the share key // The identifier is in format "share container entry {name}@{version}" let identifier = share_container_entry_module.identifier().to_string(); diff --git a/packages/rspack/src/sharing/IndependentSharedPlugin.ts b/packages/rspack/src/sharing/IndependentSharedPlugin.ts index e0452c822d38..d0c4804ce42b 100644 --- a/packages/rspack/src/sharing/IndependentSharedPlugin.ts +++ b/packages/rspack/src/sharing/IndependentSharedPlugin.ts @@ -13,9 +13,9 @@ import { } from "./CollectSharedEntryPlugin"; import { ConsumeSharedPlugin } from "./ConsumeSharedPlugin"; import { - ShareContainerPlugin, - type ShareContainerPluginOptions -} from "./ShareContainerPlugin"; + SharedContainerPlugin, + type SharedContainerPluginOptions +} from "./SharedContainerPlugin"; import { SharedUsedExportsOptimizerPlugin } from "./SharedUsedExportsOptimizerPlugin"; import type { Shared, SharedConfig } from "./SharePlugin"; import { encodeName, isRequiredVersion } from "./utils"; @@ -295,7 +295,7 @@ export class IndependentSharedPlugin { parentCompiler: Compiler, parentOutputDir: string, extraOptions?: { - currentShare: Omit; + currentShare: Omit; shareRequestsMap: ShareRequestsMap; } ) { @@ -316,14 +316,14 @@ export class IndependentSharedPlugin { const finalPlugins = []; const rspack = parentCompiler.rspack; - let extraPlugin: CollectSharedEntryPlugin | ShareContainerPlugin; + let extraPlugin: CollectSharedEntryPlugin | SharedContainerPlugin; if (!extraOptions) { extraPlugin = new CollectSharedEntryPlugin({ sharedOptions, shareScope: "default" }); } else { - extraPlugin = new ShareContainerPlugin({ + extraPlugin = new SharedContainerPlugin({ mfName, library, ...extraOptions.currentShare diff --git a/packages/rspack/src/sharing/ShareContainerPlugin.ts b/packages/rspack/src/sharing/ShareContainerPlugin.ts deleted file mode 100644 index 4839a0fe8919..000000000000 --- a/packages/rspack/src/sharing/ShareContainerPlugin.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { - type BuiltinPlugin, - BuiltinPluginName, - type RawShareContainerPluginOptions -} from "@rspack/binding"; -import { - createBuiltinPlugin, - RspackBuiltinPlugin -} from "../builtin-plugin/base"; -import type { Compilation } from "../Compilation"; -import type { Compiler } from "../Compiler"; -import type { LibraryOptions } from "../config"; -import { encodeName } from "./utils"; - -export type ShareContainerPluginOptions = { - mfName: string; - shareName: string; - version: string; - request: string; - library?: LibraryOptions; - independentShareFileName?: string; -}; - -function assert(condition: any, msg: string): asserts condition { - if (!condition) { - throw new Error(msg); - } -} - -const HOT_UPDATE_SUFFIX = ".hot-update"; - -export class ShareContainerPlugin extends RspackBuiltinPlugin { - name = BuiltinPluginName.ShareContainerPlugin; - filename = ""; - _options: RawShareContainerPluginOptions; - _shareName: string; - _globalName: string; - - constructor(options: ShareContainerPluginOptions) { - super(); - const { shareName, library, request, independentShareFileName, mfName } = - options; - const version = options.version || "0.0.0"; - this._globalName = encodeName(`${mfName}_${shareName}_${version}`); - const fileName = independentShareFileName || `${version}/share-entry.js`; - this._shareName = shareName; - this._options = { - name: shareName, - request: request, - library: (library - ? { ...library, name: this._globalName } - : undefined) || { - type: "global", - name: this._globalName - }, - version, - fileName - }; - } - getData() { - return [this._options.fileName, this._globalName, this._options.version]; - } - - raw(compiler: Compiler): BuiltinPlugin { - const { library } = this._options; - if (!compiler.options.output.enabledLibraryTypes!.includes(library.type)) { - compiler.options.output.enabledLibraryTypes!.push(library.type); - } - return createBuiltinPlugin(this.name, this._options); - } - - apply(compiler: Compiler) { - super.apply(compiler); - const shareName = this._shareName; - compiler.hooks.thisCompilation.tap( - this.name, - (compilation: Compilation) => { - compilation.hooks.processAssets.tapPromise( - { - name: "getShareContainerFile" - }, - async () => { - const remoteEntryPoint = compilation.entrypoints.get(shareName); - assert( - remoteEntryPoint, - `Can not get shared ${shareName} entryPoint!` - ); - const remoteEntryNameChunk = compilation.namedChunks.get(shareName); - assert( - remoteEntryNameChunk, - `Can not get shared ${shareName} chunk!` - ); - - const files = Array.from( - remoteEntryNameChunk.files as Iterable - ).filter( - (f: string) => - !f.includes(HOT_UPDATE_SUFFIX) && !f.endsWith(".css") - ); - assert( - files.length > 0, - `no files found for shared ${shareName} chunk` - ); - assert( - files.length === 1, - `shared ${shareName} chunk should not have multiple files!, current files: ${files.join( - "," - )}` - ); - this.filename = files[0]; - } - ); - } - ); - } -} diff --git a/packages/rspack/src/sharing/TreeShakeSharedPlugin.ts b/packages/rspack/src/sharing/TreeShakeSharedPlugin.ts index 2b737a1ed517..c180cd2ddf4c 100644 --- a/packages/rspack/src/sharing/TreeShakeSharedPlugin.ts +++ b/packages/rspack/src/sharing/TreeShakeSharedPlugin.ts @@ -38,19 +38,18 @@ export class TreeShakeSharedPlugin { return; } - if (!reshake) { - new SharedUsedExportsOptimizerPlugin( - sharedOptions, - mfConfig.injectUsedExports, - mfConfig.manifest - ).apply(compiler); - } - if ( sharedOptions.some( ([_, config]) => config.treeshake && config.import !== false ) ) { + if (!reshake) { + new SharedUsedExportsOptimizerPlugin( + sharedOptions, + mfConfig.injectUsedExports, + mfConfig.manifest + ).apply(compiler); + } this._independentSharePlugin = new IndependentSharedPlugin({ name: name, shared: shared, diff --git a/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js b/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js index 560a589ff4a5..deba8c91a473 100644 --- a/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js +++ b/tests/rspack-test/configCases/sharing/treeshake-share/rspack.config.js @@ -4,7 +4,6 @@ const { ModuleFederationPlugin } = container; /** @type {import("@rspack/core").Configuration} */ module.exports = { - entry: './index.js', optimization:{ minimize: false, chunkIds:'named', @@ -13,9 +12,6 @@ module.exports = { output: { chunkFilename: "[id].js" }, - entry: { - main: "./index.js" - }, plugins: [ new ModuleFederationPlugin({ name:'treeshake_share',