# JavaScript Engine Guide

## Overview

The `javascript.lua` module provides unified access to JavaScript execution engines:

1. **SpiderMonkey** (default) - Mozilla's JIT JavaScript engine
2. **QuickJS** - Embedded engine, small footprint
3. **Porffor** (experimental) - AOT JavaScript-to-native compiler

## Architecture Comparison

```mermaid
graph TB
    subgraph "SpiderMonkey JIT"
        A[JS Code] --> B[JIT Compile]
        B --> C[Execute]
        C --> D[Result]
    end

    subgraph "QuickJS"
        A3[JS Code] --> B3[Parse]
        B3 --> C3[Interpret/Bytecode]
        C3 --> D3[Result]
    end

    subgraph "Porffor AOT"
        A2[JS Code] --> B2[AOT Compile]
        B2 --> C2[Native Binary]
        C2 --> D2[Cache Binary]
        D2 --> E2[Execute]
        E2 --> F2[Result]
        D2 -->|Subsequent calls| E2
    end
```

## SpiderMonkey JIT (Default)

### Characteristics

- **Type**: Just-in-time (JIT) compiler
- **Startup**: Fast - no compilation step
- **Execution**: Optimized at runtime
- **Memory**: Higher memory footprint
- **Use Case**: Dynamic code evaluation, rapid prototyping, Vue rendering

### Performance

- **Operations/sec**: ~5.6M ops/sec
- **Latency**: ~0.18 μs per operation
- **Best For**: Frequent code changes, dynamic evaluation

### API Usage

```lua
local javascript = require "javascript/javascript"

-- Engine selection (default is spiderjs)
javascript.setEngine("spiderjs")

-- Create runtime context
local ctx = javascript.newRuntime()

-- Set path for module loading
javascript.setPath(rootPath, startPath)

-- Compile JavaScript code
local jsCode = "globalThis.hello = (name) => 'Hello ' + name + '!';"
javascript.setCode(ctx, jsCode, "hello", 0)

-- Execute with parameters
local result = javascript.runCode(ctx, "")

-- Switch to a different function in the same context
javascript.setCallFunction(ctx, "compileVueComponent", 1)
local result2, size2 = javascript.runCode(ctx, otherParam)

-- Clean up
javascript.freeRuntime(ctx)
```

### SpiderMonkey Notes

- **SpiderMonkey hangs on macOS arm64** in some scenarios — use QuickJS instead: `javascript.setEngine("quick-js")`
- `runCode(ctx, param)` passes the raw string to the JS function — JS must `JSON.parse()` internally
- `setCode(ctx, code, functionName, parameterCount)` loads a JS file and prepares a named function call

## QuickJS

Lightweight embedded engine. Accessed via `ctx:eval()`, `ctx:call()`, `ctx:evalFile()`.

### Characteristics

- **Type**: Interpreter + optional bytecode caching
- **Startup**: Fast
- **Performance**: ~2M ops/sec
- **Use Case**: Timer schedule calculation, module loading, testing

### API Usage

```lua
local javascript = require "javascript/javascript"
javascript.setEngine("quick-js")

local ctx = javascript.newRuntime()

-- Direct eval
local result, err = ctx:eval("2 + 2")           -- "4"
local result, err = ctx:evalInt("100 + 23")      -- 123

-- Function call
ctx:eval("function add(a, b) { return a + b; }")
local result, err = ctx:call("add", 5, 3)        -- "8"

-- File eval
local result, err = ctx:evalFile("/path/to/script.js")

-- Bytecode caching
local bytecode, err = ctx:compile(code, "test.js")
local result, err = ctx:evalBytecode(bytecode)

-- Type checking
ctx:isFunction("add")    -- true
ctx:isDefined("add")     -- true
ctx:typeOf("add")        -- "function"

-- Memory management
ctx:gc()
ctx:setMemoryLimit(1024 * 1024)
ctx:free()
```

### QuickJS Parameter Passing

`ctx:call(funcName, ...)` passes arguments directly — no JSON parsing needed:

```lua
ctx:eval("function add(a, b) { return a + b; }")
local result = ctx:call("add", 5, 3)
-- JS receives: add(5, 3) — values are native Lua types
```

## Engine Differences

| Feature | SpiderMonkey | QuickJS |
|---------|-------------|---------|
| Execution model | `setCode` + `runCode` | `ctx:eval` / `ctx:call` |
| Parameter passing | Raw string to JS function | JSON parsed, unpacked as args |
| File loading | Via `setPath` + `./file.js` | `ctx:evalFile(path)` |
| Bytecode caching | No | `ctx:compile` + `ctx:evalBytecode` |
| Multiple instances | Single context per `newRuntime` | Multiple independent runtimes |
| ES6 modules | Via `setCode` | `ctx:eval` with export detection |

## Porffor AOT (Experimental)

### Characteristics

- **Type**: Ahead-of-Time (AOT) compiler (JS → Wasm → C → native)
- **Startup**: Slower - compilation required
- **Execution**: Native performance after compilation
- **Memory**: Lower memory footprint, binaries cached
- **Use Case**: Static JS files, high-frequency execution

### Limitations

- **JavaScript Subset**: Porffor supports a limited ES6 subset
- **No Dynamic Features**: No `eval()`, `Function()` constructor
- **Module Dependencies**: Requires `acorn` parser and Node.js
- **Compilation Time**: First call slower due to compilation

### API Usage

```lua
local javascript = require "javascript/javascript"

-- Configure Porffor
javascript.usePorffor = true
javascript.porfforPath = "/path/to/porffor/runtime/index.js"
javascript.porfforCompiler = "clang"  -- or "gcc", "zig"
javascript.porfforOptLevel = "O3"     -- "Ofast", "O3", "O2", "O1", "O0"

-- Create Porffor runtime
local ctx = javascript.porfforNewRuntime()

-- Set path
javascript.porfforSetPath(rootPath, startPath)

-- Compile JavaScript to native binary
local jsCode = [[
export default function main() {
    return 10 + 20;
}
]]
javascript.porfforSetCode(ctx, jsCode, "addTenTwenty")

-- Execute compiled binary
local result = javascript.porfforRunCode(ctx, "")

-- Clean up
javascript.porfforFreeRuntime(ctx)
```

## Unified API

The unified API provides engine-agnostic code:

```lua
local javascript = require "javascript/javascript"

-- Switch between engines
javascript.usePorffor = false  -- SpiderMonkey (default)
-- javascript.usePorffor = true  -- Porffor

-- Use unified API functions
local ctx = javascript.newRuntimeUnified()
javascript.setPathUnified(rootPath, startPath)
javascript.setCodeUnified(ctx, jsCode, "functionName", paramCount)
local result = javascript.runCodeUnified(ctx, parameter)
javascript.freeRuntimeUnified(ctx)
```

## Auto-Selection (Experimental)

The unified API can automatically select the best engine based on usage patterns:

```lua
local javascript = require "javascript/javascript"

-- Enable auto-selection
javascript.autoSelectEngine = true
javascript.autoSelectThreshold = 1000  -- Switch to Porffor after 1000 calls

-- Use unified API - engine selected automatically
local ctx = javascript.newRuntimeUnified()
javascript.setCodeUnified(ctx, jsCode, "functionName", 0)

-- First calls use SpiderMonkey (fast startup)
for i = 1, 500 do
    javascript.runCodeUnified(ctx, param)
end

-- After threshold, automatically switches to Porffor
for i = 1, 10000 do
    javascript.runCodeUnified(ctx, param)
end

javascript.freeRuntimeUnified(ctx)
```

### Auto-Selection Configuration

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `autoSelectEngine` | boolean | false | Enable automatic engine selection |
| `autoSelectThreshold` | number | 1000 | Call count threshold for Porffor switch |

## Configuration Options

### SpiderMonkey

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `setDebug(level)` | number | 0 | Enable debug output |
| `setPath(rootPath, startPath)` | string, string | - | Set module loading paths |

### Porffor

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `usePorffor` | boolean | false | Enable Porffor engine |
| `porfforPath` | string | - | Path to Porffor runtime/index.js |
| `porfforCachePath` | string | "/tmp/porffor-cache/" | Cache directory for compiled binaries |
| `porfforCompiler` | string | "clang" | Compiler: "clang", "gcc", or "zig" |
| `porfforOptLevel` | string | "O3" | Optimization level: "Ofast", "O3", "O2", "O1", "O0" |

## Use Case Recommendations

### Use SpiderMonkey when

- Code changes frequently
- Dynamic code generation/evaluation needed
- Rapid development and testing
- Memory is not a constraint

### Use QuickJS when

- Running on macOS arm64 (SpiderMonkey may hang)
- Timer/schedule calculations with Later.js
- Need bytecode caching
- Lightweight embedded execution needed

### Use Porffor when

- JavaScript code is static
- High execution frequency (1000+ calls)
- Memory efficiency is important
- Maximum performance required
- Startup time is not critical

## Performance Comparison

Based on benchmark results:

| Engine | Ops/sec | Latency | Startup | Memory |
|--------|---------|---------|---------|--------|
| SpiderMonkey | 5.6M | 0.18 μs | ~4ms | Higher |
| QuickJS | ~2M | ~0.5 μs | Fast | Lower |
| Porffor (cold) | N/A | ~50ms (compilation) | ~50ms | Lower |
| Porffor (warm) | TBD | TBD | ~1ms | Lower |

**Note**: Porffor performance improves with binary caching. Subsequent calls skip compilation.

## Example: Vue Component Rendering

### SpiderMonkey

```lua
local javascript = require "javascript/javascript"
local ctx = javascript.newRuntime()
javascript.setPath("/path/to/vue-renderer/", "renderer/")

local param = '{"fileName":"test","state":{"rec":{"name":"Test"}},"html":"<nc-form>{{rec.name}}</nc-form>"}'
javascript.setCode(ctx, param, "renderPage", 1)
local html = javascript.runCode(ctx, param)

javascript.freeRuntime(ctx)
```

### QuickJS

```lua
local javascript = require "javascript/javascript"
javascript.setEngine("quick-js")

local ctx = javascript.newRuntime()
ctx:evalFile("/path/to/vue-renderer/render.js")

local result = ctx:call("renderPage", {fileName = "test", state = {rec = {name = "Test"}}})

ctx:free()
```

### Porffor (Future)

```lua
local javascript = require "javascript/javascript"
javascript.usePorffor = true

local ctx = javascript.porfforNewRuntime()
javascript.porfforSetPath("/path/to/vue-renderer/", "renderer/")

local param = '{"fileName":"test","state":{"rec":{"name":"Test"}},"html":"<nc-form>{{rec.name}}</nc-form>"}'
javascript.porfforSetCode(ctx, param, "renderPage")  -- Compiles to binary
local html = javascript.porfforRunCode(ctx, param)     -- Executes cached binary

javascript.porfforFreeRuntime(ctx)
```

## Use Cases

### Timer Schedule Calculation

`lib/timer.lua` uses QuickJS to run Later.js:

```lua
javascript.setEngine("quick-js")
local ctx = javascript.newRuntime()
ctx:eval(laterJsCode)  -- defines schedule() function
local result = ctx:call("schedule", "every 10 min", 3)
-- returns "1713283800,1713284400,1713285000"
```

### Vue Component Rendering

`plugin/vue-renderer/renderer/render.lua` uses SpiderMonkey:

```lua
local ctx = javascript.newRuntime()
javascript.setPath(rootPath, "renderer")
javascript.setCode(ctx, "./render.js", "renderPage", 1)
local result, size = javascript.runCode(ctx, jsonParam)
```

## Troubleshooting

### SpiderMonkey Issues

**Issue**: "rootPath is not set"
**Solution**: Call `javascript.setPath(rootPath, startPath)` before `setCode()`

**Issue**: "context is null"
**Solution**: Ensure `newRuntime()` succeeded and `ctx` is not nil

**Issue**: SpiderMonkey hangs on macOS arm64
**Solution**: Use QuickJS instead: `javascript.setEngine("quick-js")`

### QuickJS Issues

**Issue**: `ctx:call()` returns nil
**Solution**: Ensure the function was defined via `ctx:eval()` before calling it

**Issue**: Memory limit exceeded
**Solution**: Increase limit with `ctx:setMemoryLimit()` or call `ctx:gc()`

### Porffor Issues

**Issue**: "Cannot find package 'acorn'"
**Solution**: Install dependencies: `cd /path/to/porffor && npm install`

**Issue**: "Porffor compilation failed"
**Solution**: Check JavaScript syntax compatibility with Porffor's ES6 subset

**Issue**: "Porffor requires specific ES6 subset syntax"
**Solution**: Use simplified JavaScript without advanced features

## Testing

Run tests with:

```bash
# SpiderMonkey + QuickJS benchmark (runs both engines)
lj test/lib/javascript-test.lua

# QuickJS comprehensive tests
lj test/lib/javascript/quick-js-test.lua

# Component creation tests
lj test/lib/javascript-working-test.lua

# Timer/Later.js tests
lj test/lib/timer-test.lua
```

## Future Work

- [ ] Complete Porffor dependency setup
- [ ] Performance benchmarks with actual Vue components
- [ ] Production-ready Porffor integration
- [ ] Automatic engine selection based on usage patterns
- [ ] Hybrid mode: SpiderMonkey for dev, Porffor for production

## See Also

- [SpiderMonkey Documentation](https://spidermonkey.dev/)
- [QuickJS Documentation](https://bellard.org/quickjs/)
- [Porffor Documentation](https://porffor.dev/)
- [Vue Renderer Plugin Documentation](/Volumes/nc/nc-plugin/plugin/vue-renderer/documentation/dev/vue-renderer-plugin.md)
