go-quickjs-wasi-reactor
A Go module that embeds the QuickJS-NG WASI WebAssembly runtime (reactor model).
Related Projects
- js-quickjs-wasi-reactor - JavaScript/TypeScript harness for browser environments
- go-quickjs-wasi - Standard WASI command model with blocking
_start()entry point - paralin/quickjs - Fork with
QJS_WASI_REACTORbuild target - QuickJS-NG reactor PR - Upstream PR for reactor support
- QuickJS-NG event loop PR - Upstream PR for non-blocking event loop
Variants
This repository provides the reactor model WASM binary for re-entrant execution. If you only need simple blocking execution where QuickJS runs to completion in _start(), see the command model variant above.
About QuickJS-NG
QuickJS is a small and embeddable JavaScript engine. It aims to support the latest ECMAScript specification.
This project uses QuickJS-NG, a community-driven fork of the original QuickJS project by Fabrice Bellard and Charlie Gordon. Both projects are actively maintained; QuickJS-NG focuses on community contributions and features like the WASI reactor build used here.
Purpose
This module provides easy access to the QuickJS-NG JavaScript engine compiled to WebAssembly with WASI support using the reactor model. The WASM binary is embedded directly in the Go module, making it easy to use QuickJS in Go applications without external dependencies.
Reactor Model
Unlike the standard WASI "command" model that blocks in _start(), the reactor
model exports the raw QuickJS C API functions, enabling full control over the
JavaScript runtime lifecycle from the host environment.
The reactor exports the complete QuickJS C API including:
Core Runtime:
JS_NewRuntime,JS_FreeRuntime- Runtime lifecycleJS_NewContext,JS_FreeContext- Context lifecycleJS_Eval- Evaluate JavaScript codeJS_Call- Call JavaScript functions
Standard Library (quickjs-libc.h):
js_init_module_std,js_init_module_os,js_init_module_bjson- Module initializationjs_std_init_handlers,js_std_free_handlers- I/O handler setupjs_std_add_helpers- Add console.log, print, etc.js_std_loop_once- Run one iteration of the event loop (non-blocking)js_std_poll_io- Poll for I/O events
Memory Management:
malloc,free,realloc,calloc- For host to allocate memory
Features
- Embeds the QuickJS-NG WASI reactor WebAssembly binary
- Provides version information about the embedded QuickJS release
- High-level Go API via the
wazero-quickjssubpackage - Update script to build and copy from local QuickJS checkout
Packages
Root Package (github.com/aperturerobotics/go-quickjs-wasi-reactor)
Provides the embedded WASM binary and version information:
package main import ( "fmt" quickjswasi "github.com/aperturerobotics/go-quickjs-wasi-reactor" ) func main() { // Access the embedded WASM binary wasmBytes := quickjswasi.QuickJSWASM fmt.Printf("QuickJS WASM size: %d bytes\n", len(wasmBytes)) // Get version information fmt.Printf("QuickJS version: %s\n", quickjswasi.Version) fmt.Printf("Download URL: %s\n", quickjswasi.DownloadURL) }
Wazero QuickJS Library (github.com/aperturerobotics/go-quickjs-wasi-reactor/wazero-quickjs)
High-level Go API for running JavaScript with wazero:
package main import ( "context" "embed" "os" quickjs "github.com/aperturerobotics/go-quickjs-wasi-reactor/wazero-quickjs" "github.com/tetratelabs/wazero" ) //go:embed scripts var scriptsFS embed.FS func main() { ctx := context.Background() r := wazero.NewRuntime(ctx) defer r.Close(ctx) config := wazero.NewModuleConfig(). WithStdout(os.Stdout). WithStderr(os.Stderr). WithFS(scriptsFS) // Mount embedded filesystem qjs, _ := quickjs.NewQuickJS(ctx, r, config) defer qjs.Close(ctx) // Option 1: Initialize with CLI args to load script via WASI filesystem qjs.Init(ctx, []string{"qjs", "--std", "scripts/main.js"}) // Option 2: Initialize with --std and eval code directly // qjs.Init(ctx, []string{"qjs", "--std"}) // qjs.Eval(ctx, `console.log("Hello from QuickJS!");`, false) // Run event loop until idle qjs.RunLoop(ctx) }
Initialization Options
// Basic runtime with std modules available for import qjs.Init(ctx, nil) qjs.Eval(ctx, `import * as std from 'qjs:std'; std.printf("Hello\n")`, true) // With --std flag to expose std, os, bjson as globals qjs.Init(ctx, []string{"qjs", "--std"}) qjs.Eval(ctx, `std.printf("Hello\n")`, false) // std is already global // With script args accessible via scriptArgs global qjs.Init(ctx, []string{"qjs", "script.js", "--verbose"}) qjs.Eval(ctx, `console.log(scriptArgs)`, false) // ['qjs', 'script.js', '--verbose']
Non-Blocking Event Loop
The reactor model enables cooperative scheduling with the host:
// Instead of blocking RunLoop(), use LoopOnce() for fine-grained control for { result, err := qjs.LoopOnce(ctx) if err != nil { return err } switch { case result == quickjs.LoopIdle: // No pending work - done return nil case result == quickjs.LoopError: return errors.New("JavaScript error") case result == 0: // More microtasks pending - continue immediately continue case result > 0: // Timer pending - host can do other work before next iteration time.Sleep(time.Duration(result) * time.Millisecond) } }
I/O Polling
For scripts that use os.setReadHandler(), the host must poll for I/O:
// Poll for I/O events (non-blocking) result, _ := qjs.PollIO(ctx, 0) // Poll with timeout (blocking up to 100ms) result, _ := qjs.PollIO(ctx, 100)
See the wazero-quickjs README for more details.
Command-Line REPL
A command-line JavaScript runner with interactive REPL mode is provided:
# Install go install github.com/aperturerobotics/go-quickjs-wasi-reactor/wazero-quickjs/repl@master # Interactive REPL repl # Run a JavaScript file repl script.js # Run as ES module repl script.mjs --module
Updating
To update to the latest QuickJS-NG reactor build from a local checkout:
# Set QUICKJS_DIR to your quickjs checkout (default: ../quickjs)
QUICKJS_DIR=/path/to/quickjs ./update-quickjs.bashThis script will:
- Read the current branch and commit from your local quickjs checkout
- Copy the
qjs-wasi-reactor.wasmfile - Generate version information constants
Building the WASM file
To build the WASM file from source:
cd /path/to/quickjs # Create build directory mkdir -p build-wasi-reactor && cd build-wasi-reactor # Configure with WASI SDK cmake .. \ -DCMAKE_TOOLCHAIN_FILE=/opt/wasi-sdk/share/cmake/wasi-sdk.cmake \ -DQJS_WASI_REACTOR=ON \ -DCMAKE_BUILD_TYPE=Release # Build make -j # Copy output cp qjs.wasm ../qjs-wasi-reactor.wasm
Testing
go test ./... cd wazero-quickjs && go test ./...
License
This module is released under the same license as the embedded QuickJS-NG project.
MIT