use oxc::ast::builder::GetAstBuilder;
use oxc::{
ast::{NONE, ast},
span::SPAN,
};
use rolldown_common::NormalModule;
use rolldown_ecmascript::CJS_MODULE_REF;
#[cfg(feature = "experimental")]
use rolldown_ecmascript::CJS_ROLLDOWN_MODULE_REF;
#[cfg(feature = "experimental")]
use crate::hmr::hmr_ast_finalizer::HmrAstFinalizer;
use crate::module_finalizers::ScopeHoistingFinalizer;
#[cfg(feature = "experimental")]
pub static MODULE_EXPORTS_NAME_FOR_ESM: &str = "__rolldown_exports__";
#[cfg(feature = "experimental")]
pub static MODULE_ID_PARAM_FOR_HMR: &str = "__rolldown_module_id__";
pub trait HmrAstBuilder<'any, 'ast> {
fn builder(&self) -> oxc::ast::AstBuilder<'ast>;
fn module(&self) -> &NormalModule;
fn binding_name_for_namespace_object_ref_atom(&self) -> ast::Str<'ast>;
fn alias_name_for_import_meta_hot(&self) -> ast::Str<'ast>;
fn cjs_module_name() -> &'static str;
fn module_id_argument(&self) -> ast::Argument<'ast> {
ast::Argument::StringLiteral(ast::StringLiteral::boxed(
SPAN,
ast::Str::from_str_in(&self.module().stable_id, &self.builder()),
None,
&self.builder(),
))
}
fn create_register_module_stmt(&self) -> ast::Statement<'ast> {
let module_exports = match self.module().exports_kind {
rolldown_common::ExportsKind::Esm => {
let binding_name_for_namespace_object_ref_atom =
self.binding_name_for_namespace_object_ref_atom();
let namespace_object_ref_expr =
ast::Expression::Identifier(ast::IdentifierReference::boxed(
SPAN,
binding_name_for_namespace_object_ref_atom,
&self.builder(),
));
ast::Argument::ObjectExpression(ast::ObjectExpression::boxed(
SPAN,
oxc::allocator::Vec::from_value_in(
ast::ObjectPropertyKind::new_object_property(
SPAN,
ast::PropertyKind::Init,
ast::PropertyKey::new_static_identifier(SPAN, "exports", &self.builder()),
namespace_object_ref_expr,
true,
false,
false,
&self.builder(),
),
&self.builder(),
),
&self.builder(),
))
}
rolldown_common::ExportsKind::CommonJs => {
ast::Argument::from(ast::Expression::Identifier(ast::IdentifierReference::boxed(
SPAN,
Self::cjs_module_name(),
&self.builder(),
)))
}
rolldown_common::ExportsKind::None => {
ast::Argument::from(ast::Expression::ObjectExpression(ast::ObjectExpression::boxed(
SPAN,
oxc::allocator::Vec::new_in(&self.builder()),
&self.builder(),
)))
}
};
let arguments = oxc::allocator::Vec::from_array_in(
[self.module_id_argument(), module_exports],
&self.builder(),
);
let register_call = ast::CallExpression::boxed(
SPAN,
ast::Expression::Identifier(ast::IdentifierReference::boxed(
SPAN,
"__rolldown_runtime__.registerModule",
&self.builder(),
)),
NONE,
arguments,
false,
&self.builder(),
);
ast::Statement::ExpressionStatement(ast::ExpressionStatement::boxed(
SPAN,
ast::Expression::CallExpression(register_call),
&self.builder(),
))
}
fn create_module_hot_context_initializer_stmt(&self) -> ast::Statement<'ast> {
ast::Statement::VariableDeclaration(ast::VariableDeclaration::boxed(
SPAN,
ast::VariableDeclarationKind::Const,
oxc::allocator::Vec::from_value_in(
ast::VariableDeclarator::new(
SPAN,
ast::VariableDeclarationKind::Const,
ast::BindingPattern::new_binding_identifier(
SPAN,
self.alias_name_for_import_meta_hot(),
&self.builder(),
),
NONE,
Some(ast::Expression::CallExpression(ast::CallExpression::boxed(
SPAN,
ast::Expression::Identifier(ast::IdentifierReference::boxed(
SPAN,
"__rolldown_runtime__.createModuleHotContext",
&self.builder(),
)),
NONE,
oxc::allocator::Vec::from_value_in(self.module_id_argument(), &self.builder()),
false,
&self.builder(),
))),
false,
&self.builder(),
),
&self.builder(),
),
false,
&self.builder(),
))
}
}
#[cfg(feature = "experimental")]
impl<'any, 'ast> HmrAstBuilder<'any, 'ast> for HmrAstFinalizer<'any, 'ast> {
fn builder(&self) -> oxc::ast::AstBuilder<'ast> {
*self.ast_factory.builder()
}
fn module(&self) -> &NormalModule {
self.module
}
fn binding_name_for_namespace_object_ref_atom(&self) -> ast::Str<'ast> {
ast::Str::from_str_in(MODULE_EXPORTS_NAME_FOR_ESM, &self.builder())
}
fn alias_name_for_import_meta_hot(&self) -> ast::Str<'ast> {
ast::Str::from_str_in(&format!("hot_{}", self.module.repr_name), &self.builder())
}
fn cjs_module_name() -> &'static str {
CJS_ROLLDOWN_MODULE_REF
}
fn module_id_argument(&self) -> ast::Argument<'ast> {
ast::Argument::Identifier(ast::IdentifierReference::boxed(
SPAN,
MODULE_ID_PARAM_FOR_HMR,
&self.builder(),
))
}
}
impl<'any, 'ast> HmrAstBuilder<'any, 'ast> for ScopeHoistingFinalizer<'any, 'ast> {
fn builder(&self) -> oxc::ast::AstBuilder<'ast> {
*self.ast_factory.builder()
}
fn module(&self) -> &NormalModule {
self.ctx.module
}
fn binding_name_for_namespace_object_ref_atom(&self) -> ast::Str<'ast> {
let name = self.canonical_name_for(self.ctx.module.namespace_object_ref);
ast::Str::from_str_in(name, &self.builder())
}
fn alias_name_for_import_meta_hot(&self) -> ast::Str<'ast> {
let name =
self.canonical_name_for(self.ctx.module.hmr_hot_ref.expect("HMR hot ref should be set"));
ast::Str::from_str_in(name, &self.builder())
}
fn cjs_module_name() -> &'static str {
CJS_MODULE_REF
}
}