diff --git a/README.md b/README.md index efecc05..4255abc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A comprehensive analysis and comparison of different approaches to compile JavaS This repository explores 5 different JavaScript-to-WASM compilation approaches: -1. **QuickJS (Rust)** - 283KB gzipped ✅ **Recommended for Wasmer** +1. **QuickJS (Rust)** - 285KB gzipped ✅ **Recommended for Wasmer** 2. **Javy Static** - 519KB gzipped ✅ **Wasmer Compatible** 3. **Javy Dynamic** - 488KB + 2KB per module (Node.js only) 4. **Porffor** - 75KB gzipped (Node.js only) @@ -15,14 +15,14 @@ This repository explores 5 different JavaScript-to-WASM compilation approaches: ## 🏆 Key Results ### Wasmer Runtime Compatibility -- **✅ QuickJS**: Perfect compatibility, 283KB gzipped +- **✅ QuickJS**: Perfect compatibility, 285KB gzipped - **✅ Javy Static**: Perfect compatibility, 519KB gzipped - **❌ All others**: Require Node.js runtime or have compatibility issues ### Size Comparison (Gzipped) | Implementation | Size | Runtime | Wasmer | Best For | | --------------- | --------- | ---------- | ------ | ------------------------- | -| **QuickJS** | **283KB** | WASI | ✅ | **Production Wasmer** | +| **QuickJS** | **285KB** | WASI | ✅ | **Production Wasmer** | | **Javy Static** | **519KB** | WASI | ✅ | **Full JS Compatibility** | | Porffor | 75KB | Standard | ❌ | Size-critical Node.js | | TinyGo Basic | 92KB | Go Runtime | ❌ | Browser applications | @@ -208,4 +208,4 @@ MIT License - see [LICENSE](LICENSE) for details. --- -**For production Wasmer deployment, use QuickJS (283KB) for optimal size or Javy Static (519KB) for maximum JavaScript compatibility.** \ No newline at end of file +**For production Wasmer deployment, use QuickJS (285KB) for optimal size or Javy Static (519KB) for maximum JavaScript compatibility.** \ No newline at end of file diff --git a/docs/BINARY_SIZES.md b/docs/BINARY_SIZES.md index 8f46961..7dae9d6 100644 --- a/docs/BINARY_SIZES.md +++ b/docs/BINARY_SIZES.md @@ -18,7 +18,7 @@ This document tracks the binary sizes of different WASM implementations and opti | -------------- | ------------- | ------------ | ----------- | ------------- | ---------------------------------------------------- | | **TinyGo Opt** | 198 | **93** | 53.3% | **93KB** | Each operation adds ~93KB | | **Porffor** | 513 | **75** | **85.4%** | **75KB** | Each operation adds ~75KB | -| **QuickJS** | 703 | **286** | 59.3% | **286KB** | One-time runtime cost + minimal JS strings | +| **QuickJS** | 718 | **285** | 60.3% | **285KB** | One-time runtime cost + minimal JS strings | | **Javy Total** | 492 | **488** | 0.8% | **488KB** | **Additional operations add 4KB each (2KB gzipped)** | | Javy Plugin | 488 | 486 | 0.4% | - | Shared runtime (one-time cost) | | Javy Module | 4 | 2 | 50% | - | Per-operation cost | @@ -29,21 +29,21 @@ This document tracks the binary sizes of different WASM implementations and opti **For 1 operation:** - TinyGo: 93KB - Porffor: 75KB ⭐ **Smallest single operation** -- QuickJS: 286KB +- QuickJS: 285KB - Javy: 488KB - Goja: 3,716KB **For 5 operations:** - TinyGo: 465KB (5 × 93KB) - Porffor: 375KB (5 × 75KB) -- QuickJS: ~287KB (286KB + ~1KB JS strings) ⭐ **Best for multiple operations** +- QuickJS: ~286KB (285KB + ~1KB JS strings) ⭐ **Best for multiple operations** - Javy: 504KB (488KB + 4 × 4KB raw modules) - Goja: ~3,717KB (3,716KB + ~1KB JS strings) **For 10 operations:** - TinyGo: 930KB (10 × 93KB) - Porffor: 750KB (10 × 75KB) -- QuickJS: ~288KB (286KB + ~1KB JS strings) ⭐ **Scales excellently** +- QuickJS: ~286KB (285KB + ~1KB JS strings) ⭐ **Scales excellently** - Javy: 524KB (488KB + 9 × 4KB raw modules) - Goja: ~3,718KB (3,716KB + ~1KB JS strings) diff --git a/docs/FINAL_WASMER_SUMMARY.md b/docs/FINAL_WASMER_SUMMARY.md index a213f45..12190cb 100644 --- a/docs/FINAL_WASMER_SUMMARY.md +++ b/docs/FINAL_WASMER_SUMMARY.md @@ -4,14 +4,14 @@ After comprehensive testing of 5 different JavaScript-to-WASM approaches, **2 implementations work perfectly with Wasmer CLI**: -1. **QuickJS (Rust)**: 283KB gzipped - ✅ **RECOMMENDED** +1. **QuickJS (Rust)**: 285KB gzipped - ✅ **RECOMMENDED** 2. **Javy Static**: 519KB gzipped - ✅ **ALTERNATIVE** ## 📊 Complete Compatibility Matrix | Implementation | Raw Size | Gzipped | Wasmer CLI | Node.js | Best For | | --------------- | ----------- | ---------- | ---------- | ------- | ------------------------- | -| **QuickJS** | 692KB | **283KB** | ✅ Perfect | ✅ Yes | **Production Wasmer** | +| **QuickJS** | 718KB | **285KB** | ✅ Perfect | ✅ Yes | **Production Wasmer** | | **Javy Static** | 1.3MB | **519KB** | ✅ Perfect | ✅ Yes | **Full JS Compatibility** | | Javy Dynamic | 1.2MB+3.5KB | 488KB+2KB | ❌ No | ✅ Yes | Node.js only | | Porffor | 183KB | 75KB | ❌ No | ✅ Yes | Node.js only | @@ -20,7 +20,7 @@ After comprehensive testing of 5 different JavaScript-to-WASM approaches, **2 im ## 🏆 Wasmer Production Recommendations ### For Size-Optimized Deployment -**Choose QuickJS**: 283KB gzipped +**Choose QuickJS**: 285KB gzipped - Smallest Wasmer-compatible option - Full JavaScript engine with ECMAScript support - Perfect WASI compatibility @@ -58,7 +58,7 @@ cargo build --release --target wasm32-wasip1 # Test locally echo '{"test": "data"}' | wasmer run target/wasm32-wasip1/release/quickjs_transform.wasm -# Deploy (283KB gzipped) +# Deploy (285KB gzipped) cp target/wasm32-wasip1/release/quickjs_transform.wasm production/ ``` @@ -106,6 +106,6 @@ Future WASM standards may enable: ## ✅ Final Verdict -**For Wasmer production deployment, use QuickJS (283KB) for optimal size or Javy Static (519KB) for maximum JavaScript compatibility.** Both provide excellent performance, perfect Wasmer CLI compatibility, and production-ready reliability. +**For Wasmer production deployment, use QuickJS (285KB) for optimal size or Javy Static (519KB) for maximum JavaScript compatibility.** Both provide excellent performance, perfect Wasmer CLI compatibility, and production-ready reliability. The dynamic linking approaches (Javy plugin, module linking) are not currently supported by Wasmer CLI but may become available through future Wasmer SDK enhancements or WASM Component Model adoption. \ No newline at end of file diff --git a/docs/WASMER_COMPATIBILITY.md b/docs/WASMER_COMPATIBILITY.md index 5589b80..ecd51f4 100644 --- a/docs/WASMER_COMPATIBILITY.md +++ b/docs/WASMER_COMPATIBILITY.md @@ -6,7 +6,7 @@ This document outlines the compatibility of different WASM implementations with | Implementation | Runtime Type | Wasmer Compatible | Node.js Compatible | Notes | | -------------- | -------------- | ----------------- | ------------------ | ---------------------------------- | -| **QuickJS** | WASI | ✅ **Excellent** | ✅ Yes | Full JS engine, 286KB gzipped | +| **QuickJS** | WASI | ✅ **Excellent** | ✅ Yes | Full JS engine, 285KB gzipped | | **Porffor** | Standard WASM | ⚠️ **Partial** | ✅ Yes | Requires legacy exceptions support | | **Javy** | WASI (Dynamic) | ⚠️ **Partial** | ✅ Yes | Requires plugin loading, 488KB | | **Go/TinyGo** | Go Runtime | ❌ **No** | ✅ Yes | Requires wasm_exec.js | @@ -16,7 +16,7 @@ This document outlines the compatibility of different WASM implementations with ### 1. QuickJS (Recommended for Full JS Engine) -**Size**: 286KB gzipped +**Size**: 285KB gzipped **Runtime**: WASI (wasm32-wasip1) **Compatibility**: ✅ Perfect Wasmer compatibility @@ -30,7 +30,7 @@ wasmer run implementations/quickjs/target/wasm32-wasip1/release/quickjs_transfor **Advantages**: - Full JavaScript engine with ECMAScript compatibility -- One-time 286KB cost + minimal string overhead +- One-time 285KB cost + minimal string overhead - Excellent scaling for multiple operations - 92% smaller than Goja - Direct WASI compatibility @@ -199,7 +199,7 @@ make test-wasmer ## Summary **Best for Wasmer SDK Integration**: -1. **QuickJS**: Full JavaScript engine, excellent WASI compatibility (283KB) ⭐ **VERIFIED WORKING** +1. **QuickJS**: Full JavaScript engine, excellent WASI compatibility (285KB) ⭐ **VERIFIED WORKING** 2. **Porffor**: Size-optimized but incompatible with Wasmer (75KB) ❌ **NOT SUPPORTED** **Verified Test Results**: @@ -207,4 +207,4 @@ make test-wasmer - ❌ **Porffor + Wasmer**: Legacy exceptions not supported, even with `--enable-all` - ⚠️ **Javy + Wasmer**: Dynamic linking requires special handling -**Final Recommendation**: Use **QuickJS** as the primary choice for Wasmer SDK integration. It provides perfect WASI compatibility with full JavaScript engine capabilities at 283KB gzipped, making it ideal for production Wasmer deployments across all supported programming languages. \ No newline at end of file +**Final Recommendation**: Use **QuickJS** as the primary choice for Wasmer SDK integration. It provides perfect WASI compatibility with full JavaScript engine capabilities at 285KB gzipped, making it ideal for production Wasmer deployments across all supported programming languages. \ No newline at end of file diff --git a/implementations/quickjs/Cargo.toml b/implementations/quickjs/Cargo.toml index bb3865e..f699871 100644 --- a/implementations/quickjs/Cargo.toml +++ b/implementations/quickjs/Cargo.toml @@ -3,21 +3,13 @@ name = "quickjs-transform" version = "0.1.0" edition = "2021" -[lib] -crate-type = ["cdylib"] +[[bin]] +name = "quickjs_transform" +path = "src/main.rs" [dependencies] -wasm-bindgen = "0.2" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -console_error_panic_hook = "0.1" -js-sys = "0.3" rquickjs = { version = "0.6", features = ["bindgen"] } -[dependencies.web-sys] -version = "0.3" -features = ["console"] - [profile.release] # Tell `rustc` to optimize for small code size. opt-level = "s" diff --git a/implementations/quickjs/src/lib.rs b/implementations/quickjs/src/lib.rs deleted file mode 100644 index 13965d4..0000000 --- a/implementations/quickjs/src/lib.rs +++ /dev/null @@ -1,169 +0,0 @@ -use rquickjs::{Runtime, Context, Value}; -use std::ffi::{CStr, CString}; -use std::os::raw::c_char; - -// Direct WASM exports for WASI (no wasm-bindgen) -#[no_mangle] -pub extern "C" fn transform_data(input_ptr: *const c_char) -> *mut c_char { - // Convert C string to Rust string - let input_cstr = unsafe { CStr::from_ptr(input_ptr) }; - let json_string = match input_cstr.to_str() { - Ok(s) => s, - Err(_) => return std::ptr::null_mut(), - }; - - // Create a QuickJS runtime and context - let rt = match Runtime::new() { - Ok(rt) => rt, - Err(_) => return std::ptr::null_mut(), - }; - - let ctx = match Context::full(&rt) { - Ok(ctx) => ctx, - Err(_) => return std::ptr::null_mut(), - }; - - let result = ctx.with(|ctx| { - // JavaScript code to transform the data - let js_code = format!(r#" - (function() {{ - const inputData = `{}`; - const parsedData = JSON.parse(inputData); - - // Add processed flag and timestamp - parsedData.processed = true; - parsedData.timestamp = new Date().toISOString(); - parsedData.engine = "QuickJS-WASI"; - - // If there's a users array, increment ages - if (parsedData.users && Array.isArray(parsedData.users)) {{ - parsedData.users.forEach(user => {{ - if (typeof user.age === 'number') {{ - user.age += 1; - }} - }}); - }} - - return JSON.stringify(parsedData); - }})() - "#, json_string.replace('`', r#"\`"#)); - - // Execute the JavaScript code - let result: Result = ctx.eval(js_code.as_bytes()); - - match result { - Ok(value) => { - if let Some(s) = value.as_string() { - s.to_string().unwrap_or_default() - } else { - "null".to_string() - } - } - Err(_) => "error".to_string() - } - }); - - // Convert result to C string - match CString::new(result) { - Ok(c_string) => c_string.into_raw(), - Err(_) => std::ptr::null_mut(), - } -} - -#[no_mangle] -pub extern "C" fn execute_js(js_code_ptr: *const c_char, input_data_ptr: *const c_char) -> *mut c_char { - // Convert C strings to Rust strings - let js_code_cstr = unsafe { CStr::from_ptr(js_code_ptr) }; - let input_data_cstr = unsafe { CStr::from_ptr(input_data_ptr) }; - - let js_code = match js_code_cstr.to_str() { - Ok(s) => s, - Err(_) => return std::ptr::null_mut(), - }; - - let input_data = match input_data_cstr.to_str() { - Ok(s) => s, - Err(_) => return std::ptr::null_mut(), - }; - - // Create a QuickJS runtime and context - let rt = match Runtime::new() { - Ok(rt) => rt, - Err(_) => return std::ptr::null_mut(), - }; - - let ctx = match Context::full(&rt) { - Ok(ctx) => ctx, - Err(_) => return std::ptr::null_mut(), - }; - - let result = ctx.with(|ctx| { - // Set up the input data as a global variable - let setup_code = format!("const inputData = {};", input_data); - if ctx.eval::<(), _>(setup_code.as_bytes()).is_err() { - return "setup_error".to_string(); - } - - // Execute the user's JavaScript code - let result: Result = ctx.eval(js_code.as_bytes()); - - match result { - Ok(value) => { - if let Some(s) = value.as_string() { - s.to_string().unwrap_or_default() - } else if let Some(n) = value.as_number() { - n.to_string() - } else if let Some(b) = value.as_bool() { - b.to_string() - } else if value.is_null() { - "null".to_string() - } else if value.is_undefined() { - "undefined".to_string() - } else { - format!("{:?}", value) - } - } - Err(_) => "execution_error".to_string() - } - }); - - // Convert result to C string - match CString::new(result) { - Ok(c_string) => c_string.into_raw(), - Err(_) => std::ptr::null_mut(), - } -} - -// Memory management function for freeing strings -#[no_mangle] -pub extern "C" fn free_string(ptr: *mut c_char) { - if !ptr.is_null() { - unsafe { - let _ = CString::from_raw(ptr); - } - } -} - -// WASI entry point (required for WASI modules) -#[no_mangle] -pub extern "C" fn _start() { - // Read from stdin for Wasmer CLI compatibility - use std::io::{self, Read}; - - let mut input = String::new(); - if io::stdin().read_to_string(&mut input).is_ok() { - let input_cstring = match CString::new(input.trim()) { - Ok(s) => s, - Err(_) => return, - }; - - let result_ptr = transform_data(input_cstring.as_ptr()); - if !result_ptr.is_null() { - let result_cstr = unsafe { CStr::from_ptr(result_ptr) }; - if let Ok(result_str) = result_cstr.to_str() { - println!("{}", result_str); - } - free_string(result_ptr); - } - } -} diff --git a/implementations/quickjs/src/main.rs b/implementations/quickjs/src/main.rs new file mode 100644 index 0000000..32971fd --- /dev/null +++ b/implementations/quickjs/src/main.rs @@ -0,0 +1,67 @@ +use rquickjs::{Runtime, Context, Value}; +use std::env; +use std::io::{self, Read}; + +fn execute_js(js_code: &str, input_data: &str) -> Result { + let rt = Runtime::new().map_err(|e| format!("Failed to create runtime: {}", e))?; + let ctx = Context::full(&rt).map_err(|e| format!("Failed to create context: {}", e))?; + + ctx.with(|ctx| { + // Set up the input data as a global variable + let setup_code = format!("const inputData = `{}`;", input_data.replace('`', r#"\`"#)); + if ctx.eval::<(), _>(setup_code.as_bytes()).is_err() { + return Err("Failed to set up input data".to_string()); + } + + // Execute the user's JavaScript code + let result: Result = ctx.eval(js_code.as_bytes()); + + match result { + Ok(value) => { + if let Some(s) = value.as_string() { + Ok(s.to_string().unwrap_or_default()) + } else if let Some(n) = value.as_number() { + Ok(n.to_string()) + } else if let Some(b) = value.as_bool() { + Ok(b.to_string()) + } else if value.is_null() { + Ok("null".to_string()) + } else if value.is_undefined() { + Ok("undefined".to_string()) + } else { + Ok(format!("{:?}", value)) + } + } + Err(e) => Err(format!("JavaScript execution error: {:?}", e)) + } + }) +} + +fn main() { + let args: Vec = env::args().collect(); + + // Require JavaScript code as argument + if args.len() < 2 { + eprintln!("Usage: wasmer run quickjs.wasm 'JavaScript code' [input_data]"); + eprintln!("Example: echo '{{\"test\": 123}}' | wasmer run quickjs.wasm 'JSON.stringify({{result: JSON.parse(inputData)}})'"); + return; + } + + let js_code = args[1].clone(); + + // Read input data from stdin + let mut input_data = String::new(); + if let Err(e) = io::stdin().read_to_string(&mut input_data) { + eprintln!("Error reading input: {}", e); + return; + } + + // Trim whitespace from input + input_data = input_data.trim().to_string(); + + // Execute JavaScript with the input data + match execute_js(&js_code, &input_data) { + Ok(result) => println!("{}", result), + Err(e) => eprintln!("Error: {}", e), + } +} diff --git a/implementations/quickjs/transform.js b/implementations/quickjs/transform.js new file mode 100644 index 0000000..8c06296 --- /dev/null +++ b/implementations/quickjs/transform.js @@ -0,0 +1,25 @@ +// Sample JavaScript transformation function for QuickJS WASI +// This file can be executed with: wasmer run quickjs.wasm transform.js + +// Parse the input data (available as global variable 'inputData') +const data = JSON.parse(inputData); + +// Transform the data +const result = { + message: "Data has been processed by QuickJS WASI", + original: data, + timestamp: new Date().toISOString(), + transformed: true, + engine: "QuickJS-WASI-Dynamic", +}; + +// If there's a users array, increment ages +if (data.users && Array.isArray(data.users)) { + result.users = data.users.map((user) => ({ + ...user, + age: typeof user.age === "number" ? user.age + 1 : user.age, + })); +} + +// Return the result as JSON string +JSON.stringify(result);