use crate::ecmascript::format::utils::namespace::generate_identifier;
use crate::utils::chunk::namespace_marker::render_namespace_markers;
use crate::utils::chunk::render_chunk_exports::{
get_chunk_export_names_with_ctx, render_wrapped_entry_chunk,
};
use crate::{
ecmascript::ecma_generator::RenderedModuleSources, types::generator::GenerateContext,
utils::chunk::render_chunk_exports::render_chunk_exports,
};
use rolldown_common::{AddonRenderContext, ExternalModule, OutputExports};
use rolldown_error::{BuildDiagnostic, BuildResult};
use rolldown_sourcemap::SourceJoiner;
use rolldown_utils::concat_string;
use super::utils::{
render_chunk_directives, render_chunk_external_imports, render_factory_parameters,
render_modules_with_peek_runtime_module_at_first,
};
pub async fn render_iife<'code>(
ctx: &GenerateContext<'_>,
addon_render_context: AddonRenderContext<'code>,
module_sources: &'code RenderedModuleSources,
warnings: &mut Vec<BuildDiagnostic>,
) -> BuildResult<SourceJoiner<'code>> {
let mut source_joiner = SourceJoiner::default();
let AddonRenderContext { hashbang, banner, intro, outro, footer, directives } =
addon_render_context;
if let Some(hashbang) = hashbang {
source_joiner.append_source(hashbang);
}
if let Some(banner) = banner {
source_joiner.append_source(banner);
}
if !directives.is_empty() {
let rendered_chunk_directives = render_chunk_directives(directives.iter());
if !rendered_chunk_directives.is_empty() {
source_joiner.append_source(rendered_chunk_directives);
}
}
let export_mode = ctx.chunk.output_exports;
let export_names = get_chunk_export_names_with_ctx(ctx);
let has_exports = !export_names.is_empty();
let has_default_export = export_names.iter().any(|name| name.as_str() == "default");
let entry_module = ctx
.chunk
.entry_module(&ctx.link_output.module_table)
.expect("iife format only have entry chunk");
let named_exports = matches!(&export_mode, OutputExports::Named);
let (import_code, externals) = render_chunk_external_imports(ctx);
let externals: Vec<_> = externals
.iter()
.filter(|e| e.is_used())
.map(super::utils::ExternalImportKind::module)
.collect();
let (definition, assignment) = generate_identifier(warnings, ctx, export_mode)?;
let exports_prefix = if has_exports && named_exports {
if ctx.options.extend {
Some(assignment.as_str())
} else {
Some("{}")
}
} else {
None
};
let factory_parameters = render_factory_parameters(ctx, &externals, exports_prefix.is_some());
source_joiner.append_source(concat_string!(
definition,
if (ctx.options.extend && named_exports) || !has_exports || assignment.is_empty() {
String::new()
} else {
concat_string!(assignment, " = ")
},
"(function(",
factory_parameters,
") {\n"
));
if let Some(intro) = intro {
source_joiner.append_source(intro);
}
if named_exports && entry_module.exports_kind.is_esm() {
if let Some(marker) = render_namespace_markers(
ctx.options.es_module,
has_default_export,
&ctx.options.generated_code,
ctx.chunk.is_entry_point(),
) {
source_joiner.append_source(marker);
}
}
render_modules_with_peek_runtime_module_at_first(
ctx,
&mut source_joiner,
module_sources,
import_code,
);
if let Some(source) = render_wrapped_entry_chunk(ctx, Some(&export_mode)) {
source_joiner.append_source(source);
}
if let Some(exports) = render_chunk_exports(ctx, Some(&export_mode)) {
source_joiner.append_source(exports);
}
if let Some(outro) = outro {
source_joiner.append_source(outro);
}
if named_exports && has_exports && !ctx.options.extend {
source_joiner.append_source("return exports;");
}
let factory_arguments =
render_iife_factory_arguments(warnings, ctx, &externals, exports_prefix).await;
source_joiner.append_source(concat_string!("})(", factory_arguments, ");"));
if let Some(footer) = footer {
source_joiner.append_source(footer);
}
Ok(source_joiner)
}
async fn render_iife_factory_arguments(
warnings: &mut Vec<BuildDiagnostic>,
ctx: &GenerateContext<'_>,
externals: &[&ExternalModule],
exports_prefix: Option<&str>,
) -> String {
let mut factory_arguments = if let Some(exports_prefix) = exports_prefix {
vec![exports_prefix.to_string()]
} else {
vec![]
};
let globals = &ctx.options.globals;
for external in externals {
let global = globals.call(external.id.as_str()).await;
let target = match &global {
Some(global_name) => global_name.clone(),
None => {
warnings.push(
BuildDiagnostic::missing_global_name(
external.id.to_string(),
external.name.clone(),
external.identifier_name.clone(),
)
.with_severity_warning(),
);
external.identifier_name.to_string()
}
};
factory_arguments.push(target);
}
factory_arguments.join(", ")
}