use bitflags::bitflags;
use oxc::ast::ast::ObjectPropertyKind;
use oxc::semantic::{ReferenceId, ScopeFlags, SymbolId};
use oxc::{
allocator::{self, Allocator, Box as ArenaBox, CloneIn, Dummy, IntoIn, TakeIn},
ast::{
AstBuilder, NONE,
ast::{
self, ClassElement, Expression, IdentifierReference, ImportExpression, MemberExpression,
NumberBase, Statement, VariableDeclarationKind,
},
},
span::{GetSpan, GetSpanMut, SPAN},
};
use rolldown_common::{
AstScopes, Chunk, ChunkIdx, ConcatenateWrappedModuleKind, ExportsKind, ImportRecordIdx,
ImportRecordMeta, InlineConstMode, MemberExprRefResolution, Module, ModuleIdx,
ModuleNamespaceIncludedReason, ModuleType, NamespaceAlias, NormalModule, OutputExports,
OutputFormat, Platform, RenderedConcatenatedModuleParts, Specifier, SymbolRef, WrapKind,
};
use rolldown_ecmascript::ToSourceString;
use rolldown_ecmascript_utils::{
AstSnippet, BindingPatternExt, CallExpressionExt, ExpressionExt, StatementExt,
};
mod finalizer_context;
mod impl_visit_mut;
pub use finalizer_context::ScopeHoistingFinalizerContext;
use oxc_str::{CompactStr, Ident};
use rolldown_utils::ecmascript::is_validate_identifier_name;
use rolldown_utils::indexmap::{FxIndexMap, FxIndexSet};
use rustc_hash::{FxHashMap, FxHashSet};
use sugar_path::SugarPath;
use crate::hmr::utils::HmrAstBuilder;
use crate::utils::external_import_interop::import_record_needs_interop;
mod hmr;
mod rename;
enum CjsMemberProperty<'a, 'ast> {
Static(&'a str),
Computed(&'a ast::Expression<'ast>),
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TraverseState: u8 {
const TopLevel = 1;
const SmartInlineConst = 1 << 1;
const IsRootLevel = 1 << 2;
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FinalizedExprProcessHint: u8 {
const FromCjsWrapKindEntry = 1;
}
}
#[derive(Clone, Debug, Copy)]
pub enum KeepNameId<'a> {
SymbolId(SymbolId),
ReferenceId(ReferenceId),
CompactStr(&'a CompactStr),
}
pub struct ScopeHoistingFinalizer<'me, 'ast: 'me> {
pub ctx: ScopeHoistingFinalizerContext<'me>,
pub scope: &'me AstScopes,
pub alloc: &'ast Allocator,
pub snippet: AstSnippet<'ast>,
pub generated_init_esm_importee_ids: FxHashSet<ModuleIdx>,
pub scope_stack: Vec<ScopeFlags>,
pub state: TraverseState,
pub top_level_var_bindings: FxIndexSet<Ident<'ast>>,
pub cur_stmt_index: usize,
pub keep_name_statement_to_insert: Vec<(usize, CompactStr, CompactStr)>,
pub needs_hosted_top_level_binding: bool,
pub module_namespace_included: bool,
pub transferred_import_record: FxIndexMap<ImportRecordIdx, String>,
pub rendered_concatenated_wrapped_module_parts: RenderedConcatenatedModuleParts,
pub json_module_inlined_prop: Option<Box<FxHashMap<SymbolId, ast::Expression<'ast>>>>,
}
impl<'me, 'ast> ScopeHoistingFinalizer<'me, 'ast> {
pub fn is_global_identifier_reference(&self, id_ref: &IdentifierReference) -> bool {
let Some(reference_id) = id_ref.reference_id.get() else {
return false;
};
self.scope.is_unresolved(reference_id)
}
pub fn canonical_name_for(&self, symbol: SymbolRef) -> &'me str {
self.ctx.symbol_db.canonical_name_for_or_original(symbol, &self.ctx.chunk.canonical_names)
}
pub fn canonical_name_for_runtime(&self, name: &str) -> &'me str {
let sym_ref = self.ctx.runtime.resolve_symbol(name);
self.canonical_name_for(sym_ref)
}
pub fn canonical_ref_for_runtime(&self, name: &str) -> SymbolRef {
self.ctx.runtime.resolve_symbol(name)
}
pub fn finalized_expr_for_runtime_symbol(&self, name: &str) -> ast::Expression<'ast> {
let (expr, _) =
self.finalized_expr_for_symbol_ref(self.ctx.runtime.resolve_symbol(name), false, false);
expr
}
fn generate_transitive_esm_init(
&mut self,
module_idx: ModuleIdx,
body: &mut allocator::Vec<'ast, Statement<'ast>>,
) {
let mut stack = vec![module_idx];
while let Some(module_idx) = stack.pop() {
let Module::Normal(importee) = &self.ctx.modules[module_idx] else { continue };
let importee_linking_info = &self.ctx.linking_infos[importee.idx];
if !matches!(importee_linking_info.wrap_kind(), WrapKind::Esm) {
continue;
}
if !self.generated_init_esm_importee_ids.insert(importee.idx) {
continue;
}
if importee_linking_info.is_included
&& self.ctx.chunk_graph.module_to_chunk[importee.idx] == Some(self.ctx.chunk_idx)
{
let (wrapper_ref_expr, _) = self.finalized_expr_for_symbol_ref(
importee_linking_info.wrapper_ref.unwrap(),
false,
false,
);
let init_call = self.snippet.builder.expression_call(
SPAN,
wrapper_ref_expr,
NONE,
self.snippet.builder.vec(),
false,
);
body.push(self.snippet.builder.statement_expression(SPAN, init_call));
} else {
for rec in importee.import_records.iter().rev() {
if let Some(sub_importee_idx) = rec.resolved_module {
stack.push(sub_importee_idx);
}
}
}
}
}
fn collect_wrapped_esm_init_modules_for_import_record(
&self,
rec_idx: ImportRecordIdx,
) -> FxIndexSet<ModuleIdx> {
let mut init_modules = FxIndexSet::default();
let rec = &self.ctx.module.import_records[rec_idx];
let Some(importee_idx) = rec.resolved_module else { return init_modules };
let importee_linking_info = &self.ctx.linking_infos[importee_idx];
if rec.meta.contains(ImportRecordMeta::IsExportStar) {
for resolved_export in importee_linking_info.resolved_exports.values() {
self.add_wrapped_esm_init_module_for_symbol(resolved_export.symbol_ref, &mut init_modules);
}
return init_modules;
}
for named_import in
self.ctx.module.named_imports.values().filter(|item| item.record_idx == rec_idx)
{
match &named_import.imported {
Specifier::Star => {
for resolved_export in importee_linking_info.resolved_exports.values() {
self.add_wrapped_esm_init_module_for_symbol(
resolved_export.symbol_ref,
&mut init_modules,
);
}
}
Specifier::Literal(name) => {
if let Some(resolved_export) = importee_linking_info.resolved_exports.get(name) {
self.add_wrapped_esm_init_module_for_symbol(
resolved_export.symbol_ref,
&mut init_modules,
);
} else {
self
.add_wrapped_esm_init_module_for_symbol(named_import.imported_as, &mut init_modules);
}
}
}
}
init_modules
}
fn add_wrapped_esm_init_module_for_symbol(
&self,
symbol_ref: SymbolRef,
init_modules: &mut FxIndexSet<ModuleIdx>,
) {
let canonical_ref = self.ctx.symbol_db.canonical_ref_resolving_namespace(symbol_ref);
let meta = &self.ctx.linking_infos[canonical_ref.owner];
if matches!(meta.wrap_kind(), WrapKind::Esm)
&& meta.wrapper_ref.is_some()
&& !matches!(meta.concatenated_wrapped_module_kind, ConcatenateWrappedModuleKind::Inner)
{
init_modules.insert(canonical_ref.owner);
}
}
fn wrapped_esm_init_stmt_for_import_record(
&mut self,
rec_idx: ImportRecordIdx,
) -> Option<Statement<'ast>> {
let rec = &self.ctx.module.import_records[rec_idx];
if rec.resolved_module.is_some_and(|importee_idx| {
let importee_linking_info = &self.ctx.linking_infos[importee_idx];
matches!(importee_linking_info.wrap_kind(), WrapKind::None)
&& importee_linking_info.is_included
&& self.ctx.chunk_graph.module_to_chunk[importee_idx] == Some(self.ctx.chunk_idx)
}) {
return None;
}
let init_modules = self
.collect_wrapped_esm_init_modules_for_import_record(rec_idx)
.into_iter()
.collect::<Vec<_>>();
if init_modules.is_empty() {
return None;
}
let init_exprs = init_modules.into_iter().filter_map(|module_idx| {
if !self.generated_init_esm_importee_ids.insert(module_idx) {
return None;
}
let importee_linking_info = &self.ctx.linking_infos[module_idx];
let wrapper_ref = importee_linking_info.wrapper_ref?;
let is_tla_or_contains_tla_dependency =
importee_linking_info.is_tla_or_contains_tla_dependency;
let (wrapper_ref_expr, _) = self.finalized_expr_for_symbol_ref(wrapper_ref, false, false);
let init_call = ast::Expression::CallExpression(self.snippet.builder.alloc_call_expression(
SPAN,
wrapper_ref_expr,
NONE,
self.snippet.builder.vec(),
false,
));
Some(if is_tla_or_contains_tla_dependency {
ast::Expression::AwaitExpression(
self.snippet.builder.alloc_await_expression(SPAN, init_call),
)
} else {
init_call
})
});
let init_exprs = init_exprs.collect::<Vec<_>>();
match init_exprs.len() {
0 => None,
1 => init_exprs
.into_iter()
.next()
.map(|init_expr| self.snippet.builder.statement_expression(SPAN, init_expr)),
_ => {
let builder = self.builder();
Some(builder.statement_expression(
SPAN,
builder.expression_sequence(SPAN, builder.vec_from_iter(init_exprs)),
))
}
}
}
fn transform_or_remove_import_export_stmt(
&mut self,
stmt: &mut Statement<'ast>,
rec_idx: ImportRecordIdx,
) -> bool {
let rec = &self.ctx.module.import_records[rec_idx];
let Some(resolved_module_idx) = rec.resolved_module else { return true };
let Module::Normal(importee) = &self.ctx.modules[resolved_module_idx] else {
return true;
};
let importee_linking_info = &self.ctx.linking_infos[importee.idx];
match importee_linking_info.wrap_kind() {
WrapKind::None => {
if let Some(init_stmt) = self.wrapped_esm_init_stmt_for_import_record(rec_idx) {
*stmt = init_stmt;
return false;
}
}
WrapKind::Cjs => {
let merge_info = self.ctx.safely_merge_cjs_ns_map.get(&resolved_module_idx);
if merge_info.is_some() {
let chunk_idx = self.ctx.chunk_idx;
if let Some(symbol_ref_to_be_merged) =
self.ctx.chunk_graph.finalized_cjs_ns_map_idx_vec[chunk_idx].get(&rec.namespace_ref)
{
if symbol_ref_to_be_merged != &rec.namespace_ref {
return true;
}
}
}
let (importee_wrapper_ref_name, hint) = self.finalized_expr_for_symbol_ref(
importee_linking_info.wrapper_ref.unwrap(),
false,
false,
);
let require_call = if hint.contains(FinalizedExprProcessHint::FromCjsWrapKindEntry) {
importee_wrapper_ref_name
} else {
self.snippet.builder.expression_call(
SPAN,
importee_wrapper_ref_name,
NONE,
self.snippet.builder.vec(),
false,
)
};
let needs_toesm = if let Some(info) = merge_info {
info.needs_interop
} else {
import_record_needs_interop(self.ctx.module, rec_idx)
};
let init_expr = if needs_toesm {
let to_esm_fn_name = self.finalized_expr_for_runtime_symbol("__toESM");
self.snippet.wrap_with_to_esm(
to_esm_fn_name,
require_call,
self.ctx.module.should_consider_node_esm_spec_for_static_import(),
)
} else {
require_call
};
let binding_name_for_wrapper_call_ret = self.canonical_name_for(rec.namespace_ref);
*stmt = self.snippet.var_decl_stmt(binding_name_for_wrapper_call_ret, init_expr);
if self.transferred_import_record.contains_key(&rec_idx) {
self.transferred_import_record.insert(rec_idx, stmt.to_source_string());
return true;
}
return false;
}
WrapKind::Esm => {
if matches!(
importee_linking_info.concatenated_wrapped_module_kind,
ConcatenateWrappedModuleKind::Inner
) || self.generated_init_esm_importee_ids.contains(&importee.idx)
{
return true;
}
self.generated_init_esm_importee_ids.insert(importee.idx);
let (wrapper_ref_expr, _) = self.finalized_expr_for_symbol_ref(
importee_linking_info.wrapper_ref.unwrap(),
false,
false,
);
let init_call =
ast::Expression::CallExpression(self.snippet.builder.alloc_call_expression(
stmt.span(),
wrapper_ref_expr,
NONE,
self.snippet.builder.vec(),
false,
));
if importee_linking_info.is_tla_or_contains_tla_dependency {
*stmt = self.snippet.builder.statement_expression(
SPAN,
ast::Expression::AwaitExpression(
self.snippet.builder.alloc_await_expression(SPAN, init_call),
),
);
} else {
*stmt = self.snippet.builder.statement_expression(SPAN, init_call);
}
if self.transferred_import_record.contains_key(&rec_idx) {
self.transferred_import_record.insert(rec_idx, stmt.to_source_string());
return true;
}
return false;
}
}
true
}
fn finalized_expr_for_symbol_ref(
&self,
symbol_ref: SymbolRef,
preserve_this_semantic_if_needed: bool,
optimize_namespace_alias_transform: bool,
) -> (ast::Expression<'ast>, FinalizedExprProcessHint) {
if !symbol_ref.is_declared_in_root_scope(self.ctx.symbol_db) {
return (
self.snippet.id_ref_expr(self.canonical_name_for(symbol_ref), SPAN),
FinalizedExprProcessHint::empty(),
);
}
let mut canonical_ref = self.ctx.symbol_db.canonical_ref_for(symbol_ref);
let mut canonical_symbol = self.ctx.symbol_db.get(canonical_ref);
let namespace_alias = canonical_symbol.namespace_alias.as_ref();
if let Some(ns_alias) = namespace_alias {
if let Some(expr) = self.try_inline_constant_from_namespace_alias(symbol_ref, ns_alias) {
return expr;
}
canonical_ref = ns_alias.namespace_ref;
canonical_symbol = self.ctx.symbol_db.get(canonical_ref);
}
if let Some(meta) = self.ctx.constant_value_map.get(&canonical_ref) {
if !self.ctx.options.optimization.is_inline_const_smart_mode()
|| (self.state.contains(TraverseState::SmartInlineConst) || meta.safe_to_inline)
{
return (
meta.value.to_expression(AstBuilder::new(self.alloc)),
FinalizedExprProcessHint::empty(),
);
}
}
let mut hint = FinalizedExprProcessHint::empty();
let mut expr = if self.ctx.modules[canonical_ref.owner].is_external() {
if self.ctx.module.should_consider_node_esm_spec_for_static_import() {
if let Some(node_mode_name) = self.ctx.chunk.node_mode_external_ns_names.get(&canonical_ref)
{
self.snippet.id_ref_expr(node_mode_name.as_str(), SPAN)
} else {
self.snippet.id_ref_expr(self.canonical_name_for(canonical_ref), SPAN)
}
} else {
self.snippet.id_ref_expr(self.canonical_name_for(canonical_ref), SPAN)
}
} else {
match self.ctx.options.format {
rolldown_common::OutputFormat::Cjs => {
let chunk_idx_of_canonical_symbol = canonical_symbol.chunk_idx.unwrap_or_else(|| {
let symbol_name = canonical_ref.name(self.ctx.symbol_db);
panic!("{canonical_ref:?} {symbol_name:?} is not in any chunk, which is unexpected");
});
let cur_chunk_idx = self.ctx.chunk_graph.module_to_chunk[self.ctx.idx]
.expect("This module should be in a chunk");
let is_symbol_in_other_chunk = cur_chunk_idx != chunk_idx_of_canonical_symbol;
if is_symbol_in_other_chunk {
let (expr, extra_hint) = self.finalized_expr_for_cross_chunk_symbol(
cur_chunk_idx,
chunk_idx_of_canonical_symbol,
canonical_ref,
);
hint.insert(extra_hint);
expr
} else {
self.snippet.id_ref_expr(self.canonical_name_for(canonical_ref), SPAN)
}
}
_ => self.snippet.id_ref_expr(self.canonical_name_for(canonical_ref), SPAN),
}
};
if let Some(ns_alias) = namespace_alias {
if !optimize_namespace_alias_transform {
expr = ast::Expression::StaticMemberExpression(
self.snippet.builder.alloc_static_member_expression(
SPAN,
expr,
self.snippet.id_name(&ns_alias.property_name, SPAN),
false,
),
);
}
if preserve_this_semantic_if_needed {
expr = self.snippet.seq2_in_paren_expr(self.snippet.number_expr(0.0, "0"), expr);
}
}
(expr, hint)
}
fn finalized_expr_for_cross_chunk_symbol(
&self,
cur_chunk_idx: ChunkIdx,
target_chunk_idx: ChunkIdx,
canonical_ref: SymbolRef,
) -> (ast::Expression<'ast>, FinalizedExprProcessHint) {
let require_binding = &self.ctx.chunk_graph.chunk_table[cur_chunk_idx]
.require_binding_names_for_other_chunks[&target_chunk_idx];
let chunk = &self.ctx.chunk_graph.chunk_table[target_chunk_idx];
let is_entry_chunk_for_symbol = chunk.entry_module_idx() == Some(canonical_ref.owner);
let wrap_kind = self.ctx.linking_infos[canonical_ref.owner].wrap_kind();
if is_entry_chunk_for_symbol && matches!(wrap_kind, WrapKind::Cjs) {
let expr = match chunk.output_exports {
OutputExports::Default => self.snippet.id_ref_expr(require_binding, SPAN),
_ => self.snippet.literal_prop_access_member_expr_expr(require_binding, "default"),
};
return (expr, FinalizedExprProcessHint::FromCjsWrapKindEntry);
}
let exported_name = &self.ctx.chunk_graph.chunk_table[target_chunk_idx].exports_to_other_chunks
[&canonical_ref][0];
let is_default_export = exported_name.as_str() == "default";
let expr = match (&chunk.output_exports, is_default_export) {
(OutputExports::Default, true) => self.snippet.id_ref_expr(require_binding, SPAN),
_ => self.snippet.literal_prop_access_member_expr_expr(require_binding, exported_name),
};
(expr, FinalizedExprProcessHint::empty())
}
fn try_inline_constant_from_namespace_alias(
&self,
original_ref: SymbolRef,
namespace_alias: &NamespaceAlias,
) -> Option<(ast::Expression<'ast>, FinalizedExprProcessHint)> {
let inline_options = self.ctx.options.optimization.inline_const?;
let canonical_ref = self.ctx.symbol_db.canonical_ref_for(original_ref);
let named_import = self.ctx.module.named_imports.get(&canonical_ref)?;
if !matches!(&named_import.imported, Specifier::Literal(lit) if lit != "default") {
return None;
}
let import_record = &self.ctx.module.import_records[named_import.record_idx];
let importee = import_record
.resolved_module
.and_then(|module_idx| self.ctx.modules[module_idx].as_normal())?;
let resolved_export =
self.ctx.linking_infos[importee.idx].resolved_exports.get(&namespace_alias.property_name)?;
if resolved_export.cjs_conflicting_symbol_refs.is_some() {
return None;
}
let export_symbol = resolved_export.symbol_ref;
let canonical_export_ref = self.ctx.symbol_db.canonical_ref_for(export_symbol);
let constant_meta = self.ctx.constant_value_map.get(&canonical_export_ref)?;
if matches!(inline_options.mode, InlineConstMode::Smart)
&& (!self.state.contains(TraverseState::SmartInlineConst) || !constant_meta.safe_to_inline)
{
return None;
}
Some((
constant_meta.value.to_expression(AstBuilder::new(self.alloc)),
FinalizedExprProcessHint::empty(),
))
}
fn try_inline_enum_access(&self, expr: &ast::Expression<'_>) -> Option<ast::Expression<'ast>> {
let member = expr.get_member_expr()?;
let (object, property_name) = match member {
ast::MemberExpression::StaticMemberExpression(m) => (&m.object, m.property.name.as_str()),
ast::MemberExpression::ComputedMemberExpression(m) => {
let ast::Expression::StringLiteral(prop) = &m.expression else { return None };
(&m.object, prop.value.as_str())
}
ast::MemberExpression::PrivateFieldExpression(_) => return None,
};
if let ast::Expression::Identifier(ident) = object {
return self.try_inline_enum_member(ident, property_name);
}
if !matches!(expr, ast::Expression::ChainExpression(_))
&& let ast::MemberExpression::StaticMemberExpression(sm) = member
{
return self.try_inline_chained_enum_member(sm);
}
None
}
fn try_inline_enum_member(
&self,
ident: &ast::IdentifierReference<'_>,
property_name: &str,
) -> Option<ast::Expression<'ast>> {
let ref_id = ident.reference_id.get()?;
let symbol_id = self.scope.scoping().get_reference(ref_id).symbol_id()?;
let symbol_ref: SymbolRef = (self.ctx.idx, symbol_id).into();
self.try_inline_enum_member_by_ref(symbol_ref, property_name)
}
fn try_inline_chained_enum_member(
&self,
outer_expr: &ast::StaticMemberExpression<'_>,
) -> Option<ast::Expression<'ast>> {
let ast::Expression::StaticMemberExpression(inner_expr) = &outer_expr.object else {
return None;
};
let ast::Expression::Identifier(ns_ident) = &inner_expr.object else {
return None;
};
let ref_id = ns_ident.reference_id.get()?;
let symbol_id = self.scope.scoping().get_reference(ref_id).symbol_id()?;
let symbol_ref: SymbolRef = (self.ctx.idx, symbol_id).into();
let canonical_ref = self.ctx.symbol_db.canonical_ref_for(symbol_ref);
let importee = self.ctx.modules[canonical_ref.owner].as_normal()?;
let resolved_export = self.ctx.linking_infos[importee.idx]
.resolved_exports
.get(inner_expr.property.name.as_str())?;
if resolved_export.cjs_conflicting_symbol_refs.is_some() {
return None;
}
let canonical_export = self.ctx.symbol_db.canonical_ref_for(resolved_export.symbol_ref);
self.try_inline_enum_member_by_ref(canonical_export, outer_expr.property.name.as_str())
}
fn try_inline_enum_member_by_ref(
&self,
symbol_ref: SymbolRef,
property_name: &str,
) -> Option<ast::Expression<'ast>> {
let canonical_ref = self.ctx.symbol_db.canonical_ref_for(symbol_ref);
let module = self.ctx.modules[canonical_ref.owner].as_normal()?;
let symbol_name = canonical_ref.name(self.ctx.symbol_db);
let member_map = module.ecma_view.enum_member_value_map.get(symbol_name)?;
let meta = member_map.get(property_name)?;
Some(meta.value.to_expression(AstBuilder::new(self.alloc)))
}
fn var_declaration_to_expr_seq_and_bindings(
&self,
decl: &mut ast::VariableDeclaration<'ast>,
traverse_state: TraverseState,
) -> Option<(Expression<'ast>, Vec<Ident<'ast>>)> {
let should_hoist = (decl.kind.is_var() && traverse_state.contains(TraverseState::TopLevel))
|| (decl.kind.is_lexical() && traverse_state.contains(TraverseState::IsRootLevel));
if !should_hoist {
return None;
}
let mut ret = vec![];
let exprs = decl.declarations.iter_mut().filter_map(|var_decl| {
ret.extend(var_decl.id.get_binding_identifiers().iter().map(|item| item.name));
if let Some(ref mut init_expr) = var_decl.init {
let left = var_decl.id.take_in(self.alloc).into_assignment_target(self.alloc);
Some(ast::Expression::AssignmentExpression(
ast::AssignmentExpression {
left,
right: init_expr.take_in(self.alloc),
..ast::AssignmentExpression::dummy(self.alloc)
}
.into_in(self.alloc),
))
} else {
None
}
});
Some((self.builder().expression_sequence(SPAN, self.builder().vec_from_iter(exprs)), ret))
}
fn generate_declaration_of_module_namespace_object(&self) -> Vec<ast::Statement<'ast>> {
if !self.module_namespace_included {
return vec![];
}
let binding_name_for_namespace_object_ref =
self.canonical_name_for(self.ctx.module.namespace_object_ref);
let mut arg_obj_expr = ast::ObjectExpression::dummy(self.alloc);
arg_obj_expr.properties.extend(self.ctx.linking_info.canonical_exports(false).filter_map(
|(export, resolved_export)| {
let is_inlinable_constant = self
.ctx
.constant_value_map
.get(&self.ctx.symbol_db.canonical_ref_for(resolved_export.symbol_ref))
.is_some_and(|meta| !meta.commonjs_export);
if !self.ctx.used_symbol_refs.contains(&resolved_export.symbol_ref)
&& !is_inlinable_constant
{
return None;
}
let prop_name = export;
let (returned, _) =
self.finalized_expr_for_symbol_ref(resolved_export.symbol_ref, false, false);
Some(ast::ObjectPropertyKind::ObjectProperty(
ast::ObjectProperty {
key: if is_validate_identifier_name(prop_name) && prop_name != "__proto__" {
ast::PropertyKey::StaticIdentifier(
self.snippet.id_name(prop_name, SPAN).into_in(self.alloc),
)
} else {
ast::PropertyKey::StringLiteral(self.snippet.alloc_string_literal(prop_name, SPAN))
},
value: self.snippet.only_return_arrow_expr(returned),
computed: prop_name == "__proto__",
..ast::ObjectProperty::dummy(self.alloc)
}
.into_in(self.alloc),
))
},
));
let module_namespace_rhs =
if arg_obj_expr.properties.is_empty() && !self.ctx.options.generated_code.symbols {
Expression::ObjectExpression(self.builder().alloc(arg_obj_expr))
} else {
let obj_expr = ast::Argument::ObjectExpression(arg_obj_expr.into_in(self.alloc));
let args = if self.ctx.options.generated_code.symbols {
self.snippet.builder.vec_from_iter([obj_expr])
} else {
self.snippet.builder.vec_from_iter([
obj_expr,
ast::Argument::NumericLiteral(self.snippet.builder.alloc_numeric_literal(
SPAN,
1.0,
None,
NumberBase::Decimal,
)),
])
};
self.snippet.builder.expression_call_with_pure(
SPAN,
self.finalized_expr_for_runtime_symbol("__exportAll"),
NONE,
args,
false,
true,
)
};
let decl_stmt =
self.snippet.var_decl_stmt(binding_name_for_namespace_object_ref, module_namespace_rhs);
let export_all_externals_rec_ids = &self.ctx.linking_info.star_exports_from_external_modules;
let mut re_export_external_stmts: Option<_> = None;
if !export_all_externals_rec_ids.is_empty() {
match self.ctx.options.format {
OutputFormat::Esm => {
let re_export_name = self.canonical_name_for_runtime("__reExport");
let stmts = export_all_externals_rec_ids.iter().copied().flat_map(|idx| {
let rec = &self.ctx.module.import_records[idx];
if rec.meta.contains(ImportRecordMeta::EntryLevelExternal)
&& !self
.ctx
.linking_info
.module_namespace_included_reason
.contains(ModuleNamespaceIncludedReason::Unknown)
{
return vec![];
}
let importee_namespace_name = self.canonical_name_for(rec.namespace_ref);
let Some(Module::External(module)) =
rec.resolved_module.and_then(|module_idx| self.ctx.modules.get(module_idx))
else {
return vec![];
};
let importee_name = &module.get_import_path(self.ctx.chunk, self.ctx.resolved_paths);
let call_expr = self.snippet.re_export_call_expr(
self.snippet.id_ref_expr(re_export_name, SPAN),
self.snippet.id_ref_expr(binding_name_for_namespace_object_ref, SPAN),
self.snippet.id_ref_expr(importee_namespace_name, SPAN),
);
vec![
self.snippet.import_star_stmt(importee_name, importee_namespace_name),
self.snippet.builder.statement_expression(
SPAN,
Expression::CallExpression(call_expr.into_in(self.alloc)),
),
]
});
re_export_external_stmts = Some(stmts.collect::<Vec<_>>());
}
OutputFormat::Cjs | OutputFormat::Iife | OutputFormat::Umd => {
let stmts = export_all_externals_rec_ids.iter().copied().filter_map(|idx| {
let (importer_namespace_ref_expr, _) = self.finalized_expr_for_symbol_ref(
self.ctx.module.namespace_object_ref,
false,
false,
);
let rec = &self.ctx.module.import_records[idx];
let importee = rec.resolved_module.map(|module_idx| &self.ctx.modules[module_idx])?;
let re_export_call_expr = self.snippet.re_export_call_expr(
self.finalized_expr_for_runtime_symbol("__reExport"),
importer_namespace_ref_expr,
self.snippet.call_expr_with_arg_expr_expr(
"require",
self.snippet.string_literal_expr(importee.id().as_str(), SPAN),
),
);
Some(self.snippet.builder.statement_expression(
SPAN,
Expression::CallExpression(re_export_call_expr.into_in(self.alloc)),
))
});
re_export_external_stmts = Some(stmts.collect());
}
}
}
let mut ret = vec![decl_stmt];
ret.extend(re_export_external_stmts.unwrap_or_default());
ret
}
pub fn try_rewrite_import_meta_prop_expr(
&self,
member_expr: &ast::StaticMemberExpression<'ast>,
) -> Option<Expression<'ast>> {
if member_expr.object.is_import_meta() {
let original_expr_span = member_expr.span;
let is_node_cjs = matches!(
(self.ctx.options.platform, &self.ctx.options.format),
(Platform::Node, OutputFormat::Cjs)
);
let property_name = member_expr.property.name.as_str();
match property_name {
"url" => {
let new_expr = if is_node_cjs {
let require_call = self.snippet.builder.alloc_call_expression(
SPAN,
self.snippet.builder.expression_identifier(SPAN, "require"),
oxc::ast::NONE,
self.snippet.builder.vec1(ast::Argument::StringLiteral(
self.snippet.builder.alloc_string_literal(SPAN, "url", None),
)),
false,
);
let require_path_to_file_url = self.snippet.builder.alloc_static_member_expression(
SPAN,
ast::Expression::CallExpression(require_call),
self.snippet.builder.identifier_name(SPAN, "pathToFileURL"),
false,
);
let require_path_to_file_url_call = self.snippet.builder.alloc_call_expression(
SPAN,
ast::Expression::StaticMemberExpression(require_path_to_file_url),
oxc::ast::NONE,
self.snippet.builder.vec1(ast::Argument::Identifier(
self.snippet.builder.alloc_identifier_reference(SPAN, "__filename"),
)),
false,
);
let require_path_to_file_url_href =
self.snippet.builder.alloc_static_member_expression(
original_expr_span,
ast::Expression::CallExpression(require_path_to_file_url_call),
self.snippet.builder.identifier_name(SPAN, "href"),
false,
);
Some(ast::Expression::StaticMemberExpression(require_path_to_file_url_href))
} else {
None
};
return new_expr;
}
"dirname" | "filename" => {
let name = self.snippet.atom(&format!("__{property_name}"));
return is_node_cjs.then_some(ast::Expression::Identifier(
self.snippet.builder.alloc_identifier_reference(SPAN, name),
));
}
_ => {}
}
return self.rewrite_rollup_file_url(property_name);
}
None
}
fn rewrite_rollup_file_url(&self, property_name: &str) -> Option<Expression<'ast>> {
if let Some(reference_id) = property_name.strip_prefix("ROLLUP_FILE_URL_") {
let Ok(asset_file_name) = self.ctx.file_emitter.get_file_name(reference_id) else {
return None;
};
let absolute_asset_file_name = asset_file_name
.absolutize_with(self.ctx.options.cwd.as_path().join(&self.ctx.options.out_dir));
let relative_asset_path = &self.ctx.chunk.relative_path_for(&absolute_asset_file_name);
let new_expr = ast::Expression::StaticMemberExpression(
self.snippet.builder.alloc_static_member_expression(
SPAN,
self.snippet.builder.expression_new(
SPAN,
self.snippet.builder.expression_identifier(SPAN, "URL"),
NONE,
self.snippet.builder.vec_from_array([
ast::Argument::StringLiteral(self.snippet.builder.alloc_string_literal(
SPAN,
self.snippet.builder.str(relative_asset_path),
None,
)),
ast::Argument::StaticMemberExpression(
self.snippet.builder.alloc_static_member_expression(
SPAN,
self.snippet.builder.expression_meta_property(
SPAN,
self.snippet.builder.identifier_name(SPAN, "import"),
self.snippet.builder.identifier_name(SPAN, "meta"),
),
self.snippet.builder.identifier_name(SPAN, "url"),
false,
),
),
]),
),
self.snippet.builder.identifier_name(SPAN, "href"),
false,
),
);
return Some(new_expr);
}
None
}
pub fn handle_new_url_with_string_literal_and_import_meta_url(
&self,
expr: &mut ast::NewExpression<'ast>,
) -> Option<()> {
let &rec_idx = self.ctx.module.new_url_references.get(&expr.span())?;
let rec = &self.ctx.module.import_records[rec_idx];
let is_callee_global_url = matches!(expr.callee.as_identifier(), Some(ident) if ident.name == "URL" && self.is_global_identifier_reference(ident));
if !is_callee_global_url {
return None;
}
let is_second_arg_import_meta_url = expr
.arguments
.get(1)
.is_some_and(|arg| arg.as_expression().is_some_and(ExpressionExt::is_import_meta_url));
if !is_second_arg_import_meta_url {
return None;
}
let first_arg_expr = expr.arguments.first_mut().and_then(|a| a.as_expression_mut())?;
match &first_arg_expr {
ast::Expression::StringLiteral(_) => {}
ast::Expression::TemplateLiteral(tpl) if tpl.is_no_substitution_template() => {}
_ => return None,
}
let importee =
rec.resolved_module.and_then(|module_idx| self.ctx.modules[module_idx].as_normal())?;
let ref_id = self.ctx.file_emitter.file_ref_for_module(&importee.id)?;
let filename = self.ctx.file_emitter.get_file_name(&ref_id).ok()?;
let abs_path = self.ctx.options.cwd.join(&self.ctx.options.out_dir).join(filename.as_str());
let import_path = self.ctx.chunk.relative_path_for(abs_path.as_path());
*first_arg_expr = self.snippet.string_literal_expr(&import_path, first_arg_expr.span());
None
}
fn try_rewrite_member_expr(
&self,
member_expr: &ast::MemberExpression<'ast>,
) -> Option<Expression<'ast>> {
let span = member_expr.span();
match self.ctx.linking_info.resolved_member_expr_refs.get(&span) {
Some(MemberExprRefResolution {
resolved: object_ref,
prop_and_related_span_list: props,
target_commonjs_exported_symbol: target_commonjs_exported_symbol_meta,
..
}) => object_ref
.map(|object_ref| {
let mut is_inlined_commonjs_export = false;
let object_ref_expr = if let Some(export_meta) = target_commonjs_exported_symbol_meta
.and_then(|target_commonjs_exported_symbol_meta| {
self.ctx.constant_value_map.get(&target_commonjs_exported_symbol_meta.0)
}) {
is_inlined_commonjs_export = true;
export_meta.value.to_expression(AstBuilder::new(self.alloc))
} else {
let (object_ref_expr, _) = self.finalized_expr_for_symbol_ref(
object_ref,
false,
target_commonjs_exported_symbol_meta
.is_some_and(|(_symbol, is_exports_default)| !is_exports_default),
);
object_ref_expr
};
self.snippet.member_expr_or_ident_ref(
object_ref_expr,
&props[usize::from(is_inlined_commonjs_export)..],
span,
)
})
.or_else(|| Some(self.snippet.member_expr_with_void_zero_object(props, span))),
_ => {
let MemberExpression::StaticMemberExpression(static_member_expr) = member_expr else {
return None;
};
self.try_rewrite_import_meta_prop_expr(static_member_expr)
}
}
}
fn try_rewrite_cjs_member_expr_assignment_target(
&self,
target: &ast::SimpleAssignmentTarget<'ast>,
) -> Option<ast::SimpleAssignmentTarget<'ast>> {
let (id_ref, property) = match target {
ast::SimpleAssignmentTarget::StaticMemberExpression(member_expr) => {
let ast::Expression::Identifier(id_ref) = &member_expr.object else {
return None;
};
(id_ref, CjsMemberProperty::Static(member_expr.property.name.as_str()))
}
ast::SimpleAssignmentTarget::ComputedMemberExpression(member_expr) => {
let ast::Expression::Identifier(id_ref) = &member_expr.object else {
return None;
};
(id_ref, CjsMemberProperty::Computed(&member_expr.expression))
}
_ => return None,
};
let reference_id = id_ref.reference_id.get()?;
let symbol_id = self.scope.symbol_id_for(reference_id)?;
let symbol_ref: SymbolRef = (self.ctx.idx, symbol_id).into();
let canonical_ref = self.ctx.symbol_db.canonical_ref_for(symbol_ref);
let symbol = self.ctx.symbol_db.get(canonical_ref);
let ns_alias = symbol.namespace_alias.as_ref()?;
if ns_alias.property_name.as_str() != "default" {
return None;
}
let ns_name = self.canonical_name_for(ns_alias.namespace_ref);
let ns_id_ref = self.snippet.id_ref_expr(ns_name, SPAN);
let default_access =
ast::Expression::StaticMemberExpression(self.snippet.builder.alloc_static_member_expression(
SPAN,
ns_id_ref,
self.snippet.id_name("default", SPAN),
false,
));
match property {
CjsMemberProperty::Static(property_name) => {
let final_access = self.snippet.builder.alloc_static_member_expression(
SPAN,
default_access,
self.snippet.id_name(property_name, SPAN),
false,
);
Some(ast::SimpleAssignmentTarget::StaticMemberExpression(final_access))
}
CjsMemberProperty::Computed(expr) => {
let finalized_expr = match expr {
ast::Expression::Identifier(ident_ref) => self
.try_rewrite_identifier_reference_expr(ident_ref, false)
.unwrap_or_else(|| expr.clone_in(self.alloc)),
_ => expr.clone_in(self.alloc),
};
let final_access = self.snippet.builder.alloc_computed_member_expression(
SPAN,
default_access,
finalized_expr,
false,
);
Some(ast::SimpleAssignmentTarget::ComputedMemberExpression(final_access))
}
}
}
fn get_keep_name_info(&self, id: KeepNameId) -> Option<(&'me str, &'me str)> {
let symbol_ref: SymbolRef = match id {
KeepNameId::SymbolId(symbol_id) => (self.ctx.idx, symbol_id).into(),
KeepNameId::ReferenceId(reference_id) => {
let symbol_id = self.scope.symbol_id_for(reference_id)?;
(self.ctx.idx, symbol_id).into()
}
KeepNameId::CompactStr(_) => {
return None;
}
};
let original_name = symbol_ref.name(self.ctx.symbol_db);
let canonical_name = self.canonical_name_for(symbol_ref);
(original_name != canonical_name).then_some((original_name, canonical_name))
}
fn get_transformed_class_decl(
&self,
class: &mut allocator::Box<'ast, ast::Class<'ast>>,
) -> Option<ast::Declaration<'ast>> {
let scope_id = class.scope_id.get()?;
if self.scope.scoping().scope_parent_id(scope_id) != Some(self.scope.scoping().root_scope_id())
{
return None;
}
let id = class.id.take()?;
if let Some(symbol_id) = id.symbol_id.get() {
if self.ctx.module.self_referenced_class_decl_symbol_ids.contains(&symbol_id) {
let mut id = id.clone();
let new_name = self.canonical_name_for((self.ctx.idx, symbol_id).into());
id.name = self.snippet.atom(new_name).into();
class.id = Some(id);
}
}
Some(self.snippet.builder.declaration_variable(
class.span,
VariableDeclarationKind::Var,
self.snippet.builder.vec1(self.snippet.builder.variable_declarator(
SPAN,
VariableDeclarationKind::Var,
ast::BindingPattern::BindingIdentifier(self.snippet.builder.alloc(id)),
NONE,
Some(Expression::ClassExpression(ArenaBox::new_in(
class.as_mut().take_in(self.alloc),
self.alloc,
))),
false,
)),
false,
))
}
fn try_rewrite_global_require_call(
&self,
call_expr: &mut ast::CallExpression<'ast>,
) -> Option<Expression<'ast>> {
if call_expr.is_global_require_call(self.scope) && !call_expr.span.is_unspanned() {
if let Some(rec_idx) = self.ctx.module.imports.get(&call_expr.span).copied() {
let rec = &self.ctx.module.import_records[rec_idx];
let module_idx = rec.resolved_module?;
if rec.meta.contains(ImportRecordMeta::CallRuntimeRequire) {
*call_expr.callee.get_inner_expression_mut() =
self.finalized_expr_for_runtime_symbol("__require");
}
let rewrite_ast = match &self.ctx.modules[module_idx] {
Module::Normal(importee) => {
match importee.module_type {
ModuleType::Json => {
let importee_linking_info = &self.ctx.linking_infos[importee.idx];
let (wrap_ref_expr, hint) = self.finalized_expr_for_symbol_ref(
importee_linking_info.wrapper_ref.unwrap(),
false,
false,
);
if matches!(importee.exports_kind, ExportsKind::CommonJs) {
if hint.contains(FinalizedExprProcessHint::FromCjsWrapKindEntry) {
Some(wrap_ref_expr)
} else {
Some(ast::Expression::CallExpression(
self.snippet.alloc_simple_call_expr(wrap_ref_expr),
))
}
} else {
let (ns_name, _) =
self.finalized_expr_for_symbol_ref(importee.namespace_object_ref, false, false);
let to_commonjs_ref_name = self.finalized_expr_for_runtime_symbol("__toCommonJS");
Some(
self.snippet.seq2_in_paren_expr(
ast::Expression::CallExpression(
self.snippet.alloc_simple_call_expr(wrap_ref_expr),
),
ast::Expression::StaticMemberExpression(
ast::StaticMemberExpression {
object: self.snippet.call_expr_with_arg_expr(
to_commonjs_ref_name,
ns_name,
false,
),
property: self.snippet.id_name("default", SPAN),
..ast::StaticMemberExpression::dummy(self.alloc)
}
.into_in(self.alloc),
),
),
)
}
}
_ => {
let importee_linking_info = &self.ctx.linking_infos[importee.idx];
let (wrap_ref_expr, hint) = self.finalized_expr_for_symbol_ref(
importee_linking_info.wrapper_ref.unwrap(),
false,
false,
);
let wrap_ref_call_expr =
if hint.contains(FinalizedExprProcessHint::FromCjsWrapKindEntry) {
wrap_ref_expr
} else {
ast::Expression::CallExpression(self.snippet.builder.alloc_call_expression(
SPAN,
wrap_ref_expr,
NONE,
self.snippet.builder.vec(),
false,
))
};
if matches!(importee.exports_kind, ExportsKind::CommonJs)
|| rec.meta.contains(ImportRecordMeta::IsRequireUnused)
{
Some(wrap_ref_call_expr)
} else {
let (namespace_object_ref_expr, _) =
self.finalized_expr_for_symbol_ref(importee.namespace_object_ref, false, false);
let is_json_module = rec.meta.contains(ImportRecordMeta::JsonModule);
let to_commonjs_expr = self.finalized_expr_for_runtime_symbol("__toCommonJS");
let to_commonjs_call_expr =
ast::Expression::CallExpression(self.snippet.builder.alloc_call_expression(
SPAN,
to_commonjs_expr,
NONE,
self.snippet.builder.vec1(ast::Argument::from(namespace_object_ref_expr)),
false,
));
let final_expr = if is_json_module {
Expression::from(self.snippet.builder.member_expression_static(
SPAN,
to_commonjs_call_expr,
self.snippet.id_name("default", SPAN),
false,
))
} else {
to_commonjs_call_expr
};
Some(self.snippet.seq2_in_paren_expr(wrap_ref_call_expr, final_expr))
}
}
}
}
Module::External(importee) => {
let request_path =
call_expr.arguments.get_mut(0).expect("require should have an argument");
*request_path = ast::Argument::StringLiteral(self.snippet.alloc_string_literal(
&importee.get_import_path(self.ctx.chunk, self.ctx.resolved_paths),
request_path.span(),
));
None
}
};
return rewrite_ast;
}
}
None
}
fn try_rewrite_inline_dynamic_import_expr(
&self,
import_expr: &oxc::allocator::Box<'ast, ImportExpression<'ast>>,
) -> Option<Expression<'ast>> {
let rec_idx = self.ctx.module.imports.get(&import_expr.span)?;
let rec = &self.ctx.module.import_records[*rec_idx];
let importee_id = rec.resolved_module?;
if rec.meta.contains(ImportRecordMeta::DeadDynamicImport) {
return Some(
self
.snippet
.promise_resolve_then_call_expr(self.snippet.object_freeze_dynamic_import_polyfill()),
);
}
if self.ctx.options.code_splitting.is_disabled() {
match &self.ctx.modules[importee_id] {
Module::Normal(importee) => {
let importee_linking_info = &self.ctx.linking_infos[importee_id];
let new_expr = match importee_linking_info.wrap_kind() {
WrapKind::Esm => {
let importee_linking_info = &self.ctx.linking_infos[importee_id];
let importee_wrapper_ref_name =
self.canonical_name_for(importee_linking_info.wrapper_ref.unwrap());
let importee_namespace_name = self.canonical_name_for(importee.namespace_object_ref);
if importee_linking_info.is_tla_or_contains_tla_dependency {
Some(self.snippet.callee_then_call_expr(
self.snippet.call_expr_expr(importee_wrapper_ref_name),
self.snippet.id_ref_expr(importee_namespace_name, SPAN),
))
} else {
Some(self.snippet.promise_resolve_then_call_expr(self.snippet.seq2_in_paren_expr(
self.snippet.call_expr_expr(importee_wrapper_ref_name),
self.snippet.id_ref_expr(importee_namespace_name, SPAN),
)))
}
}
WrapKind::Cjs => {
let to_esm_fn_name = self.canonical_name_for_runtime("__toESM");
let importee_wrapper_ref_name =
self.canonical_name_for(importee_linking_info.wrapper_ref.unwrap());
Some(
self.snippet.promise_resolve_then_call_expr(
self.snippet.wrap_with_to_esm(
self
.snippet
.builder
.expression_identifier(SPAN, self.snippet.builder.str(to_esm_fn_name)),
self.snippet.call_expr_expr(importee_wrapper_ref_name),
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
),
),
)
}
WrapKind::None => {
if cfg!(debug_assertions) {
unreachable!()
}
None
}
};
return new_expr;
}
Module::External(_) => {
}
}
}
None
}
#[expect(clippy::too_many_lines)]
fn remove_unused_top_level_stmt(&mut self, program: &mut ast::Program<'ast>) -> usize {
let mut last_import_stmt_idx = None;
let old_body = program.body.take_in(self.alloc);
old_body.into_iter().enumerate().zip(self.ctx.stmt_infos.iter_enumerated().skip(1)).for_each(
|((_top_stmt_idx, mut top_stmt), (stmt_info_idx, _stmt_info))| {
let is_stmt_included = self.ctx.linking_info.stmt_info_included.has_bit(stmt_info_idx);
if !is_stmt_included {
if matches!(self.ctx.linking_info.wrap_kind(), WrapKind::Esm) {
let rec_idx = if let Some(export_all) = top_stmt.as_export_all_declaration() {
Some(self.ctx.module.imports[&export_all.span])
} else if let Some(named_decl) = top_stmt.as_export_named_declaration() {
named_decl.source.as_ref().map(|_| self.ctx.module.imports[&named_decl.span])
} else {
None
};
if let Some(importee_idx) =
rec_idx.and_then(|idx| self.ctx.module.import_records[idx].resolved_module)
{
self.generate_transitive_esm_init(importee_idx, &mut program.body);
}
}
return;
}
let is_module_decl = is_stmt_included && top_stmt.is_module_declaration_with_source();
if let Some(import_decl) = top_stmt.as_import_declaration() {
let span = import_decl.span;
let rec_idx = self.ctx.module.imports[&import_decl.span];
if self.transform_or_remove_import_export_stmt(&mut top_stmt, rec_idx) {
for comment in &mut program.comments {
if comment.attached_to == span.start {
comment.attached_to = 0;
}
if comment.attached_to > span.start {
break;
}
}
return;
}
} else if let Some(export_all_decl) = top_stmt.as_export_all_declaration() {
let rec_idx = self.ctx.module.imports[&export_all_decl.span];
if let Some(_alias) = &export_all_decl.exported {
if self.transform_or_remove_import_export_stmt(&mut top_stmt, rec_idx) {
return;
}
} else {
let rec = &self.ctx.module.import_records[rec_idx];
let Some(module_idx) = rec.resolved_module else { return };
match &self.ctx.modules[module_idx] {
Module::Normal(importee) => {
let importee_linking_info = &self.ctx.linking_infos[importee.idx];
if matches!(importee_linking_info.wrap_kind(), WrapKind::None)
&& let Some(init_stmt) = self.wrapped_esm_init_stmt_for_import_record(rec_idx)
{
program.body.push(init_stmt);
}
if matches!(importee_linking_info.wrap_kind(), WrapKind::Esm)
&& !matches!(
importee_linking_info.concatenated_wrapped_module_kind,
ConcatenateWrappedModuleKind::Inner
)
{
let wrapper_ref_name =
self.canonical_name_for(importee_linking_info.wrapper_ref.unwrap());
let mut init_expr = self.snippet.call_expr_expr(wrapper_ref_name);
if importee_linking_info.is_tla_or_contains_tla_dependency {
init_expr = ast::Expression::AwaitExpression(
self.snippet.builder.alloc_await_expression(SPAN, init_expr),
);
}
program.body.push(self.snippet.builder.statement_expression(SPAN, init_expr));
}
match importee.exports_kind {
ExportsKind::Esm => {
if importee_linking_info.has_dynamic_exports {
let (importer_namespace_ref, _) = self.finalized_expr_for_symbol_ref(
self.ctx.module.namespace_object_ref,
false,
false,
);
let (importee_namespace_ref, _) = self.finalized_expr_for_symbol_ref(
importee.namespace_object_ref,
false,
false,
);
let call_expr = self.snippet.re_export_call_expr(
self.finalized_expr_for_runtime_symbol("__reExport"),
importer_namespace_ref,
importee_namespace_ref,
);
let stmt = ast::Statement::ExpressionStatement(
self.builder().alloc_expression_statement(
SPAN,
Expression::CallExpression(call_expr.into_in(self.alloc)),
),
);
program.body.push(stmt);
}
}
ExportsKind::CommonJs => {
if !self.module_namespace_included {
return;
}
let re_export_fn_name = self.finalized_expr_for_runtime_symbol("__reExport");
let (importer_namespace_ref, _) = self.finalized_expr_for_symbol_ref(
self.ctx.module.namespace_object_ref,
false,
false,
);
let to_esm_fn_ref = self.finalized_expr_for_runtime_symbol("__toESM");
let (importee_wrapper_ref_expr, _) = self.finalized_expr_for_symbol_ref(
importee_linking_info.wrapper_ref.unwrap(),
false,
false,
);
let call_expr = self.snippet.re_export_call_expr(
re_export_fn_name,
importer_namespace_ref,
self.snippet.wrap_with_to_esm(
to_esm_fn_ref,
ast::Expression::CallExpression(
self.snippet.builder.alloc_call_expression(
SPAN,
importee_wrapper_ref_expr,
NONE,
self.snippet.builder.vec(),
false,
),
),
self.ctx.module.should_consider_node_esm_spec_for_static_import(),
),
);
let stmt = ast::Statement::ExpressionStatement(
self.builder().alloc_expression_statement(
SPAN,
Expression::CallExpression(call_expr.into_in(self.alloc)),
),
);
program.body.push(stmt);
}
ExportsKind::None => {}
}
}
Module::External(_importee) => {
match self.ctx.options.format {
rolldown_common::OutputFormat::Esm
| rolldown_common::OutputFormat::Iife
| rolldown_common::OutputFormat::Umd
| rolldown_common::OutputFormat::Cjs => {
return;
}
}
}
}
return;
}
} else if let Some(default_decl) = top_stmt.as_export_default_declaration_mut() {
use ast::ExportDefaultDeclarationKind;
let default_decl_span = default_decl.span;
match &mut default_decl.declaration {
ast::ExportDefaultDeclarationKind::Identifier(id)
if self.scope.scoping().get_reference(id.reference_id()).symbol_id().is_some_and(
|symbol_id| symbol_id == self.ctx.module.default_export_ref.symbol,
) =>
{
return;
}
decl @ ast::match_expression!(ExportDefaultDeclarationKind) => {
let expr = decl.to_expression_mut();
let canonical_name_for_default_export_ref =
self.canonical_name_for(self.ctx.module.default_export_ref);
let mut init_expr = expr.take_in(self.alloc);
if self.ctx.options.keep_names {
let inner_expr = init_expr.without_parentheses_mut();
let needs_inline_name = match inner_expr {
ast::Expression::FunctionExpression(func) if func.id.is_none() => true,
ast::Expression::ClassExpression(class_expression)
if class_expression.id.is_none() =>
{
if let Some(element) = self.keep_name_helper_for_class(
Some(KeepNameId::CompactStr(&CompactStr::from("default"))),
&class_expression.body,
) {
class_expression.body.body.insert(0, element);
}
false
}
ast::Expression::ArrowFunctionExpression(_) => true,
_ => false,
};
if needs_inline_name {
let name_ref = self.canonical_ref_for_runtime("__name");
let (finalized_callee, _) =
self.finalized_expr_for_symbol_ref(name_ref, false, false);
init_expr = self.snippet.keep_name_call_expr(
"default",
init_expr,
finalized_callee,
true, );
}
}
top_stmt =
self.snippet.var_decl_stmt(canonical_name_for_default_export_ref, init_expr);
}
ast::ExportDefaultDeclarationKind::FunctionDeclaration(func) => {
if func.id.is_none() {
let canonical_name_for_default_export_ref =
self.canonical_name_for(self.ctx.module.default_export_ref);
func.id = Some(self.snippet.id(canonical_name_for_default_export_ref, SPAN));
if self.ctx.options.keep_names {
let insert_position = program.body.len() + 1;
self.keep_name_statement_to_insert.push((
insert_position,
CompactStr::new("default"),
CompactStr::new(canonical_name_for_default_export_ref),
));
}
}
let func = func.as_mut().take_in(self.alloc);
top_stmt = ast::Statement::FunctionDeclaration(ArenaBox::new_in(func, self.alloc));
}
ast::ExportDefaultDeclarationKind::ClassDeclaration(class) => {
if class.id.is_none() {
let canonical_name_for_default_export_ref =
self.canonical_name_for(self.ctx.module.default_export_ref);
class.id = Some(self.snippet.id(canonical_name_for_default_export_ref, SPAN));
if self.ctx.options.keep_names {
let default_name = CompactStr::from("default");
if let Some(element) = self.keep_name_helper_for_class(
Some(KeepNameId::CompactStr(&default_name)),
&class.body,
) {
class.body.body.insert(0, element);
}
}
}
let mut class = class.as_mut().take_in(self.alloc);
class.span = default_decl_span;
top_stmt = ast::Statement::ClassDeclaration(ArenaBox::new_in(class, self.alloc));
}
_ => {}
}
*top_stmt.span_mut() = default_decl_span;
} else if let Some(named_decl) = top_stmt.as_export_named_declaration_mut() {
if named_decl.source.is_none() {
let named_decl_span = named_decl.span;
if let Some(decl) = &mut named_decl.declaration {
*decl.span_mut() = named_decl_span;
top_stmt = ast::Statement::from(decl.take_in(self.alloc));
} else {
return;
}
} else {
let rec_idx = self.ctx.module.imports[&named_decl.span];
if self.transform_or_remove_import_export_stmt(&mut top_stmt, rec_idx) {
return;
}
}
}
if self.ctx.options.top_level_var {
if let Statement::VariableDeclaration(var_decl) = &mut top_stmt {
var_decl.kind = ast::VariableDeclarationKind::Var;
for decl in &mut var_decl.declarations {
decl.kind = VariableDeclarationKind::Var;
}
}
if let Statement::ClassDeclaration(class_decl) = &mut top_stmt {
if let Some(mut decl) = self.get_transformed_class_decl(class_decl) {
top_stmt = Statement::from(decl.take_in(self.alloc));
}
}
}
program.body.push(top_stmt);
if is_module_decl {
last_import_stmt_idx = Some(program.body.len());
}
},
);
last_import_stmt_idx.unwrap_or(0)
}
fn process_fn(
&self,
symbol_binding_id: Option<KeepNameId>,
name_binding_id: Option<KeepNameId>,
) -> Option<(usize, CompactStr, CompactStr)> {
if !self.ctx.options.keep_names {
return None;
}
let (original_name, _) = self.get_keep_name_info(name_binding_id?)?;
let (_, canonical_name) = self.get_keep_name_info(symbol_binding_id?)?;
let original_name: CompactStr = CompactStr::new(original_name);
let new_name = CompactStr::new(canonical_name);
let insert_position = self.cur_stmt_index + 1;
Some((insert_position, original_name, new_name))
}
fn process_keep_name_for_expression(
&self,
keep_name_id: Option<KeepNameId>,
expr: &mut ast::Expression<'ast>,
) {
if !self.ctx.options.keep_names || self.ctx.runtime.id() == self.ctx.idx {
return;
}
match expr {
ast::Expression::ClassExpression(class_expression) => {
if class_expression.id.is_some() {
return;
}
if let Some(element) = self.keep_name_helper_for_class(keep_name_id, &class_expression.body)
{
class_expression.body.body.insert(0, element);
}
}
ast::Expression::FunctionExpression(fn_expression) => {
if fn_expression.id.is_some() {
return;
}
if let Some((_insert_position, original_name, _)) =
self.process_fn(keep_name_id, keep_name_id)
{
let fn_expr = expr.take_in(self.alloc);
let name_ref = self.canonical_ref_for_runtime("__name");
let (finalized_callee, _) = self.finalized_expr_for_symbol_ref(name_ref, false, false);
*expr = self.snippet.keep_name_call_expr(&original_name, fn_expr, finalized_callee, true);
}
}
ast::Expression::ArrowFunctionExpression(_fn_expr) => {
if let Some((_insert_position, original_name, _)) =
self.process_fn(keep_name_id, keep_name_id)
{
let fn_expr = expr.take_in(self.alloc);
let name_ref = self.canonical_ref_for_runtime("__name");
let (finalized_callee, _) = self.finalized_expr_for_symbol_ref(name_ref, false, false);
*expr = self.snippet.keep_name_call_expr(&original_name, fn_expr, finalized_callee, true);
}
}
_ => {}
}
}
fn keep_name_helper_for_class(
&self,
id: Option<KeepNameId>,
class_body: &ast::ClassBody<'ast>,
) -> Option<ClassElement<'ast>> {
if !self.ctx.options.keep_names {
return None;
}
let keep_name_id = id?;
if Self::class_body_has_static_name(class_body) {
return None;
}
let original_name = match keep_name_id {
KeepNameId::CompactStr(name) => {
name.clone()
}
KeepNameId::SymbolId(_) | KeepNameId::ReferenceId(_) => {
let (original_name, _) = self.get_keep_name_info(keep_name_id)?;
let original_name: CompactStr = CompactStr::new(original_name);
original_name
}
};
let name_ref = self.canonical_ref_for_runtime("__name");
let (finalized_callee, _) = self.finalized_expr_for_symbol_ref(name_ref, false, false);
Some(self.snippet.static_block_keep_name_helper(&original_name, finalized_callee))
}
fn class_body_has_static_name(body: &ast::ClassBody<'ast>) -> bool {
body.body.iter().any(|element| match element {
ClassElement::MethodDefinition(method) => {
method.r#static && method.key.static_name().is_some_and(|name| name == "name")
}
ClassElement::PropertyDefinition(prop) => {
prop.r#static && prop.key.static_name().is_some_and(|name| name == "name")
}
ClassElement::AccessorProperty(accessor) => {
accessor.r#static && accessor.key.static_name().is_some_and(|name| name == "name")
}
_ => false,
})
}
fn insert_keep_name_statements(
&self,
statements: &mut allocator::Vec<'ast, ast::Statement<'ast>>,
) {
for (stmt_index, original_name, new_name) in self.keep_name_statement_to_insert.iter().rev() {
let name_ref = self.canonical_ref_for_runtime("__name");
let (finalized_callee, _) = self.finalized_expr_for_symbol_ref(name_ref, false, false);
let target =
self.snippet.builder.expression_identifier(SPAN, self.snippet.builder.str(new_name));
statements.insert(
*stmt_index,
self.snippet.builder.statement_expression(
SPAN,
self.snippet.keep_name_call_expr(original_name, target, finalized_callee, false),
),
);
}
}
fn rewrite_dynamic_import_for_merged_entry(
&self,
expr: &mut ast::ImportExpression<'ast>,
importee: &NormalModule,
importee_chunk: &Chunk,
importee_chunk_idx: ChunkIdx,
) -> Option<Expression<'ast>> {
let importee_idx = importee.idx;
let needs_namespace_extraction = self
.ctx
.chunk_graph
.common_chunk_exported_facade_chunk_namespace
.get(&importee_chunk_idx)
.is_some_and(|set| set.contains(&importee_idx));
if !needs_namespace_extraction {
return None;
}
let is_importer_importee_in_same_chunk = importee_chunk.modules.contains(&self.ctx.idx);
if is_importer_importee_in_same_chunk {
let importee_meta = &self.ctx.linking_infos[importee.idx];
let finalized_expr = match importee_meta.wrap_kind() {
WrapKind::Cjs => {
let importee_wrapper_ref = self.ctx.linking_infos[importee.idx].wrapper_ref.unwrap();
let (finalized_importee_wrapper_ref, _) =
self.finalized_expr_for_symbol_ref(importee_wrapper_ref, false, false);
let finalized_to_esm = self.finalized_expr_for_runtime_symbol("__toESM");
let wrapper_ref_call_expr = self.snippet.builder.expression_call(
SPAN,
finalized_importee_wrapper_ref,
NONE,
self.snippet.builder.vec(),
false,
);
self.snippet.wrap_with_to_esm(
finalized_to_esm,
wrapper_ref_call_expr,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
)
}
WrapKind::Esm => {
let importee_wrapper_ref = self.ctx.linking_infos[importee.idx].wrapper_ref.unwrap();
let (finalized_importee_wrapper_ref, _) =
self.finalized_expr_for_symbol_ref(importee_wrapper_ref, false, false);
let (finalized_namespace, _) =
self.finalized_expr_for_symbol_ref(importee.namespace_object_ref, false, false);
let wrapper_call_expr = ast::Expression::CallExpression(
self.snippet.alloc_simple_call_expr(finalized_importee_wrapper_ref),
);
self.snippet.seq2_in_paren_expr(wrapper_call_expr, finalized_namespace)
}
WrapKind::None => {
let (finalized_expr, _) =
self.finalized_expr_for_symbol_ref(importee.namespace_object_ref, false, false);
finalized_expr
}
};
Some(self.snippet.promise_resolve_then_call_expr(finalized_expr))
} else {
let importee_meta = &self.ctx.linking_infos[importee_idx];
let wrap_kind = importee_meta.wrap_kind();
let primary_export_symbol = match wrap_kind {
WrapKind::Cjs | WrapKind::Esm => importee_meta.wrapper_ref,
WrapKind::None => self.ctx.modules[importee_idx].namespace_object_ref(),
};
let primary_export_name = primary_export_symbol.and_then(|sym| {
importee_chunk.exports_to_other_chunks.get(&sym).and_then(|names| names.first())
});
let namespace_export_name = if matches!(wrap_kind, WrapKind::Esm) {
self.ctx.modules[importee_idx].namespace_object_ref().and_then(|ns_ref| {
importee_chunk.exports_to_other_chunks.get(&ns_ref).and_then(|names| names.first())
})
} else {
None
};
match primary_export_name {
Some(name) => {
let base_expr = if matches!(self.ctx.options.format, OutputFormat::Cjs) {
let import_path = self.ctx.chunk.import_path_for(importee_chunk);
let require_call_expr =
ast::Expression::CallExpression(self.snippet.builder.alloc_call_expression(
SPAN,
self.snippet.builder.expression_identifier(SPAN, "require"),
NONE,
self.snippet.builder.vec1(ast::Argument::StringLiteral(
self.snippet.alloc_string_literal(&import_path, expr.span),
)),
false,
));
self.snippet.promise_resolve_then_call_expr(require_call_expr)
} else {
let import_expr = expr.take_in_box(self.builder().allocator);
Expression::ImportExpression(import_expr)
};
match wrap_kind {
WrapKind::Cjs => {
let finalized_to_esm = self.finalized_expr_for_runtime_symbol("__toESM");
let call_expr = self.snippet.then_call_cjs_wrapper_with_to_esm(
base_expr,
name,
finalized_to_esm,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
);
Some(Expression::CallExpression(call_expr))
}
WrapKind::Esm => {
if let Some(ns_name) = namespace_export_name {
let call_expr =
self.snippet.then_call_esm_wrapper_with_namespace(base_expr, name, ns_name);
Some(Expression::CallExpression(call_expr))
} else {
tracing::warn!(
"ESM wrapped module {:?} in chunk {:?} has wrapper but no namespace export.",
importee_idx,
importee_chunk_idx
);
None
}
}
WrapKind::None => {
let call_expr = self.snippet.then_extract_property(base_expr, name);
Some(Expression::CallExpression(call_expr))
}
}
}
None => {
tracing::warn!(
"Merged dynamic entry module {:?} in chunk {:?} has no export name in exports_to_other_chunks. \
This indicates an inconsistent state in the chunk graph where the module is marked as merged \
but its namespace export is not properly tracked.",
importee_idx,
importee_chunk_idx
);
None
}
}
}
}
fn try_rewrite_import_expression(&self, node: &mut ast::Expression<'ast>) -> bool {
let ast::Expression::ImportExpression(expr) = node else {
return false;
};
if expr.options.is_some() {
return false;
}
let (Some(str), Some(rec_idx)) =
(expr.source.as_static_module_request(), self.ctx.module.imports.get(&expr.span))
else {
if matches!(self.ctx.options.format, OutputFormat::Cjs)
&& !self.ctx.options.dynamic_import_in_cjs
{
let source = expr.source.take_in(self.alloc);
let require_call = self.snippet.call_expr_with_arg_expr_expr("require", source);
let to_esm_fn_name = self.finalized_expr_for_runtime_symbol("__toESM");
let wrapped = self.snippet.wrap_with_to_esm(
to_esm_fn_name,
require_call,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
);
*node = self.snippet.promise_resolve_then_call_expr(wrapped);
return true;
}
return false;
};
let mut needs_to_esm_helper = false;
let rec = &self.ctx.module.import_records[*rec_idx];
let Some(importee_idx) = rec.resolved_module else { return true };
match &self.ctx.modules[importee_idx] {
Module::Normal(importee) => {
let Some(&importee_chunk_idx) =
self.ctx.chunk_graph.entry_module_to_entry_chunk.get(&importee_idx)
else {
*node = self.snippet.builder.void_0(SPAN);
return true;
};
let Some(importee_chunk) = self.ctx.chunk_graph.chunk_table.get(importee_chunk_idx) else {
return false;
};
let import_path = self.ctx.chunk.import_path_for(importee_chunk);
expr.source = Expression::StringLiteral(
self.snippet.alloc_string_literal(&import_path, expr.source.span()),
);
if let Some(rewritten_expr) = self.rewrite_dynamic_import_for_merged_entry(
expr,
importee,
importee_chunk,
importee_chunk_idx,
) {
*node = rewritten_expr;
return true;
}
if matches!(self.ctx.options.format, OutputFormat::Cjs) {
let mut require_call_expr =
ast::Expression::CallExpression(self.snippet.builder.alloc_call_expression(
SPAN,
self.snippet.builder.expression_identifier(SPAN, "require"),
NONE,
self.snippet.builder.vec1(ast::Argument::StringLiteral(
self.snippet.alloc_string_literal(&import_path, expr.span),
)),
false,
));
if importee.exports_kind.is_commonjs() {
let to_esm_fn_name = self.finalized_expr_for_runtime_symbol("__toESM");
let require_default_expr = ast::Expression::StaticMemberExpression(
self.snippet.builder.alloc_static_member_expression(
SPAN,
require_call_expr,
self.snippet.builder.identifier_name(SPAN, "default"),
false,
),
);
require_call_expr = self.snippet.wrap_with_to_esm(
to_esm_fn_name,
require_default_expr,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
);
}
*node = self.snippet.promise_resolve_then_call_expr(require_call_expr);
return true;
}
needs_to_esm_helper = importee.exports_kind.is_commonjs();
}
Module::External(importee) => {
let import_path = importee.get_import_path(self.ctx.chunk, self.ctx.resolved_paths);
if str != import_path {
expr.source = Expression::StringLiteral(
self.snippet.alloc_string_literal(&import_path, expr.source.span()),
);
}
if matches!(self.ctx.options.format, OutputFormat::Cjs)
&& !self.ctx.options.dynamic_import_in_cjs
{
let source = expr.source.take_in(self.alloc);
let require_call = self.snippet.call_expr_with_arg_expr_expr("require", source);
let to_esm_fn_name = self.finalized_expr_for_runtime_symbol("__toESM");
let wrapped = self.snippet.wrap_with_to_esm(
to_esm_fn_name,
require_call,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
);
*node = self.snippet.promise_resolve_then_call_expr(wrapped);
return true;
}
}
}
if needs_to_esm_helper {
let original_import_expr = node.take_in(self.alloc);
let to_esm_fn_name = self.finalized_expr_for_runtime_symbol("__toESM");
let m_default_expr = ast::Expression::StaticMemberExpression(
self.snippet.builder.alloc_static_member_expression(
SPAN,
self.snippet.builder.expression_identifier(SPAN, "m"),
self.snippet.builder.identifier_name(SPAN, "default"),
false,
),
);
let to_esm_call = self.snippet.wrap_with_to_esm(
to_esm_fn_name,
m_default_expr,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
);
let arrow_fn = self.snippet.builder.alloc_arrow_function_expression(
SPAN,
true, false, NONE,
self.snippet.builder.formal_parameters(
SPAN,
ast::FormalParameterKind::ArrowFormalParameters,
self.snippet.builder.vec1(
self.snippet.builder.formal_parameter(
SPAN,
self.snippet.builder.vec(),
self
.snippet
.builder
.binding_pattern_binding_identifier(SPAN, self.snippet.builder.str("m")),
NONE,
NONE,
false,
None,
false,
false,
),
),
NONE,
),
NONE,
self.snippet.builder.function_body(
SPAN,
self.snippet.builder.vec(),
self.snippet.builder.vec1(self.snippet.builder.statement_expression(SPAN, to_esm_call)),
),
);
let callee = self.snippet.builder.alloc_static_member_expression(
SPAN,
original_import_expr,
self.snippet.builder.identifier_name(SPAN, "then"),
false,
);
let call_expr = self.snippet.builder.alloc_call_expression(
SPAN,
ast::Expression::StaticMemberExpression(callee),
NONE,
self.snippet.builder.vec1(ast::Argument::ArrowFunctionExpression(arrow_fn)),
false,
);
*node = ast::Expression::CallExpression(call_expr);
}
true
}
fn try_inline_json_module_prop(&mut self, it: &mut Statement<'ast>) -> Option<()> {
let json_module_inlined_prop = self.json_module_inlined_prop.as_mut()?;
let decl = it.as_declaration_mut()?;
let ast::Declaration::VariableDeclaration(var_decl) = decl else {
return None;
};
let first_decl = var_decl.declarations.first_mut()?;
let init = first_decl.init.as_mut()?;
let id = first_decl.id.get_binding_identifier()?.symbol_id.get();
match id {
Some(id) => {
let symbol_ref: SymbolRef = (self.ctx.idx, id).into();
if !self
.ctx
.module
.json_module_none_self_reference_included_symbol
.as_ref()?
.contains(&symbol_ref)
{
json_module_inlined_prop.insert(id, init.take_in(self.alloc));
*it = self.snippet.builder.statement_empty(SPAN);
}
}
None => {
let Expression::ObjectExpression(obj_expr) = init else {
return None;
};
for ele in &mut obj_expr.properties {
let ObjectPropertyKind::ObjectProperty(prop) = ele else {
continue;
};
let Some(identifier) = prop.value.as_identifier() else {
continue;
};
let reference_id = identifier.reference_id();
let Some(symbol_id) = self.scope.symbol_id_for(reference_id) else {
continue;
};
let Some(replaced_expr) = json_module_inlined_prop.remove(&symbol_id) else {
continue;
};
prop.value = replaced_expr;
}
}
}
Some(())
}
}