diff --git a/Cargo.lock b/Cargo.lock index 8afce0c19b7..da1977327f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3504,6 +3504,7 @@ dependencies = [ "rspack_plugin_copy", "rspack_plugin_css", "rspack_plugin_devtool", + "rspack_plugin_dll", "rspack_plugin_dynamic_entry", "rspack_plugin_ensure_chunk_conditions", "rspack_plugin_entry", @@ -4057,6 +4058,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "rspack_plugin_dll" +version = "0.1.0" +dependencies = [ + "async-trait", + "rspack_collections", + "rspack_core", + "rspack_error", + "rspack_hook", + "rspack_plugin_externals", + "rspack_util", + "rustc-hash 1.1.0", + "serde", + "serde_json", + "tracing", +] + [[package]] name = "rspack_plugin_dynamic_entry" version = "0.1.0" diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 5ecb8abb5db..e9014f09620 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -308,6 +308,10 @@ export declare enum BuiltinPluginName { SizeLimitsPlugin = 'SizeLimitsPlugin', NoEmitOnErrorsPlugin = 'NoEmitOnErrorsPlugin', ContextReplacementPlugin = 'ContextReplacementPlugin', + DllEntryPlugin = 'DllEntryPlugin', + DllReferenceAgencyPlugin = 'DllReferenceAgencyPlugin', + LibManifestPlugin = 'LibManifestPlugin', + FlagAllModulesAsUsedPlugin = 'FlagAllModulesAsUsedPlugin', HttpExternalsRspackPlugin = 'HttpExternalsRspackPlugin', CopyRspackPlugin = 'CopyRspackPlugin', HtmlRspackPlugin = 'HtmlRspackPlugin', @@ -451,6 +455,22 @@ export interface JsBeforeResolveArgs { issuer: string } +export interface JsBuildMeta { + strictEsmModule: boolean + hasTopLevelAwait: boolean + esm: boolean + exportsType: 'unset' | 'default' | 'namespace' | 'flagged' | 'dynamic' + defaultObject: 'false' | 'redirect' | JsBuildMetaDefaultObjectRedirectWarn + moduleArgument: 'module' | 'webpackModule' + exportsArgument: 'exports' | 'webpackExports' + sideEffectFree?: boolean + exportsFinalName?: Array<[string, string]> | undefined +} + +export interface JsBuildMetaDefaultObjectRedirectWarn { + redirectWarn: JsDefaultObjectRedirectWarnObject +} + export interface JsBuildTimeExecutionOption { publicPath?: string baseUri?: string @@ -524,6 +544,10 @@ export interface JsCreateData { resource: string } +export interface JsDefaultObjectRedirectWarnObject { + ignore: boolean +} + export interface JsDiagnostic { message: string help?: string @@ -1267,6 +1291,35 @@ export interface RawCssParserOptions { namedExports?: boolean } +export interface RawDllEntryPluginOptions { + context: string + entries: Array + name: string +} + +export interface RawDllManifest { + content: Record + name?: string + type?: string +} + +export interface RawDllManifestContentItem { + buildMeta?: JsBuildMeta + exports?: string[] | true + id?: string +} + +export interface RawDllReferenceAgencyPluginOptions { + context?: string + name?: string + extensions: Array + scope?: string + sourceType?: string + type: string + content?: Record + manifest?: RawDllManifest +} + export interface RawDraft { customMedia: boolean } @@ -1345,6 +1398,10 @@ export interface RawFallbackCacheGroupOptions { automaticNameDelimiter?: string } +export interface RawFlagAllModulesAsUsedPluginOptions { + explanation: string +} + export interface RawFuncUseCtx { resource?: string realResource?: string @@ -1473,6 +1530,15 @@ export interface RawLazyCompilationOption { cacheable: boolean } +export interface RawLibManifestPluginOptions { + context?: string + entryOnly?: boolean + name?: JsFilename + path: JsFilename + format?: boolean + type?: string +} + export interface RawLightningCssBrowsers { android?: number chrome?: number diff --git a/crates/rspack_binding_options/Cargo.toml b/crates/rspack_binding_options/Cargo.toml index 04842b10b08..d69492e6ad0 100644 --- a/crates/rspack_binding_options/Cargo.toml +++ b/crates/rspack_binding_options/Cargo.toml @@ -41,6 +41,7 @@ rspack_plugin_context_replacement = { version = "0.1.0", path = "../rspack_ rspack_plugin_copy = { version = "0.1.0", path = "../rspack_plugin_copy" } rspack_plugin_css = { version = "0.1.0", path = "../rspack_plugin_css" } rspack_plugin_devtool = { version = "0.1.0", path = "../rspack_plugin_devtool" } +rspack_plugin_dll = { version = "0.1.0", path = "../rspack_plugin_dll" } rspack_plugin_dynamic_entry = { version = "0.1.0", path = "../rspack_plugin_dynamic_entry" } rspack_plugin_ensure_chunk_conditions = { version = "0.1.0", path = "../rspack_plugin_ensure_chunk_conditions" } rspack_plugin_entry = { version = "0.1.0", path = "../rspack_plugin_entry" } diff --git a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs index 87358c7a828..89c706df72f 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs @@ -2,6 +2,7 @@ mod raw_banner; mod raw_bundle_info; mod raw_copy; mod raw_css_extract; +mod raw_dll; mod raw_html; mod raw_ignore; mod raw_lazy_compilation; @@ -15,6 +16,7 @@ mod raw_swc_js_minimizer; use napi::{bindgen_prelude::FromNapiValue, Env, JsUnknown}; use napi_derive::napi; +use raw_dll::{RawDllReferenceAgencyPluginOptions, RawFlagAllModulesAsUsedPluginOptions}; use raw_lightning_css_minimizer::RawLightningCssMinimizerRspackPluginOptions; use rspack_binding_values::entry::JsEntryPluginOptions; use rspack_core::{BoxPlugin, Plugin, PluginExt}; @@ -34,6 +36,9 @@ use rspack_plugin_devtool::{ SourceMapDevToolModuleOptionsPluginOptions, SourceMapDevToolPlugin, SourceMapDevToolPluginOptions, }; +use rspack_plugin_dll::{ + DllEntryPlugin, DllReferenceAgencyPlugin, FlagAllModulesAsUsedPlugin, LibManifestPlugin, +}; use rspack_plugin_dynamic_entry::DynamicEntryPlugin; use rspack_plugin_ensure_chunk_conditions::EnsureChunkConditionsPlugin; use rspack_plugin_entry::EntryPlugin; @@ -78,9 +83,13 @@ use rspack_plugin_web_worker_template::web_worker_template_plugin; use rspack_plugin_worker::WorkerPlugin; pub use self::{ - raw_banner::RawBannerPluginOptions, raw_copy::RawCopyRspackPluginOptions, - raw_html::RawHtmlRspackPluginOptions, raw_ignore::RawIgnorePluginOptions, - raw_limit_chunk_count::RawLimitChunkCountPluginOptions, raw_mf::RawContainerPluginOptions, + raw_banner::RawBannerPluginOptions, + raw_copy::RawCopyRspackPluginOptions, + raw_dll::{RawDllEntryPluginOptions, RawLibManifestPluginOptions}, + raw_html::RawHtmlRspackPluginOptions, + raw_ignore::RawIgnorePluginOptions, + raw_limit_chunk_count::RawLimitChunkCountPluginOptions, + raw_mf::RawContainerPluginOptions, raw_progress::RawProgressPluginOptions, raw_swc_js_minimizer::RawSwcJsMinimizerRspackPluginOptions, }; @@ -166,6 +175,10 @@ pub enum BuiltinPluginName { SizeLimitsPlugin, NoEmitOnErrorsPlugin, ContextReplacementPlugin, + DllEntryPlugin, + DllReferenceAgencyPlugin, + LibManifestPlugin, + FlagAllModulesAsUsedPlugin, // rspack specific plugins // naming format follow XxxRspackPlugin @@ -517,6 +530,25 @@ impl BuiltinPlugin { let options = raw_options.try_into()?; plugins.push(ContextReplacementPlugin::new(options).boxed()); } + BuiltinPluginName::DllEntryPlugin => { + let raw_options = downcast_into::(self.options)?; + let options = raw_options.into(); + plugins.push(DllEntryPlugin::new(options).boxed()); + } + BuiltinPluginName::LibManifestPlugin => { + let raw_options = downcast_into::(self.options)?; + let options = raw_options.into(); + plugins.push(LibManifestPlugin::new(options).boxed()); + } + BuiltinPluginName::FlagAllModulesAsUsedPlugin => { + let raw_options = downcast_into::(self.options)?; + plugins.push(FlagAllModulesAsUsedPlugin::new(raw_options.explanation).boxed()) + } + BuiltinPluginName::DllReferenceAgencyPlugin => { + let raw_options = downcast_into::(self.options)?; + let options = raw_options.into(); + plugins.push(DllReferenceAgencyPlugin::new(options).boxed()); + } } Ok(()) } diff --git a/crates/rspack_binding_options/src/options/raw_builtins/raw_dll.rs b/crates/rspack_binding_options/src/options/raw_builtins/raw_dll.rs new file mode 100644 index 00000000000..89f886bfe16 --- /dev/null +++ b/crates/rspack_binding_options/src/options/raw_builtins/raw_dll.rs @@ -0,0 +1,168 @@ +use napi::Either; +use napi_derive::napi; +use rspack_binding_values::{JsBuildMeta, JsFilename}; +use rspack_plugin_dll::{ + DllEntryPluginOptions, DllManifest, DllManifestContent, DllManifestContentItem, + DllManifestContentItemExports, DllReferenceAgencyPluginOptions, LibManifestPluginOptions, +}; +use rustc_hash::FxHashMap as HashMap; +use swc_core::atoms::Atom; + +#[derive(Debug)] +#[napi(object)] +pub struct RawDllEntryPluginOptions { + pub context: String, + pub entries: Vec, + pub name: String, +} + +impl From for DllEntryPluginOptions { + fn from(value: RawDllEntryPluginOptions) -> Self { + let RawDllEntryPluginOptions { + name, + context, + entries, + } = value; + + Self { + name, + context: context.into(), + entries, + } + } +} + +#[derive(Debug)] +#[napi(object, object_to_js = false)] +pub struct RawLibManifestPluginOptions { + pub context: Option, + pub entry_only: Option, + pub name: Option, + pub path: JsFilename, + pub format: Option, + pub r#type: Option, +} + +impl From for LibManifestPluginOptions { + fn from(value: RawLibManifestPluginOptions) -> Self { + let RawLibManifestPluginOptions { + context, + entry_only, + name, + path, + r#type, + format, + } = value; + + Self { + context: context.map(|c| c.into()), + format, + entry_only, + name: name.map(|n| n.into()), + path: path.into(), + r#type, + } + } +} + +#[napi(object, object_to_js = false)] +pub struct RawDllReferenceAgencyPluginOptions { + pub context: Option, + pub name: Option, + pub extensions: Vec, + pub scope: Option, + pub source_type: Option, + pub r#type: String, + pub content: Option>, + pub manifest: Option, +} + +#[napi(object, object_to_js = false)] +pub struct RawDllManifestContentItem { + pub build_meta: Option, + #[napi(ts_type = "string[] | true")] + pub exports: Option, bool>>, + pub id: Option, +} + +impl From for DllManifestContentItem { + fn from(value: RawDllManifestContentItem) -> Self { + let raw_exports = value.exports; + + let exports = raw_exports.map(|exports| match exports { + Either::A(seq) => { + DllManifestContentItemExports::Vec(seq.into_iter().map(Atom::from).collect::>()) + } + Either::B(bool) => { + if bool { + DllManifestContentItemExports::True + } else { + unreachable!() + } + } + }); + + Self { + build_meta: value.build_meta.map(|meta| meta.into()), + exports, + id: value.id, + } + } +} + +#[napi(object, object_to_js = false)] +pub struct RawDllManifest { + pub content: HashMap, + pub name: Option, + pub r#type: Option, +} + +impl From for DllManifest { + fn from(value: RawDllManifest) -> Self { + Self { + content: value + .content + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::(), + name: value.name, + r#type: value.r#type, + } + } +} + +impl From for DllReferenceAgencyPluginOptions { + fn from(value: RawDllReferenceAgencyPluginOptions) -> Self { + let RawDllReferenceAgencyPluginOptions { + context, + name, + extensions, + scope, + source_type, + r#type, + content, + manifest, + } = value; + + Self { + context: context.map(|ctx| ctx.into()), + name, + extensions, + scope, + source_type, + r#type, + content: content.map(|c| { + c.into_iter() + .map(|(k, v)| (k, v.into())) + .collect::() + }), + manifest: manifest.map(|m| m.into()), + } + } +} + +#[derive(Debug)] +#[napi(object)] +pub struct RawFlagAllModulesAsUsedPluginOptions { + pub explanation: String, +} diff --git a/crates/rspack_binding_values/src/module.rs b/crates/rspack_binding_values/src/module.rs index 6f5c090ded9..7a09b29259a 100644 --- a/crates/rspack_binding_values/src/module.rs +++ b/crates/rspack_binding_values/src/module.rs @@ -3,8 +3,9 @@ use std::{cell::RefCell, ptr::NonNull, sync::Arc}; use napi_derive::napi; use rspack_collections::IdentifierMap; use rspack_core::{ - AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, Compilation, CompilationId, - DependenciesBlock, Module, ModuleGraph, ModuleIdentifier, RuntimeModuleStage, SourceType, + AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BuildMeta, BuildMetaDefaultObject, + BuildMetaExportsType, Compilation, CompilationId, DependenciesBlock, ExportsArgument, Module, + ModuleArgument, ModuleGraph, ModuleIdentifier, RuntimeModuleStage, SourceType, }; use rspack_napi::{napi::bindgen_prelude::*, threadsafe_function::ThreadsafeFunction, OneShotRef}; use rspack_plugin_runtime::RuntimeModuleFromJs; @@ -504,3 +505,118 @@ impl From for RuntimeModuleFromJs { } } } + +#[napi(object, object_to_js = false)] +pub struct JsBuildMeta { + pub strict_esm_module: bool, + pub has_top_level_await: bool, + pub esm: bool, + #[napi(ts_type = "'unset' | 'default' | 'namespace' | 'flagged' | 'dynamic'")] + pub exports_type: String, + #[napi(ts_type = "'false' | 'redirect' | JsBuildMetaDefaultObjectRedirectWarn")] + pub default_object: JsBuildMetaDefaultObject, + #[napi(ts_type = "'module' | 'webpackModule'")] + pub module_argument: String, + #[napi(ts_type = "'exports' | 'webpackExports'")] + pub exports_argument: String, + pub side_effect_free: Option, + #[napi(ts_type = "Array<[string, string]> | undefined")] + pub exports_final_name: Option>>, +} + +impl From for BuildMeta { + fn from(value: JsBuildMeta) -> Self { + let JsBuildMeta { + strict_esm_module, + has_top_level_await, + esm, + exports_argument: raw_exports_argument, + default_object: raw_default_object, + module_argument: raw_module_argument, + exports_final_name: raw_exports_final_name, + side_effect_free, + exports_type: raw_exports_type, + } = value; + + let default_object = match raw_default_object { + Either::A(s) => match s.as_str() { + "false" => BuildMetaDefaultObject::False, + "redirect" => BuildMetaDefaultObject::Redirect, + _ => unreachable!(), + }, + Either::B(default_object) => BuildMetaDefaultObject::RedirectWarn { + ignore: default_object.redirect_warn.ignore, + }, + }; + + let exports_type = match raw_exports_type.as_str() { + "unset" => BuildMetaExportsType::Unset, + "default" => BuildMetaExportsType::Default, + "namespace" => BuildMetaExportsType::Namespace, + "flagged" => BuildMetaExportsType::Flagged, + "dynamic" => BuildMetaExportsType::Dynamic, + _ => unreachable!(), + }; + + let module_argument = match raw_module_argument.as_str() { + "module" => ModuleArgument::Module, + "webpackModule" => ModuleArgument::WebpackModule, + _ => unreachable!(), + }; + + let exports_argument = match raw_exports_argument.as_str() { + "exports" => ExportsArgument::Exports, + "webpackExports" => ExportsArgument::WebpackExports, + _ => unreachable!(), + }; + + let exports_final_name = raw_exports_final_name.map(|exports_name| { + exports_name + .into_iter() + .map(|export_name| { + let first = export_name + .first() + .expect("The buildMeta exportsFinalName item should have first value") + .clone(); + let second = export_name + .get(1) + .expect("The buildMeta exportsFinalName item should have second value") + .clone(); + (first, second) + }) + .collect::>() + }); + + Self { + strict_esm_module, + has_top_level_await, + esm, + exports_type, + default_object, + module_argument, + exports_argument, + side_effect_free, + exports_final_name, + } + } +} + +#[napi(object)] +pub struct JsBuildMetaDefaultObjectRedirectWarn { + pub redirect_warn: JsDefaultObjectRedirectWarnObject, +} + +impl From for BuildMetaDefaultObject { + fn from(value: JsBuildMetaDefaultObjectRedirectWarn) -> Self { + Self::RedirectWarn { + ignore: value.redirect_warn.ignore, + } + } +} + +#[napi(object)] +pub struct JsDefaultObjectRedirectWarnObject { + pub ignore: bool, +} + +pub type JsBuildMetaDefaultObject = Either; diff --git a/crates/rspack_core/src/compiler/make/repair/build.rs b/crates/rspack_core/src/compiler/make/repair/build.rs index 896a69b88b2..d0ce745daf5 100644 --- a/crates/rspack_core/src/compiler/make/repair/build.rs +++ b/crates/rspack_core/src/compiler/make/repair/build.rs @@ -6,8 +6,8 @@ use rspack_fs::ReadableFileSystem; use super::{process_dependencies::ProcessDependenciesTask, MakeTaskContext}; use crate::{ utils::task_loop::{Task, TaskResult, TaskType}, - AsyncDependenciesBlock, BoxDependency, BuildContext, BuildResult, CompilationId, CompilerOptions, - DependencyParents, Module, ModuleProfile, ResolverFactory, SharedPluginDriver, + AsyncDependenciesBlock, BoxDependency, BuildContext, BuildInfo, BuildResult, CompilationId, + CompilerOptions, DependencyParents, Module, ModuleProfile, ResolverFactory, SharedPluginDriver, }; #[derive(Debug)] @@ -59,12 +59,6 @@ impl Task for BuildTask { ) .await; - plugin_driver - .compilation_hooks - .succeed_module - .call(compilation_id, &mut module) - .await?; - let build_result = result.map(|t| { let diagnostics = module .clone_diagnostics() @@ -83,8 +77,10 @@ impl Task for BuildTask { vec![Box::new(BuildResultTask { module, build_result: Box::new(build_result), + plugin_driver, diagnostics, current_profile, + compilation_id, })] }) } @@ -95,7 +91,9 @@ struct BuildResultTask { pub module: Box, pub build_result: Box, pub diagnostics: Vec, + pub plugin_driver: SharedPluginDriver, pub current_profile: Option>, + pub compilation_id: CompilationId, } #[async_trait::async_trait] impl Task for BuildResultTask { @@ -108,8 +106,23 @@ impl Task for BuildResultTask { build_result, diagnostics, current_profile, + plugin_driver, + compilation_id, } = *self; + module.set_build_info(build_result.build_info); + module.set_build_meta(build_result.build_meta); + + plugin_driver + .compilation_hooks + .succeed_module + .call(compilation_id, &mut module) + .await?; + + let build_info_default = BuildInfo::default(); + + let build_info = module.build_info().unwrap_or(&build_info_default); + let artifact = &mut context.artifact; let module_graph = &mut MakeTaskContext::get_module_graph_mut(&mut artifact.module_graph_partial); @@ -125,16 +138,16 @@ impl Task for BuildResultTask { .extend(build_result.optimization_bailouts); artifact .file_dependencies - .add_batch_file(&build_result.build_info.file_dependencies); + .add_batch_file(&build_info.file_dependencies); artifact .context_dependencies - .add_batch_file(&build_result.build_info.context_dependencies); + .add_batch_file(&build_info.context_dependencies); artifact .missing_dependencies - .add_batch_file(&build_result.build_info.missing_dependencies); + .add_batch_file(&build_info.missing_dependencies); artifact .build_dependencies - .add_batch_file(&build_result.build_info.build_dependencies); + .add_batch_file(&build_info.build_dependencies); let mut queue = VecDeque::new(); let mut all_dependencies = vec![]; @@ -184,9 +197,6 @@ impl Task for BuildResultTask { let module_identifier = module.identifier(); - module.set_build_info(build_result.build_info); - module.set_build_meta(build_result.build_meta); - module_graph.add_module(module); Ok(vec![Box::new(ProcessDependenciesTask { diff --git a/crates/rspack_core/src/dependency/dependency_type.rs b/crates/rspack_core/src/dependency/dependency_type.rs index d9c9058b0f5..94ca7c89846 100644 --- a/crates/rspack_core/src/dependency/dependency_type.rs +++ b/crates/rspack_core/src/dependency/dependency_type.rs @@ -103,6 +103,8 @@ pub enum DependencyType { LoaderImport, LazyImport, ModuleDecorator, + DllEntry, + DelegatedSource, Custom(&'static str), } @@ -159,6 +161,7 @@ impl DependencyType { DependencyType::ImportMetaContext => "import.meta context", DependencyType::ContainerExposed => "container exposed", DependencyType::ContainerEntry => "container entry", + DependencyType::DllEntry => "dll entry", DependencyType::RemoteToExternal => "remote to external", DependencyType::RemoteToFallback => "fallback", DependencyType::RemoteToFallbackItem => "fallback item", @@ -169,6 +172,7 @@ impl DependencyType { DependencyType::WebpackIsIncluded => "__webpack_is_included__", DependencyType::LazyImport => "lazy import()", DependencyType::ModuleDecorator => "module decorator", + DependencyType::DelegatedSource => "delegated source", } } } diff --git a/crates/rspack_core/src/module.rs b/crates/rspack_core/src/module.rs index 15d9bd601ae..50f0b902a0e 100644 --- a/crates/rspack_core/src/module.rs +++ b/crates/rspack_core/src/module.rs @@ -15,6 +15,7 @@ use rspack_util::atom::Atom; use rspack_util::ext::{AsAny, DynHash}; use rspack_util::source_map::ModuleSourceMapConfig; use rustc_hash::FxHashSet as HashSet; +use serde::Serialize; use crate::concatenated_module::ConcatenatedModule; use crate::dependencies_block::dependencies_block_update_hash; @@ -79,7 +80,8 @@ impl Default for BuildInfo { } } -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] pub enum BuildMetaExportsType { #[default] Unset, @@ -97,7 +99,8 @@ pub enum ExportsType { Dynamic, } -#[derive(Debug, Default, Clone, Copy, Hash)] +#[derive(Debug, Default, Clone, Copy, Hash, Serialize)] +#[serde(rename_all = "camelCase")] pub enum BuildMetaDefaultObject { #[default] False, @@ -111,7 +114,8 @@ pub enum BuildMetaDefaultObject { }, } -#[derive(Debug, Default, Clone, Copy, Hash)] +#[derive(Debug, Default, Clone, Copy, Hash, Serialize)] +#[serde(rename_all = "camelCase")] pub enum ModuleArgument { #[default] Module, @@ -127,7 +131,8 @@ impl Display for ModuleArgument { } } -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] pub enum ExportsArgument { #[default] Exports, @@ -143,7 +148,8 @@ impl Display for ExportsArgument { } } -#[derive(Debug, Default, Clone, Hash)] +#[derive(Debug, Default, Clone, Hash, Serialize)] +#[serde(rename_all = "camelCase")] pub struct BuildMeta { pub strict_esm_module: bool, pub has_top_level_await: bool, @@ -152,7 +158,9 @@ pub struct BuildMeta { pub default_object: BuildMetaDefaultObject, pub module_argument: ModuleArgument, pub exports_argument: ExportsArgument, + #[serde(skip_serializing_if = "Option::is_none")] pub side_effect_free: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub exports_final_name: Option>, } diff --git a/crates/rspack_plugin_dll/Cargo.toml b/crates/rspack_plugin_dll/Cargo.toml new file mode 100644 index 00000000000..5abf4e6c256 --- /dev/null +++ b/crates/rspack_plugin_dll/Cargo.toml @@ -0,0 +1,28 @@ +[package] +description = "rspack dynamic entry plugin" +edition = "2021" +homepage.workspace = true +license = "MIT" +name = "rspack_plugin_dll" +repository = "https://github.com/web-infra-dev/rspack" +version = "0.1.0" + +[dependencies] +rspack_collections = { version = "0.1.0", path = "../rspack_collections" } +rspack_core = { version = "0.1.0", path = "../rspack_core" } +rspack_error = { version = "0.1.0", path = "../rspack_error" } +rspack_hook = { version = "0.1.0", path = "../rspack_hook" } +rspack_plugin_externals = { version = "0.1.0", path = "../rspack_plugin_externals" } +rspack_util = { version = "0.1.0", path = "../rspack_util" } + +async-trait = { workspace = true } +rustc-hash = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tracing = { workspace = true } + +[lints] +workspace = true + +[package.metadata.cargo-shear] +ignored = ["tracing"] diff --git a/crates/rspack_plugin_dll/LICENSE b/crates/rspack_plugin_dll/LICENSE new file mode 100644 index 00000000000..46310101ad8 --- /dev/null +++ b/crates/rspack_plugin_dll/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022-present Bytedance, Inc. and its affiliates. + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/rspack_plugin_dll/src/dll_entry/dll_entry_dependency.rs b/crates/rspack_plugin_dll/src/dll_entry/dll_entry_dependency.rs new file mode 100644 index 00000000000..157c1a9546e --- /dev/null +++ b/crates/rspack_plugin_dll/src/dll_entry/dll_entry_dependency.rs @@ -0,0 +1,61 @@ +use rspack_core::{ + AffectType, AsContextDependency, AsDependencyTemplate, Context, Dependency, DependencyId, + DependencyType, ModuleDependency, +}; + +use crate::DllEntryPluginOptions; + +#[derive(Debug, Clone)] +pub struct DllEntryDependency { + pub context: Context, + + pub entries: Vec, + + // TODO: The fields `name` for serialize & deserialize. + pub name: String, + + id: DependencyId, +} + +impl DllEntryDependency { + pub fn new(dll_entry_plugin_optinos: &DllEntryPluginOptions) -> Self { + let DllEntryPluginOptions { + context, + entries, + name, + } = dll_entry_plugin_optinos.clone(); + + Self { + context, + entries, + name, + id: DependencyId::new(), + } + } +} + +// It would not create module by rspack,if dependency is not ModuleDependency. +// So we impl ModuleDependency for [DllEntryDependency] +impl ModuleDependency for DllEntryDependency { + fn request(&self) -> &str { + "dll main" + } +} + +impl Dependency for DllEntryDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn could_affect_referencing_module(&self) -> AffectType { + AffectType::Transitive + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::DllEntry + } +} + +impl AsContextDependency for DllEntryDependency {} + +impl AsDependencyTemplate for DllEntryDependency {} diff --git a/crates/rspack_plugin_dll/src/dll_entry/dll_entry_plugin.rs b/crates/rspack_plugin_dll/src/dll_entry/dll_entry_plugin.rs new file mode 100644 index 00000000000..4fe2e7bd3ae --- /dev/null +++ b/crates/rspack_plugin_dll/src/dll_entry/dll_entry_plugin.rs @@ -0,0 +1,81 @@ +use std::sync::Arc; + +use rspack_core::{ + ApplyContext, Compilation, CompilationParams, CompilerCompilation, CompilerMake, CompilerOptions, + Context, DependencyType, EntryOptions, Plugin, PluginContext, +}; +use rspack_error::Result; +use rspack_hook::{plugin, plugin_hook}; + +use super::{dll_entry_dependency::DllEntryDependency, dll_module_factory}; + +#[derive(Debug, Clone, Default)] +pub struct DllEntryPluginOptions { + pub name: String, + + pub context: Context, + + pub entries: Vec, +} + +#[plugin] +#[derive(Debug)] +pub struct DllEntryPlugin { + options: DllEntryPluginOptions, +} + +impl DllEntryPlugin { + pub fn new(options: DllEntryPluginOptions) -> Self { + Self::new_inner(options) + } +} + +#[async_trait::async_trait] +impl Plugin for DllEntryPlugin { + fn name(&self) -> &'static str { + "rspack.DllEntryPlugin" + } + + fn apply(&self, ctx: PluginContext<&mut ApplyContext>, _options: &CompilerOptions) -> Result<()> { + ctx + .context + .compiler_hooks + .compilation + .tap(compilation::new(self)); + + ctx.context.compiler_hooks.make.tap(make::new(self)); + + Ok(()) + } +} + +#[plugin_hook(CompilerCompilation for DllEntryPlugin)] +async fn compilation( + &self, + compilation: &mut Compilation, + params: &mut CompilationParams, +) -> Result<()> { + compilation.set_dependency_factory( + DependencyType::DllEntry, + Arc::new(dll_module_factory::DllModuleFactory), + ); + + compilation.set_dependency_factory(DependencyType::Entry, params.normal_module_factory.clone()); + + Ok(()) +} + +#[plugin_hook(CompilerMake for DllEntryPlugin)] +async fn make(&self, compilation: &mut Compilation) -> Result<()> { + compilation + .add_entry( + Box::new(DllEntryDependency::new(&self.options)), + EntryOptions { + name: Some(self.options.name.clone()), + ..Default::default() + }, + ) + .await?; + + Ok(()) +} diff --git a/crates/rspack_plugin_dll/src/dll_entry/dll_module.rs b/crates/rspack_plugin_dll/src/dll_entry/dll_module.rs new file mode 100644 index 00000000000..df212d4c055 --- /dev/null +++ b/crates/rspack_plugin_dll/src/dll_entry/dll_module.rs @@ -0,0 +1,170 @@ +use std::{borrow::Cow, hash::Hash, sync::Arc}; + +use async_trait::async_trait; +use rspack_collections::{Identifiable, Identifier}; +use rspack_core::{ + impl_module_meta_info, impl_source_map_config, module_update_hash, rspack_sources::RawSource, + rspack_sources::Source, AsyncDependenciesBlockIdentifier, BuildContext, BuildInfo, BuildMeta, + BuildResult, CodeGenerationResult, Compilation, ConcatenationScope, Context, DependenciesBlock, + Dependency, DependencyId, EntryDependency, FactoryMeta, Module, ModuleType, RuntimeGlobals, + RuntimeSpec, SourceType, +}; +use rspack_error::{impl_empty_diagnosable_trait, Diagnostic, Result}; + +use super::dll_entry_dependency::DllEntryDependency; + +#[impl_source_map_config] +#[derive(Debug, Default)] +pub struct DllModule { + // TODO: it should be set to EntryDependency.loc + name: String, + + factory_meta: Option, + + build_info: Option, + + build_meta: Option, + + blocks: Vec, + + entries: Vec, + + context: Context, + + dependencies: Vec, +} + +impl DllModule { + pub fn new(dep: &DllEntryDependency) -> Self { + let DllEntryDependency { + entries, + context, + name, + .. + } = dep.clone(); + + Self { + name, + entries, + context, + ..Default::default() + } + } +} + +#[async_trait] +impl Module for DllModule { + impl_module_meta_info!(); + + fn module_type(&self) -> &ModuleType { + &ModuleType::JsDynamic + } + + fn source_types(&self) -> &[SourceType] { + &[SourceType::JavaScript] + } + + fn get_diagnostics(&self) -> Vec { + vec![] + } + + fn original_source(&self) -> Option<&dyn Source> { + None + } + + fn readable_identifier(&self, _context: &Context) -> Cow { + self.identifier().as_str().into() + } + + async fn build( + &mut self, + _build_context: BuildContext, + _compilation: Option<&Compilation>, + ) -> Result { + let dependencies = self + .entries + .clone() + .into_iter() + .map(|entry| EntryDependency::new(entry, self.context.clone(), None, false)) + .map(|dependency| Box::new(dependency) as Box) + .collect::>(); + + Ok(BuildResult { + dependencies, + ..Default::default() + }) + } + + fn code_generation( + &self, + _compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + _concatenation_scope: Option, + ) -> Result { + let mut runtime_requirements = RuntimeGlobals::default(); + runtime_requirements.insert(RuntimeGlobals::REQUIRE); + runtime_requirements.insert(RuntimeGlobals::MODULE); + + let mut code_generation_result = CodeGenerationResult { + runtime_requirements, + ..Default::default() + }; + + code_generation_result = code_generation_result.with_javascript(Arc::new(RawSource::from( + format!("module.exports = {}", RuntimeGlobals::REQUIRE.name()), + ))); + + Ok(code_generation_result) + } + + fn need_build(&self) -> bool { + self.build_meta.is_none() + } + + fn size(&self, _source_type: Option<&SourceType>, _compilation: Option<&Compilation>) -> f64 { + 12.0 + } + + fn update_hash( + &self, + mut hasher: &mut dyn std::hash::Hasher, + compilation: &Compilation, + runtime: Option<&RuntimeSpec>, + ) -> Result<()> { + format!("dll module {}", self.name).hash(&mut hasher); + + module_update_hash(self, hasher, compilation, runtime); + + Ok(()) + } +} + +impl Identifiable for DllModule { + fn identifier(&self) -> Identifier { + format!("dll {}", self.name).as_str().into() + } +} + +impl DependenciesBlock for DllModule { + 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 get_dependencies(&self) -> &[DependencyId] { + &self.dependencies + } + + fn remove_dependency_id(&mut self, dependency: DependencyId) { + self.dependencies.retain(|d| d != &dependency) + } +} + +impl_empty_diagnosable_trait!(DllModule); diff --git a/crates/rspack_plugin_dll/src/dll_entry/dll_module_factory.rs b/crates/rspack_plugin_dll/src/dll_entry/dll_module_factory.rs new file mode 100644 index 00000000000..57481504334 --- /dev/null +++ b/crates/rspack_plugin_dll/src/dll_entry/dll_module_factory.rs @@ -0,0 +1,22 @@ +use async_trait::async_trait; +use rspack_core::{ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult}; +use rspack_error::Result; + +use super::{dll_entry_dependency::DllEntryDependency, dll_module::DllModule}; + +#[derive(Debug)] +pub(crate) struct DllModuleFactory; + +#[async_trait] +impl ModuleFactory for DllModuleFactory { + async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result { + let dll_entry_dependency = data.dependencies[0] + .as_any() + .downcast_ref::() + .expect("unreachable"); + + Ok(ModuleFactoryResult { + module: Some(Box::new(DllModule::new(dll_entry_dependency))), + }) + } +} diff --git a/crates/rspack_plugin_dll/src/dll_entry/mod.rs b/crates/rspack_plugin_dll/src/dll_entry/mod.rs new file mode 100644 index 00000000000..2e84fff6ec9 --- /dev/null +++ b/crates/rspack_plugin_dll/src/dll_entry/mod.rs @@ -0,0 +1,4 @@ +pub mod dll_entry_dependency; +pub mod dll_entry_plugin; +pub mod dll_module; +pub mod dll_module_factory; diff --git a/crates/rspack_plugin_dll/src/dll_reference/delegated_module.rs b/crates/rspack_plugin_dll/src/dll_reference/delegated_module.rs new file mode 100644 index 00000000000..2c660f7f6f0 --- /dev/null +++ b/crates/rspack_plugin_dll/src/dll_reference/delegated_module.rs @@ -0,0 +1,244 @@ +use std::{borrow::Cow, hash::Hash, sync::Arc}; + +use async_trait::async_trait; +use rspack_collections::{Identifiable, Identifier}; +use rspack_core::{ + impl_module_meta_info, impl_source_map_config, module_raw, module_update_hash, + rspack_sources::{BoxSource, OriginalSource, RawSource, Source}, + throw_missing_module_error_block, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, + BuildInfo, BuildMeta, BuildResult, CodeGenerationResult, Compilation, ConcatenationScope, + Context, DependenciesBlock, DependencyId, FactoryMeta, LibIdentOptions, Module, ModuleDependency, + ModuleType, RuntimeGlobals, RuntimeSpec, SourceType, StaticExportsDependency, StaticExportsSpec, +}; +use rspack_error::{impl_empty_diagnosable_trait, Diagnostic, Result}; +use rspack_util::source_map::ModuleSourceMapConfig; + +use super::delegated_source_dependency::DelegatedSourceDependency; +use crate::{DllManifestContentItem, DllManifestContentItemExports}; + +pub type SourceRequest = String; + +#[impl_source_map_config] +#[derive(Debug, Default)] +pub struct DelegatedModule { + source_request: SourceRequest, + request: Option, + delegation_type: String, + user_request: String, + original_request: Option, + delegate_data: DllManifestContentItem, + dependencies: Vec, + blocks: Vec, + factory_meta: Option, + build_info: Option, + build_meta: Option, +} + +impl DelegatedModule { + pub fn new( + source_request: SourceRequest, + data: DllManifestContentItem, + delegation_type: String, + user_request: String, + original_request: Option, + ) -> Self { + Self { + source_request, + request: data.id.clone(), + delegation_type, + user_request, + original_request, + delegate_data: data, + ..Default::default() + } + } +} + +#[async_trait] +impl Module for DelegatedModule { + impl_module_meta_info!(); + + fn module_type(&self) -> &ModuleType { + &ModuleType::JsDynamic + } + + fn source_types(&self) -> &[SourceType] { + &[SourceType::JavaScript] + } + + fn lib_ident(&self, _options: LibIdentOptions) -> Option> { + self.original_request.as_ref().map(|request| request.into()) + } + + fn original_source(&self) -> Option<&dyn Source> { + None + } + + fn readable_identifier(&self, _context: &Context) -> Cow { + format!( + "delegated {} from {}", + self.user_request, self.source_request + ) + .into() + } + + fn size(&self, _source_type: Option<&SourceType>, _compilation: Option<&Compilation>) -> f64 { + 42.0 + } + + fn get_diagnostics(&self) -> Vec { + vec![] + } + + async fn build( + &mut self, + _build_context: BuildContext, + _compilation: Option<&Compilation>, + ) -> Result { + let dependencies = vec![ + Box::new(DelegatedSourceDependency::new(self.source_request.clone())), + Box::new(StaticExportsDependency::new( + match self.delegate_data.exports.clone() { + Some(exports) => match exports { + DllManifestContentItemExports::True => StaticExportsSpec::True, + DllManifestContentItemExports::Vec(vec) => StaticExportsSpec::Array(vec), + }, + None => StaticExportsSpec::True, + }, + false, + )) as BoxDependency, + ]; + Ok(BuildResult { + dependencies, + build_meta: self.delegate_data.build_meta.clone().unwrap_or_default(), + ..Default::default() + }) + } + + fn code_generation( + &self, + compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + _concatenation_scope: Option, + ) -> Result { + let mut runtime_requirements = RuntimeGlobals::default(); + runtime_requirements.insert(RuntimeGlobals::REQUIRE); + runtime_requirements.insert(RuntimeGlobals::MODULE); + let mut code_generation_result = CodeGenerationResult { + runtime_requirements, + ..Default::default() + }; + + let dep = self.dependencies[0]; + let mg = compilation.get_module_graph(); + let source_module = mg.get_module_by_dependency_id(&dep); + let dependency = mg + .dependency_by_id(&dep) + .and_then(|dep| dep.downcast_ref::()) + .expect("Should be module dependency"); + + let str = match source_module { + Some(_) => { + let mut s = format!( + "module.exports = {}", + module_raw( + compilation, + &mut code_generation_result.runtime_requirements, + &dep, + dependency.request(), + false, + ) + ); + + let request = self + .request + .as_ref() + .expect("manifest content should have `id`."); + + match self.delegation_type.as_ref() { + "require" => { + s += &format!("({})", request); + } + "object" => { + s += &format!("[{}]", request); + } + _ => panic!("delegation_type should be 'require' or 'object'"), + } + + s += ";"; + + s + } + None => throw_missing_module_error_block(&self.source_request), + }; + + let source_map = self.get_source_map_kind(); + + let source: BoxSource = if source_map.source_map() || source_map.simple_source_map() { + Arc::new(OriginalSource::new(str, self.identifier().to_string())) + } else { + let raw_source: RawSource = str.into(); + Arc::new(raw_source) + }; + + code_generation_result = code_generation_result.with_javascript(source); + + Ok(code_generation_result) + } + + fn need_build(&self) -> bool { + self.build_meta.is_none() + } + + fn update_hash( + &self, + mut hasher: &mut dyn std::hash::Hasher, + compilation: &Compilation, + runtime: Option<&RuntimeSpec>, + ) -> Result<()> { + self.delegation_type.hash(&mut hasher); + + if let Some(request) = &self.request { + request.hash(&mut hasher); + } + + module_update_hash(self, hasher, compilation, runtime); + + Ok(()) + } +} + +impl Identifiable for DelegatedModule { + fn identifier(&self) -> Identifier { + format!( + "delegated {} from {}", + self.request.as_deref().unwrap_or_default(), + self.source_request + ) + .into() + } +} + +impl DependenciesBlock for DelegatedModule { + 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 get_dependencies(&self) -> &[DependencyId] { + &self.dependencies + } + + fn remove_dependency_id(&mut self, dependency: DependencyId) { + self.dependencies.retain(|d| d != &dependency) + } +} + +impl_empty_diagnosable_trait!(DelegatedModule); diff --git a/crates/rspack_plugin_dll/src/dll_reference/delegated_plugin.rs b/crates/rspack_plugin_dll/src/dll_reference/delegated_plugin.rs new file mode 100644 index 00000000000..05760e35400 --- /dev/null +++ b/crates/rspack_plugin_dll/src/dll_reference/delegated_plugin.rs @@ -0,0 +1,152 @@ +use rspack_core::{ + ApplyContext, BoxModule, Compilation, CompilationParams, CompilerCompilation, CompilerOptions, + Context, DependencyType, LibIdentOptions, ModuleFactoryCreateData, NormalModuleCreateData, + NormalModuleFactoryFactorize, NormalModuleFactoryModule, Plugin, PluginContext, +}; +use rspack_error::Result; +use rspack_hook::{plugin, plugin_hook}; + +use super::delegated_module::DelegatedModule; +use crate::DllManifestContent; + +#[derive(Debug)] +pub struct DelegatedPluginOptions { + pub source: String, + + pub context: Option, + + pub content: DllManifestContent, + + pub r#type: String, + + pub extensions: Vec, + + pub scope: Option, + + pub compilation_context: Context, +} + +#[plugin] +#[derive(Debug)] +pub struct DelegatedPlugin { + options: DelegatedPluginOptions, +} + +impl DelegatedPlugin { + pub fn new(options: DelegatedPluginOptions) -> Self { + Self::new_inner(options) + } +} + +impl Plugin for DelegatedPlugin { + fn name(&self) -> &'static str { + "rspack.DelegatedPlugin" + } + + fn apply(&self, ctx: PluginContext<&mut ApplyContext>, _options: &CompilerOptions) -> Result<()> { + ctx + .context + .compiler_hooks + .compilation + .tap(compilation::new(self)); + + ctx + .context + .normal_module_factory_hooks + .factorize + .tap(factorize::new(self)); + + ctx + .context + .normal_module_factory_hooks + .module + .tap(nmf_module::new(self)); + + Ok(()) + } +} + +#[plugin_hook(CompilerCompilation for DelegatedPlugin)] +async fn compilation( + &self, + compilation: &mut Compilation, + params: &mut CompilationParams, +) -> Result<()> { + compilation.set_dependency_factory( + DependencyType::DelegatedSource, + params.normal_module_factory.clone(), + ); + Ok(()) +} + +#[plugin_hook(NormalModuleFactoryFactorize for DelegatedPlugin)] +async fn factorize(&self, data: &mut ModuleFactoryCreateData) -> Result> { + if let Some(scope) = &self.options.scope { + if let Some(dependency) = data.dependencies[0].as_module_dependency() { + let scope_prefix = format!("{}/", scope); + let request = dependency.request(); + if request.starts_with(&scope_prefix) { + let inner_request = format!( + ".{}", + &request.chars().skip(scope.len()).collect::() + ); + + if let Some(resolved) = self.options.content.get(&inner_request) { + return Ok(Some(Box::new(DelegatedModule::new( + self.options.source.clone(), + resolved.clone(), + self.options.r#type.clone(), + inner_request, + Some(request.to_owned()), + )))); + } + + for extension in self.options.extensions.iter() { + let request_plus_ext = format!("{}{}", inner_request, extension); + + if let Some(resolved) = self.options.content.get(&request_plus_ext) { + return Ok(Some(Box::new(DelegatedModule::new( + self.options.source.clone(), + resolved.clone(), + self.options.r#type.clone(), + request_plus_ext, + format!("{}{}", request, extension).into(), + )))); + } + } + } + } + } + + Ok(None) +} + +#[plugin_hook(NormalModuleFactoryModule for DelegatedPlugin)] +async fn nmf_module( + &self, + _data: &mut ModuleFactoryCreateData, + _create_data: &mut NormalModuleCreateData, + module: &mut BoxModule, +) -> Result<()> { + if self.options.scope.is_none() { + if let Some(request) = module.lib_ident(LibIdentOptions { + context: self.options.context.as_ref().unwrap_or(&Context::from("")), + }) { + if let Some(resolved) = self.options.content.get(request.as_ref()) { + let original_request = module.lib_ident(LibIdentOptions { + context: &self.options.compilation_context, + }); + + *module = Box::new(DelegatedModule::new( + self.options.source.clone(), + resolved.clone(), + self.options.r#type.clone(), + request.to_string(), + original_request.map(|request| request.to_string()), + )); + } + }; + } + + Ok(()) +} diff --git a/crates/rspack_plugin_dll/src/dll_reference/delegated_source_dependency.rs b/crates/rspack_plugin_dll/src/dll_reference/delegated_source_dependency.rs new file mode 100644 index 00000000000..3954dcf64d4 --- /dev/null +++ b/crates/rspack_plugin_dll/src/dll_reference/delegated_source_dependency.rs @@ -0,0 +1,51 @@ +use rspack_core::{ + AffectType, AsContextDependency, AsDependencyTemplate, Dependency, DependencyCategory, + DependencyId, DependencyType, ModuleDependency, +}; + +#[derive(Debug, Clone)] +pub struct DelegatedSourceDependency { + id: DependencyId, + request: String, +} + +impl DelegatedSourceDependency { + pub fn new(request: String) -> Self { + Self { + id: DependencyId::new(), + request, + } + } +} + +impl Dependency for DelegatedSourceDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn could_affect_referencing_module(&self) -> AffectType { + AffectType::True + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::DelegatedSource + } +} + +impl ModuleDependency for DelegatedSourceDependency { + fn request(&self) -> &str { + &self.request + } + + fn set_request(&mut self, request: String) { + self.request = request; + } +} + +impl AsContextDependency for DelegatedSourceDependency {} + +impl AsDependencyTemplate for DelegatedSourceDependency {} diff --git a/crates/rspack_plugin_dll/src/dll_reference/dll_reference_agency_plugin.rs b/crates/rspack_plugin_dll/src/dll_reference/dll_reference_agency_plugin.rs new file mode 100644 index 00000000000..98e2cb4445d --- /dev/null +++ b/crates/rspack_plugin_dll/src/dll_reference/dll_reference_agency_plugin.rs @@ -0,0 +1,89 @@ +use rspack_core::{ + ApplyContext, CompilerOptions, Context, ExternalItem, ExternalItemValue, LibraryType, Plugin, + PluginContext, +}; +use rspack_error::Result; +use rspack_hook::plugin; +use rspack_plugin_externals::ExternalsPlugin; +use rustc_hash::FxHashMap as HashMap; + +use super::delegated_plugin::{DelegatedPlugin, DelegatedPluginOptions}; +use crate::{DllManifest, DllManifestContent}; + +#[derive(Debug, Clone)] +pub struct DllReferenceAgencyPluginOptions { + pub context: Option, + pub name: Option, + pub content: Option, + pub manifest: Option, + pub extensions: Vec, + pub scope: Option, + pub source_type: Option, + pub r#type: String, +} + +#[plugin] +#[derive(Debug)] +pub struct DllReferenceAgencyPlugin { + options: DllReferenceAgencyPluginOptions, +} + +impl DllReferenceAgencyPlugin { + pub fn new(options: DllReferenceAgencyPluginOptions) -> Self { + Self::new_inner(options) + } +} + +impl Plugin for DllReferenceAgencyPlugin { + fn name(&self) -> &'static str { + "rspack.DllReferenceAgencyPlugin" + } + + fn apply(&self, ctx: PluginContext<&mut ApplyContext>, options: &CompilerOptions) -> Result<()> { + let mut name = self.options.name.clone(); + let mut source_type = self.options.source_type.clone(); + let mut resolved_content = self.options.content.clone(); + + if let Some(manifest) = &self.options.manifest { + if name.is_none() { + name = manifest.name.clone(); + } + + if source_type.is_none() { + source_type = manifest.r#type.clone(); + } + + if resolved_content.is_none() { + resolved_content = Some(manifest.content.clone()); + } + } + + let resolved_content = resolved_content.expect("Manifest should have content."); + + let name = name.expect("Should pass name or manifest should have the name attribute"); + + let source = format!("dll-reference {}", name); + + let mut external_item_object = HashMap::default(); + + external_item_object.insert(source.clone(), ExternalItemValue::String(name)); + + let external = ExternalItem::Object(external_item_object); + + ExternalsPlugin::new(source_type.unwrap_or("var".into()), vec![external]) + .apply(PluginContext::with_context(ctx.context), options)?; + + DelegatedPlugin::new(DelegatedPluginOptions { + source: source.clone(), + r#type: self.options.r#type.clone(), + scope: self.options.scope.clone(), + content: resolved_content, + extensions: self.options.extensions.clone(), + context: self.options.context.clone(), + compilation_context: options.context.clone(), + }) + .apply(PluginContext::with_context(ctx.context), options)?; + + Ok(()) + } +} diff --git a/crates/rspack_plugin_dll/src/dll_reference/mod.rs b/crates/rspack_plugin_dll/src/dll_reference/mod.rs new file mode 100644 index 00000000000..fa469e7959e --- /dev/null +++ b/crates/rspack_plugin_dll/src/dll_reference/mod.rs @@ -0,0 +1,4 @@ +pub mod delegated_module; +pub mod delegated_plugin; +pub mod delegated_source_dependency; +pub mod dll_reference_agency_plugin; diff --git a/crates/rspack_plugin_dll/src/flag_all_modules_as_used_plugin.rs b/crates/rspack_plugin_dll/src/flag_all_modules_as_used_plugin.rs new file mode 100644 index 00000000000..06c3b18743b --- /dev/null +++ b/crates/rspack_plugin_dll/src/flag_all_modules_as_used_plugin.rs @@ -0,0 +1,98 @@ +use rspack_collections::IdentifierSet; +use rspack_core::{ + get_entry_runtime, merge_runtime, ApplyContext, BoxModule, Compilation, CompilationId, + CompilationOptimizeDependencies, CompilationSucceedModule, CompilerOptions, FactoryMeta, Plugin, + PluginContext, RuntimeSpec, +}; +use rspack_error::Result; +use rspack_hook::{plugin, plugin_hook}; + +#[plugin] +#[derive(Debug, Default)] +pub struct FlagAllModulesAsUsedPlugin { + explanation: String, +} + +impl FlagAllModulesAsUsedPlugin { + pub fn new(explanation: String) -> Self { + Self::new_inner(explanation) + } +} + +impl Plugin for FlagAllModulesAsUsedPlugin { + fn name(&self) -> &'static str { + "rspack:FlagAllModulesAsUsedPlugin" + } + + fn apply(&self, ctx: PluginContext<&mut ApplyContext>, _options: &CompilerOptions) -> Result<()> { + ctx + .context + .compilation_hooks + .optimize_dependencies + .tap(optimize_dependencies::new(self)); + + ctx + .context + .compilation_hooks + .succeed_module + .tap(succeed_module::new(self)); + + Ok(()) + } +} + +#[plugin_hook(CompilationOptimizeDependencies for FlagAllModulesAsUsedPlugin)] +fn optimize_dependencies(&self, compilation: &mut Compilation) -> Result> { + let entries = &compilation.entries; + + let runtime = compilation + .entries + .iter() + .map(|(name, entry_data)| get_entry_runtime(name, &entry_data.options, entries)) + .fold(RuntimeSpec::default(), |a, b| merge_runtime(&a, &b)); + + let mut mg = compilation.get_module_graph_mut(); + + let module_id_list: IdentifierSet = mg.modules().keys().copied().collect(); + + for module_id in module_id_list { + let exports_info = mg.get_exports_info(&module_id); + exports_info.set_used_in_unknown_way(&mut mg, Some(&runtime)); + } + + Ok(None) +} + +#[plugin_hook(CompilationSucceedModule for FlagAllModulesAsUsedPlugin)] +async fn succeed_module( + &self, + _compilation_id: CompilationId, + module: &mut BoxModule, +) -> Result<()> { + // set all modules have effects. To avoid any module remove by tree shaking. + // see: https://github.com/webpack/webpack/blob/4b4ca3bb53f36a5b8fc6bc1bd976ed7af161bd80/lib/FlagAllModulesAsUsedPlugin.js#L43-L47 + module.set_factory_meta(FactoryMeta { + side_effect_free: Some(false), + }); + + let module_concatenation_bailout = module + .build_info() + .and_then(|info| info.module_concatenation_bailout.as_ref()); + + if module_concatenation_bailout.is_none() { + // webpack avoid those modules be concatenated using add a virtual module_graph_connection. + // see: https://github.com/webpack/webpack/blob/4b4ca3bb53f36a5b8fc6bc1bd976ed7af161bd80/lib/FlagAllModulesAsUsedPlugin.js#L42 + // Rspack need incremental build, so we should not add virtual connection to module. + // We can add a bail reason to avoid those modules be concatenated. + let mut build_info = module.build_info().cloned().unwrap_or_default(); + build_info.module_concatenation_bailout = Some(format!( + "Module {} is referenced by {}", + module.identifier(), + &self.explanation + )); + + module.set_build_info(build_info); + } + + Ok(()) +} diff --git a/crates/rspack_plugin_dll/src/lib.rs b/crates/rspack_plugin_dll/src/lib.rs new file mode 100644 index 00000000000..71c146ce3fb --- /dev/null +++ b/crates/rspack_plugin_dll/src/lib.rs @@ -0,0 +1,67 @@ +use rspack_core::{BuildMeta, LibraryType}; +use rspack_util::atom::Atom; +use rustc_hash::FxHashMap as HashMap; +use serde::{ser::SerializeSeq, Serialize}; + +mod dll_entry; +mod dll_reference; +mod flag_all_modules_as_used_plugin; +mod lib_manifest_plugin; + +pub type DllManifestContent = HashMap; + +#[derive(Debug, Default, Clone)] +pub enum DllManifestContentItemExports { + #[default] + True, + Vec(Vec), +} + +impl Serialize for DllManifestContentItemExports { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + DllManifestContentItemExports::True => serializer.serialize_bool(true), + DllManifestContentItemExports::Vec(vec) => { + let mut seq = serializer.serialize_seq(Some(vec.len()))?; + for item in vec { + seq.serialize_element(item)?; + } + seq.end() + } + } + } +} + +#[derive(Debug, Default, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DllManifestContentItem { + #[serde(skip_serializing_if = "Option::is_none")] + pub build_meta: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub exports: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DllManifest { + pub content: DllManifestContent, + + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option, +} + +pub use dll_entry::dll_entry_plugin::{DllEntryPlugin, DllEntryPluginOptions}; +pub use dll_reference::dll_reference_agency_plugin::{ + DllReferenceAgencyPlugin, DllReferenceAgencyPluginOptions, +}; +pub use flag_all_modules_as_used_plugin::FlagAllModulesAsUsedPlugin; +pub use lib_manifest_plugin::{LibManifestPlugin, LibManifestPluginOptions}; diff --git a/crates/rspack_plugin_dll/src/lib_manifest_plugin.rs b/crates/rspack_plugin_dll/src/lib_manifest_plugin.rs new file mode 100644 index 00000000000..bda19d5f31f --- /dev/null +++ b/crates/rspack_plugin_dll/src/lib_manifest_plugin.rs @@ -0,0 +1,185 @@ +use std::collections::HashSet; +use std::sync::Arc; + +use rspack_core::{ + rspack_sources::{BoxSource, RawSource}, + ApplyContext, Compilation, CompilationAssets, CompilerEmit, CompilerOptions, Context, + EntryDependency, Filename, LibIdentOptions, PathData, Plugin, PluginContext, ProvidedExports, + SourceType, +}; +use rspack_error::{Error, Result}; +use rspack_hook::{plugin, plugin_hook}; +use rustc_hash::FxHashMap as HashMap; + +use crate::{ + DllManifest, DllManifestContent, DllManifestContentItem, DllManifestContentItemExports, +}; + +#[derive(Debug, Clone)] +pub struct LibManifestPluginOptions { + pub context: Option, + + pub entry_only: Option, + + pub name: Option, + + pub format: Option, + + pub path: Filename, + + pub r#type: Option, +} + +#[plugin] +#[derive(Debug)] +pub struct LibManifestPlugin { + options: LibManifestPluginOptions, +} + +impl LibManifestPlugin { + pub fn new(options: LibManifestPluginOptions) -> Self { + Self::new_inner(options) + } +} + +impl Plugin for LibManifestPlugin { + fn name(&self) -> &'static str { + "rspack.LibManifestPlugin" + } + + fn apply(&self, ctx: PluginContext<&mut ApplyContext>, _options: &CompilerOptions) -> Result<()> { + ctx.context.compiler_hooks.emit.tap(emit::new(self)); + Ok(()) + } +} + +#[plugin_hook(CompilerEmit for LibManifestPlugin)] +async fn emit(&self, compilation: &mut Compilation) -> Result<()> { + let mut use_paths: HashSet = HashSet::new(); + + let chunk_graph = &compilation.chunk_graph; + + let mut manifests: CompilationAssets = HashMap::default(); + + let module_graph = compilation.get_module_graph(); + + for (_, chunk) in compilation.chunk_by_ukey.iter() { + if !chunk.can_be_initial(&compilation.chunk_group_by_ukey) { + continue; + } + + let target_path = compilation.get_path( + &self.options.path, + PathData { + chunk: Some(chunk), + ..Default::default() + }, + )?; + + if use_paths.contains(&target_path) { + return Err(Error::msg("each chunk must have a unique path")); + } + + use_paths.insert(target_path.clone()); + + let name = self.options.name.as_ref().and_then(|name| { + compilation + .get_path( + name, + PathData { + chunk: Some(chunk), + content_hash_type: Some(SourceType::JavaScript), + ..Default::default() + }, + ) + .ok() + }); + + let mut manifest_content: DllManifestContent = HashMap::default(); + + for module in chunk_graph.get_ordered_chunk_modules(&chunk.ukey, &module_graph) { + if self.options.entry_only.unwrap_or_default() + && !some_in_iterable( + module_graph + .get_incoming_connections(&module.identifier()) + .into_iter(), + |conn| { + let dep = module_graph.dependency_by_id(&conn.dependency_id); + + dep + .map(|dep| dep.is::()) + .unwrap_or_default() + }, + ) + { + continue; + } + + let context = match &self.options.context { + Some(ctx) => ctx, + None => &compilation.options.context, + }; + + let ident = module.lib_ident(LibIdentOptions { context }); + + if let Some(ident) = ident { + let exports_info = module_graph.get_exports_info(&module.identifier()); + + let provided_exports = match exports_info.get_provided_exports(&module_graph) { + ProvidedExports::Vec(vec) => Some(DllManifestContentItemExports::Vec(vec)), + ProvidedExports::True => Some(DllManifestContentItemExports::True), + _ => None, + }; + + let id = chunk_graph.get_module_id(module.identifier()); + + let build_meta = module.build_meta(); + + manifest_content.insert( + ident.into_owned(), + DllManifestContentItem { + id: id.map(|id| id.to_string()), + build_meta: build_meta.cloned(), + exports: provided_exports, + }, + ); + } + } + + let manifest = DllManifest { + name, + content: manifest_content, + r#type: self.options.r#type.clone(), + }; + + let format = self.options.format.unwrap_or_default(); + + let manifest_json = if format { + serde_json::to_string_pretty(&manifest).map_err(|e| Error::msg(format!("{}", e)))? + } else { + serde_json::to_string(&manifest).map_err(|e| Error::msg(format!("{}", e)))? + }; + + let asset = Arc::new(RawSource::from(manifest_json)) as BoxSource; + + manifests.insert(target_path, asset.into()); + } + + for (filename, asset) in manifests { + compilation.emit_asset(filename, asset); + } + + Ok(()) +} + +fn some_in_iterable(iterable: I, filter: F) -> bool +where + F: Fn(I::Item) -> bool, +{ + for item in iterable { + if filter(item) { + return true; + } + } + false +} diff --git a/crates/rspack_plugin_javascript/src/plugin/module_concatenation_plugin.rs b/crates/rspack_plugin_javascript/src/plugin/module_concatenation_plugin.rs index d4d3d558197..1b532608b54 100644 --- a/crates/rspack_plugin_javascript/src/plugin/module_concatenation_plugin.rs +++ b/crates/rspack_plugin_javascript/src/plugin/module_concatenation_plugin.rs @@ -281,11 +281,10 @@ impl ModuleConcatenationPlugin { // TODO: ADD module connection explanations if !active_non_modules_connections.is_empty() { let problem = { - // let importing_explanations: HashSet<_> = active_non_modules_connections + // let importing_explanations = active_non_modules_connections // .iter() - // .flat_map(|&c| c.explanation.as_ref()) - // .cloned() - // .collect(); + // .flat_map(|&c| c.explanation()) + // .collect::>(); // let mut explanations: Vec<_> = importing_explanations.into_iter().collect(); // explanations.sort(); format!( @@ -304,6 +303,7 @@ impl ModuleConcatenationPlugin { return Some(problem); } } + let mut incoming_connections_from_modules = HashMap::default(); for (origin_module, connections) in incoming_connections.iter() { if let Some(origin_module) = origin_module { diff --git a/packages/rspack/etc/core.api.md b/packages/rspack/etc/core.api.md index 0e04193a8bf..12685109bed 100644 --- a/packages/rspack/etc/core.api.md +++ b/packages/rspack/etc/core.api.md @@ -32,6 +32,7 @@ import { JsAlterAssetTagsData } from '@rspack/binding'; import type { JsAssetInfo } from '@rspack/binding'; import { JsBeforeAssetTagGenerationData } from '@rspack/binding'; import { JsBeforeEmitData } from '@rspack/binding'; +import type { JsBuildMeta } from '@rspack/binding'; import { JsChunk } from '@rspack/binding'; import { JsChunkGroup } from '@rspack/binding'; import { JsChunkGroupOrigin } from '@rspack/binding'; @@ -1441,6 +1442,68 @@ type DIRTY = { // @public (undocumented) const DIRTY: (value: T) => DIRTY; +// @public (undocumented) +export class DllPlugin { + constructor(options: DllPluginOptions); + // (undocumented) + apply(compiler: Compiler): void; +} + +// @public (undocumented) +export type DllPluginOptions = { + context?: string; + entryOnly?: boolean; + format?: boolean; + name?: string; + path: string; + type?: string; +}; + +// @public (undocumented) +export class DllReferencePlugin { + constructor(options: DllReferencePluginOptions); + // (undocumented) + apply(compiler: Compiler): void; +} + +// @public (undocumented) +export type DllReferencePluginOptions = { + context?: string; + extensions?: string[]; + manifest: string | DllReferencePluginOptionsManifest; + name?: string; + scope?: string; + sourceType?: DllReferencePluginOptionsSourceType; + type?: "require" | "object"; +} | { + content: DllReferencePluginOptionsContent; + context?: string; + extensions?: string[]; + name: string; + scope?: string; + sourceType?: DllReferencePluginOptionsSourceType; + type?: "require" | "object"; +}; + +// @public +export interface DllReferencePluginOptionsContent { + [k: string]: { + buildMeta?: JsBuildMeta; + exports?: string[] | true; + id?: string; + }; +} + +// @public +export interface DllReferencePluginOptionsManifest { + content: DllReferencePluginOptionsContent; + name?: string; + type?: DllReferencePluginOptionsSourceType; +} + +// @public +export type DllReferencePluginOptionsSourceType = "var" | "assign" | "this" | "window" | "global" | "commonjs" | "commonjs2" | "commonjs-module" | "amd" | "amd-require" | "umd" | "umd2" | "jsonp" | "system"; + // @public (undocumented) interface Drafts { customMedia?: boolean; @@ -4870,6 +4933,13 @@ declare namespace rspackExports { ExternalsPlugin, HotModuleReplacementPlugin, NoEmitOnErrorsPlugin, + DllPlugin, + DllPluginOptions, + DllReferencePlugin, + DllReferencePluginOptions, + DllReferencePluginOptionsSourceType, + DllReferencePluginOptionsContent, + DllReferencePluginOptionsManifest, EnvironmentPlugin, LoaderOptionsPlugin, LoaderTargetPlugin, diff --git a/packages/rspack/src/builtin-plugin/DllEntryPlugin.ts b/packages/rspack/src/builtin-plugin/DllEntryPlugin.ts new file mode 100644 index 00000000000..593ac273675 --- /dev/null +++ b/packages/rspack/src/builtin-plugin/DllEntryPlugin.ts @@ -0,0 +1,24 @@ +import { + BuiltinPluginName, + type RawDllEntryPluginOptions +} from "@rspack/binding"; +import { create } from "./base"; + +export type DllEntryPluginOptions = { + name: string; +}; + +export const DllEntryPlugin = create( + BuiltinPluginName.DllEntryPlugin, + ( + context: string, + entries: string[], + options: DllEntryPluginOptions + ): RawDllEntryPluginOptions => { + return { + context, + entries, + name: options.name + }; + } +); diff --git a/packages/rspack/src/builtin-plugin/DllReferenceAgencyPlugin.ts b/packages/rspack/src/builtin-plugin/DllReferenceAgencyPlugin.ts new file mode 100644 index 00000000000..ed7750eb9d8 --- /dev/null +++ b/packages/rspack/src/builtin-plugin/DllReferenceAgencyPlugin.ts @@ -0,0 +1,15 @@ +import { + BuiltinPluginName, + type RawDllReferenceAgencyPluginOptions +} from "@rspack/binding"; +import { create } from "./base"; + +export type DllReferenceAgencyPluginOptions = + RawDllReferenceAgencyPluginOptions; + +export const DllReferenceAgencyPlugin = create( + BuiltinPluginName.DllReferenceAgencyPlugin, + ( + options: DllReferenceAgencyPluginOptions + ): RawDllReferenceAgencyPluginOptions => options +); diff --git a/packages/rspack/src/builtin-plugin/FlagAllModulesAsUsedPlugin.ts b/packages/rspack/src/builtin-plugin/FlagAllModulesAsUsedPlugin.ts new file mode 100644 index 00000000000..897b1c8c2cb --- /dev/null +++ b/packages/rspack/src/builtin-plugin/FlagAllModulesAsUsedPlugin.ts @@ -0,0 +1,14 @@ +import { + BuiltinPluginName, + type RawFlagAllModulesAsUsedPluginOptions +} from "@rspack/binding"; +import { create } from "./base"; + +export const FlagAllModulesAsUsedPlugin = create( + BuiltinPluginName.FlagAllModulesAsUsedPlugin, + (explanation: string): RawFlagAllModulesAsUsedPluginOptions => { + return { + explanation + }; + } +); diff --git a/packages/rspack/src/builtin-plugin/LibManifestPlugin.ts b/packages/rspack/src/builtin-plugin/LibManifestPlugin.ts new file mode 100644 index 00000000000..c2f647d1edc --- /dev/null +++ b/packages/rspack/src/builtin-plugin/LibManifestPlugin.ts @@ -0,0 +1,35 @@ +import { + BuiltinPluginName, + type RawLibManifestPluginOptions +} from "@rspack/binding"; +import { create } from "./base"; + +export type LibManifestPluginOptions = { + context?: string; + + entryOnly?: boolean; + + format?: boolean; + + name?: string; + + path: string; + + type?: string; +}; + +export const LibManifestPlugin = create( + BuiltinPluginName.LibManifestPlugin, + (options: LibManifestPluginOptions): RawLibManifestPluginOptions => { + const { context, entryOnly, format, name, path, type } = options; + + return { + context, + entryOnly, + format, + name, + path, + type + }; + } +); diff --git a/packages/rspack/src/builtin-plugin/index.ts b/packages/rspack/src/builtin-plugin/index.ts index 05aba1e9664..d77a6acbb7e 100644 --- a/packages/rspack/src/builtin-plugin/index.ts +++ b/packages/rspack/src/builtin-plugin/index.ts @@ -67,3 +67,6 @@ export * from "./WorkerPlugin"; export * from "./FetchCompileAsyncWasmPlugin"; export * from "./NoEmitOnErrorsPlugin"; export * from "./ContextReplacementPlugin"; +export * from "./LibManifestPlugin"; +export * from "./DllEntryPlugin"; +export * from "./DllReferenceAgencyPlugin"; diff --git a/packages/rspack/src/exports.ts b/packages/rspack/src/exports.ts index fae2e2d781a..cc406ecb2d9 100644 --- a/packages/rspack/src/exports.ts +++ b/packages/rspack/src/exports.ts @@ -103,6 +103,14 @@ export { DynamicEntryPlugin } from "./builtin-plugin"; export { ExternalsPlugin } from "./builtin-plugin"; export { HotModuleReplacementPlugin } from "./builtin-plugin"; export { NoEmitOnErrorsPlugin } from "./builtin-plugin"; +export { DllPlugin, type DllPluginOptions } from "./lib/DllPlugin"; +export { + DllReferencePlugin, + type DllReferencePluginOptions, + type DllReferencePluginOptionsSourceType, + type DllReferencePluginOptionsContent, + type DllReferencePluginOptionsManifest +} from "./lib/DllReferencePlugin"; export { EnvironmentPlugin } from "./lib/EnvironmentPlugin"; export { LoaderOptionsPlugin } from "./lib/LoaderOptionsPlugin"; export { LoaderTargetPlugin } from "./lib/LoaderTargetPlugin"; diff --git a/packages/rspack/src/lib/DllPlugin.ts b/packages/rspack/src/lib/DllPlugin.ts new file mode 100644 index 00000000000..26ab533342f --- /dev/null +++ b/packages/rspack/src/lib/DllPlugin.ts @@ -0,0 +1,97 @@ +/** + * The following code is modified based on + * https://github.com/webpack/webpack/blob/4b4ca3bb53f36a5b8fc6bc1bd976ed7af161bd80/lib/DllPlugin.js + * + * MIT Licensed + * Author Tobias Koppers @sokra + * Copyright (c) JS Foundation and other contributors + * https://github.com/webpack/webpack/blob/main/LICENSE + */ + +import z from "zod"; +import type { Compiler } from "../Compiler"; +import { LibManifestPlugin } from "../builtin-plugin"; +import { DllEntryPlugin } from "../builtin-plugin/DllEntryPlugin"; +import { FlagAllModulesAsUsedPlugin } from "../builtin-plugin/FlagAllModulesAsUsedPlugin"; +import { validate } from "../util/validate"; + +export type DllPluginOptions = { + /** + * Context of requests in the manifest file (defaults to the webpack context). + */ + context?: string; + + /** + * If true, only entry points will be exposed. + * @default true + */ + entryOnly?: boolean; + + /** + * If true, manifest json file (output) will be formatted. + */ + format?: boolean; + + /** + * Name of the exposed dll function (external name, use value of 'output.library'). + */ + name?: string; + + /** + * Absolute path to the manifest json file (output). + */ + path: string; + + /** + * Type of the dll bundle (external type, use value of 'output.libraryTarget'). + */ + type?: string; +}; + +const dllPluginOptions = z.object({ + context: z.string().optional(), + entryOnly: z.boolean().optional(), + format: z.boolean().optional(), + name: z.string().optional(), + path: z.string(), + type: z.string().optional() +}) satisfies z.ZodType; + +export class DllPlugin { + private options: DllPluginOptions; + + constructor(options: DllPluginOptions) { + validate(options, dllPluginOptions); + this.options = { + ...options, + entryOnly: options.entryOnly !== false + }; + } + + apply(compiler: Compiler) { + compiler.hooks.entryOption.tap(DllPlugin.name, (context, entry) => { + if (typeof entry === "function") { + throw new Error( + "DllPlugin doesn't support dynamic entry (function) yet" + ); + } + + for (const name of Object.keys(entry)) { + const options = { + name + }; + const entries = entry[name].import || []; + + new DllEntryPlugin(context, entries, options).apply(compiler); + } + + return true; + }); + + new LibManifestPlugin(this.options).apply(compiler); + + if (!this.options.entryOnly) { + new FlagAllModulesAsUsedPlugin("DllPlugin").apply(compiler); + } + } +} diff --git a/packages/rspack/src/lib/DllReferencePlugin.ts b/packages/rspack/src/lib/DllReferencePlugin.ts new file mode 100644 index 00000000000..c312128fd69 --- /dev/null +++ b/packages/rspack/src/lib/DllReferencePlugin.ts @@ -0,0 +1,304 @@ +/** + * The following code is modified based on + * https://github.com/webpack/webpack/blob/4b4ca3bb53f36a5b8fc6bc1bd976ed7af161bd80/lib/DllReferencePlugin.js + * + * MIT Licensed + * Author Tobias Koppers @sokra + * Copyright (c) JS Foundation and other contributors + * https://github.com/webpack/webpack/blob/main/LICENSE + */ + +import type { JsBuildMeta } from "@rspack/binding"; +import z from "zod"; +import type { CompilationParams } from "../Compilation"; +import type { Compiler } from "../Compiler"; +import { DllReferenceAgencyPlugin } from "../builtin-plugin"; +import { makePathsRelative } from "../util/identifier"; +import { validate } from "../util/validate"; +import WebpackError from "./WebpackError"; + +export type DllReferencePluginOptions = + | { + /** + * Context of requests in the manifest (or content property) as absolute path. + */ + context?: string; + /** + * Extensions used to resolve modules in the dll bundle (only used when using 'scope'). + */ + extensions?: string[]; + /** + * An object containing content and name or a string to the absolute path of the JSON manifest to be loaded upon compilation. + */ + manifest: string | DllReferencePluginOptionsManifest; + /** + * The name where the dll is exposed (external name, defaults to manifest.name). + */ + name?: string; + /** + * Prefix which is used for accessing the content of the dll. + */ + scope?: string; + /** + * How the dll is exposed (libraryTarget, defaults to manifest.type). + */ + sourceType?: DllReferencePluginOptionsSourceType; + /** + * The way how the export of the dll bundle is used. + */ + type?: "require" | "object"; + } + | { + /** + * The mappings from request to module info. + */ + content: DllReferencePluginOptionsContent; + /** + * Context of requests in the manifest (or content property) as absolute path. + */ + context?: string; + /** + * Extensions used to resolve modules in the dll bundle (only used when using 'scope'). + */ + extensions?: string[]; + /** + * The name where the dll is exposed (external name). + */ + name: string; + /** + * Prefix which is used for accessing the content of the dll. + */ + scope?: string; + /** + * How the dll is exposed (libraryTarget). + */ + sourceType?: DllReferencePluginOptionsSourceType; + /** + * The way how the export of the dll bundle is used. + */ + type?: "require" | "object"; + }; +/** + * The type how the dll is exposed (external type). + */ +export type DllReferencePluginOptionsSourceType = + | "var" + | "assign" + | "this" + | "window" + | "global" + | "commonjs" + | "commonjs2" + | "commonjs-module" + | "amd" + | "amd-require" + | "umd" + | "umd2" + | "jsonp" + | "system"; + +/** + * An object containing content, name and type. + */ +export interface DllReferencePluginOptionsManifest { + /** + * The mappings from request to module info. + */ + content: DllReferencePluginOptionsContent; + /** + * The name where the dll is exposed (external name). + */ + name?: string; + /** + * The type how the dll is exposed (external type). + */ + type?: DllReferencePluginOptionsSourceType; +} +/** + * The mappings from request to module info. + */ +export interface DllReferencePluginOptionsContent { + /** + * Module info. + */ + [k: string]: { + /** + * Meta information about the module. + */ + buildMeta?: JsBuildMeta; + /** + * Information about the provided exports of the module. + */ + exports?: string[] | true; + /** + * Module ID. + */ + id?: string; + }; +} + +const dllReferencePluginOptionsContentItem = z.object({ + buildMeta: z.custom().optional(), + exports: z.array(z.string()).or(z.literal(true)).optional(), + id: z.string().optional() +}); + +const dllReferencePluginOptionsContent = z.record( + dllReferencePluginOptionsContentItem +) satisfies z.ZodType; + +const dllReferencePluginOptionsSourceType = z.enum([ + "var", + "assign", + "this", + "window", + "global", + "commonjs", + "commonjs2", + "commonjs-module", + "amd", + "amd-require", + "umd", + "umd2", + "jsonp", + "system" +]) satisfies z.ZodType; + +const dllReferencePluginOptionsManifest = z.object({ + content: dllReferencePluginOptionsContent, + name: z.string().optional(), + type: dllReferencePluginOptionsSourceType.optional() +}) satisfies z.ZodType; + +const dllReferencePluginOptions = z.union([ + z.object({ + context: z.string().optional(), + extensions: z.array(z.string()).optional(), + manifest: z.string().or(dllReferencePluginOptionsManifest), + name: z.string().optional(), + scope: z.string().optional(), + sourceType: dllReferencePluginOptionsSourceType.optional(), + type: z.enum(["require", "object"]).optional() + }), + z.object({ + content: dllReferencePluginOptionsContent, + context: z.string().optional(), + extensions: z.array(z.string()).optional(), + name: z.string(), + scope: z.string().optional(), + sourceType: dllReferencePluginOptionsSourceType.optional(), + type: z.enum(["require", "object"]).optional() + }) +]) satisfies z.ZodType; + +export class DllReferencePlugin { + private options: DllReferencePluginOptions; + + private errors: WeakMap; + + constructor(options: DllReferencePluginOptions) { + validate(options, dllReferencePluginOptions); + + this.options = options; + this.errors = new WeakMap(); + } + + apply(compiler: Compiler) { + compiler.hooks.beforeCompile.tapPromise( + DllReferencePlugin.name, + async params => { + const manifest = await new Promise< + DllReferencePluginOptionsManifest | undefined + >((resolve, reject) => { + if ("manifest" in this.options) { + const manifest = this.options.manifest; + + if (typeof manifest === "string") { + const manifestParameter = manifest; + + compiler.inputFileSystem?.readFile( + manifestParameter, + "utf8", + (err, result) => { + if (err) return reject(err); + + if (!result) + return reject( + new DllManifestError( + manifestParameter, + `Can't read anything from ${manifestParameter}` + ) + ); + + try { + const manifest: DllReferencePluginOptionsManifest = + JSON.parse(result); + resolve(manifest); + } catch (parseError) { + const manifestPath = makePathsRelative( + compiler.context, + manifestParameter, + compiler.root + ); + + this.errors.set( + params, + new DllManifestError( + manifestPath, + (parseError as Error).message + ) + ); + } + } + ); + } else { + resolve(manifest); + } + } else { + resolve(undefined); + } + }); + + if (!this.errors.has(params)) { + new DllReferenceAgencyPlugin({ + ...this.options, + type: this.options.type || "require", + extensions: this.options.extensions || [ + "", + ".js", + ".json", + ".wasm" + ], + manifest + }).apply(compiler); + } + } + ); + + compiler.hooks.compilation.tap( + DllReferencePlugin.name, + (compilation, params) => { + if ( + "manifest" in this.options && + typeof this.options.manifest === "string" + ) { + const error = this.errors.get(params); + if (error) { + compilation.errors.push(error); + } + + compilation.fileDependencies.add(this.options.manifest); + } + } + ); + } +} + +class DllManifestError extends WebpackError { + constructor(filename: string, message: string) { + super(); + + this.name = "DllManifestError"; + this.message = `Dll manifest ${filename}\n${message}`; + } +} diff --git a/tests/webpack-test/configCases/dll-plugin-entry/0-create-dll/test.filter.js b/tests/webpack-test/configCases/dll-plugin-entry/0-create-dll/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/dll-plugin-entry/0-create-dll/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/dll-plugin-entry/1-use-dll/test.filter.js b/tests/webpack-test/configCases/dll-plugin-entry/1-use-dll/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/dll-plugin-entry/1-use-dll/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/dll-plugin-entry/2-error-non-entry/test.filter.js b/tests/webpack-test/configCases/dll-plugin-entry/2-error-non-entry/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/dll-plugin-entry/2-error-non-entry/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/dll-plugin-format/0-create-dll/test.filter.js b/tests/webpack-test/configCases/dll-plugin-format/0-create-dll/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/dll-plugin-format/0-create-dll/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/dll-plugin-side-effects/0-create-dll/test.filter.js b/tests/webpack-test/configCases/dll-plugin-side-effects/0-create-dll/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/dll-plugin-side-effects/0-create-dll/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/dll-plugin-side-effects/1-use-dll/test.filter.js b/tests/webpack-test/configCases/dll-plugin-side-effects/1-use-dll/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/dll-plugin-side-effects/1-use-dll/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/dll-plugin/0-create-dll/test.filter.js b/tests/webpack-test/configCases/dll-plugin/0-create-dll/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/dll-plugin/0-create-dll/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/constants.js b/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/constants.js new file mode 100644 index 00000000000..84fc2484ce4 --- /dev/null +++ b/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/constants.js @@ -0,0 +1,2 @@ +export const constant1 = 'constant1'; +export const constant2 = 'constant2'; diff --git a/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/index.js b/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/index.js new file mode 100644 index 00000000000..89b290e87fa --- /dev/null +++ b/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/index.js @@ -0,0 +1,5 @@ +import * as _constants from './constants'; +export var constants = _constants; +export { default as someFunction } from './someFunction'; + +if(Math.random() < 0) console.log(constants); diff --git a/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/package.json b/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/package.json new file mode 100644 index 00000000000..ce5fa639dd0 --- /dev/null +++ b/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/package.json @@ -0,0 +1,4 @@ +{ + "main": "index.js", + "sideEffects": false +} diff --git a/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/someFunction.js b/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/someFunction.js new file mode 100644 index 00000000000..757d25c6ae7 --- /dev/null +++ b/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/someFunction.js @@ -0,0 +1,3 @@ +export default function someFunction() { + console.log('This is some function'); +} diff --git a/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/working-constants.js b/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/working-constants.js new file mode 100644 index 00000000000..cd433005d3a --- /dev/null +++ b/tests/webpack-test/configCases/dll-plugin/0-issue-10475/node_modules/test-package/working-constants.js @@ -0,0 +1,7 @@ +export const constant1 = 'constant1'; +export const constant2 = 'constant2'; + +export default { + constant1, + constant2, +}; diff --git a/tests/webpack-test/configCases/dll-plugin/0-issue-10475/test.filter.js b/tests/webpack-test/configCases/dll-plugin/0-issue-10475/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/dll-plugin/0-issue-10475/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/dll-plugin/1-issue-10475/test.filter.js b/tests/webpack-test/configCases/dll-plugin/1-issue-10475/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/dll-plugin/1-issue-10475/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/dll-plugin/1-use-dll/test.filter.js b/tests/webpack-test/configCases/dll-plugin/1-use-dll/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/dll-plugin/1-use-dll/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/dll-plugin/2-use-dll-without-scope/test.filter.js b/tests/webpack-test/configCases/dll-plugin/2-use-dll-without-scope/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/dll-plugin/2-use-dll-without-scope/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/dll-plugin/3-use-dll-with-hashid/test.filter.js b/tests/webpack-test/configCases/dll-plugin/3-use-dll-with-hashid/test.filter.js index 3be456dcd23..687e4d88602 100644 --- a/tests/webpack-test/configCases/dll-plugin/3-use-dll-with-hashid/test.filter.js +++ b/tests/webpack-test/configCases/dll-plugin/3-use-dll-with-hashid/test.filter.js @@ -1 +1 @@ -module.exports = () => {return false} \ No newline at end of file +module.exports = () => { return false } diff --git a/tests/webpack-test/configCases/dll-plugin/3-use-dll-with-hashid/webpack.config.js b/tests/webpack-test/configCases/dll-plugin/3-use-dll-with-hashid/webpack.config.js index 73538730cf4..6954b4715c3 100644 --- a/tests/webpack-test/configCases/dll-plugin/3-use-dll-with-hashid/webpack.config.js +++ b/tests/webpack-test/configCases/dll-plugin/3-use-dll-with-hashid/webpack.config.js @@ -19,6 +19,7 @@ module.exports = { ] }, optimization: { + // TODO: moduleIds: "hashed" is not implements moduleIds: "hashed" }, plugins: [ diff --git a/website/docs/en/plugins/webpack/_meta.json b/website/docs/en/plugins/webpack/_meta.json index 23091b0b540..cbd7e28af29 100644 --- a/website/docs/en/plugins/webpack/_meta.json +++ b/website/docs/en/plugins/webpack/_meta.json @@ -26,5 +26,7 @@ "runtime-chunk-plugin", "source-map-dev-tool-plugin", "split-chunks-plugin", - "internal-plugins" + "internal-plugins", + "dll-plugin", + "dll-reference-plugin" ] diff --git a/website/docs/en/plugins/webpack/dll-plugin.mdx b/website/docs/en/plugins/webpack/dll-plugin.mdx new file mode 100644 index 00000000000..94ec665b97c --- /dev/null +++ b/website/docs/en/plugins/webpack/dll-plugin.mdx @@ -0,0 +1,98 @@ +import { Table } from '@builtIns'; +import WebpackLicense from '@components/WebpackLicense'; + + + +# DllPlugin + +The `DllPlugin` is used in a separate rspack configuration exclusively to create a dll-only-bundle. + +## Options + +- **Type:** + +```ts +type DllPluginOptions = { + context?: string; + entryOnly?: boolean; + format?: boolean; + name?: string; + path: string; + type?: string; +}; +``` + + + +## Examples + +```js +new rspack.DllPlugin({ + path: path.resolve(__dirname, 'manifest.json'), + name: '[name]_dll_lib', +}); +``` + +The Plugin will create a `manifest.json` which is written to the given path. +It contains mappings from require and import requests to module ids. + +The `manifest.json` is used by the [DllReferencePlugin](/plugins/webpack/dll-reference-plugin) + +Combine this plugin with `output.library` options to expose the dll function. diff --git a/website/docs/en/plugins/webpack/dll-reference-plugin.mdx b/website/docs/en/plugins/webpack/dll-reference-plugin.mdx new file mode 100644 index 00000000000..1555dfd6886 --- /dev/null +++ b/website/docs/en/plugins/webpack/dll-reference-plugin.mdx @@ -0,0 +1,166 @@ +import { Table } from '@builtIns'; +import WebpackLicense from '@components/WebpackLicense'; + + + +# DllReferencePlugin + +The `DllReferencePlugin` is used be reference the dll-only-bundle to require pre-built dependencies. + +## Options + +- **Types:** + +```ts +type DllReferencePluginOptionsContent = { + /** + * Module info. + */ + [k: string]: { + /** + * Meta information about the module. + */ + buildMeta?: { + [k: string]: any; + }; + /** + * Information about the provided exports of the module. + */ + exports?: string[] | true; + /** + * Module ID. + */ + id?: string; + }; +}; + +type DllReferencePluginOptionsManifest = { + /** + * The mappings from request to module info. + */ + content: DllReferencePluginOptionsContent; + /** + * The name where the dll is exposed (external name). + */ + name?: string; + /** + * The type how the dll is exposed (external type). + */ + type?: DllReferencePluginOptionsSourceType; +}; + +/** + * The type how the dll is exposed (external type). + */ +type DllReferencePluginOptionsSourceType = + | 'var' + | 'assign' + | 'this' + | 'window' + | 'global' + | 'commonjs' + | 'commonjs2' + | 'commonjs-module' + | 'amd' + | 'amd-require' + | 'umd' + | 'umd2' + | 'jsonp' + | 'system'; + +type DllReferencePluginOptions = + | { + /** + * Context of requests in the manifest (or content property) as absolute path. + */ + context?: string; + /** + * Extensions used to resolve modules in the dll bundle (only used when using 'scope'). + */ + extensions?: string[]; + /** + * An object containing content and name or a string to the absolute path of the JSON manifest to be loaded upon compilation. + */ + manifest: string | DllReferencePluginOptionsManifest; + /** + * The name where the dll is exposed (external name, defaults to manifest.name). + */ + name?: string; + /** + * Prefix which is used for accessing the content of the dll. + */ + scope?: string; + /** + * How the dll is exposed (libraryTarget, defaults to manifest.type). + */ + sourceType?: DllReferencePluginOptionsSourceType; + /** + * The way how the export of the dll bundle is used. + */ + type?: 'require' | 'object'; + } + | { + /** + * The mappings from request to module info. + */ + content: DllReferencePluginOptionsContent; + /** + * Context of requests in the manifest (or content property) as absolute path. + */ + context?: string; + /** + * Extensions used to resolve modules in the dll bundle (only used when using 'scope'). + */ + extensions?: string[]; + /** + * The name where the dll is exposed (external name). + */ + name: string; + /** + * Prefix which is used for accessing the content of the dll. + */ + scope?: string; + /** + * How the dll is exposed (libraryTarget). + */ + sourceType?: DllReferencePluginOptionsSourceType; + /** + * The way how the export of the dll bundle is used. + */ + type?: 'require' | 'object'; + }; +``` + +This plugin references a dll manifest file to map dependency names to module ids, then require them as needed. + +## Examples + +### Basics Example + +```js +new rspack.DllReferencePlugin({ + // Manifest should be generated by DllPlugin + manifest: require('../lib/manifest.json'), + + name: '[name]_dll_lib', +}); +``` + +Application require dependencies will reference to pre-built using `DllPlugin`. + +### With Scope + +The content of the dll is accessible under a module prefix when set scope. + +```js +new rspack.DllReferencePlugin({ + // Manifest should be generated by DllPlugin + manifest: require('../lib/manifest.json'), + + name: '[name]_dll_lib', + + scope: 'xyz', +}); +``` + +Access via `require('xzy/abc')`, you can require `abc` from another pre-built lib. diff --git a/website/docs/zh/plugins/webpack/_meta.json b/website/docs/zh/plugins/webpack/_meta.json index 23091b0b540..cbd7e28af29 100644 --- a/website/docs/zh/plugins/webpack/_meta.json +++ b/website/docs/zh/plugins/webpack/_meta.json @@ -26,5 +26,7 @@ "runtime-chunk-plugin", "source-map-dev-tool-plugin", "split-chunks-plugin", - "internal-plugins" + "internal-plugins", + "dll-plugin", + "dll-reference-plugin" ] diff --git a/website/docs/zh/plugins/webpack/dll-plugin.mdx b/website/docs/zh/plugins/webpack/dll-plugin.mdx new file mode 100644 index 00000000000..f47a1bf815f --- /dev/null +++ b/website/docs/zh/plugins/webpack/dll-plugin.mdx @@ -0,0 +1,98 @@ +import { Table } from '@builtIns'; +import WebpackLicense from '@components/WebpackLicense'; + + + +# DllPlugin + +Dll 插件用于在一个单独的 rspack 配置中生成一个 dll 库,DllReference 插件通过该插件生成的 manifest.json 能够将依赖映射到对应的位置上。 + +## 选项 + +- **类型:** + +```ts +type DllPluginOptions = { + context?: string; + entryOnly?: boolean; + format?: boolean; + name?: string; + path: string; + type?: string; +}; +``` + +
+ +## 示例 + +```js +new rspack.DllPlugin({ + path: path.resolve(__dirname, 'manifest.json'), + name: '[name]_dll_lib', +}); +``` + +插件将会生成 `manifest.json` 文件并将其输出到指定路径。 +manifest 文件包含了从 `require` 和 `import` 请求到模块id的映射。 + +`manifest.json` 将会被 [DllReferencePlugin](/plugins/webpack/dll-reference-plugin) 消费,通过"动态链接库"的方式将依赖链接起来。 + +此插件与 `output.library` 的选项相结合可以暴露出dll 函数。 diff --git a/website/docs/zh/plugins/webpack/dll-reference-plugin.mdx b/website/docs/zh/plugins/webpack/dll-reference-plugin.mdx new file mode 100644 index 00000000000..864a7fbcf39 --- /dev/null +++ b/website/docs/zh/plugins/webpack/dll-reference-plugin.mdx @@ -0,0 +1,165 @@ +import WebpackLicense from '@components/WebpackLicense'; + + + +# DllReferencePlugin + +`DllReferencePlugin` 将引用的模块链接到预构建的模块。 + +## 选项 + +- **类型:** + +```ts +type DllReferencePluginOptionsContent = { + /** + * Module info. + */ + [k: string]: { + /** + * Meta information about the module. + */ + buildMeta?: { + [k: string]: any; + }; + /** + * Information about the provided exports of the module. + */ + exports?: string[] | true; + /** + * Module ID. + */ + id?: string; + }; +}; + +type DllReferencePluginOptionsManifest = { + /** + * The mappings from request to module info. + */ + content: DllReferencePluginOptionsContent; + /** + * The name where the dll is exposed (external name). + */ + name?: string; + /** + * The type how the dll is exposed (external type). + */ + type?: DllReferencePluginOptionsSourceType; +}; + +/** + * The type how the dll is exposed (external type). + */ +type DllReferencePluginOptionsSourceType = + | 'var' + | 'assign' + | 'this' + | 'window' + | 'global' + | 'commonjs' + | 'commonjs2' + | 'commonjs-module' + | 'amd' + | 'amd-require' + | 'umd' + | 'umd2' + | 'jsonp' + | 'system'; + +type DllReferencePluginOptions = + | { + /** + * Context of requests in the manifest (or content property) as absolute path. + */ + context?: string; + /** + * Extensions used to resolve modules in the dll bundle (only used when using 'scope'). + */ + extensions?: string[]; + /** + * An object containing content and name or a string to the absolute path of the JSON manifest to be loaded upon compilation. + */ + manifest: string | DllReferencePluginOptionsManifest; + /** + * The name where the dll is exposed (external name, defaults to manifest.name). + */ + name?: string; + /** + * Prefix which is used for accessing the content of the dll. + */ + scope?: string; + /** + * How the dll is exposed (libraryTarget, defaults to manifest.type). + */ + sourceType?: DllReferencePluginOptionsSourceType; + /** + * The way how the export of the dll bundle is used. + */ + type?: 'require' | 'object'; + } + | { + /** + * The mappings from request to module info. + */ + content: DllReferencePluginOptionsContent; + /** + * Context of requests in the manifest (or content property) as absolute path. + */ + context?: string; + /** + * Extensions used to resolve modules in the dll bundle (only used when using 'scope'). + */ + extensions?: string[]; + /** + * The name where the dll is exposed (external name). + */ + name: string; + /** + * Prefix which is used for accessing the content of the dll. + */ + scope?: string; + /** + * How the dll is exposed (libraryTarget). + */ + sourceType?: DllReferencePluginOptionsSourceType; + /** + * The way how the export of the dll bundle is used. + */ + type?: 'require' | 'object'; + }; +``` + +当依赖被引用的时候,插件通过 dll manifest 文件将依赖名和预打包的模块链接起来。 + +## 示例 + +### 基础示例 + +```js +new rspack.DllReferencePlugin({ + // 这里引用的 manifest 文件应该是由 Dll 插件生成的 + manifest: require('../lib/manifest.json'), + + name: '[name]_dll_lib', +}); +``` + +应用需要的依赖项将链接到由 `DllPlugin` 预构建的模块。 + +### 使用 Scope + +通过配置 `scope`, dll 中的内容可以使用模块前缀的方式引用 + +```js +new rspack.DllReferencePlugin({ + // 这里引用的 manifest 文件应该是由 Dll 插件生成的 + manifest: require('../lib/manifest.json'), + + name: '[name]_dll_lib', + + scope: 'xyz', +}); +``` + +举例来说,通过 `require('xzy/abc')` 可以获取预构建模块中 `abc` 模块。