Package quickjs is a pure Go embeddable Javascript engine. It supports the ECMA script 14 (ES2023) specification including modules, asynchronous generators, proxies and BigInt.
See also the original C QuickJS library.
Performance ¶
Geomeans of time/op over a set of benchmarks, relative to CCGO, lower number is better. Detailed results available in the testdata/benchmarks directory.
CCGO: modernc.org/quickjs@v0.16.3 GOJA: github.com/dop251/goja@v0.0.0-20251008123653-cf18d89f3cf6 QJS: github.com/fastschema/qjs@v0.0.5 CCGO GOJA QJS ----------------------------------------------- darwin/amd64 1.000 1.169 0.952 darwin/arm64 1.000 1.106 0.928 freebsd/amd64 1.000 1.271 0.866 (qemu) freebsd/arm64 1.000 1.064 0.746 (qemu) linux/386 1.000 1.738 59.275 (qemu) linux/amd64 1.000 1.942 1.014 linux/arm 1.000 2.215 85.887 linux/arm64 1.000 1.315 1.023 linux/loong64 1.000 1.690 68.809 linux/ppc64le 1.000 1.306 44.612 linux/riscv64 1.000 1.370 55.163 linux/s390x 1.000 1.359 45.084 (qemu) windows/amd64 1.000 1.338 1.034 windows/arm64 1.000 1.516 1.205 ----------------------------------------------- CCGO GOJA QJS
Notes ¶
Parts of the documentation were copied from the quickjs documentation, see LICENSE-QUICKJS for details.
Supported platforms and architectures ¶
These combinations of GOOS and GOARCH are currently supported
OS Arch ------------- darwin amd64 darwin arm64 freebsd amd64 freebsd arm64 linux 386 linux amd64 linux arm linux arm64 linux loong64 linux ppc64le linux riscv64 linux s390x windows amd64 windows arm64
Builders ¶
Builder results are available here.
Change Log ¶
2025-10-10: Upgrade to QuickJS release 2025-09-13.
Multiple concurrent Javascript virtual machines communicating via Go channels.
package main // import "modernc.org/quickjs"
import (
"fmt"
)
// Multiple concurrent Javascript virtual machines communicating via Go channels.
func main() {
tx := make(chan string, 1)
rx := make(chan string, 1)
client, _ := NewVM()
defer client.Close()
registerFuncs(client, tx, rx)
go func() { // Start the server.
server, _ := NewVM()
defer server.Close()
registerFuncs(server, rx, tx)
server.Eval("send(receive()+' reply');", EvalGlobal)
}()
fmt.Println(client.Eval("send('ping'); receive();", EvalGlobal)) // Ping the server.
}
func registerFuncs(m *VM, tx, rx chan string) {
m.RegisterFunc("send", func(s string) { tx <- s }, false)
m.RegisterFunc("receive", func() string { return <-rx }, false)
}
Output: ping reply <nil>
- Constants
- Variables
- func Version() string
- type Atom
- type Object
- type Undefined
- type Unsupported
- type VM
- func (m *VM) Call(function string, args ...any) (r any, err error)
- func (m *VM) CallValue(function string, args ...any) (r Value, err error)
- func (m *VM) Close() error
- func (m *VM) Compile(javascript string, flags int) ([]byte, error)
- func (m *VM) Eval(javascript string, flags int) (r any, err error)
- func (m *VM) EvalBytecode(bytecode []byte) (r any, err error)
- func (m *VM) EvalBytecodeValue(bytecode []byte) (r Value, err error)
- func (m *VM) EvalThis(obj Value, javascript string, flags int) (r any, err error)
- func (m *VM) EvalThisValue(obj Value, javascript string, flags int) (r Value, err error)
- func (m *VM) EvalValue(javascript string, flags int) (r Value, err error)
- func (m *VM) GetProperty(this Value, prop Atom) (r any, err error)
- func (m *VM) GetPropertyValue(this Value, prop Atom) (r Value, err error)
- func (m *VM) GlobalObject() (r Value)
- func (m *VM) Interrupt()
- func (m *VM) NewAtom(s string) (r Atom, err error)
- func (m *VM) NewFloat64(n float64) Value
- func (m *VM) NewInt(n int) Value
- func (m *VM) NewObjectValue() (r Value, err error)
- func (m *VM) NewString(s string) (r Value, err error)
- func (m *VM) RegisterFunc(name string, f any, wantThis bool) (err error)
- func (m *VM) SetCanBlock(value bool)
- func (m *VM) SetDefaultModuleLoader()
- func (m *VM) SetEvalTimeout(d time.Duration) error
- func (m *VM) SetGCThreshold(threshold uintptr)
- func (m *VM) SetMemoryLimit(limit uintptr)
- func (m *VM) SetProperty(this Value, prop Atom, val any) (err error)
- func (m *VM) SetPropertyValue(this Value, prop Atom, val Value) (err error)
- func (m *VM) StdAddHelpers() error
- type Value
- func (v Value) Any() (r any, err error)
- func (v Value) Dup() Value
- func (v *Value) Free()
- func (v Value) GetProperty(this Value, prop Atom) (r any, err error)
- func (v Value) GetPropertyValue(prop Atom) (r Value, err error)
- func (v Value) IsUndefined() bool
- func (v Value) MarshalJSON() (r []byte, err error)
- func (v Value) SetProperty(prop Atom, val any) (err error)
- func (v Value) SetPropertyValue(prop Atom, val Value) (err error)
- func (v *Value) VM() *VM
- Package (Ping)
- Object.MarshalJSON
- Object.String
- VM.Call (Function)
- VM.Call (Method)
- VM.Eval (Exception)
- VM.Eval (Expression)
- VM.Eval (Object)
- VM.Interrupt
- VM.RegisterFunc (Error)
- VM.RegisterFunc (MultipleReturn)
- VM.RegisterFunc (SingleReturn)
- VM.RegisterFunc (ThisNonNull)
- VM.RegisterFunc (ThisNonNull2)
- VM.RegisterFunc (ThisNull)
- VM.RegisterFunc (ThisNull2)
- VM.RegisterFunc (Void)
- VM.SetDefaultModuleLoader
- VM.SetEvalTimeout
Eval flags.
Version returns the underlying QuickJS version.
Atom is an unique identifier of, for example, a string value. Atom values are VM-specific.
Object represents the value of a Javascript object, but not the javascript object instance itself. Do not compare instances of Object.
MarshalJSON implements encoding/json.Marshaler.
JSON marshalling.
m, _ := NewVM()
defer m.Close()
obj, _ := m.Eval("obj = {a: 42+314, b: 'foo'}; obj;", EvalGlobal)
s, _ := (obj.(*Object).MarshalJSON())
fmt.Printf("%s\n", s)
Output: {"a":356,"b":"foo"}
String implements fmt.Stringer.
JSON marshalling.
m, _ := NewVM()
defer m.Close()
obj, _ := m.Eval("obj = {a: 42+314, b: 'foo'}; obj;", EvalGlobal)
fmt.Printf("%s\n", obj)
Output: {"a":356,"b":"foo"}
Undefined represents the Javascript value "undefined".
String implements fmt.Stringer.
type Unsupported struct{}
Unsupported represents an unsupported Javascript value.
String implements fmt.Stringer.
VM represents a Javascript context (or Realm). Each VM has its own global objects and system objects.
Note: VM is not safe for concurrent use by multiple goroutines.
NewVM returns a newly created VM.
Call evaluates 'function(args...)' and returns the resulting (value, error).
Argument types must be one of:
Go argument type Javascript argument type ---------------------------------------------------------------- nil null Undefined undefined string string int*/uint* (value in int32 range) int int*/uint* (value out of int32 range) float bool bool float64 float64 *math/big.Int BigInt *Object object Value native Javascript Value any other type object from JSON produced by encoding.json/Marshall(arg)
Call a Javascript function.
m, _ := NewVM()
defer m.Close()
fmt.Println(m.Call("parseInt", "1234"))
Output: 1234 <nil>
Call a Javascript method.
m, _ := NewVM()
defer m.Close()
fmt.Println(m.Call("Math.abs", -1234))
Output: 1234 <nil>
CallValue is like Call but returns (Value, error) like EvalValue
Note: See the Value documentation for details about manual memory management of Value objects.
Close releases the resources held by 'm'.
Compile compiles the bytecode representation of the passed script.
The returned bytecode can be later executed using EvalBytecode or EvalBytecodeValue.
Note: The bytecode format is linked to a given QuickJS version.
Eval evaluates a script or module source in 'javascript'.
Javascript result type Go result type Go result error ------------------------------------------------------------------------------- exception nil non-nil null nil nil undefined Undefined nil string string nil int int nil bool bool nil float64 float64 nil BigInt *math/big.Int nil object *Object nil any other type Unsupported nil
Getting exception.
m, _ := NewVM()
defer m.Close()
fmt.Println(m.Eval("throw new Error('failed');", EvalGlobal))
Output: <nil> Error: failed
Evaluate a simple Javascript expression.
m, _ := NewVM()
defer m.Close()
fmt.Println(m.Eval("1+2", EvalGlobal))
Output: 3 <nil>
Object example.
m, _ := NewVM()
defer m.Close()
fmt.Println(m.Eval("obj = {a: 42+314, b: 'foo'}; obj;", EvalGlobal))
Output: {"a":356,"b":"foo"} <nil>
EvalBytecode is like Eval but uses QuickJS bytecode.
Note: The bytecode format is linked to a given QuickJS version. Moreover, no security check is done before its execution. Hence the bytecode should not be loaded from untrusted sources.
Note: Javascript 'this' is always set to the global context.
EvalBytecodeValue is like EvalValue but operates on QuickJS bytecode.
Exceptions thrown during evaluation of the script are returned as Go errors.
If no error is returned, the caller must properly handle the returned Value using Dup/Free.
Note: See the Value documentation for details about manual memory management of Value objects.
EvalThis is like eval but sets javascript 'this' to 'obj'.
EvalThisValue is like EvalValue but sets javascript 'this' to obj.
EvalValue evaluates a script or module source in 'javascript' and returns the resulting Value, or an error, if any.
Exceptions thrown during evaluation of the script are returned as Go errors.
If no error is returned, the caller must properly handle the returned Value using Dup/Free.
Note: See the Value documentation for details about manual memory management of Value objects.
GetPropertyValue returns this.prop.
Note: See the Value documentation for details about manual memory management of Value objects.
GlobalObject returns m's global object.
Note: See the Value documentation for details about manual memory management of Value objects.
Interrupt requests 'm' to interrupt Javascript evaluation. Interrupt can be called asynchronously at any time m is evaluating a script.
const timeout = time.Second
m, _ := NewVM()
defer m.Close()
go func() {
time.Sleep(timeout)
m.Interrupt()
}()
t0 := time.Now()
r, err := m.Eval(`
function f() {
var sink;
for (var i = 0; i < 10000; i++) {
sink += 42;
sink -= 42;
}
}
(function() {
for (var i = 0; i < 10000; i++) {
f();
}
return 42;
})();
`, EvalGlobal)
d := time.Since(t0)
step := timeout / 5
d = d / step * step
fmt.Println(r, err, d)
Output: <nil> InternalError: interrupted 1s
NewAtom returns an unique indentifier of 's' or an error, if any.
NewFloat64 returns a new Value from 'n'.
Note: See the Value documentation for details about manual memory management of Value objects.
NewInt returns a new Value from 'n'.
Note: See the Value documentation for details about manual memory management of Value objects.
NewObjectValue returns a new Value representing '{}'.
NewString returns a new Value from 's'.
Note: See the Value documentation for details about manual memory management of Value objects.
RegisterFunc registers a Go function 'f' and makes it callable from Javascript.
The 'f' argument can be a regular Go function, a closure, a method expression or a method value. All of them are called 'Go function' below.
The Go function can have zero or more parameters. If 'wantThis' is true then the first parameter of the Go function will get the Javascript value of 'this'. Depending on context, 'this' can be Javascript null or undefined.
Go functions with multiple results return them as an Javascript array.
Go nil errors are converted to Javascript null.
Go non-nil errors are converted to Javascript strings using the Error() method.
Any Go <-> Javascript failing type conversion between arguments/return values throws a Javascript type error exception.
There is a limit on the total number of currently registered Go functions.
Note: The 'name' argument should be a valid Javascript identifier. It is not currently enforced but this may change later.
Call error returning Go function from Javascript.
m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(a int) error {
if a < 0 {
return fmt.Errorf("negative")
}
return nil
}, false)
fmt.Println(m.Eval("gofunc(-1)", EvalGlobal))
fmt.Println(m.Eval("gofunc(1)", EvalGlobal))
Output: negative <nil> <nil> <nil>
Call multiple return Go function from Javascript.
m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(a, b int) (int, int) { return 10 * a, 100 * b }, false)
fmt.Println(m.Eval("gofunc(2, 3)", EvalGlobal))
Output: [20,300] <nil>
Call single return Go function from Javascript.
m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(a, b, c int) int { return a + b*c }, false)
fmt.Println(m.Eval("gofunc(2, 3, 5)", EvalGlobal))
Output: 17 <nil>
Passing Javascript 'this' to a Go function.
m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(this any) any { return this }, true)
fmt.Println(m.Eval("var obj = { foo: 314, method: gofunc }; obj.method()", EvalGlobal))
Output: {"foo":314} <nil>
Passing Javascript 'this' to a Go function.
m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(this any, n int) (any, int) { return this, 10 * n }, true)
fmt.Println(m.Eval("var obj = { foo: 314, method: gofunc }; obj.method(42)", EvalGlobal))
Output: [{"foo":314},420] <nil>
Passing undefined Javascript 'this' to a Go function.
m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(this any) any { return this }, true)
fmt.Println(m.Eval("gofunc()", EvalGlobal))
Output: undefined <nil>
Passing undefined Javascript 'this' to a Go function.
m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(this any, n int) (any, int) { return this, 10 * n }, true)
fmt.Println(m.Eval("gofunc(42)", EvalGlobal))
Output: [null,420] <nil>
Call void Go function from Javascript.
m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(a, b, c int) { fmt.Println(a + b*c) }, false)
fmt.Println(m.Eval("gofunc(2, 3, 5)", EvalGlobal))
Output: 17 undefined <nil>
func (m *VM) SetDefaultModuleLoader()
SetDefaultModuleLoader will enable loading module using the default module loader.
Enabling the module loader.
m, _ := NewVM()
defer m.Close()
m.SetDefaultModuleLoader()
// testdata/power.js:
// export const name = "Power";
//
// export function square(x) {
// return x*x;
// }
//
// export function cube(x) {
// return x*x*x;
// }
m.Eval("import * as Power from './testdata/power.js'; globalThis.Power = Power;", EvalModule)
fmt.Println(m.Eval("[Power.square(2), Power.cube(2)];", EvalGlobal))
Output: [4,8] <nil>
SetEvalTimeout sets the timeout for the various eval functions. Passing zero 'd' disables timeouts.
m, _ := NewVM()
defer m.Close()
for _, timeout := range []time.Duration{100 * time.Millisecond, time.Second, 3 * time.Second} {
m.SetEvalTimeout(timeout)
t0 := time.Now()
r, err := m.Eval(`
function f() {
var sink;
for (var i = 0; i < 10000; i++) {
sink += 42;
sink -= 42;
}
}
(function() {
for (var i = 0; i < 10000; i++) {
f();
}
return 42;
})();
`, EvalGlobal)
d := time.Since(t0)
min := timeout / 2
max := timeout * 3 / 2
fmt.Println(r, err, timeout, d >= min && d <= max)
}
Output: <nil> InternalError: interrupted 100ms true <nil> InternalError: interrupted 1s true <nil> InternalError: interrupted 3s true
SetGCThreshold sets the memory threshold at which a GC will be performed, in bytes.
SetMemoryLimit limits m's maxmimum memory usage, in bytes. Small values of 'limit' are not honored because the VM needs to allocate memory also for the exception object itself. The particular value of "small" is unspecified and subject to change without notice.
SetProperty sets this.prop = val.
SetPropertyValue sets this.prop = val.
Value represents a native Javascript value. Values are reference counted and their lifetime is managed by an independent Javascript garbage collector. To avoid memory corruption/leaks caused by tripping the Javascript GC, a Value must not
- be copied. Use the Dup method instead.
- become unreachable without calling its Free method.
- be used after its Free() method was called.
- outlive its VM.
It is recommended to use native Go values instead of Value where possible.
When passing a Value down the call stack use Dup. For example in main
v, _ := EvalValue(someScript) defer v.Free() foo(v.Dup()) // Instead of foo(v)
In 'foo' Free must be used. For example
func foo(v Value) {
defer v.Free()
...
}
This ensures the/only topmost Free marks 'v' eligible for garbage collection.
Beware that the correct setup/handling becomes more complicated when using closures, Values are sent through a channel etc. In particular, if a goroutine 1 passes a Dup of 'v' to goroutine 2 and goroutine 1 completes and thus frees 'v' before goroutine 2 completes, the reference counting mechanism will fail. In other words, every Free must be strictly paired with the Dup that preceded obtaining the Value and the Dup/Free calls must respect the original nesting. This is correct.
Dup // in main
Dup // in foo
Free // in foo
Free // in main
This will fail, for example in the above discussed goroutines scenario.
Dup // in g1
Dup // in g2
Free // in g1
Free // in g2
The fix might be in this case to arrange goroutine 1 to wait for goroutine 2 to complete before executing Free in goroutine 1.
See TestMemgrind for an example of how to detect memory leaks caused not only by improper Value use.
Any attemtps to convert 'v' to any using the same rules as there are for the return value of VM.Eval.
Free marks 'v' as no longer used and updates its reference count. 'v' must not be used afterwards.
IsUndefined reports whether 'v' represents the Javascript value 'undefined'.
MarshalJSON implements encoding/json.Marshaler.
SetPropertyValue sets v.prop = val.
VM returns the VM associated with 'v'.