use bitflags::bitflags;
use oxc::allocator::GetAllocator;
use oxc::ast::ast::{BindingIdentifier, CallExpression, IdentifierName, ObjectPropertyKind};
use oxc::ast::builder::{AstBuilder, GetAstBuilder};
use oxc::semantic::{NodeId, ReferenceId, ScopeFlags, SymbolId};
use oxc::{
allocator::{self, Allocator, CloneIn, Dummy, IntoIn, ReplaceWith, TakeIn},
ast::ast::{
self, ClassElement, Expression, IdentifierReference, ImportExpression, NumberBase, Statement,
VariableDeclarationKind,
},
span::{GetSpan, GetSpanMut, SPAN, Span},
};
use rolldown_common::{
AstScopes, Chunk, ChunkIdx, ChunkKind, 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::{
BindingIdentifierFactoryExt as _, BindingPatternExt, CallExpressionExt,
CallExpressionFactoryExt as _, ClassElementFactoryExt as _, ExpressionExt,
ExpressionFactoryExt as _, IdentifierNameFactoryExt as _, StatementExt, StatementFactoryExt as _,
parse_injected_expression,
};
use rolldown_error::EmptyImportMetaKind;
use std::borrow::Cow;
mod finalizer_context;
mod impl_visit_mut;
use finalizer_context::ModuleWrapperMode;
pub use finalizer_context::ScopeHoistingFinalizerContext;
use oxc_str::{CompactStr, Ident};
use rolldown_std_utils::absolutize_path_buf;
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::esm_init_obligations::{
ObligationPurpose, WrappedEsmInitTarget, WrappedEsmInitTargetContext,
collect_entry_reexported_wrapper_inits, collect_wrapped_esm_init_targets_for_import_record,
record_is_init_obligation,
};
use crate::stages::generate_stage::order_wrap_state::OrderCjsCarrierKey;
use crate::utils;
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 ast_builder: AstBuilder<'ast>,
pub generated_init_esm_importee_ids: FxHashSet<ModuleIdx>,
pub generated_order_cjs_carriers: FxHashSet<OrderCjsCarrierKey>,
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>>>>,
pub missing_file_reference_ids: FxIndexMap<CompactStr, Span>,
pub resolve_file_url_errors: Vec<(Cow<'static, str>, String)>,
pub surviving_import_meta_spans: FxIndexMap<Span, EmptyImportMetaKind>,
}
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 wrapped_esm_init_call_expr(
&self,
importee_idx: ModuleIdx,
call_span: Span,
mark_pure_if_noop: bool,
await_if_tla: bool,
) -> ast::Expression<'ast> {
let importee_linking_info = &self.ctx.linking_infos[importee_idx];
let target = self
.ctx
.order_wrap_state
.esm_init_target(importee_idx, importee_linking_info)
.expect("wrapped ESM init call should have an init target");
let (wrapper_ref_expr, _) =
self.finalized_expr_for_symbol_ref(target.wrapper_ref, false, false);
let init_call = ast::Expression::new_call_expression_with_pure(
call_span,
wrapper_ref_expr,
None,
[],
false,
mark_pure_if_noop && self.ctx.final_esm_init_metadata.init_is_noop(importee_idx),
self,
);
if await_if_tla && target.tla_tainted {
ast::Expression::new_await_expression(SPAN, init_call, self)
} else {
init_call
}
}
fn order_cjs_carrier_init_call_expr(
&self,
key: OrderCjsCarrierKey,
call_span: Span,
) -> ast::Expression<'ast> {
let carrier = self
.ctx
.order_wrap_state
.order_cjs_carrier(key)
.expect("order CJS carrier call should have a target");
let (wrapper_ref_expr, _) =
self.finalized_expr_for_symbol_ref(carrier.wrapper_ref, false, false);
ast::Expression::new_call_expression(call_span, wrapper_ref_expr, None, [], false, self)
}
fn wrapped_esm_init_target_wrapper_ref(&self, target: WrappedEsmInitTarget) -> SymbolRef {
match target {
WrappedEsmInitTarget::Module(module_idx) => {
self
.ctx
.order_wrap_state
.esm_init_target(module_idx, &self.ctx.linking_infos[module_idx])
.expect("module init target should have a wrapper")
.wrapper_ref
}
WrappedEsmInitTarget::CjsCarrier(key) => {
self
.ctx
.order_wrap_state
.order_cjs_carrier(key)
.expect("CJS carrier init target should have a wrapper")
.wrapper_ref
}
}
}
fn wrapped_esm_init_target_call_expr(
&self,
target: WrappedEsmInitTarget,
call_span: Span,
) -> ast::Expression<'ast> {
match target {
WrappedEsmInitTarget::Module(module_idx) => {
self.wrapped_esm_init_call_expr(module_idx, call_span, true, false)
}
WrappedEsmInitTarget::CjsCarrier(key) => {
self.order_cjs_carrier_init_call_expr(key, call_span)
}
}
}
fn wrapped_esm_init_targets_with_namespace_expr(
&self,
targets: &[WrappedEsmInitTarget],
namespace: ast::Expression<'ast>,
) -> ast::Expression<'ast> {
if targets.is_empty() {
return namespace;
}
let mut expressions = allocator::Vec::with_capacity_in(targets.len() + 1, self);
expressions
.extend(targets.iter().map(|target| self.wrapped_esm_init_target_call_expr(*target, SPAN)));
expressions.push(namespace);
ast::Expression::new_parenthesized_expression(
SPAN,
ast::Expression::new_sequence_expression(SPAN, expressions, self),
self,
)
}
fn wrapper_is_reachable_in_chunk(&self, wrapper_ref: SymbolRef) -> bool {
let canonical_ref = self.ctx.symbol_db.canonical_ref_for(wrapper_ref);
self.ctx.chunk.canonical_names.contains_key(&canonical_ref)
}
fn wrapped_esm_init_stmt_for_import_record(
&mut self,
rec_idx: ImportRecordIdx,
) -> Option<Statement<'ast>> {
let ast_builder = AstBuilder::new(self.allocator());
let targets = collect_wrapped_esm_init_targets_for_import_record(
&WrappedEsmInitTargetContext {
importer: self.ctx.module,
importer_meta: self.ctx.linking_info,
modules: self.ctx.modules,
metas: self.ctx.linking_infos,
stmt_infos: self.ctx.index_stmt_infos,
symbol_db: self.ctx.symbol_db,
constant_value_map: self.ctx.constant_value_map,
inline_const_mode: self.ctx.options.optimization.inline_const.map(|config| config.mode),
order_wrap_state: self.ctx.order_wrap_state,
strict_execution_order: self.ctx.options.is_strict_execution_order_enabled(),
},
rec_idx,
|symbol_ref| self.ctx.used_symbol_refs.contains(&symbol_ref),
|wrapper_ref| self.wrapper_is_reachable_in_chunk(wrapper_ref),
|forwarding_module_idx| {
self.ctx.chunk_graph.module_to_chunk[forwarding_module_idx] == Some(self.ctx.chunk_idx)
},
);
let mut init_exprs = targets.into_iter().filter_map(|target| match target {
WrappedEsmInitTarget::Module(module_idx) => {
if !self.generated_init_esm_importee_ids.insert(module_idx) {
return None;
}
Some(self.wrapped_esm_init_call_expr(module_idx, SPAN, true, true))
}
WrappedEsmInitTarget::CjsCarrier(key) => {
if !self.generated_order_cjs_carriers.insert(key) {
return None;
}
Some(self.order_cjs_carrier_init_call_expr(key, SPAN))
}
});
let first = init_exprs.next()?;
let Some(second) = init_exprs.next() else {
return Some(ast::Statement::new_expression_statement(SPAN, first, &ast_builder));
};
let mut exprs = oxc::allocator::Vec::with_capacity_in(2, &ast_builder);
exprs.push(first);
exprs.push(second);
exprs.extend(init_exprs);
Some(ast::Statement::new_expression_statement(
SPAN,
ast::Expression::new_sequence_expression(SPAN, exprs, &ast_builder),
&ast_builder,
))
}
fn entry_reexported_wrapper_init_prelude(&mut self) -> Vec<Statement<'ast>> {
if self.ctx.options.is_strict_execution_order_enabled() {
return vec![];
}
let ChunkKind::EntryPoint { module: entry_id, .. } = self.ctx.chunk.kind else {
return vec![];
};
if entry_id != self.ctx.idx || !matches!(self.ctx.wrapper_mode(), ModuleWrapperMode::None) {
return vec![];
}
let inits = collect_entry_reexported_wrapper_inits(
entry_id,
self.ctx.linking_info,
self.ctx.linking_infos,
self.ctx.modules,
self.ctx.symbol_db,
Some(&self.ctx.chunk.canonical_names),
);
let mut stmts = vec![];
for init in inits {
if !self.generated_init_esm_importee_ids.insert(init.owner) {
continue;
}
let init_expr = self.wrapped_esm_init_call_expr(init.owner, SPAN, true, true);
stmts.push(ast::Statement::new_expression_statement(SPAN, init_expr, self));
}
stmts
}
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];
if self.ctx.order_wrap_state.is_consumer_local_reexport_route(importee.idx) {
if record_is_init_obligation(
ObligationPurpose::Emit,
self.ctx.order_wrap_state,
self.ctx.idx,
rec,
rec_idx,
true,
) && let Some(init_stmt) = self.wrapped_esm_init_stmt_for_import_record(rec_idx)
{
*stmt = init_stmt;
return false;
}
return true;
}
match importee_linking_info.wrap_kind() {
WrapKind::None => {
if record_is_init_obligation(
ObligationPurpose::Emit,
self.ctx.order_wrap_state,
self.ctx.idx,
rec,
rec_idx,
true,
) && let Some(init_stmt) = self.wrapped_esm_init_stmt_for_import_record(rec_idx)
{
*stmt = init_stmt;
return false;
}
}
WrapKind::Cjs => {
let carrier_key = OrderCjsCarrierKey { importer: self.ctx.idx, record: rec_idx };
if self.ctx.order_wrap_state.has_order_cjs_carrier(carrier_key) {
debug_assert!(!self.transferred_import_record.contains_key(&rec_idx));
return true;
}
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 {
ast::Expression::new_call_expression(
SPAN,
importee_wrapper_ref_name,
None,
[],
false,
self,
)
};
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");
Expression::new_to_esm_wrapper(
to_esm_fn_name,
require_call,
self.ctx.module.should_consider_node_esm_spec_for_static_import(),
self,
)
} else {
require_call
};
let binding_name_for_wrapper_call_ret = self.canonical_name_for(rec.namespace_ref);
*stmt = Statement::new_var_decl(binding_name_for_wrapper_call_ret, init_expr, self);
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 init_expr = self.wrapped_esm_init_call_expr(importee.idx, stmt.span(), false, true);
*stmt = ast::Statement::new_expression_statement(SPAN, init_expr, self);
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 (
Expression::new_id_ref_expr(SPAN, self.canonical_name_for(symbol_ref), self),
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(self.ast_builder.builder()),
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)
{
Expression::new_id_ref_expr(SPAN, node_mode_name.as_str(), self)
} else {
Expression::new_id_ref_expr(SPAN, self.canonical_name_for(canonical_ref), self)
}
} else {
Expression::new_id_ref_expr(SPAN, self.canonical_name_for(canonical_ref), self)
}
} 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 {
Expression::new_id_ref_expr(SPAN, self.canonical_name_for(canonical_ref), self)
}
}
_ => Expression::new_id_ref_expr(SPAN, self.canonical_name_for(canonical_ref), self),
}
};
if let Some(ns_alias) = namespace_alias {
if !optimize_namespace_alias_transform {
expr = ast::Expression::new_static_member_expression(
SPAN,
expr,
IdentifierName::new_id_name(SPAN, &ns_alias.property_name, self),
false,
self,
);
}
if preserve_this_semantic_if_needed {
expr = Expression::new_seq_in_parens(
ast::Expression::new_numeric_literal(
SPAN,
0.0,
Some("0".into()),
NumberBase::Decimal,
self,
),
expr,
self,
);
}
}
(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 => Expression::new_id_ref_expr(SPAN, require_binding, self),
_ => Expression::new_member_access_expr(require_binding, "default", self),
};
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) => Expression::new_id_ref_expr(SPAN, require_binding, self),
_ => Expression::new_member_access_expr(require_binding, exported_name, self),
};
(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(self.ast_builder.builder()),
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(self.ast_builder.builder()))
}
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.take_in(self).into_iter().filter_map(|var_decl| {
ret.extend(var_decl.id.get_binding_identifiers().iter().map(|item| item.name));
let init_expr = var_decl.init?;
let left = var_decl.id.into_assignment_target(self);
Some(ast::Expression::new_assignment_expression(
SPAN,
ast::AssignmentOperator::Assign,
left,
init_expr,
self,
))
});
Some((
ast::Expression::new_sequence_expression(
SPAN,
oxc::allocator::Vec::from_iter_in(exprs, self),
self,
),
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.allocator());
let namespace_is_semantically_observed = self
.ctx
.linking_info
.module_namespace_included_reason
.contains(ModuleNamespaceIncludedReason::Unknown)
|| (self
.ctx
.linking_info
.module_namespace_included_reason
.contains(ModuleNamespaceIncludedReason::ReExportDynamicExports)
&& self.ctx.linking_info.has_dynamic_exports)
|| self
.ctx
.order_wrap_state
.requires_semantic_namespace(self.ctx.module.namespace_object_ref, |importer_idx| {
self.ctx.chunk_graph.module_is_in_live_chunk(importer_idx)
});
let simulated_facade_export_names = (!namespace_is_semantically_observed)
.then(|| {
self
.ctx
.order_wrap_state
.simulated_facade_export_names(self.ctx.module.namespace_object_ref)
})
.flatten();
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 let Some(export_names) = simulated_facade_export_names {
if !export_names.contains(export) {
return None;
}
} else if !self.ctx.retained_export_symbols.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);
let key = if is_validate_identifier_name(prop_name) && prop_name != "__proto__" {
ast::PropertyKey::StaticIdentifier(
IdentifierName::new_id_name(SPAN, prop_name, self).into_in(self.allocator()),
)
} else {
ast::PropertyKey::new_string_literal(
SPAN,
oxc::ast::ast::Str::from_str_in(prop_name, self),
None,
self,
)
};
Some(ast::ObjectPropertyKind::new_object_property(
SPAN,
ast::PropertyKind::Init,
key,
Expression::new_arrow_returning(returned, self),
false,
false,
prop_name == "__proto__",
self,
))
},
));
let module_namespace_rhs =
if arg_obj_expr.properties.is_empty() && !self.ctx.options.generated_code.symbols {
Expression::ObjectExpression(oxc::allocator::Box::new_in(arg_obj_expr, self))
} else {
let obj_expr = ast::Argument::ObjectExpression(arg_obj_expr.into_in(self.allocator()));
let args = if self.ctx.options.generated_code.symbols {
oxc::allocator::Vec::from_iter_in([obj_expr], self)
} else {
oxc::allocator::Vec::from_iter_in(
[
obj_expr,
ast::Argument::new_numeric_literal(SPAN, 1.0, None, NumberBase::Decimal, self),
],
self,
)
};
ast::Expression::new_call_expression_with_pure(
SPAN,
self.finalized_expr_for_runtime_symbol("__exportAll"),
None,
args,
false,
true,
self,
)
};
let decl_stmt =
Statement::new_var_decl(binding_name_for_namespace_object_ref, module_namespace_rhs, self);
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 !self
.ctx
.linking_info
.ns_star_external_re_export_emitted(rec.meta, self.ctx.options.format)
{
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 = CallExpression::new_re_export_call(
Expression::new_id_ref_expr(SPAN, re_export_name, self),
Expression::new_id_ref_expr(SPAN, binding_name_for_namespace_object_ref, self),
Expression::new_id_ref_expr(SPAN, importee_namespace_name, self),
self,
);
vec![
Statement::new_import_star_stmt(importee_name, importee_namespace_name, self),
ast::Statement::new_expression_statement(
SPAN,
Expression::CallExpression(call_expr.into_in(self.allocator())),
self,
),
]
});
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 = CallExpression::new_re_export_call(
self.finalized_expr_for_runtime_symbol("__reExport"),
importer_namespace_ref_expr,
Expression::new_call_with_arg(
ast::Expression::new_identifier(SPAN, "require", self),
ast::Expression::new_string_literal(
SPAN,
oxc::ast::ast::Str::from_str_in(importee.id().as_str(), self),
None,
self,
),
false,
self,
),
self,
);
Some(ast::Statement::new_expression_statement(
SPAN,
Expression::CallExpression(re_export_call_expr.into_in(self.allocator())),
self,
))
});
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(
&mut self,
member_expr: &ast::MemberExpression<'ast>,
) -> Option<Expression<'ast>> {
if member_expr.object().is_import_meta() {
let original_expr_span = member_expr.span();
let can_polyfill_import_meta_url = self.can_polyfill_import_meta_url();
let property_name = member_expr.static_property_name()?;
match property_name {
"url" => {
let new_expr = if can_polyfill_import_meta_url {
let require_call = ast::Expression::new_call_expression(
SPAN,
ast::Expression::new_identifier(SPAN, "require", self),
None,
[ast::Argument::new_string_literal(SPAN, "url", None, self)],
false,
self,
);
let require_path_to_file_url = ast::Expression::new_static_member_expression(
SPAN,
require_call,
ast::IdentifierName::new(SPAN, "pathToFileURL", self),
false,
self,
);
let require_path_to_file_url_call = ast::Expression::new_call_expression(
SPAN,
require_path_to_file_url,
None,
[ast::Argument::new_identifier(SPAN, "__filename", self)],
false,
self,
);
let require_path_to_file_url_href = ast::Expression::new_static_member_expression(
original_expr_span,
require_path_to_file_url_call,
ast::IdentifierName::new(SPAN, "href", self),
false,
self,
);
Some(require_path_to_file_url_href)
} else {
if !self.ctx.options.format.keep_esm_import_export_syntax() {
self.record_surviving_import_meta(
member_expr.object().span(),
EmptyImportMetaKind::Url,
);
}
None
};
return new_expr;
}
"dirname" | "filename" => {
let name = oxc::ast::ast::Str::from_str_in(&format!("__{property_name}"), self);
return can_polyfill_import_meta_url
.then_some(ast::Expression::new_identifier(SPAN, name, self));
}
_ => {}
}
return self.rewrite_rolldown_file_url(
property_name,
original_expr_span,
member_expr.node_id(),
);
}
None
}
fn can_polyfill_import_meta_url(&self) -> bool {
matches!(
(self.ctx.options.platform, &self.ctx.options.format),
(Platform::Node, OutputFormat::Cjs)
)
}
pub fn record_surviving_import_meta(&mut self, span: Span, kind: EmptyImportMetaKind) {
self.surviving_import_meta_spans.entry(span).or_insert(kind);
}
fn rewrite_rolldown_file_url(
&mut self,
property_name: &str,
original_expr_span: Span,
node_id: NodeId,
) -> Option<Expression<'ast>> {
if let Some(reference_id) =
utils::file_url::strip_file_url_prefix(property_name).map(|file_url| file_url.reference_id)
{
let resolved_file_urls = self.ctx.resolved_file_urls;
if let Some(resolved) = resolved_file_urls.get(&(self.ctx.idx, node_id)) {
match parse_injected_expression(self.allocator(), &resolved.code) {
Ok(mut expr) => {
let mut rewriter = ResolveFileUrlHookResultSpanRewriter(original_expr_span);
oxc::ast_visit::VisitJsMut::visit_expression(&mut rewriter, &mut expr);
return Some(expr);
}
Err(diagnostics) => {
self.resolve_file_url_errors.push((
resolved.plugin_name.clone(),
format!(
"The `resolveFileUrl` hook returned code that is not a valid expression for referenceId={reference_id}: {}
{diagnostics}",
resolved.code
),
));
return None;
}
}
}
let Ok(asset_file_name) = self.ctx.file_emitter.get_file_name(reference_id) else {
self
.missing_file_reference_ids
.entry(CompactStr::new(reference_id))
.or_insert(original_expr_span);
return None;
};
let output_dir =
absolutize_path_buf(self.ctx.options.cwd.as_path().join(&self.ctx.options.out_dir));
let absolute_asset_file_name = asset_file_name.absolutize_with(output_dir);
let relative_asset_path = &self.ctx.chunk.relative_path_for(&absolute_asset_file_name);
if !self.ctx.options.format.keep_esm_import_export_syntax()
&& !self.can_polyfill_import_meta_url()
{
self.record_surviving_import_meta(original_expr_span, EmptyImportMetaKind::RolldownFileUrl);
}
let new_expr = ast::Expression::new_static_member_expression(
SPAN,
ast::Expression::new_new_expression(
SPAN,
ast::Expression::new_identifier(SPAN, "URL", self),
None,
[
ast::Argument::new_string_literal(
SPAN,
oxc::ast::ast::Str::from_str_in(relative_asset_path, self),
None,
self,
),
ast::Argument::new_static_member_expression(
SPAN,
ast::Expression::new_import_meta(
original_expr_span,
self,
),
ast::IdentifierName::new(SPAN, "url", self),
false,
self,
),
],
self,
),
ast::IdentifierName::new(SPAN, "href", self),
false,
self,
);
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.node_id())?;
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 = ast::Expression::new_string_literal(
first_arg_expr.span(),
oxc::ast::ast::Str::from_str_in(&import_path, self),
None,
self,
);
None
}
fn try_rewrite_member_expr(
&mut self,
member_expr: &ast::MemberExpression<'ast>,
) -> Option<Expression<'ast>> {
let span = member_expr.span();
match self.ctx.linking_info.resolved_member_expr_refs.get(&member_expr.node_id()) {
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(self.ast_builder.builder())
} 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
};
Expression::new_member_expr_or_ident_ref(
object_ref_expr,
&props[usize::from(is_inlined_commonjs_export)..],
span,
self,
)
})
.or_else(|| Some(Expression::new_member_expr_with_void_zero_object(props, span, self))),
_ => self.try_rewrite_import_meta_prop_expr(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 = Expression::new_id_ref_expr(SPAN, ns_name, self);
let default_access = ast::Expression::new_static_member_expression(
SPAN,
ns_id_ref,
ast::IdentifierName::new(SPAN, "default", self),
false,
self,
);
match property {
CjsMemberProperty::Static(property_name) => {
let final_access = ast::SimpleAssignmentTarget::new_static_member_expression(
SPAN,
default_access,
IdentifierName::new_id_name(SPAN, property_name, self),
false,
self,
);
Some(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.allocator())),
_ => expr.clone_in(self.allocator()),
};
let final_access = ast::SimpleAssignmentTarget::new_computed_member_expression(
SPAN,
default_access,
finalized_expr,
false,
self,
);
Some(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,
mut class: allocator::Box<'ast, ast::Class<'ast>>,
) -> Result<ast::Declaration<'ast>, allocator::Box<'ast, ast::Class<'ast>>> {
let Some(scope_id) = class.scope_id.get() else { return Err(class) };
if self.scope.scoping().scope_parent_id(scope_id) != Some(self.scope.scoping().root_scope_id())
{
return Err(class);
}
let Some(id) = class.id.take() else { return Err(class) };
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 = oxc::ast::ast::Str::from_str_in(new_name, self).into();
class.id = Some(id);
}
}
Ok(ast::Declaration::new_variable_declaration(
class.span,
VariableDeclarationKind::Var,
[ast::VariableDeclarator::new(
SPAN,
VariableDeclarationKind::Var,
ast::BindingPattern::BindingIdentifier(oxc::allocator::Box::new_in(id, self)),
None,
Some(Expression::ClassExpression(class)),
false,
self,
)],
false,
self,
))
}
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) {
if let Some(rec_idx) = self.ctx.module.imports.get(&call_expr.node_id()).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::new_call_expression(
SPAN,
wrap_ref_expr,
None,
[],
false,
self,
))
}
} 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(Expression::new_seq_in_parens(
ast::Expression::new_call_expression(
SPAN,
wrap_ref_expr,
None,
[],
false,
self,
),
ast::Expression::new_static_member_expression(
SPAN,
Expression::new_call_with_arg(to_commonjs_ref_name, ns_name, false, self),
ast::IdentifierName::new(SPAN, "default", self),
false,
self,
),
self,
))
}
}
_ => {
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::new_call_expression_with_pure(
SPAN,
wrap_ref_expr,
None,
[],
false,
self.ctx.final_esm_init_metadata.init_is_noop(importee.idx),
self,
)
};
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::new_call_expression(
SPAN,
to_commonjs_expr,
None,
[ast::Argument::from(namespace_object_ref_expr)],
false,
self,
);
let final_expr = if is_json_module {
Expression::new_static_member_expression(
SPAN,
to_commonjs_call_expr,
ast::IdentifierName::new(SPAN, "default", self),
false,
self,
)
} else {
to_commonjs_call_expr
};
Some(Expression::new_seq_in_parens(wrap_ref_call_expr, final_expr, self))
}
}
}
}
Module::External(importee) => {
let request_path =
call_expr.arguments.get_mut(0).expect("require should have an argument");
*request_path = ast::Argument::new_string_literal(
request_path.span(),
oxc::ast::ast::Str::from_str_in(
&importee.get_import_path(self.ctx.chunk, self.ctx.resolved_paths),
self,
),
None,
self,
);
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.node_id())?;
let rec = &self.ctx.module.import_records[*rec_idx];
let importee_id = rec.resolved_module?;
if rec.meta.contains(ImportRecordMeta::DeadDynamicImport) {
return Some(Expression::new_promise_resolve_then(
Expression::new_call_with_arg(
Expression::new_member_access_expr("Object", "freeze", self),
ast::Expression::new_object_expression(
SPAN,
[ast::ObjectPropertyKind::new_object_property(
SPAN,
ast::PropertyKind::Init,
ast::PropertyKey::new_static_identifier(SPAN, "__proto__", self),
ast::Expression::new_null_literal(SPAN, self),
false,
false,
false,
self,
)],
self,
),
true,
self,
),
self,
));
}
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(Expression::new_callee_then_call(
ast::Expression::new_call_expression(
SPAN,
Expression::new_id_ref_expr(SPAN, importee_wrapper_ref_name, self),
None,
[],
false,
self,
),
Expression::new_id_ref_expr(SPAN, importee_namespace_name, self),
self,
))
} else {
Some(Expression::new_promise_resolve_then(
Expression::new_seq_in_parens(
ast::Expression::new_call_expression(
SPAN,
Expression::new_id_ref_expr(SPAN, importee_wrapper_ref_name, self),
None,
[],
false,
self,
),
Expression::new_id_ref_expr(SPAN, importee_namespace_name, self),
self,
),
self,
))
}
}
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(Expression::new_promise_resolve_then(
Expression::new_to_esm_wrapper(
Expression::new_id_ref_expr(SPAN, to_esm_fn_name, self),
ast::Expression::new_call_expression(
SPAN,
Expression::new_id_ref_expr(SPAN, importee_wrapper_ref_name, self),
None,
[],
false,
self,
),
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
self,
),
self,
))
}
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);
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_order_runtime_stmt =
self.ctx.order_wrap_state.forces_runtime_stmt(self.ctx.runtime, self.ctx.idx, stmt_info);
let is_stmt_included =
self.ctx.linking_info.stmt_info_included.has_bit(stmt_info_idx) || is_order_runtime_stmt;
if !is_stmt_included {
if let Some(targets) = self
.ctx
.final_esm_init_metadata
.transitive_init_targets(self.ctx.idx)
.and_then(|targets_by_stmt| targets_by_stmt.get(&stmt_info_idx))
{
for &target in targets {
let init_expr = match target {
WrappedEsmInitTarget::Module(importee_idx) => {
if !self.generated_init_esm_importee_ids.insert(importee_idx) {
continue;
}
self.wrapped_esm_init_call_expr(importee_idx, SPAN, true, true)
}
WrappedEsmInitTarget::CjsCarrier(key) => {
if !self.generated_order_cjs_carriers.insert(key) {
continue;
}
self.order_cjs_carrier_init_call_expr(key, SPAN)
}
};
program.body.push(ast::Statement::new_expression_statement(SPAN, init_expr, self));
}
}
let overlay_records = self
.ctx
.order_wrap_state
.import_overlays_for_statement(self.ctx.idx, stmt_info_idx)
.map(|(key, overlay)| {
(
key.record,
overlay.reexports_dynamic_exports,
!overlay.retained_reexport_path.is_empty(),
)
})
.collect::<Vec<_>>();
for (rec_idx, reexports_dynamic_exports, has_retained_reexport_path) in overlay_records {
if !has_retained_reexport_path
&& let Some(init_stmt) = self.wrapped_esm_init_stmt_for_import_record(rec_idx)
{
program.body.push(init_stmt);
}
if !reexports_dynamic_exports {
continue;
}
let Some(importee_idx) = self.ctx.module.import_records[rec_idx].resolved_module else {
continue;
};
let Some(importee) = self.ctx.modules[importee_idx].as_normal() else {
continue;
};
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 = CallExpression::new_re_export_call(
self.finalized_expr_for_runtime_symbol("__reExport"),
importer_namespace_ref,
importee_namespace_ref,
self,
);
program.body.push(ast::Statement::new_expression_statement(
top_stmt.span(),
Expression::CallExpression(call_expr.into_in(self.allocator())),
self,
));
}
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.node_id()];
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.node_id()];
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];
let consumer_local_route =
self.ctx.order_wrap_state.is_consumer_local_reexport_route(importee.idx);
if record_is_init_obligation(
ObligationPurpose::Emit,
self.ctx.order_wrap_state,
self.ctx.idx,
rec,
rec_idx,
true,
) && (matches!(importee_linking_info.wrap_kind(), WrapKind::None)
|| consumer_local_route)
&& 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)
&& !consumer_local_route
&& !matches!(
importee_linking_info.concatenated_wrapped_module_kind,
ConcatenateWrappedModuleKind::Inner
)
{
let (wrapper_ref, _) = self.finalized_expr_for_symbol_ref(
importee_linking_info.wrapper_ref.unwrap(),
false,
false,
);
let mut init_expr =
ast::Expression::new_call_expression(SPAN, wrapper_ref, None, [], false, self);
if importee_linking_info.is_tla_or_contains_tla_dependency {
init_expr = ast::Expression::new_await_expression(SPAN, init_expr, self);
}
program
.body
.push(ast::Statement::new_expression_statement(SPAN, init_expr, self));
}
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 = CallExpression::new_re_export_call(
self.finalized_expr_for_runtime_symbol("__reExport"),
importer_namespace_ref,
importee_namespace_ref,
self,
);
let call_expr =
Expression::CallExpression(call_expr.into_in(self.allocator()));
let stmt = ast::Statement::new_expression_statement(SPAN, call_expr, self);
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 = CallExpression::new_re_export_call(
re_export_fn_name,
importer_namespace_ref,
Expression::new_to_esm_wrapper(
to_esm_fn_ref,
ast::Expression::new_call_expression(
SPAN,
importee_wrapper_ref_expr,
None,
[],
false,
self,
),
self.ctx.module.should_consider_node_esm_spec_for_static_import(),
self,
),
self,
);
let stmt = ast::Statement::new_expression_statement(
SPAN,
Expression::CallExpression(call_expr.into_in(self.allocator())),
self,
);
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 ast::Statement::ExportDefaultDeclaration(default_decl) = top_stmt {
use ast::ExportDefaultDeclarationKind;
let default_decl_span = default_decl.span;
match default_decl.unbox().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 mut init_expr = decl.into_expression();
let canonical_name_for_default_export_ref =
self.canonical_name_for(self.ctx.module.default_export_ref);
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 = Expression::new_keep_name_call(
"default",
init_expr,
finalized_callee,
true, self,
);
}
}
top_stmt =
Statement::new_var_decl(canonical_name_for_default_export_ref, init_expr, self);
}
ast::ExportDefaultDeclarationKind::FunctionDeclaration(mut 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(BindingIdentifier::new_id(
SPAN,
canonical_name_for_default_export_ref,
self,
));
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),
));
}
}
top_stmt = ast::Statement::FunctionDeclaration(func);
}
ast::ExportDefaultDeclarationKind::ClassDeclaration(mut 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(BindingIdentifier::new_id(
SPAN,
canonical_name_for_default_export_ref,
self,
));
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);
}
}
}
class.span = default_decl_span;
top_stmt = ast::Statement::ClassDeclaration(class);
}
ast::ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) => {
unreachable!("TypeScript declarations are stripped before the finalizer runs")
}
}
*top_stmt.span_mut() = default_decl_span;
} else if let ast::Statement::ExportDeclaration(export_decl) = top_stmt {
let export_decl_span = export_decl.span;
let mut decl = export_decl.unbox().declaration;
*decl.span_mut() = export_decl_span;
top_stmt = ast::Statement::from(decl);
} else if matches!(top_stmt, ast::Statement::ExportNamedDeclaration(_)) {
return;
} else if let Some(export_from_decl) = top_stmt.as_export_from_declaration_mut() {
let rec_idx = self.ctx.module.imports[&export_from_decl.node_id()];
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) = top_stmt {
top_stmt = match self.get_transformed_class_decl(class_decl) {
Ok(decl) => Statement::from(decl),
Err(class_decl) => Statement::ClassDeclaration(class_decl),
};
}
}
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 name_ref = self.canonical_ref_for_runtime("__name");
let (finalized_callee, _) = self.finalized_expr_for_symbol_ref(name_ref, false, false);
expr.replace_with(|fn_expr| {
Expression::new_keep_name_call(&original_name, fn_expr, finalized_callee, true, self)
});
}
}
ast::Expression::ArrowFunctionExpression(_fn_expr) => {
if let Some((_insert_position, original_name, _)) =
self.process_fn(keep_name_id, keep_name_id)
{
let name_ref = self.canonical_ref_for_runtime("__name");
let (finalized_callee, _) = self.finalized_expr_for_symbol_ref(name_ref, false, false);
expr.replace_with(|fn_expr| {
Expression::new_keep_name_call(&original_name, fn_expr, finalized_callee, true, self)
});
}
}
_ => {}
}
}
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(ClassElement::new_static_block_keep_name(&original_name, finalized_callee, self))
}
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 = Expression::new_id_ref_expr(SPAN, new_name, self);
statements.insert(
*stmt_index,
ast::Statement::new_expression_statement(
SPAN,
Expression::new_keep_name_call(original_name, target, finalized_callee, false, self),
self,
),
);
}
}
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 = if matches!(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 = ast::Expression::new_call_expression(
SPAN,
finalized_importee_wrapper_ref,
None,
[],
false,
self,
);
Expression::new_to_esm_wrapper(
finalized_to_esm,
wrapper_ref_call_expr,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
self,
)
} else if let Some(targets) =
self.ctx.order_wrap_state.consumer_local_namespace_targets(importee_idx)
{
let (finalized_namespace, _) =
self.finalized_expr_for_symbol_ref(importee.namespace_object_ref, false, false);
self.wrapped_esm_init_targets_with_namespace_expr(targets, finalized_namespace)
} else if let Some(target) =
self.ctx.order_wrap_state.esm_init_target(importee_idx, importee_meta)
{
debug_assert!(!target.tla_tainted);
let wrapper_call_expr = self.wrapped_esm_init_call_expr(importee_idx, SPAN, true, false);
let (finalized_namespace, _) =
self.finalized_expr_for_symbol_ref(importee.namespace_object_ref, false, false);
Expression::new_seq_in_parens(wrapper_call_expr, finalized_namespace, self)
} else {
let (finalized_expr, _) =
self.finalized_expr_for_symbol_ref(importee.namespace_object_ref, false, false);
finalized_expr
};
Some(Expression::new_promise_resolve_then(finalized_expr, self))
} else {
let importee_meta = &self.ctx.linking_infos[importee_idx];
let wrap_kind = importee_meta.wrap_kind();
let consumer_local_targets =
self.ctx.order_wrap_state.consumer_local_namespace_targets(importee_idx);
let init_target = (!matches!(wrap_kind, WrapKind::Cjs) && consumer_local_targets.is_none())
.then(|| self.ctx.order_wrap_state.esm_init_target(importee_idx, importee_meta))
.flatten();
let primary_export_symbol = if matches!(wrap_kind, WrapKind::Cjs) {
importee_meta.wrapper_ref
} else if consumer_local_targets.is_some() {
self.ctx.modules[importee_idx].namespace_object_ref()
} else if let Some(target) = &init_target {
Some(target.wrapper_ref)
} else {
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 init_target.is_some() || consumer_local_targets.is_some() {
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::new_call_expression(
SPAN,
ast::Expression::new_identifier(SPAN, "require", self),
None,
[ast::Argument::new_string_literal(
expr.span,
oxc::ast::ast::Str::from_str_in(&import_path, self),
None,
self,
)],
false,
self,
);
Expression::new_promise_resolve_then(require_call_expr, self)
} else {
let import_expr = expr.take_in_box(self);
Expression::ImportExpression(import_expr)
};
if matches!(wrap_kind, WrapKind::Cjs) {
let finalized_to_esm = self.finalized_expr_for_runtime_symbol("__toESM");
let call_expr = CallExpression::new_then_call_cjs_wrapper_with_to_esm(
base_expr,
name,
finalized_to_esm,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
self,
);
Some(Expression::CallExpression(call_expr))
} else if let Some(targets) = consumer_local_targets {
let Some(ns_name) = namespace_export_name else {
tracing::warn!(
"Consumer-local dynamic entry {:?} in chunk {:?} has no namespace export.",
importee_idx,
importee_chunk_idx
);
return None;
};
let mut wrapper_names = Vec::with_capacity(targets.len());
for &target in targets {
let wrapper_ref = self.wrapped_esm_init_target_wrapper_ref(target);
let Some(wrapper_name) = importee_chunk
.exports_to_other_chunks
.get(&wrapper_ref)
.and_then(|names| names.first())
else {
tracing::warn!(
"Consumer-local dynamic entry {:?} in chunk {:?} is missing an init-target export.",
importee_idx,
importee_chunk_idx
);
return None;
};
wrapper_names.push(wrapper_name.as_str());
}
let call_expr = CallExpression::new_then_call_esm_wrappers_with_namespace(
base_expr,
&wrapper_names,
ns_name,
self,
);
Some(Expression::CallExpression(call_expr))
} else if init_target.is_some() {
if let Some(ns_name) = namespace_export_name {
let call_expr = CallExpression::new_then_call_esm_wrappers_with_namespace(
base_expr,
&[name],
ns_name,
self,
);
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
}
} else {
let call_expr = CallExpression::new_then_extract_property(base_expr, name, self);
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;
};
let (Some(str), Some(rec_idx)) =
(expr.source.as_static_module_request(), self.ctx.module.imports.get(&expr.node_id()))
else {
if matches!(self.ctx.options.format, OutputFormat::Cjs)
&& !self.ctx.options.dynamic_import_in_cjs
&& expr.options.is_none()
{
let to_esm_fn_name = self.finalized_expr_for_runtime_symbol("__toESM");
node.replace_with(|old| {
let ast::Expression::ImportExpression(import_expr) = old else { unreachable!() };
let require_call = Expression::new_call_with_arg(
ast::Expression::new_identifier(SPAN, "require", self),
import_expr.unbox().source,
false,
self,
);
let wrapped = Expression::new_to_esm_wrapper(
to_esm_fn_name,
require_call,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
self,
);
Expression::new_promise_resolve_then(wrapped, self)
});
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 expr.options.is_none();
};
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 = ast::Expression::new_void_0(SPAN, self);
return true;
};
let Some(importee_chunk) = self.ctx.chunk_graph.chunk_table.get(importee_chunk_idx) else {
return false;
};
expr.options = None;
let import_path = self.ctx.chunk.import_path_for(importee_chunk);
expr.source = Expression::new_string_literal(
expr.source.span(),
oxc::ast::ast::Str::from_str_in(&import_path, self),
None,
self,
);
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::new_call_expression(
SPAN,
ast::Expression::new_identifier(SPAN, "require", self),
None,
[ast::Argument::new_string_literal(
expr.span,
oxc::ast::ast::Str::from_str_in(&import_path, self),
None,
self,
)],
false,
self,
);
if importee.exports_kind.is_commonjs() {
let to_esm_fn_name = self.finalized_expr_for_runtime_symbol("__toESM");
let require_default_expr = ast::Expression::new_static_member_expression(
SPAN,
require_call_expr,
ast::IdentifierName::new(SPAN, "default", self),
false,
self,
);
require_call_expr = Expression::new_to_esm_wrapper(
to_esm_fn_name,
require_default_expr,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
self,
);
}
*node = Expression::new_promise_resolve_then(require_call_expr, self);
return true;
}
needs_to_esm_helper = importee.exports_kind.is_commonjs();
}
Module::External(importee) => {
if expr.options.is_some() {
return false;
}
let import_path = importee.get_import_path(self.ctx.chunk, self.ctx.resolved_paths);
if str != import_path {
expr.source = Expression::new_string_literal(
expr.source.span(),
oxc::ast::ast::Str::from_str_in(&import_path, self),
None,
self,
);
}
if matches!(self.ctx.options.format, OutputFormat::Cjs)
&& !self.ctx.options.dynamic_import_in_cjs
{
let to_esm_fn_name = self.finalized_expr_for_runtime_symbol("__toESM");
node.replace_with(|old| {
let ast::Expression::ImportExpression(import_expr) = old else { unreachable!() };
let require_call = Expression::new_call_with_arg(
ast::Expression::new_identifier(SPAN, "require", self),
import_expr.unbox().source,
false,
self,
);
let wrapped = Expression::new_to_esm_wrapper(
to_esm_fn_name,
require_call,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
self,
);
Expression::new_promise_resolve_then(wrapped, self)
});
return true;
}
}
}
if needs_to_esm_helper {
let to_esm_fn_name = self.finalized_expr_for_runtime_symbol("__toESM");
node.replace_with(|original_import_expr| {
let m_default_expr = ast::Expression::new_static_member_expression(
SPAN,
ast::Expression::new_identifier(SPAN, "m", self),
ast::IdentifierName::new(SPAN, "default", self),
false,
self,
);
let to_esm_call = Expression::new_to_esm_wrapper(
to_esm_fn_name,
m_default_expr,
self.ctx.module.should_consider_node_esm_spec_for_dynamic_import(),
self,
);
let arrow_fn = ast::Argument::new_arrow_function_expression(
SPAN,
false, None,
ast::FormalParameters::boxed(
SPAN,
ast::FormalParameterKind::ArrowFormalParameters,
[ast::FormalParameter::new(
SPAN,
[],
ast::BindingPattern::new_binding_identifier(SPAN, "m", self),
None,
None,
false,
None,
false,
false,
self,
)],
None,
self,
),
None,
ast::ArrowFunctionBody::from(to_esm_call),
self,
);
let callee = ast::Expression::new_static_member_expression(
SPAN,
original_import_expr,
ast::IdentifierName::new(SPAN, "then", self),
false,
self,
);
ast::Expression::new_call_expression(SPAN, callee, None, [arrow_fn], false, self)
});
}
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()?;
first_decl.init.as_ref()?;
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, first_decl.init.take()?);
*it = ast::Statement::new_empty_statement(SPAN, self);
}
}
None => {
let Some(Expression::ObjectExpression(obj_expr)) = first_decl.init.as_mut() 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(())
}
}
impl<'ast> GetAstBuilder<'ast> for ScopeHoistingFinalizer<'_, 'ast> {
type Builder = AstBuilder<'ast>;
#[inline]
fn builder(&self) -> &AstBuilder<'ast> {
&self.ast_builder
}
}
impl<'ast> GetAllocator<'ast> for ScopeHoistingFinalizer<'_, 'ast> {
#[inline]
fn allocator(&self) -> &'ast Allocator {
self.ast_builder.allocator()
}
}
struct ResolveFileUrlHookResultSpanRewriter(Span);
impl oxc::ast_visit::VisitJsMut<'_> for ResolveFileUrlHookResultSpanRewriter {
fn visit_span(&mut self, span: &mut Span) {
*span = self.0;
}
}