Qjs
QJS is a CGO-Free, modern, secure JavaScript runtime for Go applications, built on the powerful QuickJS engine and Wazero WebAssembly runtime
Install / Use
/learn @fastschema/QjsREADME
QJS - JavaScript in Go with QuickJS and Wazero
<p align="center"> <a href="https://pkg.go.dev/github.com/fastschema/qjs#section-readme" target="_blank" rel="noopener"> <img src="https://img.shields.io/badge/go.dev-reference-blue?logo=go&logoColor=white" alt="Go.Dev reference" /> </a> <a href="https://goreportcard.com/report/github.com/fastschema/qjs" target="_blank" rel="noopener"> <img src="https://goreportcard.com/badge/github.com/fastschema/qjs" alt="go report card" /> </a> <a href="https://codecov.io/gh/fastschema/qjs/branch/master" > <img src="https://codecov.io/gh/fastschema/qjs/branch/master/graph/badge.svg?token=yluqOtL5z0"/> </a> <a href="https://github.com/fastschema/qjs/actions" target="_blank" rel="noopener"> <img src="https://github.com/fastschema/qjs/actions/workflows/ci.yml/badge.svg" alt="test status" /> </a> <a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener"> <img src="https://img.shields.io/badge/license-MIT-brightgreen.svg" alt="MIT license" /> </a> </p> <p align="center"> <a href="https://app.fossa.com/projects/git%2Bgithub.com%2Ffastschema%2Fqjs?ref=badge_shield&issueType=license" alt="FOSSA Status"> <img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2Ffastschema%2Fqjs.svg?type=shield&issueType=license"/> </a> <a href="https://app.fossa.com/projects/git%2Bgithub.com%2Ffastschema%2Fqjs?ref=badge_shield&issueType=security" alt="FOSSA Status"> <img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2Ffastschema%2Fqjs.svg?type=shield&issueType=security"/> </a> </p>QJS is a CGO-Free, modern, secure JavaScript runtime for Go applications, built on the powerful QuickJS engine and Wazero WebAssembly runtime.
QJS allows you to run JavaScript code safely and efficiently, with full support for ES2023 features, async/await, and Go-JS interoperability.
Features
- JavaScript ES6+ Support: Full ES2023 compatibility via QuickJS (NG fork).
- WebAssembly Execution: Secure, sandboxed runtime using Wazero.
- Go-JS Interoperability: Seamless data conversion between Go and JavaScript.
- ProxyValue Support: Zero-copy sharing of Go values with JavaScript via lightweight proxies.
- Function Binding: Expose Go functions to JavaScript and vice versa.
- Async/Await: Full support for asynchronous JavaScript execution.
- Memory Safety: Memory-safe execution environment with configurable limits.
- No CGO Dependencies: Pure Go implementation with WebAssembly.
Benchmarks
Factorial Calculation
Computing factorial(10) 1,000,000 times
| Iteration | GOJA | ModerncQuickJS | QJS | | --- | --- | --- | --- | | 1 | 1.128s | 1.897s | 737.635ms | | 2 | 1.134s | 1.936s | 742.670ms | | 3 | 1.123s | 1.898s | 738.737ms | | 4 | 1.120s | 1.900s | 754.692ms | | 5 | 1.132s | 1.918s | 756.924ms | | Average | 1.127s | 1.910s | 746.132ms | | Total | 5.637s | 9.549s | 3.731s | | Speed | 1.51x | 2.56x | 1.00x |
Benchmarks run on AMD Ryzen 7 7840HS, 32GB RAM, Linux
AreWeFastYet V8-V7
| Metric | GOJA | ModerncQuickJS | QJS | | --- | --- | --- | --- | | Richards | 345 | 189 | 434 | | DeltaBlue | 411 | 205 | 451 | | Crypto | 203 | 305 | 393 | | RayTrace | 404 | 347 | 488 | | EarleyBoyer | 779 | 531 | 852 | | RegExp | 381 | 145 | 142 | | Splay | 1289 | 856 | 1408 | | NavierStokes | 324 | 436 | 588 | | Score (version 7) | 442 | 323 | 498 | | Duration (seconds) | 78.349s | 97.240s | 72.004s |
Benchmarks run on AMD Ryzen 7 7840HS, 32GB RAM, Linux
Example Usage
Basic Execution
rt, err := qjs.New()
if err != nil {
log.Fatal(err)
}
defer rt.Close()
ctx := rt.Context()
result, err := ctx.Eval("test.js", qjs.Code(`
const person = {
name: "Alice",
age: 30,
city: "New York"
};
const info = Object.keys(person).map(key =>
key + ": " + person[key]
).join(", ");
// The last expression is the return value
({ person: person, info: info });
`))
if err != nil {
log.Fatal("Eval error:", err)
}
defer result.Free()
// Output: name: Alice, age: 30, city: New York
log.Println(result.GetPropertyStr("info").String())
// Output: Alice
log.Println(result.GetPropertyStr("person").GetPropertyStr("name").String())
// Output: 30
log.Println(result.GetPropertyStr("person").GetPropertyStr("age").Int32())
Go function binding
ctx.SetFunc("goFunction", func(this *qjs.This) (*qjs.Value, error) {
return this.Context().NewString("Hello from Go!"), nil
})
result, err := ctx.Eval("test.js", qjs.Code(`
const message = goFunction();
message;
`))
if err != nil {
log.Fatal("Eval error:", err)
}
defer result.Free()
// Output: Hello from Go!
log.Println(result.String())
HTTP Handlers in JavaScript
package main
import (
"fmt"
"log"
"net/http"
"github.com/fastschema/qjs"
)
func must[T any](val T, err error) T {
if err != nil {
log.Fatalf("Error: %v", err)
}
return val
}
const script = `
// JS handlers for HTTP routes
const about = () => {
return "QuickJS in Go - Hello World!";
};
const contact = () => {
return "Contact us at contact@example.com";
};
export default { about, contact };
`
func main() {
rt := must(qjs.New())
defer rt.Close()
ctx := rt.Context()
// Precompile the script to bytecode
byteCode := must(ctx.Compile("script.js", qjs.Code(script), qjs.TypeModule()))
// Use a pool of runtimes for concurrent requests
pool := qjs.NewPool(3, &qjs.Option{}, func(r *qjs.Runtime) error {
results := must(r.Context().Eval("script.js", qjs.Bytecode(byteCode), qjs.TypeModule()))
// Store the exported functions in the global object for easy access
r.Context().Global().SetPropertyStr("handlers", results)
return nil
})
// Register HTTP handlers based on JS functions
val := must(ctx.Eval("script.js", qjs.Bytecode(byteCode), qjs.TypeModule()))
methodNames := must(val.GetOwnPropertyNames())
val.Free()
for _, methodName := range methodNames {
http.HandleFunc("/"+methodName, func(w http.ResponseWriter, r *http.Request) {
runtime := must(pool.Get())
defer pool.Put(runtime)
// Call the corresponding JS function
handlers := runtime.Context().Global().GetPropertyStr("handlers")
result := must(handlers.InvokeJS(methodName))
fmt.Fprint(w, result.String())
result.Free()
})
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from Go's HTTP server!")
})
log.Println("Server listening on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Server error: %v\n", err)
}
}
Async operations
Awaiting a promise
ctx.SetAsyncFunc("asyncFunction", func(this *qjs.This) {
go func() {
time.Sleep(100 * time.Millisecond)
result := this.Context().NewString("Async result from Go!")
this.Promise().Resolve(result)
}()
})
result, err := ctx.Eval("test.js", qjs.Code(`
async function main() {
const result = await asyncFunction();
return result;
}
({ main: main() });
`))
if err != nil {
log.Fatal("Eval error:", err)
}
defer result.Free()
mainFunc := result.GetPropertyStr("main")
// Wait for the promise to resolve
val, err := mainFunc.Await()
if err != nil {
log.Fatal("Await error:", err)
}
// Output: Async result from Go!
log.Println("Awaited value:", val.String())
Top level await
// asyncFunction is already defined above
result, err := ctx.Eval("test.js", qjs.Code(`
async function main() {
const result = await asyncFunction();
return result;
}
await main()
`), qjs.FlagAsync())
if err != nil {
log.Fatal("Eval error:", err)
}
defer result.Free()
log.Println(result.String())
Call JS function from Go
// Call JS function from Go
result, err := ctx.Eval("test.js", qjs.Code(`
function add(a, b) {
return a + b;
}
function errorFunc() {
throw new Error("test error");
}
({
addFunc: add,
errorFunc: errorFunc
});
`))
if err != nil {
log.Fatal("Eval error:", err)
}
defer result.Free()
jsAddFunc := result.GetPropertyStr("addFunc")
defer jsAddFunc.Free()
goAddFunc, err := qjs.JsFuncToGo[func(int, int) (int, error)](jsAddFunc)
if err != nil {
log.Fatal("Func conversion error:", err)
}
total, err := goAddFunc(1, 2)
if err != nil {
log.Fatal("Func execution error:", err)
}
// Output: 3
log.Println("Addition result:", total)
jsErrorFunc := result.GetPropertyStr("errorFunc")
defer jsErrorFunc.Free()
goErrorFunc, err := qjs.JsFuncToGo[func() (any, error)](jsErrorFunc)
if err != nil {
log.Fatal("Func conversion error:", err)
}
_, err = goErrorFunc()
if err != nil {
// Output:
// JS function execution failed: Error: test error
// at errorFunc (test.js:7:13)
log.Println(err.Error())
}
ES Modules
// Load a utility module
if _, err = ctx.Load("math-utils.js", qjs.Code(`
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export function power(base, exponent) {
return Math.pow(base, exponent);
}
export const PI = 3.14159;
export const E = 2.71828;
export default {
add,
multiply,
power,
PI,
E
};
`)); err != nil {
log.Fatal("Module load error:", err)
}
// Use the module
result, err := ctx.Eval("use-math.js", qjs.Code(`
import mathUtils, { add, multiply, power, PI } from 'math-utils.js';
const calculations = {
addition: add(10, 20),
multiplication: multiply(6, 7),
power: power(2, 8),
circleArea: PI * power(5, 2),
defaultAdd: mathUtils.add(10, 20)
};
export default calculations;
`), qjs.TypeModule())
if err != nil {
log.Fatal("Module eval error:", err)
}
// Output:
// Addition: 30
// Multiplication: 42
// Power: 256
// Circle Area: 78.54
// Default Add: 30
fmt.Printf("Addition: %d\n", result.GetPropertyStr("addition").Int32())
fmt.Printf("Multiplication: %.0f\n", result.GetPropertyStr("multiplication").Float64())
fmt.Printf("Power: %.0f\n", result.GetPropertyStr("power").Float64())
fmt.Printf("Circle Area: %.2f\n", result.GetPropertyStr("ci
