use std::borrow::Cow;
use std::fmt::Write as _;
use oxc_str::CompactStr;
use rolldown_common::{
Chunk, ChunkKind, ExportsKind, IndexModules, ModuleIdx, NormalizedBundlerOptions, OutputExports,
OutputFormat, Platform, SymbolRef, SymbolRefDb, WrapKind,
};
use rolldown_utils::{
concat_string,
ecmascript::{property_access_str, to_module_import_export_name},
indexmap::FxIndexSet,
};
use rustc_hash::FxHashSet;
use crate::esm_init_obligations::{WrappedEsmInitTarget, collect_entry_reexported_wrapper_inits};
use crate::{stages::link_stage::LinkStageOutput, types::generator::GenerateContext};
pub fn render_wrapped_entry_chunk(
ctx: &GenerateContext<'_>,
export_mode: Option<&OutputExports>,
) -> Option<String> {
if let ChunkKind::EntryPoint { module: entry_id, .. } = ctx.chunk.kind {
let entry_meta = &ctx.link_output.metas[entry_id];
if matches!(entry_meta.wrap_kind(), WrapKind::Cjs) {
let wrapper_ref = entry_meta.wrapper_ref.as_ref().unwrap();
let wrapper_ref_name = ctx.finalized_string_pattern_for_symbol_ref(
*wrapper_ref,
ctx.chunk_idx,
&ctx.chunk.canonical_names,
);
return match ctx.options.format {
OutputFormat::Esm => {
Some(concat_string!("export default ", wrapper_ref_name.as_str(), "();\n"))
}
OutputFormat::Cjs => {
if matches!(&export_mode, Some(OutputExports::Named)) {
Some(render_object_define_property("default", &concat_string!(wrapper_ref_name, "()")))
} else {
Some(concat_string!("module.exports = ", wrapper_ref_name, "();\n"))
}
}
OutputFormat::Iife | OutputFormat::Umd => {
if matches!(&export_mode, Some(OutputExports::Named)) {
Some(render_object_define_property("default", &concat_string!(wrapper_ref_name, "()")))
} else {
Some(concat_string!("return ", wrapper_ref_name, "();\n"))
}
}
};
}
if let Some(targets) = ctx.order_wrap_state.consumer_local_namespace_targets(entry_id) {
let mut rendered = String::new();
for target in targets {
let wrapper_ref = match target {
WrappedEsmInitTarget::Module(module_idx) => {
let target =
ctx.esm_init_target(*module_idx).expect("entry init target should have a wrapper");
debug_assert!(!target.tla_tainted);
target.wrapper_ref
}
WrappedEsmInitTarget::CjsCarrier(key) => {
ctx
.order_wrap_state
.order_cjs_carrier(*key)
.expect("entry CJS carrier should exist")
.wrapper_ref
}
};
let wrapper_name = ctx.finalized_string_pattern_for_symbol_ref(
wrapper_ref,
ctx.chunk_idx,
&ctx.chunk.canonical_names,
);
writeln!(rendered, "{wrapper_name}();").expect("writing to a string cannot fail");
}
return Some(rendered);
}
let reexport_init_calls = render_entry_reexported_wrapper_init_calls(ctx, entry_id, entry_meta);
match ctx.esm_init_target(entry_id) {
Some(target) => {
let wrapper_ref_name = ctx.finalized_string_pattern_for_symbol_ref(
target.wrapper_ref,
ctx.chunk_idx,
&ctx.chunk.canonical_names,
);
let own_init_call = if target.tla_tainted {
concat_string!("await ", wrapper_ref_name, "();")
} else {
concat_string!(wrapper_ref_name, "();")
};
match reexport_init_calls {
Some(mut calls) => {
calls.push_str(&own_init_call);
Some(calls)
}
None => Some(own_init_call),
}
}
None => reexport_init_calls,
}
} else {
None
}
}
fn render_entry_reexported_wrapper_init_calls(
ctx: &GenerateContext<'_>,
entry_id: ModuleIdx,
entry_meta: &crate::types::linking_metadata::LinkingMetadata,
) -> Option<String> {
if ctx.options.is_strict_execution_order_enabled() {
return None;
}
let entry_hosted_here = ctx.chunk_graph.module_to_chunk[entry_id] == Some(ctx.chunk_idx);
let entry_is_wrapped = ctx.esm_init_target(entry_id).is_some();
if entry_hosted_here && !entry_is_wrapped {
return None;
}
let inits = collect_entry_reexported_wrapper_inits(
entry_id,
entry_meta,
&ctx.link_output.metas,
&ctx.link_output.module_table.modules,
&ctx.link_output.symbol_db,
Some(&ctx.chunk.canonical_names),
);
let mut rendered = String::new();
for init in inits {
if entry_hosted_here && ctx.chunk_graph.module_to_chunk[init.owner] == Some(ctx.chunk_idx) {
continue;
}
let wrapper_name = ctx.finalized_string_pattern_for_symbol_ref(
init.wrapper_ref,
ctx.chunk_idx,
&ctx.chunk.canonical_names,
);
if init.tla_tainted {
writeln!(rendered, "await {wrapper_name}();").expect("writing to a string cannot fail");
} else {
writeln!(rendered, "{wrapper_name}();").expect("writing to a string cannot fail");
}
}
(!rendered.is_empty()).then_some(rendered)
}
#[expect(clippy::too_many_lines)] pub fn render_chunk_exports(
ctx: &GenerateContext<'_>,
export_mode: Option<&OutputExports>,
) -> Option<String> {
let GenerateContext { chunk, link_output, options, .. } = ctx;
let mut export_items: Vec<(CompactStr, SymbolRef)> = ctx.render_export_items_index_vec
[ctx.chunk_idx]
.iter()
.flat_map(|(symbol_ref, names)| {
let symbol_ref = *symbol_ref;
names.iter().map(move |name| (name.clone(), symbol_ref))
})
.collect();
match options.format {
OutputFormat::Esm => {
if let ChunkKind::EntryPoint { module: entry_id, .. } = chunk.kind {
let entry_meta = &link_output.metas[entry_id];
if matches!(entry_meta.wrap_kind(), WrapKind::Cjs) {
export_items.retain(|(exported_name, _)| exported_name.as_str() != "default");
}
}
if export_items.is_empty() && !matches!(ctx.options.platform, Platform::Node) {
return None;
}
let mut s = String::new();
let rendered_items = export_items
.into_iter()
.map(|(exported_name, export_ref)| {
let canonical_ref = link_output.symbol_db.canonical_ref_for(export_ref);
let symbol = link_output.symbol_db.get(canonical_ref);
let canonical_name = link_output
.symbol_db
.canonical_name_for_or_original(canonical_ref, &chunk.canonical_names);
if let Some(ns_alias) = &symbol.namespace_alias {
let canonical_ns_name = link_output
.symbol_db
.canonical_name_for_or_original(ns_alias.namespace_ref, &chunk.canonical_names);
let property_name = &ns_alias.property_name;
s.push_str("var ");
s.push_str(canonical_name);
s.push_str(" = ");
s.push_str(canonical_ns_name);
s.push('.');
s.push_str(property_name);
s.push_str(";\n");
}
if canonical_name == exported_name {
Cow::Borrowed(canonical_name)
} else {
Cow::Owned(concat_string!(
canonical_name,
" as ",
to_module_import_export_name(&exported_name)
))
}
})
.collect::<Vec<_>>();
s.push_str("export { ");
for (i, item) in rendered_items.iter().enumerate() {
if i > 0 {
s.push_str(", ");
}
s.push_str(item);
}
s.push_str(" };");
Some(s)
}
OutputFormat::Cjs | OutputFormat::Iife | OutputFormat::Umd => {
let mut s = String::new();
match chunk.kind {
ChunkKind::EntryPoint { module, .. } => {
let module =
&link_output.module_table[module].as_normal().expect("should be normal module");
if !matches!(module.exports_kind, ExportsKind::Esm) {
export_items.retain(|(_, export_ref)| {
let canonical_ref = link_output.symbol_db.canonical_ref_for(*export_ref);
canonical_ref.owner != module.idx
});
}
if !export_items.is_empty() {
let rendered_items = export_items
.into_iter()
.map(|(exported_name, export_ref)| {
let canonical_ref = link_output.symbol_db.canonical_ref_for(export_ref);
let exported_value = ctx.finalized_string_pattern_for_symbol_ref(
canonical_ref,
ctx.chunk_idx,
&chunk.canonical_names,
);
match export_mode {
Some(OutputExports::Named) => {
let is_order_wrapper_ref =
ctx.order_wrap_state.is_execution_order_wrapper_ref(canonical_ref);
let keep_live_binding = must_keep_live_binding(
export_ref,
&link_output.symbol_db,
options,
&link_output.module_table.modules,
);
debug_assert!(
!is_order_wrapper_ref || keep_live_binding,
"a strict-execution-order wrapper ref must never take the value-snapshot \
export branch (`exports.{exported_name} = {exported_value}`): it would \
freeze the pre-rebind function and re-run the module body",
);
if is_order_wrapper_ref || keep_live_binding {
render_object_define_property(&exported_name, &exported_value)
} else if exported_name.as_str() == "__proto__" {
render_object_define_property_value(&exported_name, &exported_value)
} else {
concat_string!(
property_access_str("exports", exported_name.as_str()),
" = ",
exported_value.as_str(),
";"
)
}
}
Some(OutputExports::Default) => {
if matches!(options.format, OutputFormat::Cjs) {
concat_string!("module.exports = ", exported_value.as_str(), ";")
} else {
concat_string!("return ", exported_value.as_str(), ";")
}
}
Some(OutputExports::None) => String::new(),
_ => unreachable!(),
}
})
.collect::<Vec<_>>();
s.push_str(&rendered_items.join("\n"));
}
let meta = &ctx.link_output.metas[module.idx];
let external_modules = meta
.star_exports_from_external_modules
.iter()
.filter_map(|rec_idx| module.ecma_view.import_records[*rec_idx].resolved_module)
.chain(ctx.chunk.entry_level_external_module_idx.iter().copied())
.collect::<FxIndexSet<ModuleIdx>>();
let mut imported_external_modules: FxHashSet<SymbolRef> = ctx
.chunk
.direct_imports_from_external_modules
.iter()
.map(|(idx, _)| {
let external = &ctx.link_output.module_table[*idx]
.as_external()
.expect("Should be external module here");
external.namespace_ref
})
.chain(ctx.chunk.import_symbol_from_external_modules.iter().map(|idx| {
let external = &ctx.link_output.module_table[*idx]
.as_external()
.expect("Should be external module here");
external.namespace_ref
}))
.collect();
external_modules.iter().for_each(|idx| {
let external = &ctx.link_output.module_table[*idx].as_external().expect("Should be external module here");
let binding_ref_name = ctx
.link_output
.symbol_db
.canonical_name_for_or_original(external.namespace_ref, &ctx.chunk.canonical_names);
let import_stmt = concat_string!(
"Object.keys(",binding_ref_name, ").forEach(function (k) {\n",
" if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {\n",
" enumerable: true,\n",
" get: function () { return ",binding_ref_name,"[k]; }\n",
" });\n",
"});\n"
);
s.push('\n');
if imported_external_modules.insert(external.namespace_ref) {
writeln!(s, "var {} = require(\"{}\");", binding_ref_name, external.get_import_path(chunk, ctx.resolved_paths)).unwrap();
}
s.push_str(&import_stmt);
});
}
ChunkKind::Common => {
let rendered_items = export_items
.into_iter()
.map(|(exported_name, export_ref)| {
let canonical_ref = link_output.symbol_db.canonical_ref_for(export_ref);
let symbol = link_output.symbol_db.get(canonical_ref);
let canonical_name = link_output
.symbol_db
.canonical_name_for_or_original(canonical_ref, &chunk.canonical_names);
match &symbol.namespace_alias {
Some(ns_alias) => {
let canonical_ns_name = link_output
.symbol_db
.canonical_name_for_or_original(ns_alias.namespace_ref, &chunk.canonical_names);
let property_name = &ns_alias.property_name;
render_object_define_property(
&exported_name,
&concat_string!(canonical_ns_name, ".", property_name),
)
}
_ => render_object_define_property(&exported_name, canonical_name),
}
})
.collect::<Vec<_>>();
s.push_str(&rendered_items.join("\n"));
}
}
if s.is_empty() {
return None;
}
Some(s)
}
}
}
#[inline]
pub fn render_object_define_property(key: &str, value: &str) -> String {
concat_string!(
"Object.defineProperty(exports, '",
key,
"', {
enumerable: true,
get: function () {
return ",
value,
";
}
});"
)
}
#[inline]
pub fn render_object_define_property_value(key: &str, value: &str) -> String {
concat_string!(
"Object.defineProperty(exports, '",
key,
"', {
enumerable: true,
value: ",
value,
"
});"
)
}
pub fn get_export_items(chunk: &Chunk) -> Vec<(CompactStr, SymbolRef)> {
let mut export_items = chunk
.exports_to_other_chunks
.iter()
.flat_map(|(export_ref, alias_list)| {
alias_list.iter().map(|alias| (alias.clone(), *export_ref))
})
.collect::<Vec<_>>();
export_items.sort_unstable_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
export_items
}
pub fn get_chunk_export_names(chunk: &Chunk, graph: &LinkStageOutput) -> Vec<CompactStr> {
if let ChunkKind::EntryPoint { module: entry_id, .. } = &chunk.kind {
let entry_meta = &graph.metas[*entry_id];
if matches!(entry_meta.wrap_kind(), WrapKind::Cjs) {
return vec![CompactStr::new("default")];
}
}
get_export_items(chunk).into_iter().map(|(exported_name, _)| exported_name).collect::<Vec<_>>()
}
pub fn get_chunk_export_names_with_ctx(ctx: &GenerateContext<'_>) -> Vec<CompactStr> {
let GenerateContext { chunk, link_output, render_export_items_index_vec, .. } = ctx;
if let ChunkKind::EntryPoint { module: entry_id, .. } = &chunk.kind {
let entry_meta = &link_output.metas[*entry_id];
if matches!(entry_meta.wrap_kind(), WrapKind::Cjs) {
return vec![CompactStr::new("default")];
}
}
render_export_items_index_vec[ctx.chunk_idx].values().flatten().cloned().collect::<Vec<_>>()
}
fn must_keep_live_binding(
export_ref: SymbolRef,
symbol_db: &SymbolRefDb,
options: &NormalizedBundlerOptions,
modules: &IndexModules,
) -> bool {
let canonical_ref = symbol_db.canonical_ref_for(export_ref);
if canonical_ref.is_declared_by_const(symbol_db) {
return false;
}
if canonical_ref.is_not_reassigned(symbol_db) {
return false;
}
if !options.external_live_bindings
&& canonical_ref.is_created_by_import_stmt_that_target_external(symbol_db, modules)
{
return false;
}
true
}