A Go module that embeds the QuickJS-NG WASI WebAssembly runtime (reactor model).
- 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
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.
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.
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.
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
- 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
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)
}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)
}// 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']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)
}
}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.
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 --moduleTo 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
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.wasmgo test ./...
cd wazero-quickjs && go test ./...This module is released under the same license as the embedded QuickJS-NG project.
MIT