diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 6b502e5b5e8b..781e2af94943 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -1143,6 +1143,7 @@ export interface RawAssetGeneratorOptions { outputPath?: JsFilename publicPath?: "auto" | JsFilename dataUrl?: RawAssetGeneratorDataUrlOptions | ((source: Buffer, context: RawAssetGeneratorDataUrlFnCtx) => string) + importMode?: "url" | "preserve" } export interface RawAssetInlineGeneratorOptions { @@ -1167,6 +1168,7 @@ export interface RawAssetResourceGeneratorOptions { filename?: JsFilename outputPath?: JsFilename publicPath?: "auto" | JsFilename + importMode?: "url" | "preserve" } export interface RawBannerPluginOptions { diff --git a/crates/rspack_binding_values/src/raw_options/raw_module/mod.rs b/crates/rspack_binding_values/src/raw_options/raw_module/mod.rs index 66f891feefa2..a93ff4d517dd 100644 --- a/crates/rspack_binding_values/src/raw_options/raw_module/mod.rs +++ b/crates/rspack_binding_values/src/raw_options/raw_module/mod.rs @@ -541,6 +541,9 @@ pub struct RawAssetGeneratorOptions { ts_type = "RawAssetGeneratorDataUrlOptions | ((source: Buffer, context: RawAssetGeneratorDataUrlFnCtx) => string)" )] pub data_url: Option, + + #[napi(ts_type = r#""url" | "preserve""#)] + pub import_mode: Option, } impl From for AssetGeneratorOptions { @@ -553,6 +556,7 @@ impl From for AssetGeneratorOptions { data_url: value .data_url .map(|i| RawAssetGeneratorDataUrlWrapper(i).into()), + import_mode: value.import_mode.map(|n| n.into()), } } } @@ -585,6 +589,8 @@ pub struct RawAssetResourceGeneratorOptions { pub output_path: Option, #[napi(ts_type = "\"auto\" | JsFilename")] pub public_path: Option, + #[napi(ts_type = r#""url" | "preserve""#)] + pub import_mode: Option, } impl From for AssetResourceGeneratorOptions { @@ -594,6 +600,7 @@ impl From for AssetResourceGeneratorOptions { filename: value.filename.map(|i| i.into()), output_path: value.output_path.map(|i| i.into()), public_path: value.public_path.map(|i| i.into()), + import_mode: value.import_mode.map(|i| i.into()), } } } diff --git a/crates/rspack_core/src/artifacts/code_generation_results.rs b/crates/rspack_core/src/artifacts/code_generation_results.rs index 68ef01bd5725..41e435587e02 100644 --- a/crates/rspack_core/src/artifacts/code_generation_results.rs +++ b/crates/rspack_core/src/artifacts/code_generation_results.rs @@ -31,6 +31,10 @@ impl CodeGenerationDataUrl { } } +// For performance, mark the js modules containing AUTO_PUBLIC_PATH_PLACEHOLDER +#[derive(Clone, Debug)] +pub struct CodeGenerationPublicPathAutoReplace(pub bool); + #[derive(Clone, Debug)] pub struct CodeGenerationDataFilename { filename: String, diff --git a/crates/rspack_core/src/concatenated_module.rs b/crates/rspack_core/src/concatenated_module.rs index 9764506fbe94..322f79717e34 100644 --- a/crates/rspack_core/src/concatenated_module.rs +++ b/crates/rspack_core/src/concatenated_module.rs @@ -45,13 +45,14 @@ use crate::{ subtract_runtime_condition, to_identifier, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, BuildMeta, BuildMetaDefaultObject, BuildMetaExportsType, BuildResult, ChunkGraph, ChunkInitFragments, CodeGenerationDataTopLevelDeclarations, - CodeGenerationExportsFinalNames, CodeGenerationResult, Compilation, ConcatenatedModuleIdent, - ConcatenationScope, ConnectionState, Context, DependenciesBlock, DependencyId, - DependencyTemplate, DependencyType, ErrorSpan, ExportInfoProvided, ExportsArgument, ExportsType, - FactoryMeta, IdentCollector, LibIdentOptions, MaybeDynamicTargetExportInfoHashKey, Module, - ModuleDependency, ModuleGraph, ModuleGraphConnection, ModuleIdentifier, ModuleLayer, ModuleType, - Resolve, RuntimeCondition, RuntimeGlobals, RuntimeSpec, SourceType, SpanExt, Template, - UsageState, UsedName, DEFAULT_EXPORT, NAMESPACE_OBJECT_EXPORT, + CodeGenerationExportsFinalNames, CodeGenerationPublicPathAutoReplace, CodeGenerationResult, + Compilation, ConcatenatedModuleIdent, ConcatenationScope, ConnectionState, Context, + DependenciesBlock, DependencyId, DependencyTemplate, DependencyType, ErrorSpan, + ExportInfoProvided, ExportsArgument, ExportsType, FactoryMeta, IdentCollector, LibIdentOptions, + MaybeDynamicTargetExportInfoHashKey, Module, ModuleDependency, ModuleGraph, + ModuleGraphConnection, ModuleIdentifier, ModuleLayer, ModuleType, Resolve, RuntimeCondition, + RuntimeGlobals, RuntimeSpec, SourceType, SpanExt, Template, UsageState, UsedName, DEFAULT_EXPORT, + NAMESPACE_OBJECT_EXPORT, }; type ExportsDefinitionArgs = Vec<(String, String)>; @@ -193,6 +194,8 @@ pub struct ConcatenatedModuleInfo { pub global_scope_ident: Vec, pub idents: Vec, pub binding_to_ref: HashMap<(Atom, SyntaxContext), Vec>, + + pub public_path_auto_replace: Option, } #[derive(Debug, Clone)] @@ -672,6 +675,7 @@ impl Module for ConcatenatedModule { let mut all_used_names: HashSet = RESERVED_NAMES.iter().map(|s| Atom::new(*s)).collect(); let mut top_level_declarations: HashSet = HashSet::default(); + let mut public_path_auto_replace: bool = false; for module_info_id in modules_with_info.iter() { let Some(ModuleInfo::Concatenated(info)) = module_to_info_map.get_mut(module_info_id) else { @@ -800,6 +804,11 @@ impl Module for ConcatenatedModule { info.namespace_object_name = Some(namespace_object_name.clone()); top_level_declarations.insert(namespace_object_name); } + + // Handle publicPathAutoReplace for perf + if let Some(info_auto) = info.public_path_auto_replace { + public_path_auto_replace = public_path_auto_replace || info_auto; + } } // Handle external type @@ -1288,6 +1297,13 @@ impl Module for ConcatenatedModule { code_generation_result.add(SourceType::JavaScript, CachedSource::new(result).boxed()); code_generation_result.chunk_init_fragments = chunk_init_fragments; code_generation_result.runtime_requirements = runtime_requirements; + + if public_path_auto_replace { + code_generation_result + .data + .insert(CodeGenerationPublicPathAutoReplace(true)); + } + code_generation_result .data .insert(CodeGenerationDataTopLevelDeclarations::new( @@ -1701,6 +1717,7 @@ impl ConcatenatedModule { .module_by_identifier(&module_id) .unwrap_or_else(|| panic!("should have module {module_id}")); let codegen_res = module.code_generation(compilation, runtime, Some(concatenation_scope))?; + let CodeGenerationResult { mut inner, mut chunk_init_fragments, @@ -1779,6 +1796,12 @@ impl ConcatenatedModule { module_info.internal_source = Some(source); module_info.source = Some(result_source); module_info.chunk_init_fragments = chunk_init_fragments; + if let Some(CodeGenerationPublicPathAutoReplace(true)) = codegen_res + .data + .get::( + ) { + module_info.public_path_auto_replace = Some(true); + } Ok(ModuleInfo::Concatenated(Box::new(module_info))) } else { Ok(info) diff --git a/crates/rspack_core/src/options/module.rs b/crates/rspack_core/src/options/module.rs index 937cf6523f6a..b2ab20c650f9 100644 --- a/crates/rspack_core/src/options/module.rs +++ b/crates/rspack_core/src/options/module.rs @@ -472,6 +472,45 @@ impl From for AssetInlineGeneratorOptions { } } +#[cacheable] +#[derive(Debug, Clone, Copy, MergeFrom)] +struct AssetGeneratorImportModeFlags(u8); +bitflags! { + impl AssetGeneratorImportModeFlags: u8 { + const URL = 1 << 0; + const PRESERVE = 1 << 1; + } +} + +#[cacheable] +#[derive(Debug, Clone, Copy, MergeFrom)] +pub struct AssetGeneratorImportMode(AssetGeneratorImportModeFlags); + +impl AssetGeneratorImportMode { + pub fn is_url(&self) -> bool { + self.0.contains(AssetGeneratorImportModeFlags::URL) + } + pub fn is_preserve(&self) -> bool { + self.0.contains(AssetGeneratorImportModeFlags::PRESERVE) + } +} + +impl From for AssetGeneratorImportMode { + fn from(s: String) -> Self { + match s.as_str() { + "url" => Self(AssetGeneratorImportModeFlags::URL), + "preserve" => Self(AssetGeneratorImportModeFlags::PRESERVE), + _ => unreachable!("AssetGeneratorImportMode error"), + } + } +} + +impl Default for AssetGeneratorImportMode { + fn default() -> Self { + Self(AssetGeneratorImportModeFlags::URL) + } +} + #[cacheable] #[derive(Debug, Clone, MergeFrom)] pub struct AssetResourceGeneratorOptions { @@ -479,6 +518,7 @@ pub struct AssetResourceGeneratorOptions { pub filename: Option, pub output_path: Option, pub public_path: Option, + pub import_mode: Option, } impl From for AssetResourceGeneratorOptions { @@ -488,6 +528,7 @@ impl From for AssetResourceGeneratorOptions { filename: value.filename, output_path: value.output_path, public_path: value.public_path, + import_mode: value.import_mode, } } } @@ -500,6 +541,7 @@ pub struct AssetGeneratorOptions { pub output_path: Option, pub public_path: Option, pub data_url: Option, + pub import_mode: Option, } pub struct AssetGeneratorDataUrlFnCtx<'a> { diff --git a/crates/rspack_plugin_asset/Cargo.toml b/crates/rspack_plugin_asset/Cargo.toml index a4a641a01cdc..6b4607a10104 100644 --- a/crates/rspack_plugin_asset/Cargo.toml +++ b/crates/rspack_plugin_asset/Cargo.toml @@ -8,19 +8,19 @@ version = "0.2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait = { workspace = true } -mime_guess = { workspace = true } -rayon = { workspace = true } -rspack_base64 = { workspace = true } +async-trait = { workspace = true } +mime_guess = { workspace = true } +rayon = { workspace = true } +rspack_base64 = { workspace = true } rspack_cacheable = { workspace = true } -rspack_core = { workspace = true } -rspack_error = { workspace = true } -rspack_hash = { workspace = true } -rspack_hook = { workspace = true } -rspack_util = { workspace = true } -serde_json = { workspace = true } -tracing = { workspace = true } -urlencoding = { workspace = true } +rspack_core = { workspace = true } +rspack_error = { workspace = true } +rspack_hash = { workspace = true } +rspack_hook = { workspace = true } +rspack_util = { workspace = true } +serde_json = { workspace = true } +tracing = { workspace = true } +urlencoding = { workspace = true } [package.metadata.cargo-shear] ignored = ["tracing"] diff --git a/crates/rspack_plugin_asset/src/asset_exports_dependency.rs b/crates/rspack_plugin_asset/src/asset_exports_dependency.rs new file mode 100644 index 000000000000..432175bbebd3 --- /dev/null +++ b/crates/rspack_plugin_asset/src/asset_exports_dependency.rs @@ -0,0 +1,65 @@ +use rspack_cacheable::{cacheable, cacheable_dyn}; +use rspack_core::{ + AsContextDependency, AsModuleDependency, Compilation, Dependency, DependencyId, + DependencyTemplate, ExportNameOrSpec, ExportsOfExportsSpec, ExportsSpec, ModuleGraph, + RuntimeSpec, TemplateContext, TemplateReplaceSource, +}; + +#[cacheable] +#[derive(Debug, Clone, Default)] +pub struct AssetExportsDependency { + id: DependencyId, +} + +impl AssetExportsDependency { + pub fn new() -> Self { + Self { + id: DependencyId::new(), + } + } +} + +#[cacheable_dyn] +impl Dependency for AssetExportsDependency { + fn id(&self) -> &rspack_core::DependencyId { + &self.id + } + + fn get_exports(&self, _mg: &ModuleGraph) -> Option { + Some(ExportsSpec { + exports: ExportsOfExportsSpec::Array(vec![ExportNameOrSpec::String("default".into())]), + priority: Some(1), + terminal_binding: Some(true), + ..Default::default() + }) + } + + fn could_affect_referencing_module(&self) -> rspack_core::AffectType { + rspack_core::AffectType::False + } +} + +impl AsModuleDependency for AssetExportsDependency {} +impl AsContextDependency for AssetExportsDependency {} + +#[cacheable_dyn] +impl DependencyTemplate for AssetExportsDependency { + fn apply( + &self, + _source: &mut TemplateReplaceSource, + _code_generatable_context: &mut TemplateContext, + ) { + } + + fn dependency_id(&self) -> Option { + Some(self.id) + } + + fn update_hash( + &self, + _hasher: &mut dyn std::hash::Hasher, + _compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) { + } +} diff --git a/crates/rspack_plugin_asset/src/lib.rs b/crates/rspack_plugin_asset/src/lib.rs index 493b6310e201..27d738696780 100644 --- a/crates/rspack_plugin_asset/src/lib.rs +++ b/crates/rspack_plugin_asset/src/lib.rs @@ -2,24 +2,29 @@ use std::{borrow::Cow, hash::Hasher, path::PathBuf}; +use asset_exports_dependency::AssetExportsDependency; use async_trait::async_trait; use rayon::prelude::*; use rspack_cacheable::{cacheable, cacheable_dyn}; use rspack_core::{ rspack_sources::{BoxSource, RawBufferSource, RawStringSource, SourceExt}, - AssetGeneratorDataUrl, AssetGeneratorDataUrlFnCtx, AssetInfo, AssetParserDataUrl, - BuildMetaDefaultObject, BuildMetaExportsType, ChunkGraph, ChunkUkey, CodeGenerationDataAssetInfo, - CodeGenerationDataFilename, CodeGenerationDataUrl, Compilation, CompilationRenderManifest, - CompilerOptions, Filename, GenerateContext, GeneratorOptions, LocalFilenameFn, Module, - ModuleGraph, NormalModule, ParseContext, ParserAndGenerator, PathData, Plugin, PublicPath, - RenderManifestEntry, ResourceData, RuntimeGlobals, RuntimeSpec, SourceType, - NAMESPACE_OBJECT_EXPORT, + AssetGeneratorDataUrl, AssetGeneratorDataUrlFnCtx, AssetGeneratorImportMode, AssetInfo, + AssetParserDataUrl, BuildMetaDefaultObject, BuildMetaExportsType, ChunkGraph, ChunkUkey, + CodeGenerationDataAssetInfo, CodeGenerationDataFilename, CodeGenerationDataUrl, + CodeGenerationPublicPathAutoReplace, Compilation, CompilationRenderManifest, CompilerOptions, + Filename, GenerateContext, GeneratorOptions, LocalFilenameFn, Module, ModuleGraph, NormalModule, + ParseContext, ParserAndGenerator, PathData, Plugin, PublicPath, RenderManifestEntry, + ResourceData, RuntimeGlobals, RuntimeSpec, SourceType, NAMESPACE_OBJECT_EXPORT, }; use rspack_error::{error, Diagnostic, IntoTWithDiagnosticArray, Result}; use rspack_hash::{RspackHash, RspackHashDigest}; use rspack_hook::{plugin, plugin_hook}; use rspack_util::{ext::DynHash, identifier::make_paths_relative}; +mod asset_exports_dependency; + +pub const AUTO_PUBLIC_PATH_PLACEHOLDER: &str = "__RSPACK_PLUGIN_ASSET_AUTO_PUBLIC_PATH__"; + #[plugin] #[derive(Debug, Default)] pub struct AssetPlugin; @@ -299,6 +304,23 @@ impl AssetParserAndGenerator { let public_path = PublicPath::ensure_ends_with_slash(public_path); Ok((public_path, info)) } + + fn get_import_mode( + &self, + module_generator_options: Option<&GeneratorOptions>, + ) -> Result { + let import_mode = module_generator_options + .and_then(|x| x.get_asset()) + .and_then(|x| x.import_mode) + .or_else(|| { + module_generator_options + .and_then(|x| x.get_asset_resource()) + .and_then(|x| x.import_mode) + }) + .unwrap_or_default(); + + Ok(import_mode) + } } // Webpack's default parser.dataUrlCondition.maxSize @@ -401,8 +423,10 @@ impl ParserAndGenerator for AssetParserAndGenerator { Ok( rspack_core::ParseResult { - // Assets do not have dependencies - dependencies: vec![], + // different from webpack + // Rspack: when set asset as entry, output a js chunk with default export + // webpack: Assets do not have dependencies + dependencies: vec![Box::new(AssetExportsDependency::new())], blocks: vec![], source, presentational_dependencies: vec![], @@ -429,12 +453,13 @@ impl ParserAndGenerator for AssetParserAndGenerator { .expect("module should be a NormalModule in AssetParserAndGenerator"); let module_generator_options = normal_module.get_generator_options(); + let import_mode = self.get_import_mode(module_generator_options)?; + let result = match generate_context.requested_source_type { SourceType::JavaScript => { let exported_content = if parsed_asset_config.is_inline() { let resource_data: &ResourceData = normal_module.resource_resolved_data(); let data_url = module_generator_options.and_then(|x| x.asset_data_url()); - let encoded_source: String; if let Some(custom_data_url) = @@ -474,7 +499,15 @@ impl ParserAndGenerator for AssetParserAndGenerator { true, )?; - let asset_path = if let Some(public_path) = + let asset_path = if import_mode.is_preserve() { + generate_context + .data + .insert(CodeGenerationPublicPathAutoReplace(true)); + serde_json::to_string(&format!( + "{AUTO_PUBLIC_PATH_PLACEHOLDER}{original_filename}" + )) + .map_err(|e| error!(e.to_string()))? + } else if let Some(public_path) = module_generator_options.and_then(|x| x.asset_public_path()) { let public_path = match public_path { @@ -503,6 +536,7 @@ impl ParserAndGenerator for AssetParserAndGenerator { original_filename ) }; + asset_info.set_source_filename(source_file_name); generate_context @@ -524,6 +558,39 @@ impl ParserAndGenerator for AssetParserAndGenerator { } else { unreachable!() }; + + if import_mode.is_preserve() && parsed_asset_config.is_resource() { + let is_module = compilation.options.output.module; + if let Some(ref mut scope) = generate_context.concatenation_scope { + scope.register_namespace_export(NAMESPACE_OBJECT_EXPORT); + if is_module { + return Ok( + RawStringSource::from(format!( + r#"import {NAMESPACE_OBJECT_EXPORT} from {exported_content};"# + )) + .boxed(), + ); + } else { + let supports_const = compilation.options.output.environment.supports_const(); + let declaration_kind = if supports_const { "const" } else { "var" }; + return Ok( + RawStringSource::from(format!( + r#"{declaration_kind} {NAMESPACE_OBJECT_EXPORT} = require({exported_content});"# + )) + .boxed(), + ); + } + } else { + generate_context + .runtime_requirements + .insert(RuntimeGlobals::MODULE); + return Ok( + RawStringSource::from(format!(r#"module.exports = require({exported_content});"#)) + .boxed(), + ); + } + }; + if let Some(ref mut scope) = generate_context.concatenation_scope { scope.register_namespace_export(NAMESPACE_OBJECT_EXPORT); let supports_const = compilation.options.output.environment.supports_const(); diff --git a/crates/rspack_plugin_javascript/src/plugin/impl_plugin_for_js_plugin.rs b/crates/rspack_plugin_javascript/src/plugin/impl_plugin_for_js_plugin.rs index 024175cb578b..042b39b68d5c 100644 --- a/crates/rspack_plugin_javascript/src/plugin/impl_plugin_for_js_plugin.rs +++ b/crates/rspack_plugin_javascript/src/plugin/impl_plugin_for_js_plugin.rs @@ -251,21 +251,6 @@ async fn render_manifest( { return Ok(()); } - let (source, _) = compilation - .old_cache - .chunk_render_occasion - .use_cache(compilation, chunk, &SourceType::JavaScript, || async { - let source = if is_hot_update { - self.render_chunk(compilation, chunk_ukey).await? - } else if is_main_chunk { - self.render_main(compilation, chunk_ukey).await? - } else { - self.render_chunk(compilation, chunk_ukey).await? - }; - Ok((CachedSource::new(source).boxed(), Vec::new())) - }) - .await?; - let filename_template = get_js_chunk_filename_template( chunk, &compilation.options.output, @@ -295,6 +280,28 @@ async fn render_manifest( &mut asset_info, )?; asset_info.set_javascript_module(compilation.options.output.module); + + let (source, _) = compilation + .old_cache + .chunk_render_occasion + .use_cache(compilation, chunk, &SourceType::JavaScript, || async { + let source = if is_hot_update { + self + .render_chunk(compilation, chunk_ukey, &output_path) + .await? + } else if is_main_chunk { + self + .render_main(compilation, chunk_ukey, &output_path) + .await? + } else { + self + .render_chunk(compilation, chunk_ukey, &output_path) + .await? + }; + Ok((CachedSource::new(source).boxed(), Vec::new())) + }) + .await?; + manifest.push(RenderManifestEntry { source, filename: output_path, diff --git a/crates/rspack_plugin_javascript/src/plugin/mod.rs b/crates/rspack_plugin_javascript/src/plugin/mod.rs index abaecb6503a6..871ee294837a 100644 --- a/crates/rspack_plugin_javascript/src/plugin/mod.rs +++ b/crates/rspack_plugin_javascript/src/plugin/mod.rs @@ -514,6 +514,7 @@ impl JsPlugin { &self, compilation: &Compilation, chunk_ukey: &ChunkUkey, + output_path: &str, ) -> Result { let hooks = Self::get_compilation_hooks(compilation); let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey); @@ -583,8 +584,13 @@ impl JsPlugin { all_modules.clone() }; - let chunk_modules_result = - render_chunk_modules(compilation, chunk_ukey, &chunk_modules, all_strict)?; + let chunk_modules_result = render_chunk_modules( + compilation, + chunk_ukey, + &chunk_modules, + all_strict, + output_path, + )?; let has_chunk_modules_result = chunk_modules_result.is_some(); if has_chunk_modules_result || runtime_requirements.contains(RuntimeGlobals::MODULE_FACTORIES) @@ -645,6 +651,7 @@ impl JsPlugin { chunk_ukey, all_strict, has_chunk_modules_result, + output_path, )? } else { None @@ -655,7 +662,7 @@ impl JsPlugin { .module_by_identifier(m_identifier) .expect("should have module"); let Some((mut rendered_module, fragments, additional_fragments)) = - render_module(compilation, chunk_ukey, m, all_strict, false)? + render_module(compilation, chunk_ukey, m, all_strict, false, output_path)? else { continue; }; @@ -793,6 +800,7 @@ impl JsPlugin { }) } + #[allow(clippy::too_many_arguments)] pub fn get_renamed_inline_module( &self, all_modules: &[&BoxModule], @@ -801,6 +809,7 @@ impl JsPlugin { chunk_ukey: &ChunkUkey, all_strict: bool, has_chunk_modules_result: bool, + output_path: &str, ) -> Result>>> { let inner_strict = !all_strict && all_modules.iter().all(|m| { @@ -841,7 +850,7 @@ impl JsPlugin { if let Ok(acc) = acc.as_mut() { if let Some((rendered_module, ..)) = - render_module(compilation, chunk_ukey, m, all_strict, false)? + render_module(compilation, chunk_ukey, m, all_strict, false, output_path)? { let code = rendered_module; let mut use_cache = false; @@ -1111,6 +1120,7 @@ impl JsPlugin { &self, compilation: &Compilation, chunk_ukey: &ChunkUkey, + output_path: &str, ) -> Result { let hooks = Self::get_compilation_hooks(compilation); let module_graph = &compilation.get_module_graph(); @@ -1139,9 +1149,14 @@ impl JsPlugin { all_strict = true; } } - let (chunk_modules_source, chunk_init_fragments) = - render_chunk_modules(compilation, chunk_ukey, &chunk_modules, all_strict)? - .unwrap_or_else(|| (RawStringSource::from_static("{}").boxed(), Vec::new())); + let (chunk_modules_source, chunk_init_fragments) = render_chunk_modules( + compilation, + chunk_ukey, + &chunk_modules, + all_strict, + output_path, + )? + .unwrap_or_else(|| (RawStringSource::from_static("{}").boxed(), Vec::new())); let mut render_source = RenderSource { source: chunk_modules_source, }; 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 ceac4894f526..ff6cb6c8d42b 100644 --- a/crates/rspack_plugin_javascript/src/plugin/module_concatenation_plugin.rs +++ b/crates/rspack_plugin_javascript/src/plugin/module_concatenation_plugin.rs @@ -547,7 +547,6 @@ impl ModuleConcatenationPlugin { return Ok(()); } - // let modules_set = config.get_modules(); for m in modules_set { used_modules.insert(*m); @@ -555,6 +554,9 @@ impl ModuleConcatenationPlugin { let box_module = module_graph .module_by_identifier(&root_module_id) .expect("should have module"); + let root_module_source_types = box_module.source_types(); + let is_root_module_asset_module = root_module_source_types.contains(&SourceType::Asset); + let root_module_ctxt = RootModuleContext { id: root_module_id, readable_identifier: box_module @@ -673,12 +675,32 @@ impl ModuleConcatenationPlugin { } } } - // module_graph - // .module_identifier_to_module - // .remove(&root_module_id); - // compilation.chunk_graph.clear - chunk_graph.replace_module(&root_module_id, &new_module.id()); + // different from webpack + // Rspack: if entry is an asset module, outputs a js chunk and a asset chunk + // Webpack: if entry is an asset module, outputs an asset chunk + // these lines of codes fix a bug: when asset module (NormalModule) is concatenated into ConcatenatedModule, the asset will be lost + // because `chunk_graph.replace_module(&root_module_id, &new_module.id());` will remove the asset module from chunk, and I add this module back to fix this bug + if is_root_module_asset_module { + chunk_graph.replace_module(&root_module_id, &new_module.id()); + chunk_graph.add_module(root_module_id); + for chunk_ukey in chunk_graph.get_module_chunks(new_module.id()).clone() { + let module = module_graph + .module_by_identifier(&root_module_id) + .expect("should exist module"); + + let source_types = chunk_graph.get_chunk_module_source_types(&chunk_ukey, module); + let new_source_types = source_types + .iter() + .filter(|source_type| !matches!(source_type, SourceType::JavaScript)) + .copied() + .collect(); + chunk_graph.set_chunk_modules_source_types(&chunk_ukey, root_module_id, new_source_types); + chunk_graph.connect_chunk_and_module(chunk_ukey, root_module_id); + } + } else { + chunk_graph.replace_module(&root_module_id, &new_module.id()); + } module_graph.move_module_connections(&root_module_id, &new_module.id(), |c, dep| { let other_module = if *c.module_identifier() == root_module_id { diff --git a/crates/rspack_plugin_javascript/src/runtime.rs b/crates/rspack_plugin_javascript/src/runtime.rs index c6ff43c84db7..345a1d6d1e85 100644 --- a/crates/rspack_plugin_javascript/src/runtime.rs +++ b/crates/rspack_plugin_javascript/src/runtime.rs @@ -1,9 +1,11 @@ use rayon::prelude::*; use rspack_core::chunk_graph_chunk::ChunkId; -use rspack_core::rspack_sources::{BoxSource, ConcatSource, RawStringSource, SourceExt}; +use rspack_core::rspack_sources::{ + BoxSource, ConcatSource, RawStringSource, ReplaceSource, Source, SourceExt, +}; use rspack_core::{ - to_normal_comment, BoxModule, ChunkGraph, ChunkInitFragments, ChunkUkey, Compilation, - RuntimeGlobals, SourceType, + to_normal_comment, BoxModule, ChunkGraph, ChunkInitFragments, ChunkUkey, + CodeGenerationPublicPathAutoReplace, Compilation, PublicPath, RuntimeGlobals, SourceType, }; use rspack_error::{error, Result}; use rspack_util::diff_mode::is_diff_mode; @@ -11,18 +13,28 @@ use rustc_hash::FxHashSet as HashSet; use crate::{JsPlugin, RenderSource}; +pub const AUTO_PUBLIC_PATH_PLACEHOLDER: &str = "__RSPACK_PLUGIN_ASSET_AUTO_PUBLIC_PATH__"; + pub fn render_chunk_modules( compilation: &Compilation, chunk_ukey: &ChunkUkey, ordered_modules: &Vec<&BoxModule>, all_strict: bool, + output_path: &str, ) -> Result> { let mut module_code_array = ordered_modules .par_iter() .filter_map(|module| { - render_module(compilation, chunk_ukey, module, all_strict, true) - .transpose() - .map(|result| result.map(|(s, f, a)| (module.identifier(), s, f, a))) + render_module( + compilation, + chunk_ukey, + module, + all_strict, + true, + output_path, + ) + .transpose() + .map(|result| result.map(|(s, f, a)| (module.identifier(), s, f, a))) }) .collect::>>()?; @@ -67,6 +79,7 @@ pub fn render_module( module: &BoxModule, all_strict: bool, factory: bool, + output_path: &str, ) -> Result> { let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey); let code_gen_result = compilation @@ -75,15 +88,47 @@ pub fn render_module( let Some(origin_source) = code_gen_result.get(&SourceType::JavaScript) else { return Ok(None); }; + let hooks = JsPlugin::get_compilation_hooks(compilation); let mut module_chunk_init_fragments = match code_gen_result.data.get::() { Some(fragments) => fragments.clone(), None => ChunkInitFragments::default(), }; - let mut render_source = RenderSource { - source: origin_source.clone(), + let mut render_source = if code_gen_result + .data + .get::() + .is_some() + { + let content = origin_source.source(); + let len = AUTO_PUBLIC_PATH_PLACEHOLDER.len(); + let auto_public_path_matches: Vec<_> = content + .match_indices(AUTO_PUBLIC_PATH_PLACEHOLDER) + .map(|(index, _)| (index, index + len)) + .collect(); + if !auto_public_path_matches.is_empty() { + let mut replace = ReplaceSource::new(origin_source.clone()); + for (start, end) in auto_public_path_matches { + let mut relative = PublicPath::render_auto_public_path(compilation, output_path); + if relative.is_empty() { + relative = String::from("./"); + } + replace.replace(start as u32, end as u32, &relative, None); + } + RenderSource { + source: replace.boxed(), + } + } else { + RenderSource { + source: origin_source.clone(), + } + } + } else { + RenderSource { + source: origin_source.clone(), + } }; + hooks.render_module_content.call( compilation, module, diff --git a/crates/rspack_plugin_library/src/modern_module_library_plugin.rs b/crates/rspack_plugin_library/src/modern_module_library_plugin.rs index 5f3aaa00bcec..f539922abd43 100644 --- a/crates/rspack_plugin_library/src/modern_module_library_plugin.rs +++ b/crates/rspack_plugin_library/src/modern_module_library_plugin.rs @@ -7,7 +7,7 @@ use rspack_core::{ CodeGenerationExportsFinalNames, Compilation, CompilationFinishModules, CompilationOptimizeChunkModules, CompilationParams, CompilerCompilation, CompilerOptions, ConcatenatedModule, ConcatenatedModuleExportsDefinitions, DependenciesBlock, Dependency, - DependencyId, LibraryOptions, ModuleGraph, ModuleIdentifier, Plugin, PluginContext, SourceType, + DependencyId, LibraryOptions, ModuleGraph, ModuleIdentifier, Plugin, PluginContext, }; use rspack_error::{error_bail, Result}; use rspack_hash::RspackHash; @@ -113,17 +113,6 @@ impl ModernModuleLibraryPlugin { .collect::>(); for module_id in unconcatenated_module_ids.into_iter() { - // skip the asset module when the entry is asset module - let module_graph = compilation.get_module_graph(); - let Some(module) = module_graph.module_by_identifier(module_id) else { - continue; - }; - let source_types = module.source_types(); - let is_asset_module = source_types.contains(&SourceType::Asset); - if is_asset_module { - continue; - } - let chunk_runtime = compilation .chunk_graph .get_module_runtimes(*module_id, &compilation.chunk_by_ukey) diff --git a/packages/rspack-test-tools/tests/__snapshots__/Config.test.js.snap b/packages/rspack-test-tools/tests/__snapshots__/Config.test.js.snap index 8015f1cfc80f..980c9cc99a06 100644 --- a/packages/rspack-test-tools/tests/__snapshots__/Config.test.js.snap +++ b/packages/rspack-test-tools/tests/__snapshots__/Config.test.js.snap @@ -502,14 +502,6 @@ export { a }; `; exports[`config config/library/modern-module-force-concaten step should pass: asset as entry should not be concatenated 1`] = ` -var __webpack_modules__ = ({ -"273": (function (module, __unused_webpack_exports, __webpack_require__) { -module.exports = __webpack_require__.p + "fc90103f80b6abc6.png"; - -}), - -}); -/************************************************************************/ // The module cache var __webpack_module_cache__ = {}; @@ -540,10 +532,10 @@ __webpack_require__.p = ""; })(); /************************************************************************/ -// startup -// Load entry module and return exports -// This entry module doesn't tell about it's top-level declarations so it can't be inlined -var __webpack_exports__ = __webpack_require__("273"); + +;// CONCATENATED MODULE: ./h/file.png +const file_namespaceObject = __webpack_require__.p + "fc90103f80b6abc6.png"; +export { file_namespaceObject as default }; `; exports[`config config/library/modern-module-force-concaten step should pass: external module should bail out when bundling 1`] = ` diff --git a/packages/rspack-test-tools/tests/__snapshots__/NewCodeSplitting-config.test.js.snap b/packages/rspack-test-tools/tests/__snapshots__/NewCodeSplitting-config.test.js.snap index 0928bbe5f01b..47fc0023b736 100644 --- a/packages/rspack-test-tools/tests/__snapshots__/NewCodeSplitting-config.test.js.snap +++ b/packages/rspack-test-tools/tests/__snapshots__/NewCodeSplitting-config.test.js.snap @@ -502,14 +502,6 @@ export { a }; `; exports[`new-code-splitting config cases new-code-splitting config cases/library/modern-module-force-concaten step should pass: asset as entry should not be concatenated 1`] = ` -var __webpack_modules__ = ({ -"273": (function (module, __unused_webpack_exports, __webpack_require__) { -module.exports = __webpack_require__.p + "fc90103f80b6abc6.png"; - -}), - -}); -/************************************************************************/ // The module cache var __webpack_module_cache__ = {}; @@ -540,10 +532,10 @@ __webpack_require__.p = ""; })(); /************************************************************************/ -// startup -// Load entry module and return exports -// This entry module doesn't tell about it's top-level declarations so it can't be inlined -var __webpack_exports__ = __webpack_require__("273"); + +;// CONCATENATED MODULE: ./h/file.png +const file_namespaceObject = __webpack_require__.p + "fc90103f80b6abc6.png"; +export { file_namespaceObject as default }; `; exports[`new-code-splitting config cases new-code-splitting config cases/library/modern-module-force-concaten step should pass: external module should bail out when bundling 1`] = ` diff --git a/packages/rspack-test-tools/tests/__snapshots__/NewCodeSplitting-stats-output.test.js.snap b/packages/rspack-test-tools/tests/__snapshots__/NewCodeSplitting-stats-output.test.js.snap index b26354ba4a82..2ae462e99768 100644 --- a/packages/rspack-test-tools/tests/__snapshots__/NewCodeSplitting-stats-output.test.js.snap +++ b/packages/rspack-test-tools/tests/__snapshots__/NewCodeSplitting-stats-output.test.js.snap @@ -16,11 +16,11 @@ chunk {909} (runtime: main) bundle.js (main) 7 bytes (asset) 159 bytes (javascri | [no exports used] | ModuleConcatenation bailout: Module is an entry point | ./raw.png [193] 42 bytes (javascript) 7 bytes (asset) {909} [depth 1] [built] [code generated] - | [no exports] + | [exports: default] | [no exports used] | esm import ./raw.png ./index.js ./raw.png [193] 42 bytes (javascript) 7 bytes (asset) {909} [depth 1] [built] [code generated] - [no exports] + [exports: default] [no exports used] esm import ./raw.png ./index.js modules by path ./ 235 bytes (javascript) 7 bytes (asset) @@ -44,11 +44,11 @@ modules by path ./ 235 bytes (javascript) 7 bytes (asset) | [no exports used] | ModuleConcatenation bailout: Module is an entry point | ./raw.png [193] 42 bytes (javascript) 7 bytes (asset) {909} [depth 1] [built] [code generated] - | [no exports] + | [exports: default] | [no exports used] | esm import ./raw.png ./index.js ./raw.png [193] 42 bytes (javascript) 7 bytes (asset) {909} [depth 1] [built] [code generated] - [no exports] + [exports: default] [no exports used] esm import ./raw.png ./index.js runtime modules 1.61 KiB diff --git a/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap b/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap index f65566e8ea4f..15d11b1ecd4b 100644 --- a/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap +++ b/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap @@ -16,11 +16,11 @@ chunk {909} (runtime: main) bundle.js (main) 7 bytes (asset) 159 bytes (javascri | [no exports used] | ModuleConcatenation bailout: Module is an entry point | ./raw.png [193] 42 bytes (javascript) 7 bytes (asset) {909} [depth 1] [built] [code generated] - | [no exports] + | [exports: default] | [no exports used] | esm import ./raw.png ./index.js ./raw.png [193] 42 bytes (javascript) 7 bytes (asset) {909} [depth 1] [built] [code generated] - [no exports] + [exports: default] [no exports used] esm import ./raw.png ./index.js modules by path ./ 235 bytes (javascript) 7 bytes (asset) @@ -44,11 +44,11 @@ modules by path ./ 235 bytes (javascript) 7 bytes (asset) | [no exports used] | ModuleConcatenation bailout: Module is an entry point | ./raw.png [193] 42 bytes (javascript) 7 bytes (asset) {909} [depth 1] [built] [code generated] - | [no exports] + | [exports: default] | [no exports used] | esm import ./raw.png ./index.js ./raw.png [193] 42 bytes (javascript) 7 bytes (asset) {909} [depth 1] [built] [code generated] - [no exports] + [exports: default] [no exports used] esm import ./raw.png ./index.js runtime modules 1.61 KiB diff --git a/packages/rspack-test-tools/tests/configCases/asset/preserve-import/img.png b/packages/rspack-test-tools/tests/configCases/asset/preserve-import/img.png new file mode 100644 index 000000000000..b74b839e2b86 Binary files /dev/null and b/packages/rspack-test-tools/tests/configCases/asset/preserve-import/img.png differ diff --git a/packages/rspack-test-tools/tests/configCases/asset/preserve-import/rspack.config.js b/packages/rspack-test-tools/tests/configCases/asset/preserve-import/rspack.config.js new file mode 100644 index 000000000000..42ad9b21730d --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/asset/preserve-import/rspack.config.js @@ -0,0 +1,34 @@ +const assert = require("assert"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + context: __dirname, + entry: { + index: './img.png' + }, + module: { + rules: [ + { + test: /\.png$/, + type: "asset/resource", + generator: { + importMode: "preserve" + } + } + ] + }, + plugins: [ + new (class { + apply(compiler) { + compiler.hooks.compilation.tap("MyPlugin", compilation => { + compilation.hooks.processAssets.tap("MyPlugin", assets => { + let list = Object.keys(assets); + const js = list.find(item => item.endsWith("js")); + const jsContent = assets[js].source().toString(); + assert(/require\(['"]\.\/(\w*)\.png['"]\)/.test(jsContent)) + }) + }); + } + })() + ] +}; diff --git a/packages/rspack-test-tools/tests/configCases/asset/preserve-import/test.config.js b/packages/rspack-test-tools/tests/configCases/asset/preserve-import/test.config.js new file mode 100644 index 000000000000..fd77cc4aa17c --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/asset/preserve-import/test.config.js @@ -0,0 +1 @@ +exports.noTest = true; diff --git a/packages/rspack-test-tools/tests/configCases/library/modern-module-asset-entry/img.png b/packages/rspack-test-tools/tests/configCases/library/modern-module-asset-entry/img.png new file mode 100644 index 000000000000..b74b839e2b86 Binary files /dev/null and b/packages/rspack-test-tools/tests/configCases/library/modern-module-asset-entry/img.png differ diff --git a/packages/rspack-test-tools/tests/configCases/library/modern-module-asset-entry/rspack.config.js b/packages/rspack-test-tools/tests/configCases/library/modern-module-asset-entry/rspack.config.js new file mode 100644 index 000000000000..34fadf7a8224 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/library/modern-module-asset-entry/rspack.config.js @@ -0,0 +1,58 @@ +const assert = require("assert"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + context: __dirname, + entry: { + index: './img.png' + }, + output: { + filename: `[name].js`, + chunkFilename: `async.js`, + module: true, + library: { + type: "modern-module", + }, + iife: false, + chunkFormat: "module", + chunkLoading: 'import', + }, + module: { + rules: [ + { + test: /\.png$/, + type: "asset/resource", + generator: { + filename: 'static/img/[name].png', + importMode: "preserve" + } + } + ] + }, + experiments: { + outputModule: true + }, + optimization: { + concatenateModules: true, + avoidEntryIife: true, + minimize: false + }, + plugins:[ + new (class { + apply(compiler) { + compiler.hooks.compilation.tap("MyPlugin", compilation => { + compilation.hooks.processAssets.tap("MyPlugin", assets => { + let list = Object.keys(assets); + const js = list.find(item => item.endsWith("js")); + const jsContent = assets[js].source().toString(); + + const preseveImport = /import\simg_namespaceObject\sfrom ['"]\.\/static\/img\/img\.png['"]/.test(jsContent); + assert(preseveImport); + const hasExports = /export\s{\simg_namespaceObject\sas\sdefault\s}/.test(jsContent); + assert(hasExports); + }) + }); + } + })() + ] +}; diff --git a/packages/rspack-test-tools/tests/configCases/library/modern-module-asset-entry/test.config.js b/packages/rspack-test-tools/tests/configCases/library/modern-module-asset-entry/test.config.js new file mode 100644 index 000000000000..08ea6c319c8e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/library/modern-module-asset-entry/test.config.js @@ -0,0 +1 @@ +exports.noTests = true; diff --git a/packages/rspack/etc/core.api.md b/packages/rspack/etc/core.api.md index 1cd56c723272..07e5a49d7c3a 100644 --- a/packages/rspack/etc/core.api.md +++ b/packages/rspack/etc/core.api.md @@ -186,6 +186,9 @@ export type AssetInlineGeneratorOptions = { // @public export type AssetModuleFilename = Filename; +// @public +export type AssetModuleImportMode = "url" | "preserve"; + // @public export type AssetModuleOutputPath = Filename; @@ -208,6 +211,7 @@ export type AssetResourceGeneratorOptions = { filename?: Filename; outputPath?: AssetModuleOutputPath; publicPath?: PublicPath; + importMode?: AssetModuleImportMode; }; // @public (undocumented) @@ -5348,6 +5352,7 @@ declare namespace rspackExports { AssetGeneratorDataUrl, AssetInlineGeneratorOptions, AssetModuleOutputPath, + AssetModuleImportMode, AssetResourceGeneratorOptions, AssetGeneratorOptions, CssGeneratorExportsConvention, @@ -7394,12 +7399,12 @@ export const rspackOptions: z.ZodObject<{ requireResolve: z.ZodOptional; importDynamic: z.ZodOptional; }, "strict", z.ZodTypeAny, { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7414,12 +7419,12 @@ export const rspackOptions: z.ZodObject<{ requireResolve?: boolean | undefined; importDynamic?: boolean | undefined; }, { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7455,12 +7460,12 @@ export const rspackOptions: z.ZodObject<{ requireResolve: z.ZodOptional; importDynamic: z.ZodOptional; }, "strict", z.ZodTypeAny, { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7475,12 +7480,12 @@ export const rspackOptions: z.ZodObject<{ requireResolve?: boolean | undefined; importDynamic?: boolean | undefined; }, { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7516,12 +7521,12 @@ export const rspackOptions: z.ZodObject<{ requireResolve: z.ZodOptional; importDynamic: z.ZodOptional; }, "strict", z.ZodTypeAny, { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7536,12 +7541,12 @@ export const rspackOptions: z.ZodObject<{ requireResolve?: boolean | undefined; importDynamic?: boolean | undefined; }, { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7577,12 +7582,12 @@ export const rspackOptions: z.ZodObject<{ requireResolve: z.ZodOptional; importDynamic: z.ZodOptional; }, "strict", z.ZodTypeAny, { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7597,12 +7602,12 @@ export const rspackOptions: z.ZodObject<{ requireResolve?: boolean | undefined; importDynamic?: boolean | undefined; }, { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7633,12 +7638,12 @@ export const rspackOptions: z.ZodObject<{ namedExports?: boolean | undefined; } | undefined; javascript?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7654,12 +7659,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/auto"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7675,12 +7680,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/dynamic"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7696,12 +7701,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/esm"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7732,12 +7737,12 @@ export const rspackOptions: z.ZodObject<{ namedExports?: boolean | undefined; } | undefined; javascript?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7753,12 +7758,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/auto"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7774,12 +7779,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/dynamic"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -7795,12 +7800,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/esm"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8063,12 +8068,12 @@ export const rspackOptions: z.ZodObject<{ namedExports?: boolean | undefined; } | undefined; javascript?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8084,12 +8089,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/auto"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8105,12 +8110,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/dynamic"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8126,12 +8131,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/esm"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8211,12 +8216,12 @@ export const rspackOptions: z.ZodObject<{ namedExports?: boolean | undefined; } | undefined; javascript?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8232,12 +8237,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/auto"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8253,12 +8258,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/dynamic"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8274,12 +8279,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/esm"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8383,12 +8388,12 @@ export const rspackOptions: z.ZodObject<{ namedExports?: boolean | undefined; } | undefined; javascript?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8404,12 +8409,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/auto"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8425,12 +8430,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/dynamic"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8446,12 +8451,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/esm"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -8988,12 +8993,12 @@ export const rspackOptions: z.ZodObject<{ namedExports?: boolean | undefined; } | undefined; javascript?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -9009,12 +9014,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/auto"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -9030,12 +9035,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/dynamic"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -9051,12 +9056,12 @@ export const rspackOptions: z.ZodObject<{ importDynamic?: boolean | undefined; } | undefined; "javascript/esm"?: { + url?: boolean | "relative" | undefined; dynamicImportMode?: "eager" | "lazy" | "weak" | "lazy-once" | undefined; dynamicImportPreload?: number | boolean | undefined; dynamicImportPrefetch?: number | boolean | undefined; dynamicImportFetchPriority?: "auto" | "low" | "high" | undefined; importMeta?: boolean | undefined; - url?: boolean | "relative" | undefined; exprContextCritical?: boolean | undefined; wrappedContextCritical?: boolean | undefined; wrappedContextRegExp?: RegExp | undefined; @@ -10514,6 +10519,7 @@ declare namespace t { AssetGeneratorDataUrl, AssetInlineGeneratorOptions, AssetModuleOutputPath, + AssetModuleImportMode, AssetResourceGeneratorOptions, AssetGeneratorOptions, CssGeneratorExportsConvention, diff --git a/packages/rspack/src/config/adapter.ts b/packages/rspack/src/config/adapter.ts index 3a281887ebc5..3b419f99a97e 100644 --- a/packages/rspack/src/config/adapter.ts +++ b/packages/rspack/src/config/adapter.ts @@ -674,7 +674,8 @@ function getRawAssetResourceGeneratorOptions( emit: options.emit, filename: options.filename, outputPath: options.outputPath, - publicPath: options.publicPath + publicPath: options.publicPath, + importMode: options.importMode }; } diff --git a/packages/rspack/src/config/types.ts b/packages/rspack/src/config/types.ts index d8946cad968b..8fef25a407f1 100644 --- a/packages/rspack/src/config/types.ts +++ b/packages/rspack/src/config/types.ts @@ -1148,6 +1148,14 @@ export type AssetInlineGeneratorOptions = { /** Emit the asset in the specified folder relative to 'output.path'. */ export type AssetModuleOutputPath = Filename; +/** + * If "url", a URL pointing to the asset will be generated based on publicPath. + * If "preserve", preserve import/require statement from generated asset. + * Only for modules with module type 'asset' or 'asset/resource'. + * @default "url" + */ +export type AssetModuleImportMode = "url" | "preserve"; + /** Options for asset modules. */ export type AssetResourceGeneratorOptions = { /** @@ -1164,6 +1172,14 @@ export type AssetResourceGeneratorOptions = { /** This option determines the URL prefix of the referenced 'asset' or 'asset/resource'*/ publicPath?: PublicPath; + + /** + * If "url", a URL pointing to the asset will be generated based on publicPath. + * If "preserve", preserve import/require statement from generated asset. + * Only for modules with module type 'asset' or 'asset/resource'. + * @default "url" + */ + importMode?: AssetModuleImportMode; }; /** Generator options for asset modules. */ diff --git a/website/docs/en/config/module.mdx b/website/docs/en/config/module.mdx index f3d0f28b4ac1..b90157f21e42 100644 --- a/website/docs/en/config/module.mdx +++ b/website/docs/en/config/module.mdx @@ -486,6 +486,44 @@ When set to 'base64', module source will be encoded using Base64 algorithm. Sett A mimetype for data URI. Resolves from module resource extension by default. Only for modules with module type `'asset'` or `'asset/inline'`. +#### module.generator.asset.importMode + +- **Type:** `'url' | 'preserve'` +- **Default:** `'url'` + +If `"url"`, a URL pointing to the asset will be generated based on [publicPath](#modulegeneratorassetpublicpath). +If `"preserve"`, preserve import/require statement from generated asset. + +Only for modules with module type `'asset'` or `'asset/resource'`. + +- `'asset'`: + +```js title=rspack.config.js +module.exports = { + module: { + generator: { + asset: { + importMode: 'preserve', + }, + }, + }, +}; +``` + +- `'asset/resource'`: + +```js title=rspack.config.js +module.exports = { + module: { + generator: { + 'asset/resource': { + importMode: 'preserve', + }, + }, + }, +}; +``` + #### module.generator.asset.filename - **Type:** `string | ((pathData: PathData, assetInfo?: AssetInfo) => string)` @@ -591,6 +629,10 @@ Same as [`module.generator["asset"].dataUrl.mimetype`](#modulegeneratorassetdata Generator options for `asset/resource` modules. +#### module.generator["asset/resource"].importMode + +Same as [`module.generator["asset"].importMode`](#modulegeneratorassetimportmode). + #### module.generator["asset/resource"].filename Same as [`module.generator["asset"].filename`](#modulegeneratorassetfilename). diff --git a/website/docs/zh/config/module.mdx b/website/docs/zh/config/module.mdx index b6b1ee8a2ae6..a80da1507dd8 100644 --- a/website/docs/zh/config/module.mdx +++ b/website/docs/zh/config/module.mdx @@ -486,6 +486,44 @@ module.exports = { dataUrl 的 MIME 类型,默认从模块资源扩展名解析。仅对模块类型为 `'asset/inline'` 的模块生效。 +#### module.generator.asset.importMode + +- **类型:** `'url' | 'preserve'` +- **默认值:** `'url'` + +如果为 `"url"`,将基于 [publicPath](#modulegeneratorassetpublicpath) 生成指向 asset 的 URL。 +如果为 `"preserve"`,将保留指向 asset 的 import 或 require 语句。 + +仅对模块类型为 `'asset'` 和 `'asset/resource'` 的模块生效。 + +- `'asset'`: + +```js title=rspack.config.js +module.exports = { + module: { + generator: { + asset: { + importMode: 'preserve', + }, + }, + }, +}; +``` + +- `'asset/resource'`: + +```js title=rspack.config.js +module.exports = { + module: { + generator: { + 'asset/resource': { + importMode: 'preserve', + }, + }, + }, +}; +``` + #### module.generator.asset.filename - **类型:** `string | ((pathData: PathData, assetInfo?: AssetInfo) => string)` @@ -591,6 +629,10 @@ module.exports = { `asset/resource` 模块的生成器选项。 +#### module.generator["asset/resource"].importMode + +和 [`module.generator["asset"].importMode`](#modulegeneratorassetimportmode) 一样。 + #### module.generator["asset/resource"].filename 和 [`module.generator["asset"].filename`](#modulegeneratorassetfilename) 一样。