use bitflags::bitflags;
use oxc::ast::ast::ObjectPropertyKind;
use oxc::semantic::{ScopeFlags, SymbolId};
use oxc::{
allocator::{self, Allocator, Box as ArenaBox, CloneIn, Dummy, IntoIn, TakeIn},
ast::{
AstBuilder, NONE,
ast::{
self, BindingIdentifier, ClassElement, Expression, IdentifierReference, ImportExpression,
MemberExpression, NumberBase, Statement, VariableDeclarationKind,
},
},
span::{Atom, GetSpan, GetSpanMut, SPAN},
};
use rolldown_common::{
AstScopes, ConcatenateWrappedModuleKind, ExportsKind, ImportRecordIdx, ImportRecordMeta,
MemberExprRefResolution, Module, ModuleIdx, ModuleNamespaceIncludedReason, ModuleType,
OutputFormat, Platform, RenderedConcatenatedModuleParts, 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::{FinalizerMutableState, ScopeHoistingFinalizerContext};
use oxc::span::CompactStr;
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;
mod hmr;
mod rename;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TraverseState: u8 {
const TopLevel = 1;
const SmartInlineConst = 1 << 1;
const IsRootLevel = 1 << 2;
}
}
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<Atom<'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 CompactStr {
self.ctx.symbol_db.canonical_name_for(symbol, &self.ctx.chunk.canonical_names).unwrap_or_else(|| {
panic!(
"canonical name not found for {symbol:?}, original_name: {:?} in module {:?} when finalizing module {:?} in chunk {:?}",
symbol.name(self.ctx.symbol_db),
self.ctx.modules.get(symbol.owner).map_or("unknown", |module| module.stable_id()),
self.ctx.modules.get(self.ctx.id).map_or("unknown", |module| module.stable_id()),
self.ctx.chunk.create_reasons.join(";")
);
})
}
pub fn canonical_name_for_runtime(&self, name: &str) -> &CompactStr {
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> {
self.finalized_expr_for_symbol_ref(self.ctx.runtime.resolve_symbol(name), false, false)
}
fn transform_or_remove_import_export_stmt(
&mut self,
stmt: &mut Statement<'ast>,
rec_id: ImportRecordIdx,
) -> bool {
let rec = &self.ctx.module.import_records[rec_id];
let Module::Normal(importee) = &self.ctx.modules[rec.resolved_module] else {
return true;
};
let importee_linking_info = &self.ctx.linking_infos[importee.idx];
match importee_linking_info.wrap_kind() {
WrapKind::None => {
}
WrapKind::Cjs => {
if rec.meta.contains(ImportRecordMeta::SafelyMergeCjsNs) {
let chunk_idx = self.ctx.chunk_id;
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 to_esm_fn_name = self.finalized_expr_for_runtime_symbol("__toESM");
let importee_wrapper_ref_name = self.finalized_expr_for_symbol_ref(
importee_linking_info.wrapper_ref.unwrap(),
false,
false,
);
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,
self.snippet.wrap_with_to_esm(
to_esm_fn_name,
self.snippet.builder.expression_call(
SPAN,
importee_wrapper_ref_name,
NONE,
self.snippet.builder.vec(),
false,
),
self.ctx.module.should_consider_node_esm_spec_for_static_import(),
),
);
if self.transferred_import_record.contains_key(&rec_id) {
self.transferred_import_record.insert(rec_id, 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 self.ctx.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_id) {
self.transferred_import_record.insert(rec_id, 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> {
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);
}
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;
if let Some(ns_alias) = namespace_alias {
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));
}
}
let mut expr = if self.ctx.modules[canonical_ref.owner].is_external() {
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_id.unwrap_or_else(|| {
let symbol_name = canonical_ref.name(self.ctx.symbol_db);
eprintln!(
"{canonical_ref:?} {symbol_name:?} is not in any chunk, which is unexpected",
);
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.id]
.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 exported_name = &self.ctx.chunk_graph.chunk_table[chunk_idx_of_canonical_symbol]
.exports_to_other_chunks[&canonical_ref][0];
let require_binding = &self.ctx.chunk_graph.chunk_table[cur_chunk_idx]
.require_binding_names_for_other_chunks[&chunk_idx_of_canonical_symbol];
self.snippet.literal_prop_access_member_expr_expr(require_binding, exported_name)
} 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
}
fn var_declaration_to_expr_seq_and_bindings(
&self,
decl: &mut ast::VariableDeclaration<'ast>,
traverse_state: TraverseState,
) -> Option<(Expression<'ast>, Vec<Atom<'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.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>> {
let module_namespace_included_reason = self.ctx.linking_info.module_namespace_included_reason;
let is_namespace_referenced = matches!(self.ctx.module.exports_kind, ExportsKind::Esm)
&& if module_namespace_included_reason.contains(ModuleNamespaceIncludedReason::Unknown) {
true
} else if module_namespace_included_reason
.contains(ModuleNamespaceIncludedReason::ReExportExternalModule)
{
self.ctx.linking_info.has_dynamic_exports
} else {
false
};
if !is_namespace_referenced {
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).map(
|(export, resolved_export)| {
let prop_name = export;
let returned = self.finalized_expr_for_symbol_ref(resolved_export.symbol_ref, false, false);
ast::ObjectPropertyKind::ObjectProperty(
ast::ObjectProperty {
key: if is_validate_identifier_name(prop_name) {
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),
..ast::ObjectProperty::dummy(self.alloc)
}
.into_in(self.alloc),
)
},
));
let module_namespace_rhs = if arg_obj_expr.properties.is_empty() {
Expression::ObjectExpression(self.builder().alloc(arg_obj_expr))
} else {
self.snippet.builder.expression_call_with_pure(
SPAN,
self.finalized_expr_for_runtime_symbol("__export"),
NONE,
self
.snippet
.builder
.vec_from_array([ast::Argument::ObjectExpression(arg_obj_expr.into_in(self.alloc))]),
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() {
let re_export_fn_ref = self.finalized_expr_for_runtime_symbol("__reExport");
match self.ctx.options.format {
OutputFormat::Esm => {
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 m = self.ctx.modules.get(rec.resolved_module);
let Some(Module::External(module)) = m else {
return vec![];
};
let importee_name = &module.get_import_path(self.ctx.chunk, None);
vec![
self.snippet.import_star_stmt(importee_name, importee_namespace_name),
self.snippet.builder.statement_expression(
SPAN,
self.snippet.call_expr_with_2arg_expr(
re_export_fn_ref.clone_in(self.alloc),
self.snippet.id_ref_expr(binding_name_for_namespace_object_ref, SPAN),
self.snippet.id_ref_expr(importee_namespace_name, SPAN),
),
),
]
});
re_export_external_stmts = Some(stmts.collect::<Vec<_>>());
}
OutputFormat::Cjs | OutputFormat::Iife | OutputFormat::Umd => {
let stmts = export_all_externals_rec_ids.iter().copied().map(|idx| {
let re_export_fn_ref = self.finalized_expr_for_runtime_symbol("__reExport");
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 = &self.ctx.modules[rec.resolved_module];
let expression = self.snippet.call_expr_with_2arg_expr(
re_export_fn_ref,
importer_namespace_ref_expr,
self.snippet.call_expr_with_arg_expr_expr(
"require",
self.snippet.string_literal_expr(importee.id(), SPAN),
),
);
ast::Statement::ExpressionStatement(
ast::ExpressionStatement { span: expression.span(), expression }.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.atom(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_string_literal = expr.arguments.first_mut().and_then(|arg| match arg {
ast::Argument::StringLiteral(string_literal) => Some(string_literal),
_ => None,
})?;
let importee = &self.ctx.modules[rec.resolved_module].as_normal()?;
let chunk_idx = &self.ctx.chunk_graph.module_to_chunk[importee.idx]?;
let chunk = &self.ctx.chunk_graph.chunk_table[*chunk_idx];
let asset_filename = &chunk.asset_absolute_preliminary_filenames[&importee.idx];
let import_path = self.ctx.chunk.relative_path_for(asset_filename.as_path());
first_arg_string_literal.value = self.snippet.atom(&import_path);
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 {
self.finalized_expr_for_symbol_ref(
object_ref,
false,
target_commonjs_exported_symbol_meta
.is_some_and(|(_symbol, is_exports_default)| !is_exports_default),
)
};
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 get_conflicted_info(&self, id: &BindingIdentifier<'ast>) -> Option<(&str, &CompactStr)> {
let symbol_id = id.symbol_id.get()?;
let symbol_ref: SymbolRef = (self.ctx.id, symbol_id).into();
let original_name = symbol_ref.name(self.ctx.symbol_db);
let canonical_name = self.canonical_name_for(symbol_ref);
(original_name != canonical_name.as_str()).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.id, symbol_id).into());
id.name = self.snippet.atom(new_name);
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,
self.snippet.builder.binding_pattern(
ast::BindingPatternKind::BindingIdentifier(self.snippet.builder.alloc(id)),
NONE,
false,
),
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_id) = self.ctx.module.imports.get(&call_expr.span).copied() {
let rec = &self.ctx.module.import_records[rec_id];
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[rec.resolved_module] {
Module::Normal(importee) => {
match importee.module_type {
ModuleType::Json => {
let importee_linking_info = &self.ctx.linking_infos[importee.idx];
let wrap_ref_expr = self.finalized_expr_for_symbol_ref(
importee_linking_info.wrapper_ref.unwrap(),
false,
false,
);
if matches!(importee.exports_kind, ExportsKind::CommonJs) {
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 = self.finalized_expr_for_symbol_ref(
importee_linking_info.wrapper_ref.unwrap(),
false,
false,
);
let wrap_ref_call_expr =
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, None),
request_path.span(),
));
None
}
};
return rewrite_ast;
}
}
None
}
fn try_rewrite_inline_dynamic_import_expr(
&self,
import_expr: &ImportExpression<'ast>,
) -> Option<Expression<'ast>> {
let rec_id = self.ctx.module.imports.get(&import_expr.span)?;
let rec = &self.ctx.module.import_records[*rec_id];
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.inline_dynamic_imports {
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.atom(to_esm_fn_name.as_str()),
),
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(_) => {
}
}
}
if matches!(self.ctx.options.format, OutputFormat::Cjs) {
match &self.ctx.modules[importee_id] {
Module::Normal(importee) => {
let importee_chunk_id = self.ctx.chunk_graph.entry_module_to_entry_chunk[&importee_id];
let importee_chunk = &self.ctx.chunk_graph.chunk_table[importee_chunk_id];
let import_path = self.ctx.chunk.import_path_for(importee_chunk);
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, import_expr.span),
)),
false,
));
if importee.exports_kind.is_commonjs() {
let to_dynamic_import_esm_fn_name =
self.finalized_expr_for_runtime_symbol("__toDynamicImportESM");
let mut arguments = self.snippet.builder.vec();
if self.ctx.module.should_consider_node_esm_spec_for_dynamic_import() {
arguments.push(ast::Argument::from(self.snippet.builder.expression_numeric_literal(
SPAN,
1.0,
None,
NumberBase::Decimal,
)));
}
let to_dynamic_import_esm_fn_call =
ast::Expression::CallExpression(self.snippet.builder.alloc_call_expression(
SPAN,
to_dynamic_import_esm_fn_name,
NONE,
arguments,
false,
));
require_call_expr =
ast::Expression::CallExpression(self.snippet.builder.alloc_call_expression(
SPAN,
to_dynamic_import_esm_fn_call,
NONE,
self.snippet.builder.vec1(ast::Argument::from(require_call_expr)),
false,
));
}
let new_expr = self.snippet.promise_resolve_then_call_expr(require_call_expr);
return Some(new_expr);
}
Module::External(_) => {
}
}
}
None
}
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.module.stmt_infos.iter().skip(1)).for_each(
|((_top_stmt_idx, mut top_stmt), stmt_info)| {
if !stmt_info.is_included {
return;
}
let is_module_decl = top_stmt.is_module_declaration_with_source();
if let Some(import_decl) = top_stmt.as_import_declaration() {
let rec_id = self.ctx.module.imports[&import_decl.span];
if self.transform_or_remove_import_export_stmt(&mut top_stmt, rec_id) {
return;
}
} else if let Some(export_all_decl) = top_stmt.as_export_all_declaration() {
let rec_id = 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_id) {
return;
}
} else {
let rec = &self.ctx.module.import_records[rec_id];
match &self.ctx.modules[rec.resolved_module] {
Module::Normal(importee) => {
let importee_linking_info = &self.ctx.linking_infos[importee.idx];
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());
program.body.push(self.snippet.call_expr_stmt(wrapper_ref_name));
}
match importee.exports_kind {
ExportsKind::Esm => {
if importee_linking_info.has_dynamic_exports {
let re_export_fn_ref = 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 importee_namespace_ref = self.finalized_expr_for_symbol_ref(
importee.namespace_object_ref,
false,
false,
);
let expression = self.snippet.call_expr_with_2arg_expr(
re_export_fn_ref,
importer_namespace_ref,
importee_namespace_ref,
);
let stmt = ast::Statement::ExpressionStatement(
ast::ExpressionStatement { span: expression.span(), expression }
.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,
);
program.body.push(ast::Statement::ExpressionStatement(
ast::ExpressionStatement {
span: SPAN,
expression: self.snippet.call_expr_with_2arg_expr_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(),
),
),
}
.into_in(self.alloc),
));
}
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 {
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);
top_stmt = self
.snippet
.var_decl_stmt(canonical_name_for_default_export_ref, expr.take_in(self.alloc));
}
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));
}
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));
}
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_id = self.ctx.module.imports[&named_decl.span];
if self.transform_or_remove_import_export_stmt(&mut top_stmt, rec_id) {
return;
}
}
} else 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<&BindingIdentifier<'ast>>,
name_binding_id: Option<&BindingIdentifier<'ast>>,
) -> Option<(usize, CompactStr, CompactStr)> {
if !self.ctx.options.keep_names {
return None;
}
let (original_name, _) = self.get_conflicted_info(name_binding_id.as_ref()?)?;
let (_, canonical_name) = self.get_conflicted_info(symbol_binding_id.as_ref()?)?;
let original_name: CompactStr = CompactStr::new(original_name);
let new_name = canonical_name.clone();
let insert_position = self.cur_stmt_index + 1;
Some((insert_position, original_name, new_name))
}
fn keep_name_helper_for_class(
&self,
id: Option<&BindingIdentifier<'ast>>,
) -> Option<ClassElement<'ast>> {
if !self.ctx.options.keep_names {
return None;
}
let (original_name, _) = self.get_conflicted_info(id.as_ref()?)?;
let original_name: CompactStr = CompactStr::new(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 try_rewrite_import_expression(&self, node: &mut ast::Expression<'ast>) -> bool {
if let ast::Expression::ImportExpression(expr) = node {
if expr.options.is_none()
&& let Some(rec_id) = self.ctx.module.imports.get(&expr.span)
{
if let Some(str) = expr.source.as_static_module_request() {
let mut needs_to_esm_helper = false;
let rec = &self.ctx.module.import_records[*rec_id];
let importee_id = rec.resolved_module;
match &self.ctx.modules[importee_id] {
Module::Normal(importee) => {
let importee_chunk_id =
self.ctx.chunk_graph.entry_module_to_entry_chunk[&rec.resolved_module];
let importee_chunk = &self.ctx.chunk_graph.chunk_table[importee_chunk_id];
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()),
);
needs_to_esm_helper = importee.exports_kind.is_commonjs();
}
Module::External(importee) => {
let import_path = importee.get_import_path(self.ctx.chunk, None);
if str != import_path {
expr.source = Expression::StringLiteral(
self.snippet.alloc_string_literal(&import_path, expr.source.span()),
);
}
}
}
if needs_to_esm_helper {
let original_import_expr = node.take_in(self.alloc);
let to_dynamic_import_esm_fn_name =
self.finalized_expr_for_runtime_symbol("__toDynamicImportESM");
let mut arguments = self.snippet.builder.vec();
if self.ctx.module.should_consider_node_esm_spec_for_dynamic_import() {
arguments.push(ast::Argument::from(self.snippet.builder.expression_numeric_literal(
SPAN,
1.0,
None,
NumberBase::Decimal,
)));
}
let to_dynamic_import_esm_fn_call =
ast::Expression::CallExpression(self.snippet.builder.alloc_call_expression(
SPAN,
to_dynamic_import_esm_fn_name,
NONE,
arguments,
false,
));
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::from(to_dynamic_import_esm_fn_call)),
false,
);
*node = ast::Expression::CallExpression(call_expr);
}
return true;
}
}
}
false
}
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.id, 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 {
return None;
};
let identifier = prop.value.as_identifier()?;
let reference_id = identifier.reference_id();
let symbol_id = self.scope.symbol_id_for(reference_id)?;
let Some(replaced_expr) = json_module_inlined_prop.remove(&symbol_id) else {
continue;
};
prop.value = replaced_expr;
}
}
}
Some(())
}
}