use oxc::{
allocator::TakeIn,
ast::ast::{self, Expression, NumberBase},
span::SPAN,
};
use oxc_traverse::Traverse;
use rolldown_common::EcmaModuleAstUsage;
use rolldown_ecmascript::{
CJS_EXPORTS_REF_STR, CJS_MODULE_REF_STR, CJS_ROLLDOWN_EXPORTS_REF,
CJS_ROLLDOWN_EXPORTS_REF_IDENT, CJS_ROLLDOWN_MODULE_REF, CJS_ROLLDOWN_MODULE_REF_IDENT,
};
use rolldown_ecmascript_utils::ExpressionFactoryExt as _;
use rustc_hash::FxHashSet;
use crate::hmr::{
hmr_ast_finalizer::HmrAstFinalizer,
utils::{HmrAstBuilder, MODULE_ID_PARAM_FOR_HMR},
};
impl<'ast> Traverse<'ast, ()> for HmrAstFinalizer<'_, 'ast> {
fn enter_program(
&mut self,
node: &mut ast::Program<'ast>,
ctx: &mut oxc_traverse::TraverseCtx<'ast, ()>,
) {
let taken_body = node.body.take_in(self);
node.body.reserve_exact(taken_body.len());
taken_body.into_iter().for_each(|top_level_stmt| {
self.handle_top_level_stmt(&mut node.body, top_level_stmt, ctx.scoping());
});
}
fn exit_program(
&mut self,
node: &mut ast::Program<'ast>,
ctx: &mut oxc_traverse::TraverseCtx<'ast, ()>,
) {
let mut try_block = ast::BlockStatement::boxed(SPAN, [], self);
let mut load_exports_stmts = std::mem::take(&mut self.generated_load_exports_stmts);
let mut dependencies_init_fn_stmts: Vec<ast::Statement<'ast>> = Vec::new();
let mut initialized = FxHashSet::default();
let mut next_copy = 0;
for dep in &self.dependencies {
let module = &self.modules[*dep];
if module.as_normal().is_some() {
dependencies_init_fn_stmts.push(ast::Statement::new_expression_statement(
SPAN,
self.make_init_module_call(module),
self,
));
}
if let Some(stmt) = load_exports_stmts.remove(dep) {
dependencies_init_fn_stmts.push(stmt);
}
initialized.insert(*dep);
while let Some(source) = self.re_export_all_dependencies.get_index(next_copy) {
let source_module = &self.modules[*source];
if source_module.as_normal().is_some() && !initialized.contains(source) {
break;
}
dependencies_init_fn_stmts.push(self.create_re_export_call_stmt(source_module));
next_copy += 1;
}
}
debug_assert!(load_exports_stmts.is_empty());
let runtime_module_register = self.generate_runtime_module_register_for_hmr(ctx.scoping());
let cjs_module_locals: Vec<ast::Statement<'ast>> = if self.module.exports_kind.is_commonjs() {
let empty_exports_object = ast::Expression::new_object_expression(SPAN, [], self);
let module_object = ast::Expression::new_object_expression(
SPAN,
[ast::ObjectPropertyKind::new_object_property(
SPAN,
ast::PropertyKind::Init,
ast::PropertyKey::new_static_identifier(SPAN, "exports", self),
empty_exports_object,
true,
false,
false,
self,
)],
self,
);
vec![
ast::Statement::from(ast::Declaration::new_variable_declaration(
SPAN,
ast::VariableDeclarationKind::Var,
oxc::allocator::Vec::from_value_in(
ast::VariableDeclarator::new(
SPAN,
ast::BindingPattern::new_binding_identifier(
SPAN,
CJS_ROLLDOWN_MODULE_REF_IDENT,
self,
),
None,
Some(module_object),
false,
self,
),
self,
),
false,
self,
)),
ast::Statement::from(ast::Declaration::new_variable_declaration(
SPAN,
ast::VariableDeclarationKind::Var,
oxc::allocator::Vec::from_value_in(
ast::VariableDeclarator::new(
SPAN,
ast::BindingPattern::new_binding_identifier(
SPAN,
CJS_ROLLDOWN_EXPORTS_REF_IDENT,
self,
),
None,
Some(Expression::new_member_access_expr(CJS_ROLLDOWN_MODULE_REF, "exports", self)),
false,
self,
),
self,
),
false,
self,
)),
]
} else {
vec![]
};
try_block.body.reserve_exact(
cjs_module_locals.len()
+ runtime_module_register.len()
+ node.body.len()
+ dependencies_init_fn_stmts.len()
+ 1,
);
try_block.body.extend(cjs_module_locals);
try_block.body.extend(runtime_module_register);
try_block.body.extend(dependencies_init_fn_stmts);
try_block.body.push(self.create_module_hot_context_initializer_stmt());
try_block.body.extend(node.body.take_in(self));
node
.body
.extend(std::mem::take(&mut self.generated_static_import_stmts_from_external).into_values());
let final_block = ast::BlockStatement::boxed(SPAN, [], self);
let try_stmt =
ast::Statement::new_try_statement(SPAN, try_block, None, Some(final_block), self);
let module_id_param = ast::FormalParameter::new(
SPAN,
[],
ast::BindingPattern::new_binding_identifier(SPAN, MODULE_ID_PARAM_FOR_HMR, self),
None,
None,
false,
None,
false,
false,
self,
);
let params = ast::FormalParameters::boxed(
SPAN,
ast::FormalParameterKind::Signature,
[module_id_param],
None,
self,
);
let is_async = self.module.ast_usage.contains(EcmaModuleAstUsage::TopLevelAwait);
let mut user_code_wrapper = ast::Function::boxed(
SPAN,
ast::FunctionType::FunctionExpression,
None,
false,
is_async,
false,
None,
None,
params,
None,
Some(ast::FunctionBody::boxed(SPAN, [], [try_stmt], self)),
self,
);
user_code_wrapper.pife = self.use_pife_for_module_wrappers;
let mut register_factory_args = oxc::allocator::Vec::with_capacity_in(3, self);
register_factory_args.push(ast::Argument::new_string_literal(
SPAN,
oxc::ast::ast::Str::from_str_in(&self.module.stable_id, self),
None,
self,
));
register_factory_args
.push(ast::Argument::from(ast::Expression::FunctionExpression(user_code_wrapper)));
if self.stamp != 0 {
register_factory_args.push(ast::Argument::new_numeric_literal(
SPAN,
f64::from(self.stamp),
None,
ast::NumberBase::Decimal,
self,
));
}
let register_factory_call = ast::Expression::new_call_expression(
SPAN,
Expression::new_identifier(SPAN, "__rolldown_runtime__.registerFactory", self),
None,
register_factory_args,
false,
self,
);
node.body.push(ast::Statement::new_expression_statement(SPAN, register_factory_call, self));
}
fn enter_call_expression(
&mut self,
node: &mut ast::CallExpression<'ast>,
_ctx: &mut oxc_traverse::TraverseCtx<'ast, ()>,
) {
self.rewrite_hot_accept_call_deps(node);
}
fn exit_call_expression(
&mut self,
node: &mut ast::CallExpression<'ast>,
ctx: &mut oxc_traverse::TraverseCtx<'ast, ()>,
) {
self.guard_import_binding_callee(&mut node.callee, ctx);
}
fn exit_tagged_template_expression(
&mut self,
node: &mut ast::TaggedTemplateExpression<'ast>,
ctx: &mut oxc_traverse::TraverseCtx<'ast, ()>,
) {
self.guard_import_binding_callee(&mut node.tag, ctx);
}
fn exit_expression(
&mut self,
node: &mut oxc::ast::ast::Expression<'ast>,
ctx: &mut oxc_traverse::TraverseCtx<'ast, ()>,
) {
if let ast::Expression::ThisExpression(this_expr) = node
&& self.module.exports_kind.is_commonjs()
&& self.module.ecma_view.this_expr_replace_map.contains_key(&this_expr.node_id())
{
*node = Expression::new_id_ref_expr(SPAN, CJS_ROLLDOWN_EXPORTS_REF, self);
return;
}
self.try_rewrite_dynamic_import(node);
self.try_rewrite_require(node, ctx);
self.rewrite_import_meta_hot(node);
}
fn exit_identifier_reference(
&mut self,
node: &mut ast::IdentifierReference<'ast>,
ctx: &mut oxc_traverse::TraverseCtx<'ast, ()>,
) {
self.rewrite_identifier_reference(node, ctx);
}
}
impl<'ast> HmrAstFinalizer<'_, 'ast> {
fn rewrite_identifier_reference(
&self,
ident: &mut ast::IdentifierReference<'ast>,
ctx: &oxc_traverse::TraverseCtx<'ast, ()>,
) {
let Some(reference_id) = ident.reference_id.get() else {
return;
};
let reference = ctx.scoping().get_reference(reference_id);
if let Some(symbol_id) = reference.symbol_id() {
if let Some(binding_name) = self.import_bindings.get(&symbol_id) {
ident.name = oxc::ast::ast::Str::from_str_in(binding_name.as_str(), self).into();
}
} else if ident.name == CJS_EXPORTS_REF_STR {
ident.name = CJS_ROLLDOWN_EXPORTS_REF_IDENT;
} else if ident.name == CJS_MODULE_REF_STR {
ident.name = CJS_ROLLDOWN_MODULE_REF_IDENT;
}
}
fn guard_import_binding_callee(
&self,
callee: &mut Expression<'ast>,
ctx: &oxc_traverse::TraverseCtx<'ast, ()>,
) {
let callee = callee.without_parentheses_mut();
let Expression::Identifier(ident) = callee else {
return;
};
let Some(reference_id) = ident.reference_id.get() else {
return;
};
let Some(symbol_id) = ctx.scoping().get_reference(reference_id).symbol_id() else {
return;
};
if !self.import_bindings.get(&symbol_id).is_some_and(|name| name.contains(['.', '['])) {
return;
}
let member = callee.take_in(self);
*callee = Expression::new_seq_in_parens(
Expression::new_numeric_literal(SPAN, 0.0, Some("0".into()), NumberBase::Decimal, self),
member,
self,
);
}
}