1use std::time::Instant;23use napi_derive::napi;4use react_compiler::entrypoint::PluginOptions;5use react_compiler::entrypoint::compile_program;6use react_compiler::timing::TimingEntry;7use react_compiler_ast::File;8use react_compiler_ast::scope::ScopeInfo;9use serde::Deserialize;1011/// Deserialize JSON with no recursion limit (for deeply nested ASTs).12fn from_json_str<'de, T: Deserialize<'de>>(s: &'de str) -> serde_json::Result<T> {13 let mut deserializer = serde_json::Deserializer::from_str(s);14 deserializer.disable_recursion_limit();15 T::deserialize(&mut deserializer)16}1718/// Main entry point for the React Compiler.19///20/// Receives a full program AST, scope information, and resolved options21/// as JSON strings. Returns a JSON string containing the CompileResult.22///23/// This function is called by the JS shim (bridge.ts) via napi-rs.24/// Spawns a dedicated thread with 64MB stack to handle deeply nested ASTs25/// that would overflow the default Node.js thread stack.26#[napi]27pub fn compile(ast_json: String, scope_json: String, options_json: String) -> napi::Result<String> {28 let handle = std::thread::Builder::new()29 .stack_size(64 * 1024 * 1024) // 64MB stack30 .spawn(move || compile_inner(ast_json, scope_json, options_json))31 .map_err(|e| napi::Error::from_reason(format!("Failed to spawn compiler thread: {}", e)))?;3233 match handle.join() {34 Ok(result) => result,35 Err(panic_payload) => {36 let msg = if let Some(s) = panic_payload.downcast_ref::<&str>() {37 format!("Rust compiler panicked: {}", s)38 } else if let Some(s) = panic_payload.downcast_ref::<String>() {39 format!("Rust compiler panicked: {}", s)40 } else {41 "Rust compiler panicked (unknown payload)".to_string()42 };43 Err(napi::Error::from_reason(msg))44 }45 }46}4748fn compile_inner(49 ast_json: String,50 scope_json: String,51 options_json: String,52) -> napi::Result<String> {53 // Check if profiling is enabled by peeking at the options JSON54 let profiling = options_json.contains("\"__profiling\":true");5556 let deser_start = Instant::now();5758 let ast: File = from_json_str(&ast_json)59 .map_err(|e| napi::Error::from_reason(format!("Failed to parse AST JSON: {}", e)))?;6061 let scope: ScopeInfo = from_json_str(&scope_json)62 .map_err(|e| napi::Error::from_reason(format!("Failed to parse scope JSON: {}", e)))?;6364 let opts: PluginOptions = from_json_str(&options_json)65 .map_err(|e| napi::Error::from_reason(format!("Failed to parse options JSON: {}", e)))?;6667 let deser_duration = deser_start.elapsed();6869 let compile_start = Instant::now();70 let mut result = compile_program(ast, scope, opts);71 let compile_duration = compile_start.elapsed();7273 // If profiling is enabled, prepend NAPI deserialization timing and append serialization timing74 if profiling {75 let napi_deser_entry = TimingEntry {76 name: "napi_deserialize".to_string(),77 duration_us: deser_duration.as_micros() as u64,78 };7980 // Insert NAPI timing entries81 match &mut result {82 react_compiler::entrypoint::CompileResult::Success { timing, .. } => {83 timing.insert(0, napi_deser_entry);84 }85 react_compiler::entrypoint::CompileResult::Error { timing, .. } => {86 timing.insert(0, napi_deser_entry);87 }88 }8990 // Add compile_program duration (the total Rust compilation time including pass timing)91 let compile_entry = TimingEntry {92 name: "napi_compile_program".to_string(),93 duration_us: compile_duration.as_micros() as u64,94 };95 match &mut result {96 react_compiler::entrypoint::CompileResult::Success { timing, .. } => {97 timing.push(compile_entry);98 }99 react_compiler::entrypoint::CompileResult::Error { timing, .. } => {100 timing.push(compile_entry);101 }102 }103 }104105 let ser_start = Instant::now();106 let result_json = serde_json::to_string(&result)107 .map_err(|e| napi::Error::from_reason(format!("Failed to serialize result: {}", e)))?;108109 if profiling {110 // We need to inject the serialization timing into the already-serialized JSON.111 // Since timing is a JSON array at the end of the result, we can append to it.112 let ser_duration = ser_start.elapsed();113 let ser_entry = format!(114 r#"{{"name":"napi_serialize","duration_us":{}}}"#,115 ser_duration.as_micros()116 );117118 // Find the timing array in the JSON and append our entry119 if let Some(pos) = result_json.rfind("\"timing\":[") {120 // Find the closing ] of the timing array121 let timing_start = pos + "\"timing\":[".len();122 if let Some(close_bracket) = result_json[timing_start..].rfind(']') {123 let abs_close = timing_start + close_bracket;124 let mut patched = result_json[..abs_close].to_string();125 if abs_close > timing_start {126 // Array is non-empty, add comma127 patched.push(',');128 }129 patched.push_str(&ser_entry);130 patched.push_str(&result_json[abs_close..]);131 return Ok(patched);132 }133 }134 }135136 Ok(result_json)137}