use oxc::allocator::GetAllocator;
use oxc::ast::builder::AstBuilder;
use oxc::{ast::ast, span::SPAN};
use rolldown_common::NormalModule;
use rolldown_ecmascript::CJS_MODULE_REF;
#[cfg(feature = "experimental")]
use rolldown_ecmascript::CJS_ROLLDOWN_MODULE_REF;
use rolldown_ecmascript_utils::ExpressionFactoryExt as _;
#[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) -> 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::new_string_literal(
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::new_identifier(
SPAN,
binding_name_for_namespace_object_ref_atom,
&self.builder(),
);
Some(ast::Argument::new_object_expression(
SPAN,
[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(),
))
}
rolldown_common::ExportsKind::CommonJs => {
Some(ast::Argument::new_identifier(SPAN, Self::cjs_module_name(), &self.builder()))
}
rolldown_common::ExportsKind::None => None,
};
let register_call = ast::Expression::new_call_expression(
SPAN,
ast::Expression::new_identifier(SPAN, "__rolldown_runtime__.registerModule", &self.builder()),
None,
oxc::allocator::Vec::from_iter_in(
std::iter::once(self.module_id_argument()).chain(module_exports),
&self.builder(),
),
false,
&self.builder(),
);
ast::Statement::new_expression_statement(SPAN, register_call, &self.builder())
}
fn create_module_hot_context_initializer_stmt(&self) -> ast::Statement<'ast> {
ast::Statement::new_variable_declaration(
SPAN,
ast::VariableDeclarationKind::Const,
oxc::allocator::Vec::from_value_in(
ast::VariableDeclarator::new(
SPAN,
ast::BindingPattern::new_binding_identifier(
SPAN,
self.alias_name_for_import_meta_hot(),
&self.builder(),
),
None,
Some(ast::Expression::new_call_expression(
SPAN,
ast::Expression::new_identifier(
SPAN,
"__rolldown_runtime__.createModuleHotContext",
&self.builder(),
),
None,
[self.module_id_argument()],
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) -> AstBuilder<'ast> {
AstBuilder::new(self.ast_builder.allocator())
}
fn module(&self) -> &NormalModule {
self.module
}
fn binding_name_for_namespace_object_ref_atom(&self) -> ast::Str<'ast> {
ast::Str::from(MODULE_EXPORTS_NAME_FOR_ESM)
}
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::new_identifier(SPAN, MODULE_ID_PARAM_FOR_HMR, &self.builder())
}
}
impl<'any, 'ast> HmrAstBuilder<'any, 'ast> for ScopeHoistingFinalizer<'any, 'ast> {
fn builder(&self) -> AstBuilder<'ast> {
AstBuilder::new(self.ast_builder.allocator())
}
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
}
}
const LAZY_PROXY_QUERY: &str = "?rolldown-lazy=1";
const URI_COMPONENT_ENCODE_SET: &percent_encoding::AsciiSet = &percent_encoding::NON_ALPHANUMERIC
.remove(b'-')
.remove(b'_')
.remove(b'.')
.remove(b'!')
.remove(b'~')
.remove(b'*')
.remove(b'\'')
.remove(b'(')
.remove(b')');
pub fn create_request_lazy_call<'ast, B>(
proxy_module_id: &str,
stable_proxy_id: &str,
builder: &B,
) -> ast::Expression<'ast>
where
B: oxc::ast::builder::GetAstBuilder<'ast> + GetAllocator<'ast>,
{
let url_expr = {
let url_head = format!(
"/@vite/lazy?id={}&clientId=",
percent_encoding::utf8_percent_encode(proxy_module_id, URI_COMPONENT_ENCODE_SET)
);
let quasis = oxc::allocator::Vec::from_iter_in(
[
ast::TemplateElement::new(
SPAN,
ast::TemplateElementValue {
raw: ast::Str::from_str_in(&url_head, builder),
cooked: None,
},
false,
builder,
),
ast::TemplateElement::new(
SPAN,
ast::TemplateElementValue { raw: ast::Str::from(""), cooked: None },
true,
builder,
),
],
builder,
);
let expressions = oxc::allocator::Vec::from_iter_in(
[ast::Expression::new_member_access_expr("__rolldown_runtime__", "clientId", builder)],
builder,
);
ast::Expression::new_template_literal(SPAN, quasis, expressions, builder)
};
let fetch_chunk = ast::Expression::new_arrow_function_expression(
SPAN,
false,
None,
ast::FormalParameters::boxed(
SPAN,
ast::FormalParameterKind::ArrowFormalParameters,
[],
None,
builder,
),
None,
ast::ArrowFunctionBody::from(ast::Expression::new_import_expression(
SPAN, url_expr, None, None, builder,
)),
builder,
);
ast::Expression::new_call_expression(
SPAN,
ast::Expression::new_identifier(SPAN, "__rolldown_runtime__.requestLazy", builder),
None,
[
ast::Argument::new_string_literal(
SPAN,
ast::Str::from_str_in(
stable_proxy_id.strip_suffix(LAZY_PROXY_QUERY).unwrap_or(stable_proxy_id),
builder,
),
None,
builder,
),
ast::Argument::from(fetch_chunk),
],
false,
builder,
)
}