use std::{borrow::Cow, hash::Hasher, path::PathBuf};
use asset_exports_dependency::AssetExportsDependency;
use rayon::prelude::*;
use rspack_cacheable::{cacheable, cacheable_dyn};
use rspack_core::{
AssetBuildInfo, AssetGeneratorDataUrl, AssetGeneratorDataUrlFnCtx, AssetGeneratorImportMode,
AssetInfo, AssetParserDataUrl, BuildMetaDefaultObject, BuildMetaExportsType, ChunkGraph,
ChunkUkey, CodeGenerationDataAssetInfo, CodeGenerationDataFilename, CodeGenerationDataUrl,
CodeGenerationPublicPathAutoReplace, Compilation, CompilationRenderManifest, CompilerOptions,
DependencyType, Filename, GenerateContext, GeneratorOptions, ManifestAssetType, Module,
ModuleArgument, ModuleGraph, NAMESPACE_OBJECT_EXPORT, NormalModule, ParseContext,
ParserAndGenerator, ParserOptions, PathData, Plugin, PublicPath, RenderManifestEntry,
ResourceData, RuntimeGlobals, RuntimeSpec, SourceType,
rspack_sources::{BoxSource, RawStringSource, SourceExt},
};
use rspack_error::{Diagnostic, IntoTWithDiagnosticArray, Result, error};
use rspack_hash::{RspackHash, RspackHashDigest};
use rspack_hook::{plugin, plugin_hook};
use rspack_util::{base64, ext::DynHash, fx_hash::FxHashSet, identifier::make_paths_relative};
mod asset_exports_dependency;
pub use rspack_core::CanonicalizedDataUrlOption;
pub const AUTO_PUBLIC_PATH_PLACEHOLDER: &str = "__RSPACK_PLUGIN_ASSET_AUTO_PUBLIC_PATH__";
#[plugin]
#[derive(Debug, Default)]
pub struct AssetPlugin;
static JS_AND_CSS_URL_TYPES: &[SourceType; 2] = &[SourceType::JavaScript, SourceType::CssUrl];
static JS_TYPES: &[SourceType; 1] = &[SourceType::JavaScript];
static CSS_URL_TYPES: &[SourceType; 1] = &[SourceType::CssUrl];
static ASSET_AND_JS_AND_CSS_URL_TYPES: &[SourceType; 3] = &[
SourceType::Asset,
SourceType::JavaScript,
SourceType::CssUrl,
];
static ASSET_AND_JS_TYPES: &[SourceType; 2] = &[SourceType::Asset, SourceType::JavaScript];
static ASSET_AND_CSS_URL_TYPES: &[SourceType; 2] = &[SourceType::Asset, SourceType::CssUrl];
static ASSET_TYPES: &[SourceType; 1] = &[SourceType::Asset];
const DEFAULT_ENCODING: &str = "base64";
#[cacheable]
#[derive(Debug, Clone)]
enum DataUrlOptions {
Inline(bool),
Source,
Bytes,
Auto(Option<AssetParserDataUrl>),
}
#[cacheable]
#[derive(Debug, Clone)]
pub struct AssetParserAndGenerator {
emit: bool,
data_url: DataUrlOptions,
}
impl AssetParserAndGenerator {
pub fn with_auto(option: Option<AssetParserDataUrl>, emit: bool) -> Self {
Self {
emit,
data_url: DataUrlOptions::Auto(option),
}
}
pub fn with_inline() -> Self {
Self {
emit: false,
data_url: DataUrlOptions::Inline(true),
}
}
pub fn with_resource(emit: bool) -> Self {
Self {
emit,
data_url: DataUrlOptions::Inline(false),
}
}
pub fn with_source() -> Self {
Self {
emit: false,
data_url: DataUrlOptions::Source,
}
}
pub fn with_bytes() -> Self {
Self {
emit: false,
data_url: DataUrlOptions::Bytes,
}
}
fn decode_data_uri_content(encoding: &str, content: &str, source: &BoxSource) -> Vec<u8> {
if encoding == "base64"
&& let Some(cleaned) = base64::clean_base64(content)
{
return base64::decode_to_vec(cleaned.as_bytes())
.unwrap_or_else(|_| source.buffer().to_vec());
}
match urlencoding::decode(content) {
Ok(decoded_content) => decoded_content.as_bytes().to_vec(),
Err(_) => content.as_bytes().to_vec(),
}
}
fn hash_for_source(
&self,
source: &BoxSource,
compiler_options: &CompilerOptions,
) -> RspackHashDigest {
let mut hasher = RspackHash::from(&compiler_options.output);
hasher.write(&source.buffer());
hasher.digest(&compiler_options.output.hash_digest)
}
async fn get_data_url(
&self,
resource_data: &ResourceData,
data_url: Option<&AssetGeneratorDataUrl>,
source: &BoxSource,
module: &dyn Module,
compilation: &Compilation,
) -> Option<String> {
let func_ctx = AssetGeneratorDataUrlFnCtx {
filename: resource_data.resource().to_owned(),
module,
compilation,
};
if let Some(AssetGeneratorDataUrl::Func(data_url)) = data_url {
return Some(
data_url(source.buffer().to_vec(), func_ctx)
.await
.expect("call data_url function failed"),
);
}
None
}
fn get_mimetype(
&self,
resource_data: &ResourceData,
data_url: Option<&AssetGeneratorDataUrl>,
) -> Result<String> {
if let Some(AssetGeneratorDataUrl::Options(data_url)) = data_url
&& let Some(mimetype) = &data_url.mimetype
{
return Ok(mimetype.to_owned());
}
if let Some(mimetype) = resource_data.mimetype()
&& let Some(parameters) = resource_data.parameters()
{
return Ok(format!("{mimetype}{parameters}"));
}
if let Some(resource_path) = resource_data.path() {
return mime_guess::MimeGuess::from_path(resource_path)
.first_raw()
.map(ToOwned::to_owned)
.ok_or_else(|| {
error!(
"DataUrl can't be generated automatically, because there is no mimetype for \"{ext:?}\" in mimetype database. Either pass a mimetype via \"generator.mimetype\" or use type: \"asset/resource\" to create a resource file instead of a DataUrl",
ext = resource_path.extension()
)
});
}
Err(error!(
"DataUrl can't be generated automatically. Either pass a mimetype via \"generator.mimetype\" or use type: \"asset/resource\" to create a resource file instead of a DataUrl"
))
}
fn get_encoding(
&self,
resource_data: &ResourceData,
data_url: Option<&AssetGeneratorDataUrl>,
) -> String {
if let Some(AssetGeneratorDataUrl::Options(data_url)) = data_url
&& let Some(encoding) = &data_url.encoding
{
return encoding.to_string();
}
if let Some(encoding) = resource_data.encoding() {
return encoding.to_owned();
}
String::from(DEFAULT_ENCODING)
}
fn get_encoded_content(
&self,
resource_data: &ResourceData,
encoding: &str,
source: &BoxSource,
) -> Result<String> {
if let Some(encoded_content) = resource_data.encoded_content()
&& let Some(resource_encoding) = resource_data.encoding()
&& resource_encoding == encoding
&& AssetParserAndGenerator::decode_data_uri_content(encoding, encoded_content, source)
.eq(&source.buffer().to_vec())
{
return Ok(encoded_content.to_owned());
}
if encoding.is_empty() {
return Ok(urlencoding::encode_binary(&source.buffer()).into_owned());
}
if encoding == DEFAULT_ENCODING {
return Ok(base64::encode_to_string(source.buffer()));
}
Err(error!("Unsupported encoding {encoding}"))
}
fn get_source_file_name(&self, module: &NormalModule, compilation: &Compilation) -> String {
let relative = make_paths_relative(
compilation.options.context.as_ref(),
module
.match_resource()
.unwrap_or(module.resource_resolved_data())
.resource(),
);
if let Some(stripped) = relative.strip_prefix("./") {
return stripped.to_owned();
}
relative
}
async fn get_asset_module_filename(
&self,
module: &NormalModule,
module_generator_options: Option<&GeneratorOptions>,
compilation: &Compilation,
contenthash: Option<&str>,
source_file_name: &str,
use_output_path: bool,
) -> Result<(String, String, AssetInfo)> {
let asset_filename_template = module
.build_info()
.asset
.as_ref()
.and_then(|x| x.filename.as_ref())
.or_else(|| module_generator_options.and_then(|x| x.asset_filename()))
.unwrap_or(&compilation.options.output.asset_module_filename);
let path_data = PathData::default()
.module_id_optional(
ChunkGraph::get_module_id(&compilation.module_ids_artifact, module.id())
.map(|s| s.as_str()),
)
.content_hash_optional(contenthash)
.hash_optional(contenthash)
.filename(source_file_name);
let (mut filename, mut asset_info) = compilation
.get_asset_path_with_info(asset_filename_template, path_data)
.await?;
let original_filename = filename.clone();
if use_output_path {
let output_path = module_generator_options.and_then(|x| x.asset_output_path());
if let Some(output_path) = output_path {
let (output_path, another_asset_info) = compilation
.get_asset_path_with_info(output_path, path_data)
.await?;
let output_path = PathBuf::from(output_path);
let file_path = PathBuf::from(filename);
filename = output_path.join(file_path).to_string_lossy().to_string();
asset_info.merge_another_asset(another_asset_info);
}
}
Ok((original_filename, filename, asset_info))
}
async fn get_public_path(
&self,
module: &NormalModule,
compilation: &Compilation,
contenthash: Option<&str>,
source_file_name: &str,
template: &Filename,
) -> Result<(String, AssetInfo)> {
let (public_path, info) = compilation
.get_asset_path_with_info(
template,
PathData::default()
.module_id_optional(
ChunkGraph::get_module_id(&compilation.module_ids_artifact, module.id())
.map(|s| s.as_str()),
)
.content_hash_optional(contenthash)
.hash_optional(contenthash)
.filename(source_file_name),
)
.await?;
let public_path = PublicPath::ensure_ends_with_slash(public_path);
Ok((public_path, info))
}
pub fn get_import_mode(
&self,
module_generator_options: Option<&GeneratorOptions>,
) -> Result<AssetGeneratorImportMode> {
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)
}
}
const DEFAULT_MAX_SIZE: f64 = 8096.0;
#[cacheable_dyn]
#[async_trait::async_trait]
impl ParserAndGenerator for AssetParserAndGenerator {
fn source_types(&self, module: &dyn Module, module_graph: &ModuleGraph) -> &[SourceType] {
let mut source_types = FxHashSet::default();
let module_id = module.identifier();
for connection in module_graph.get_incoming_connections(&module_id) {
if let Some(module) = connection
.original_module_identifier
.and_then(|id| module_graph.module_by_identifier(&id))
{
let module_type = module.module_type();
source_types.insert(SourceType::from(module_type));
} else {
let dependency = module_graph.dependency_by_id(&connection.dependency_id);
if matches!(dependency.dependency_type(), DependencyType::LoaderImport) {
source_types.insert(SourceType::JavaScript);
}
}
}
let is_import_mode_preserve = self
.get_import_mode(
module
.as_normal_module()
.and_then(|x| x.get_generator_options()),
)
.is_ok_and(|x| x.is_preserve());
if module
.build_info()
.asset
.as_ref()
.is_some_and(|x| x.data_url.is_source() || x.data_url.is_inline() || x.data_url.is_bytes())
|| !self.emit
{
if source_types.is_empty() {
return JS_TYPES;
} else {
let has_js = source_types.contains(&SourceType::JavaScript);
let has_css = source_types.contains(&SourceType::Css);
if has_js && has_css {
return JS_AND_CSS_URL_TYPES;
} else if has_css {
return CSS_URL_TYPES;
} else {
return JS_TYPES;
}
}
}
if source_types.is_empty() {
return if is_import_mode_preserve {
ASSET_AND_JS_TYPES
} else {
ASSET_TYPES
};
}
let has_js = source_types.contains(&SourceType::JavaScript);
let has_css = source_types.contains(&SourceType::Css);
if has_js && has_css {
ASSET_AND_JS_AND_CSS_URL_TYPES
} else if has_css {
ASSET_AND_CSS_URL_TYPES
} else {
ASSET_AND_JS_TYPES
}
}
fn size(&self, module: &dyn Module, source_type: Option<&SourceType>) -> f64 {
let original_source_size = module.source().map_or(0, |source| source.size()) as f64;
match source_type.unwrap_or(&SourceType::Asset) {
SourceType::Asset => original_source_size,
SourceType::JavaScript | SourceType::CssUrl => {
if module.source().is_none() {
return 0.0;
}
let parsed_size = module.build_info().asset.as_ref().map(|asset| {
match &asset.data_url {
CanonicalizedDataUrlOption::Source | CanonicalizedDataUrlOption::Bytes => {
original_source_size
}
CanonicalizedDataUrlOption::Asset(is_inline) => {
if *is_inline {
original_source_size * 1.34 + 36.0
} else {
42.0
}
}
}
});
parsed_size.unwrap_or_default()
}
_ => unreachable!(),
}
}
async fn parse<'a>(
&mut self,
parse_context: rspack_core::ParseContext<'a>,
) -> Result<rspack_error::TWithDiagnosticArray<rspack_core::ParseResult>> {
let ParseContext {
source,
build_meta,
build_info,
module_parser_options,
..
} = parse_context;
build_info.strict = true;
build_meta.exports_type = BuildMetaExportsType::Default;
build_meta.default_object = BuildMetaDefaultObject::False;
let size = source.size();
let data_url = match &self.data_url {
DataUrlOptions::Source => CanonicalizedDataUrlOption::Source,
DataUrlOptions::Bytes => CanonicalizedDataUrlOption::Bytes,
DataUrlOptions::Inline(val) => CanonicalizedDataUrlOption::Asset(*val),
DataUrlOptions::Auto(option) => {
let limit_size = module_parser_options
.and_then(|x| {
x.get_asset()
.and_then(|x| x.data_url_condition.as_ref())
.and_then(|x| match x {
AssetParserDataUrl::Options(x) => x.max_size,
})
})
.or_else(|| {
option.as_ref().and_then(|x| match x {
AssetParserDataUrl::Options(x) => x.max_size,
})
})
.unwrap_or(DEFAULT_MAX_SIZE);
CanonicalizedDataUrlOption::Asset(size <= limit_size as usize)
}
};
build_info.asset = Some(Box::new(AssetBuildInfo {
data_url,
filename: None,
}));
Ok(
rspack_core::ParseResult {
dependencies: vec![Box::new(AssetExportsDependency::new())],
blocks: vec![],
source,
presentational_dependencies: vec![],
code_generation_dependencies: vec![],
side_effects_bailout: None,
}
.with_empty_diagnostic(),
)
}
async fn generate(
&self,
source: &BoxSource,
module: &dyn Module,
generate_context: &mut GenerateContext,
) -> Result<BoxSource> {
let compilation = generate_context.compilation;
let asset_build_info = module
.build_info()
.asset
.as_ref()
.expect("should have asset build info in generate phase");
let parsed_asset_config = &asset_build_info.data_url;
let normal_module = module
.as_normal_module()
.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)?;
match generate_context.requested_source_type {
SourceType::JavaScript | SourceType::CssUrl => {
let exported_content = if parsed_asset_config.is_bytes() {
let mut encoded_source = base64::encode_to_string(source.buffer());
if generate_context.requested_source_type == SourceType::CssUrl {
encoded_source = format!("data:application/octet-stream;base64,{encoded_source}");
generate_context
.data
.insert(CodeGenerationDataUrl::new(encoded_source.clone()));
rspack_util::json_stringify_str(&encoded_source)
} else {
format!(
"{}({})",
generate_context
.runtime_template
.render_runtime_globals(&RuntimeGlobals::TO_BINARY),
rspack_util::json_stringify_str(&encoded_source)
)
}
} else 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) = self
.get_data_url(resource_data, data_url, source, module, compilation)
.await
{
encoded_source = custom_data_url;
} else {
let mimetype = self.get_mimetype(resource_data, data_url)?;
let encoding = self.get_encoding(resource_data, data_url);
let encoded_content = self.get_encoded_content(resource_data, &encoding, source)?;
encoded_source = format!(
r#"data:{mimetype}{},{encoded_content}"#,
if encoding.is_empty() {
String::new()
} else {
format!(";{encoding}")
}
);
}
generate_context
.data
.insert(CodeGenerationDataUrl::new(encoded_source.clone()));
rspack_util::json_stringify_str(&encoded_source)
} else if parsed_asset_config.is_resource() {
let contenthash = self.hash_for_source(source, &compilation.options);
let contenthash = contenthash.rendered(compilation.options.output.hash_digest_length);
let source_file_name = self.get_source_file_name(normal_module, compilation);
let (original_filename, filename, mut asset_info) = self
.get_asset_module_filename(
normal_module,
module_generator_options,
compilation,
Some(contenthash),
&source_file_name,
true,
)
.await?;
let asset_path = if import_mode.is_preserve() {
generate_context
.data
.insert(CodeGenerationPublicPathAutoReplace(true));
rspack_util::json_stringify_str(&format!(
"{AUTO_PUBLIC_PATH_PLACEHOLDER}{original_filename}"
))
} else if let Some(public_path) =
module_generator_options.and_then(|x| x.asset_public_path())
{
let public_path = match public_path {
PublicPath::Filename(template) => {
let (public_path, another_asset_info) = self
.get_public_path(
normal_module,
compilation,
Some(contenthash),
&source_file_name,
template,
)
.await?;
asset_info.merge_another_asset(another_asset_info);
public_path
}
PublicPath::Auto => public_path.render(compilation, &filename).await,
};
rspack_util::json_stringify_str(&format!("{public_path}{original_filename}"))
} else {
format!(
r#"{} + "{}""#,
generate_context
.runtime_template
.render_runtime_globals(&RuntimeGlobals::PUBLIC_PATH),
original_filename
)
};
asset_info.set_source_filename(source_file_name);
generate_context
.data
.insert(CodeGenerationDataFilename::new(
filename,
match module_generator_options
.and_then(|x| x.asset_public_path())
.unwrap_or_else(|| &compilation.options.output.public_path)
{
PublicPath::Filename(p) => PublicPath::render_filename(compilation, p).await,
PublicPath::Auto => AUTO_PUBLIC_PATH_PLACEHOLDER.to_string(),
},
));
generate_context
.data
.insert(CodeGenerationDataAssetInfo::new(asset_info));
asset_path
} else if parsed_asset_config.is_source() {
format!(r"{:?}", source.source().into_string_lossy())
} else {
unreachable!()
};
if generate_context.requested_source_type == SourceType::CssUrl {
return Ok(RawStringSource::from_static("").boxed());
}
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 {
return Ok(
RawStringSource::from(format!(
r#"{module}.exports = require({exported_content});"#,
module = generate_context
.runtime_template
.render_module_argument(ModuleArgument::Module)
))
.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();
let declaration_kind = if supports_const { "const" } else { "var" };
Ok(
RawStringSource::from(format!(
r#"{declaration_kind} {NAMESPACE_OBJECT_EXPORT} = {exported_content};"#
))
.boxed(),
)
} else {
Ok(
RawStringSource::from(format!(
r#"{module}.exports = {exported_content};"#,
module = generate_context
.runtime_template
.render_module_argument(ModuleArgument::Module)
))
.boxed(),
)
}
}
SourceType::Asset => {
if parsed_asset_config.is_source() || parsed_asset_config.is_inline() {
Err(error!(
"Inline or Source asset does not have source type `asset`"
))
} else {
let contenthash = self.hash_for_source(source, &compilation.options);
let contenthash = contenthash.rendered(compilation.options.output.hash_digest_length);
let source_file_name = self.get_source_file_name(normal_module, compilation);
let (_, filename, mut asset_info) = self
.get_asset_module_filename(
normal_module,
module_generator_options,
compilation,
Some(contenthash),
&source_file_name,
true,
)
.await?;
generate_context
.data
.insert(CodeGenerationDataFilename::new(
filename,
match module_generator_options
.and_then(|x| x.asset_public_path())
.unwrap_or_else(|| &compilation.options.output.public_path)
{
PublicPath::Filename(p) => PublicPath::render_filename(compilation, p).await,
PublicPath::Auto => AUTO_PUBLIC_PATH_PLACEHOLDER.to_string(),
},
));
asset_info.set_source_filename(source_file_name);
generate_context
.data
.insert(CodeGenerationDataAssetInfo::new(asset_info));
Ok(source.clone().boxed())
}
}
_ => panic!(
"Unsupported source type: {:?}",
generate_context.requested_source_type
),
}
}
fn get_concatenation_bailout_reason(
&self,
_module: &dyn rspack_core::Module,
_mg: &ModuleGraph,
_cg: &ChunkGraph,
) -> Option<Cow<'static, str>> {
None
}
async fn get_runtime_hash(
&self,
module: &NormalModule,
compilation: &Compilation,
_runtime: Option<&RuntimeSpec>,
) -> Result<RspackHashDigest> {
let mut hasher = RspackHash::from(&compilation.options.output);
let asset_build_info = module
.build_info()
.asset
.as_ref()
.expect("should have asset build info in generate phase");
let parsed_asset_config = &asset_build_info.data_url;
let module_generator_options = module.get_generator_options();
if parsed_asset_config.is_inline()
&& let Some(AssetGeneratorDataUrl::Options(data_url_options)) =
module_generator_options.and_then(|x| x.asset_data_url())
{
data_url_options.dyn_hash(&mut hasher);
} else if parsed_asset_config.is_resource() {
let source_file_name = self.get_source_file_name(module, compilation);
let (filename, _, _) = self
.get_asset_module_filename(
module,
module_generator_options,
compilation,
None,
&source_file_name,
false,
)
.await?;
filename.dyn_hash(&mut hasher);
match module_generator_options.and_then(|x| x.asset_public_path()) {
Some(public_path) => match public_path {
PublicPath::Filename(template) => {
let (public_path, _) = self
.get_public_path(module, compilation, None, &source_file_name, template)
.await?;
public_path.dyn_hash(&mut hasher);
}
PublicPath::Auto => "auto".dyn_hash(&mut hasher),
},
None => "no-path".dyn_hash(&mut hasher),
};
}
Ok(hasher.digest(&compilation.options.output.hash_digest))
}
}
#[plugin_hook(CompilationRenderManifest for AssetPlugin)]
async fn render_manifest(
&self,
compilation: &Compilation,
chunk_ukey: &ChunkUkey,
manifest: &mut Vec<RenderManifestEntry>,
_diagnostics: &mut Vec<Diagnostic>,
) -> Result<()> {
let chunk = compilation
.build_chunk_graph_artifact
.chunk_by_ukey
.expect_get(chunk_ukey);
let module_graph = compilation.get_module_graph();
let ordered_modules = compilation
.build_chunk_graph_artifact
.chunk_graph
.get_chunk_modules_identifier_by_source_type(chunk_ukey, SourceType::Asset, module_graph);
let assets = ordered_modules
.par_iter()
.map(|mid| {
let code_gen_result = compilation
.code_generation_results
.get(mid, Some(chunk.runtime()));
let result = code_gen_result.get(&SourceType::Asset).map(|source| {
let asset_filename = code_gen_result
.data
.get::<CodeGenerationDataFilename>()
.expect("should have filename for asset module")
.filename();
let asset_info = code_gen_result
.data
.get::<CodeGenerationDataAssetInfo>()
.expect("should have asset_info")
.inner()
.to_owned()
.with_asset_type(ManifestAssetType::Asset);
RenderManifestEntry {
source: source.clone(),
filename: asset_filename.to_owned(),
has_filename: true,
info: asset_info,
auxiliary: true,
}
});
Ok(result)
})
.collect::<Result<Vec<Option<RenderManifestEntry>>>>()?
.into_par_iter()
.flatten()
.collect::<Vec<RenderManifestEntry>>();
manifest.extend(assets);
Ok(())
}
impl Plugin for AssetPlugin {
fn name(&self) -> &'static str {
"asset"
}
fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
ctx
.compilation_hooks
.render_manifest
.tap(render_manifest::new(self));
ctx.register_parser_and_generator_builder(
rspack_core::ModuleType::Asset,
Box::new(move |options| {
let data_url_condition = options
.parser_options_computed(ParserOptions::get_asset)
.and_then(|x| x.data_url_condition.clone());
let emit: Option<bool> = options
.generator_options_computed(GeneratorOptions::get_asset)
.and_then(|x| x.emit);
Box::new(AssetParserAndGenerator::with_auto(
data_url_condition,
emit.unwrap_or(true),
))
}),
);
ctx.register_parser_and_generator_builder(
rspack_core::ModuleType::AssetInline,
Box::new(|_| Box::new(AssetParserAndGenerator::with_inline())),
);
ctx.register_parser_and_generator_builder(
rspack_core::ModuleType::AssetResource,
Box::new(move |options| {
let emit = options
.generator_options_computed(GeneratorOptions::get_asset_resource)
.and_then(|x| x.emit);
Box::new(AssetParserAndGenerator::with_resource(emit.unwrap_or(true)))
}),
);
ctx.register_parser_and_generator_builder(
rspack_core::ModuleType::AssetSource,
Box::new(move |_| Box::new(AssetParserAndGenerator::with_source())),
);
ctx.register_parser_and_generator_builder(
rspack_core::ModuleType::AssetBytes,
Box::new(move |_| Box::new(AssetParserAndGenerator::with_bytes())),
);
Ok(())
}
}