use anyhow::{Context, Result};
use crate::esm_module_registry::EsmModuleRegistry;
use crate::ssr_isolate::SsrIsolate;
#[derive(Clone)]
pub struct EsmSourceModule {
pub specifier: String,
pub source: String,
}
impl SsrIsolate {
pub fn load_esm_modules(
&mut self,
polyfills_js: &str,
dep_modules: &[EsmSourceModule],
source_modules: &[EsmSourceModule],
entry_specifier: &str,
entry_source: &str,
dep_aliases: &[(String, String)],
) -> Result<()> {
let mut registry = EsmModuleRegistry::new();
v8::scope_with_context!(scope, &mut self.isolate, &self.context);
if !polyfills_js.is_empty() {
v8_eval!(scope, polyfills_js, "v8-polyfills.js")
.context("Failed to evaluate V8 polyfills")?;
}
for module in dep_modules {
registry
.compile_module(scope, &module.specifier, &module.source)
.with_context(|| format!("Failed to compile dep module: {}", module.specifier))?;
}
for (alias, target) in dep_aliases {
registry.alias_module(alias, target);
}
for module in source_modules {
registry
.compile_module(scope, &module.specifier, &module.source)
.with_context(|| format!("Failed to compile module: {}", module.specifier))?;
}
registry
.compile_module(scope, entry_specifier, entry_source)
.with_context(|| format!("Failed to compile entry module: {entry_specifier}"))?;
registry
.instantiate_and_evaluate(scope, entry_specifier)
.with_context(|| format!("Failed to evaluate entry module: {entry_specifier}"))?;
let ctx = scope.get_current_context();
let global = ctx.global(scope);
let render_fn = v8_get_global_fn!(scope, global, "__rex_render_page")?;
let gssp_fn = v8_get_global_fn!(scope, global, "__rex_get_server_side_props")?;
let gsp_fn = v8_get_global_fn!(scope, global, "__rex_get_static_props")?;
self.render_fn = v8::Global::new(scope, render_fn);
self.gssp_fn = v8::Global::new(scope, gssp_fn);
self.gsp_fn = v8::Global::new(scope, gsp_fn);
self.api_handler_fn = v8_get_optional_fn!(scope, global, "__rex_call_api_handler");
self.document_fn = v8_get_optional_fn!(scope, global, "__rex_render_document");
self.middleware_fn = v8_get_optional_fn!(scope, global, "__rex_run_middleware");
self.rsc_flight_fn = v8_get_optional_fn!(scope, global, "__rex_render_flight");
self.rsc_to_html_fn = v8_get_optional_fn!(scope, global, "__rex_render_rsc_to_html");
self.mcp_call_fn = v8_get_optional_fn!(scope, global, "__rex_call_mcp_tool");
self.mcp_list_fn = v8_get_optional_fn!(scope, global, "__rex_list_mcp_tools");
self.server_action_fn = v8_get_optional_fn!(scope, global, "__rex_call_server_action");
self.server_action_encoded_fn =
v8_get_optional_fn!(scope, global, "__rex_call_server_action_encoded");
self.form_action_fn = v8_get_optional_fn!(scope, global, "__rex_call_form_action");
self.app_route_handler_fn =
v8_get_optional_fn!(scope, global, "__rex_call_app_route_handler");
self.gsp_paths_fn = v8_get_optional_fn!(scope, global, "__rex_get_static_paths");
tracing::debug!("ESM modules loaded into V8 context");
Ok(())
}
pub fn invalidate_esm_module(
&mut self,
dep_modules: &[EsmSourceModule],
source_modules: &[EsmSourceModule],
entry_specifier: &str,
entry_source: &str,
dep_aliases: &[(String, String)],
) -> Result<()> {
let mut registry = EsmModuleRegistry::new();
v8::scope_with_context!(scope, &mut self.isolate, &self.context);
v8_eval!(scope, "globalThis.__rex_pages = {};", "<invalidate>")?;
for module in dep_modules {
registry
.compile_module(scope, &module.specifier, &module.source)
.with_context(|| format!("Failed to recompile dep module: {}", module.specifier))?;
}
for (alias, target) in dep_aliases {
registry.alias_module(alias, target);
}
for module in source_modules {
registry
.compile_module(scope, &module.specifier, &module.source)
.with_context(|| format!("Failed to recompile module: {}", module.specifier))?;
}
registry
.compile_module(scope, entry_specifier, entry_source)
.with_context(|| format!("Failed to compile entry: {entry_specifier}"))?;
registry
.instantiate_and_evaluate(scope, entry_specifier)
.with_context(|| format!("Failed to evaluate entry: {entry_specifier}"))?;
let ctx = scope.get_current_context();
let global = ctx.global(scope);
let render_fn = v8_get_global_fn!(scope, global, "__rex_render_page")?;
let gssp_fn = v8_get_global_fn!(scope, global, "__rex_get_server_side_props")?;
let gsp_fn = v8_get_global_fn!(scope, global, "__rex_get_static_props")?;
self.render_fn = v8::Global::new(scope, render_fn);
self.gssp_fn = v8::Global::new(scope, gssp_fn);
self.gsp_fn = v8::Global::new(scope, gsp_fn);
self.api_handler_fn = v8_get_optional_fn!(scope, global, "__rex_call_api_handler");
self.document_fn = v8_get_optional_fn!(scope, global, "__rex_render_document");
self.middleware_fn = v8_get_optional_fn!(scope, global, "__rex_run_middleware");
self.rsc_flight_fn = v8_get_optional_fn!(scope, global, "__rex_render_flight");
self.rsc_to_html_fn = v8_get_optional_fn!(scope, global, "__rex_render_rsc_to_html");
self.mcp_call_fn = v8_get_optional_fn!(scope, global, "__rex_call_mcp_tool");
self.mcp_list_fn = v8_get_optional_fn!(scope, global, "__rex_list_mcp_tools");
self.server_action_fn = v8_get_optional_fn!(scope, global, "__rex_call_server_action");
self.server_action_encoded_fn =
v8_get_optional_fn!(scope, global, "__rex_call_server_action_encoded");
self.form_action_fn = v8_get_optional_fn!(scope, global, "__rex_call_form_action");
self.app_route_handler_fn =
v8_get_optional_fn!(scope, global, "__rex_call_app_route_handler");
self.gsp_paths_fn = v8_get_optional_fn!(scope, global, "__rex_get_static_paths");
tracing::debug!("ESM module invalidated and reloaded");
Ok(())
}
pub fn eval_script(&mut self, script_js: &str, label: &str) -> Result<()> {
v8::scope_with_context!(scope, &mut self.isolate, &self.context);
v8_eval!(scope, script_js, label)
.with_context(|| format!("Failed to evaluate script: {label}"))?;
let ctx = scope.get_current_context();
let global = ctx.global(scope);
self.rsc_flight_fn = v8_get_optional_fn!(scope, global, "__rex_render_flight");
self.rsc_to_html_fn = v8_get_optional_fn!(scope, global, "__rex_render_rsc_to_html");
tracing::debug!(label, "Script evaluated in V8 context");
Ok(())
}
}