English | 简体ä¸ć–‡
Go bindings to QuickJS: a fast, small, and embeddable ES2020 JavaScript interpreter.
- Evaluate script
- Compile script into bytecode and Eval from bytecode
- Operate JavaScript values and objects in Go
- Bind Go function to JavaScript async/sync function
- Simple exception throwing and catching
- Marshal/Unmarshal Go values to/from JavaScript values
- Full TypedArray support (Int8Array, Uint8Array, Float32Array, etc.)
- Create JavaScript Classes from Go with ClassBuilder
- **Create JavaScript Modules from Go with ModuleBuilder*o
- Cross-platform: Prebuilt QuickJS static libraries for Linux (x64/arm64), Windows (x64/x86), MacOS (x64/arm64).
(See deps/libs for details. For Windows build tips, see: #151 (comment))
- Use
Value.IsException()
orContext.HasException()
to check for exceptions - Use
Context.Exception()
to get the exception as a Go error - Always call
defer value.Free()
for returned values to prevent memory leaks - Check
Context.HasException()
after operations that might throw
- Call
value.Free()
for*Value
objects you create or receive. QuickJS uses reference counting for memory management, so if a value is referenced by other objects, you only need to ensure the referencing objects are properly freed. - Runtime and Context objects have their own cleanup methods (
Close()
). Close them once you are done using them. - Use
runtime.SetFinalizer()
cautiously as it may interfere with QuickJS's GC.
- QuickJS is not thread-safe. For concurrency or isolation, use a thread pool pattern with pre-initialized runtimes, or manage separate Runtime/Context instances for different tasks or users (such as : https://github.com/buke/js-executor).
- Reuse Runtime and Context objects when possible.
- Avoid frequent conversion between Go and JS values.
- Consider using bytecode compilation for frequently executed scripts.
- Use appropriate
EvalOptions
for different script types. - Handle both JavaScript exceptions and Go errors appropriately.
- Test memory usage under load to prevent leaks.
import "github.com/buke/quickjs-go"
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
// Create a new runtime
rt := quickjs.NewRuntime()
defer rt.Close()
// Create a new context
ctx := rt.NewContext()
defer ctx.Close()
ret := ctx.Eval("'Hello ' + 'QuickJS!'")
defer ret.Free()
if ret.IsException() {
err := ctx.Exception()
println(err.Error())
return
}
fmt.Println(ret.ToString())
}
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
// Create a new runtime
rt := quickjs.NewRuntime()
defer rt.Close()
// Create a new context
ctx := rt.NewContext()
defer ctx.Close()
test := ctx.NewObject()
test.Set("A", ctx.NewString("String A"))
test.Set("B", ctx.NewString("String B"))
test.Set("C", ctx.NewString("String C"))
ctx.Globals().Set("test", test)
ret := ctx.Eval(`Object.keys(test).map(key => test[key]).join(" ")`)
defer ret.Free()
if ret.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println(ret.ToString())
}
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
// Create a new runtime
rt := quickjs.NewRuntime()
defer rt.Close()
// Create a new context
ctx := rt.NewContext()
defer ctx.Close()
// Create a new object
test := ctx.NewObject()
// bind properties to the object
test.Set("A", ctx.NewString("String A"))
test.Set("B", ctx.NewInt32(0))
test.Set("C", ctx.NewBool(false))
// bind go function to js object
test.Set("hello", ctx.NewFunction(func(ctx *quickjs.Context, this *quickjs.Value, args []*quickjs.Value) *quickjs.Value {
return ctx.NewString("Hello " + args[0].ToString())
}))
// bind "test" object to global object
ctx.Globals().Set("test", test)
// call js function by js
js_ret := ctx.Eval(`test.hello("Javascript!")`)
defer js_ret.Free()
if js_ret.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println(js_ret.ToString())
// call js function by go
go_ret := test.Call("hello", ctx.NewString("Golang!"))
defer go_ret.Free()
if go_ret.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println(go_ret.ToString())
// bind go function to Javascript async function using Function + Promise
ctx.Globals().Set("testAsync", ctx.NewFunction(func(ctx *quickjs.Context, this *quickjs.Value, args []*quickjs.Value) *quickjs.Value {
return ctx.Promise(func(resolve, reject func(*quickjs.Value)) {
resolve(ctx.NewString("Hello Async Function!"))
})
}))
ret := ctx.Eval(`
var ret;
testAsync().then(v => ret = v)
`)
defer ret.Free()
if ret.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
// wait for promise resolve
ctx.Loop()
//get promise result
asyncRet := ctx.Eval("ret")
defer asyncRet.Free()
if asyncRet.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println(asyncRet.ToString())
// Output:
// Hello Javascript!
// Hello Golang!
// Hello Async Function!
}
package main
import (
"fmt"
"errors"
"github.com/buke/quickjs-go"
)
func main() {
// Create a new runtime
rt := quickjs.NewRuntime()
defer rt.Close()
// Create a new context
ctx := rt.NewContext()
defer ctx.Close()
ctx.Globals().SetFunction("A", func(ctx *quickjs.Context, this *quickjs.Value, args []*quickjs.Value) *quickjs.Value {
// raise error
return ctx.ThrowError(errors.New("expected error"))
})
result := ctx.Eval("A()")
defer result.Free()
if result.IsException() {
actual := ctx.Exception()
fmt.Println(actual.Error())
}
}
QuickJS-Go provides support for JavaScript TypedArrays, enabling binary data processing between Go and JavaScript.
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// Create various TypedArrays from Go slices
int8Data := []int8{-128, -1, 0, 1, 127}
int8Array := ctx.NewInt8Array(int8Data)
uint8Data := []uint8{0, 128, 255}
uint8Array := ctx.NewUint8Array(uint8Data)
float32Data := []float32{-3.14, 0.0, 2.718, 100.5}
float32Array := ctx.NewFloat32Array(float32Data)
int64Data := []int64{-9223372036854775808, 0, 9223372036854775807}
bigInt64Array := ctx.NewBigInt64Array(int64Data)
// Set TypedArrays as global variables
ctx.Globals().Set("int8Array", int8Array)
ctx.Globals().Set("uint8Array", uint8Array)
ctx.Globals().Set("float32Array", float32Array)
ctx.Globals().Set("bigInt64Array", bigInt64Array)
// Use in JavaScript
result := ctx.Eval(`
// Check types
const results = {
int8Type: int8Array instanceof Int8Array,
uint8Type: uint8Array instanceof Uint8Array,
float32Type: float32Array instanceof Float32Array,
bigInt64Type: bigInt64Array instanceof BigInt64Array,
// Calculate sum of float32 array
float32Sum: float32Array.reduce((sum, val) => sum + val, 0)
};
results;
`)
defer result.Free()
if result.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Results:", result.JSONStringify())
}
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// Create TypedArrays in JavaScript
jsTypedArrays := ctx.Eval(`
({
int8: new Int8Array([-128, -1, 0, 1, 127]),
uint16: new Uint16Array([0, 32768, 65535]),
float64: new Float64Array([Math.PI, Math.E, 42.5]),
bigUint64: new BigUint64Array([0n, 18446744073709551615n])
})
`)
defer jsTypedArrays.Free()
if jsTypedArrays.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
// Convert to Go slices
int8Array := jsTypedArrays.Get("int8")
defer int8Array.Free()
if int8Array.IsInt8Array() {
goInt8Slice, err := int8Array.ToInt8Array()
if err == nil {
fmt.Printf("Int8Array: %v\n", goInt8Slice)
}
}
uint16Array := jsTypedArrays.Get("uint16")
defer uint16Array.Free()
if uint16Array.IsUint16Array() {
goUint16Slice, err := uint16Array.ToUint16Array()
if err == nil {
fmt.Printf("Uint16Array: %v\n", goUint16Slice)
}
}
float64Array := jsTypedArrays.Get("float64")
defer float64Array.Free()
if float64Array.IsFloat64Array() {
goFloat64Slice, err := float64Array.ToFloat64Array()
if err == nil {
fmt.Printf("Float64Array: %v\n", goFloat64Slice)
}
}
bigUint64Array := jsTypedArrays.Get("bigUint64")
defer bigUint64Array.Free()
if bigUint64Array.IsBigUint64Array() {
goBigUint64Slice, err := bigUint64Array.ToBigUint64Array()
if err == nil {
fmt.Printf("BigUint64Array: %v\n", goBigUint64Slice)
}
}
}
Go Type | JavaScript TypedArray | Context Method | Value Method |
---|---|---|---|
[]int8 |
Int8Array |
ctx.NewInt8Array() |
val.ToInt8Array() |
[]uint8 |
Uint8Array |
ctx.NewUint8Array() |
val.ToUint8Array() |
[]uint8 |
Uint8ClampedArray |
ctx.NewUint8ClampedArray() |
val.ToUint8Array() |
[]int16 |
Int16Array |
ctx.NewInt16Array() |
val.ToInt16Array() |
[]uint16 |
Uint16Array |
ctx.NewUint16Array() |
val.ToUint16Array() |
[]int32 |
Int32Array |
ctx.NewInt32Array() |
val.ToInt32Array() |
[]uint32 |
Uint32Array |
ctx.NewUint32Array() |
val.ToUint32Array() |
[]float32 |
Float32Array |
ctx.NewFloat32Array() |
val.ToFloat32Array() |
[]float64 |
Float64Array |
ctx.NewFloat64Array() |
val.ToFloat64Array() |
[]int64 |
BigInt64Array |
ctx.NewBigInt64Array() |
val.ToBigInt64Array() |
[]uint64 |
BigUint64Array |
ctx.NewBigUint64Array() |
val.ToBigUint64Array() |
[]byte |
ArrayBuffer |
ctx.NewArrayBuffer() |
val.ToByteArray() |
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// Create various arrays
regularArray := ctx.Eval(`[1, 2, 3]`)
defer regularArray.Free()
int32Array := ctx.NewInt32Array([]int32{1, 2, 3})
float64Array := ctx.NewFloat64Array([]float64{1.1, 2.2, 3.3})
// Set arrays as global variables to be referenced by globals
ctx.Globals().Set("int32Array", int32Array)
ctx.Globals().Set("float64Array", float64Array)
// Detect array types
fmt.Printf("Regular array IsArray: %v\n", regularArray.IsArray())
fmt.Printf("Regular array IsTypedArray: %v\n", regularArray.IsTypedArray())
fmt.Printf("Int32Array IsTypedArray: %v\n", int32Array.IsTypedArray())
fmt.Printf("Int32Array IsInt32Array: %v\n", int32Array.IsInt32Array())
fmt.Printf("Int32Array IsFloat64Array: %v\n", int32Array.IsFloat64Array())
fmt.Printf("Float64Array IsTypedArray: %v\n", float64Array.IsTypedArray())
fmt.Printf("Float64Array IsFloat64Array: %v\n", float64Array.IsFloat64Array())
fmt.Printf("Float64Array IsInt32Array: %v\n", float64Array.IsInt32Array())
}
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// Process image-like data (simulate RGB pixels)
imageData := []uint8{
255, 0, 0, // Red pixel
0, 255, 0, // Green pixel
0, 0, 255, // Blue pixel
255, 255, 0, // Yellow pixel
}
// Send to JavaScript as Uint8Array
imageArray := ctx.NewUint8Array(imageData)
ctx.Globals().Set("imageData", imageArray)
// Process in JavaScript
result := ctx.Eval(`
// Convert RGB to grayscale
const grayscale = new Uint8Array(imageData.length / 3);
for (let i = 0; i < imageData.length; i += 3) {
const r = imageData[i];
const g = imageData[i + 1];
const b = imageData[i + 2];
grayscale[i / 3] = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
}
grayscale;
`)
defer result.Free()
if result.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
// Convert back to Go
if result.IsUint8Array() {
grayscaleData, err := result.ToUint8Array()
if err == nil {
fmt.Printf("Original RGB: %v\n", imageData)
fmt.Printf("Grayscale: %v\n", grayscaleData)
}
}
}
QuickJS-Go provides conversion between Go and JavaScript values through the Marshal
and Unmarshal
methods.
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// Marshal Go values to JavaScript
data := map[string]interface{}{
"name": "John Doe",
"age": 30,
"active": true,
"scores": []int{85, 92, 78},
"address": map[string]string{
"city": "New York",
"country": "USA",
},
// TypedArray will be automatically created for typed slices
"floatData": []float32{1.1, 2.2, 3.3},
"intData": []int32{100, 200, 300},
"byteData": []byte{0x48, 0x65, 0x6C, 0x6C, 0x6F}, // "Hello" in bytes
}
jsVal, err := ctx.Marshal(data)
if err != nil {
panic(err)
}
defer jsVal.Free()
// Use the marshaled value in JavaScript
ctx.Globals().Set("user", jsVal)
result := ctx.Eval(`
const info = user.name + " is " + user.age + " years old";
const floatArrayType = user.floatData instanceof Float32Array;
const intArrayType = user.intData instanceof Int32Array;
const byteArrayType = user.byteData instanceof ArrayBuffer;
({
info: info,
floatArrayType: floatArrayType,
intArrayType: intArrayType,
byteArrayType: byteArrayType,
byteString: new TextDecoder().decode(user.byteData)
});
`)
defer result.Free()
if result.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Result:", result.JSONStringify())
// Unmarshal JavaScript values back to Go
var userData map[string]interface{}
err = ctx.Unmarshal(jsVal, &userData)
if err != nil {
panic(err)
}
fmt.Printf("Unmarshaled: %+v\n", userData)
}
package main
import (
"fmt"
"time"
"github.com/buke/quickjs-go"
)
type User struct {
ID int64 `js:"id"`
Name string `js:"name"`
Email string `json:"email_address"`
CreatedAt time.Time `js:"created_at"`
IsActive bool `js:"is_active"`
Tags []string `js:"tags"`
// TypedArray fields
Scores []float32 `js:"scores"` // Will become Float32Array
Data []int32 `js:"data"` // Will become Int32Array
Binary []byte `js:"binary"` // Will become ArrayBuffer
// unexported fields are ignored
password string
// Fields with "-" tag are skipped
Secret string `js:"-"`
}
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
user := User{
ID: 123,
Name: "Alice",
Email: "alice@example.com",
CreatedAt: time.Now(),
IsActive: true,
Tags: []string{"admin", "user"},
Scores: []float32{95.5, 87.2, 92.0},
Data: []int32{1000, 2000, 3000},
Binary: []byte{0x41, 0x42, 0x43}, // "ABC"
password: "secret123",
Secret: "top-secret",
}
// Marshal struct to JavaScript
jsVal, err := ctx.Marshal(user)
if err != nil {
panic(err)
}
defer jsVal.Free()
// Check TypedArray types in JavaScript
ctx.Globals().Set("user", jsVal)
result := ctx.Eval(`
({
scoresType: user.scores instanceof Float32Array,
dataType: user.data instanceof Int32Array,
binaryType: user.binary instanceof ArrayBuffer,
binaryString: new TextDecoder().decode(user.binary),
avgScore: user.scores.reduce((sum, score) => sum + score) / user.scores.length
});
`)
defer result.Free()
if result.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
// Modify in JavaScript
modifyResult := ctx.Eval(`
user.name = "Alice Smith";
user.tags.push("moderator");
// Modify TypedArray data
user.scores[0] = 98.5;
user;
`)
defer modifyResult.Free()
if modifyResult.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
// Unmarshal back to Go struct
var updatedUser User
err = ctx.Unmarshal(modifyResult, &updatedUser)
if err != nil {
panic(err)
}
fmt.Printf("Updated user: %+v\n", updatedUser)
// Note: password and Secret fields remain unchanged (not serialized)
}
package main
import (
"fmt"
"strings"
"github.com/buke/quickjs-go"
)
type CustomType struct {
Value string
}
// Implement Marshaler interface
func (c CustomType) MarshalJS(ctx *quickjs.Context) (*quickjs.Value, error) {
return ctx.NewString("custom:" + c.Value), nil
}
// Implement Unmarshaler interface
func (c *CustomType) UnmarshalJS(ctx *quickjs.Context, val *quickjs.Value) error {
if val.IsString() {
str := val.ToString()
if strings.HasPrefix(str, "custom:") {
c.Value = str[7:] // Remove "custom:" prefix
} else {
c.Value = str
}
}
return nil
}
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// Marshal custom type
custom := CustomType{Value: "hello"}
jsVal, err := ctx.Marshal(custom)
if err != nil {
panic(err)
}
defer jsVal.Free()
fmt.Println("Marshaled:", jsVal.ToString()) // Output: custom:hello
// Unmarshal back
var result CustomType
err = ctx.Unmarshal(jsVal, &result)
if err != nil {
panic(err)
}
fmt.Printf("Unmarshaled: %+v\n", result) // Output: {Value:hello}
}
Go to JavaScript:
bool
→ JavaScript booleanint
,int8
,int16
,int32
→ JavaScript number (32-bit)int64
→ JavaScript number (64-bit)uint
,uint8
,uint16
,uint32
→ JavaScript number (32-bit unsigned)uint64
→ JavaScript BigIntfloat32
,float64
→ JavaScript numberstring
→ JavaScript string[]byte
→ JavaScript ArrayBuffer[]int8
→ JavaScript Int8Array[]uint8
→ JavaScript Uint8Array[]int16
→ JavaScript Int16Array[]uint16
→ JavaScript Uint16Array[]int32
→ JavaScript Int32Array[]uint32
→ JavaScript Uint32Array[]float32
→ JavaScript Float32Array[]float64
→ JavaScript Float64Array[]int64
→ JavaScript BigInt64Array[]uint64
→ JavaScript BigUint64Arrayslice/array
→ JavaScript Array (for non-typed arrays)map
→ JavaScript Objectstruct
→ JavaScript Objectpointer
→ recursively marshal pointed value (nil becomes null)
JavaScript to Go:
- JavaScript null/undefined → Go nil pointer or zero value
- JavaScript boolean → Go bool
- JavaScript number → Go numeric types (with appropriate conversion)
- JavaScript BigInt → Go
uint64
/int64
/*big.Int
- JavaScript string → Go string
- JavaScript Array → Go slice/array
- JavaScript Object → Go map/struct
- JavaScript ArrayBuffer → Go
[]byte
- JavaScript Int8Array → Go
[]int8
- JavaScript Uint8Array/Uint8ClampedArray → Go
[]uint8
- JavaScript Int16Array → Go
[]int16
- JavaScript Uint16Array → Go
[]uint16
- JavaScript Int32Array → Go
[]int32
- JavaScript Uint32Array → Go
[]uint32
- JavaScript Float32Array → Go
[]float32
- JavaScript Float64Array → Go
[]float64
- JavaScript BigInt64Array → Go
[]int64
- JavaScript BigUint64Array → Go
[]uint64
When unmarshaling into interface{}
, the following types are used:
nil
for null/undefinedbool
for booleanint64
for integer numbersfloat64
for floating-point numbersstring
for string[]interface{}
for Arraymap[string]interface{}
for Object*big.Int
for BigInt[]byte
for ArrayBuffer
The ModuleBuilder API allows you to create JavaScript modules from Go code, making Go functions, values, and objects available for standard ES6 import syntax in JavaScript applications.
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// Create a math module with Go functions and values
addFunc := ctx.NewFunction(func(ctx *quickjs.Context, this *quickjs.Value, args []*quickjs.Value) *quickjs.Value {
if len(args) >= 2 {
return ctx.NewFloat64(args[0].ToFloat64() + args[1].ToFloat64())
}
return ctx.NewFloat64(0)
})
defer addFunc.Free()
// Build the module using fluent API
module := quickjs.NewModuleBuilder("math").
Export("PI", ctx.NewFloat64(3.14159)).
Export("add", addFunc).
Export("version", ctx.NewString("1.0.0")).
Export("default", ctx.NewString("Math Module"))
err := module.Build(ctx)
if err != nil {
panic(err)
}
// Use the module in JavaScript with standard ES6 import
result := ctx.Eval(`
(async function() {
// Named imports
const { PI, add, version } = await import('math');
// Use imported functions and values
const sum = add(PI, 1.0);
return { sum, version };
})()
`, quickjs.EvalAwait(true))
defer result.Free()
if result.IsException() {
err := ctx.Exception()
panic(err)
}
fmt.Println("Module result:", result.JSONStringify())
// Output: Module result: {"sum":4.14159,"version":"1.0.0"}
}
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// Create a utilities module with complex objects
config := ctx.NewObject()
config.Set("appName", ctx.NewString("MyApp"))
config.Set("version", ctx.NewString("2.0.0"))
config.Set("debug", ctx.NewBool(true))
greetFunc := ctx.NewFunction(func(ctx *quickjs.Context, this *quickjs.Value, args []*quickjs.Value) *quickjs.Value {
name := "World"
if len(args) > 0 {
name = args[0].ToString()
}
return ctx.NewString(fmt.Sprintf("Hello, %s!", name))
})
defer greetFunc.Free()
jsonVal := ctx.ParseJSON(`{"MAX": 100, "MIN": 1}`)
defer jsonVal.Free()
// Create module with various export types
module := quickjs.NewModuleBuilder("utils").
Export("config", config). // Object export
Export("greet", greetFunc). // Function export
Export("constants", jsonVal). // JSON export
Export("default", ctx.NewString("Utils Library")) // Default export
err := module.Build(ctx)
if err != nil {
panic(err)
}
// Use mixed imports in JavaScript
result := ctx.Eval(`
(async function() {
// Import from utils module
const { greet, config, constants } = await import('utils');
// Combine functionality
const message = greet("JavaScript");
const info = config.appName + " v" + config.version;
const limits = "Max: " + constants.MAX + ", Min: " + constants.MIN;
return { message, info, limits };
})()
`, quickjs.EvalAwait(true))
defer result.Free()
if result.IsException() {
err := ctx.Exception()
panic(err)
}
fmt.Println("Advanced module result:", result.JSONStringify())
}
package main
import (
"fmt"
"strings"
"github.com/buke/quickjs-go"
)
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// Create math module
addFunc := ctx.NewFunction(func(ctx *quickjs.Context, this *quickjs.Value, args []*quickjs.Value) *quickjs.Value {
if len(args) >= 2 {
return ctx.NewFloat64(args[0].ToFloat64() + args[1].ToFloat64())
}
return ctx.NewFloat64(0)
})
defer addFunc.Free()
mathModule := quickjs.NewModuleBuilder("math").
Export("add", addFunc).
Export("PI", ctx.NewFloat64(3.14159))
// Create string utilities module
upperFunc := ctx.NewFunction(func(ctx *quickjs.Context, this *quickjs.Value, args []*quickjs.Value) *quickjs.Value {
if len(args) > 0 {
return ctx.NewString(strings.ToUpper(args[0].ToString()))
}
return ctx.NewString("")
})
defer upperFunc.Free()
stringModule := quickjs.NewModuleBuilder("strings").
Export("upper", upperFunc)
// Build both modules
err := mathModule.Build(ctx)
if err != nil {
panic(err)
}
err = stringModule.Build(ctx)
if err != nil {
panic(err)
}
// Use multiple modules together
result := ctx.Eval(`
(async function() {
// Import from multiple modules
const { add, PI } = await import('math');
const { upper } = await import('strings');
// Combine functionality
const sum = add(PI, 1);
const message = "Result: " + sum.toFixed(2);
const finalMessage = upper(message);
return finalMessage;
})()
`, quickjs.EvalAwait(true))
defer result.Free()
if result.IsException() {
err := ctx.Exception()
panic(err)
}
fmt.Println("Multiple modules result:", result.ToString())
// Output: Multiple modules result: RESULT: 4.14
}
Core Methods:
NewModuleBuilder(name)
- Create a new module builder with the specified nameExport(name, value)
- Add a named export to the module (chainable method)Build(ctx)
- Register the module in the JavaScript context
The ClassBuilder API allows you to create JavaScript classes from Go code.
Create JavaScript classes manually with control over methods, properties, and accessors:
package main
import (
"fmt"
"math"
"github.com/buke/quickjs-go"
)
type Point struct {
X, Y float64
Name string
}
func (p *Point) Distance() float64 {
return math.Sqrt(p.X*p.X + p.Y*p.Y)
}
func (p *Point) Move(dx, dy float64) {
p.X += dx
p.Y += dy
}
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// Create Point class using ClassBuilder
pointConstructor, _ := quickjs.NewClassBuilder("Point").
Constructor(func(ctx *quickjs.Context, instance *quickjs.Value, args []*quickjs.Value) (interface{}, error) {
x, y := 0.0, 0.0
name := "Unnamed Point"
if len(args) > 0 { x = args[0].ToFloat64() }
if len(args) > 1 { y = args[1].ToFloat64() }
if len(args) > 2 { name = args[2].ToString() }
// Return Go object for automatic association
return &Point{X: x, Y: y, Name: name}, nil
}).
// Accessors provide getter/setter functionality with custom logic
Accessor("x",
func(ctx *quickjs.Context, this *quickjs.Value) *quickjs.Value {
point, _ := this.GetGoObject()
return ctx.NewFloat64(point.(*Point).X)
},
func(ctx *quickjs.Context, this *quickjs.Value, value *quickjs.Value) *quickjs.Value {
point, _ := this.GetGoObject()
point.(*Point).X = value.ToFloat64()
return ctx.NewUndefined()
}).
Accessor("y",
func(ctx *quickjs.Context, this *quickjs.Value) *quickjs.Value {
point, _ := this.GetGoObject()
return ctx.NewFloat64(point.(*Point).Y)
},
func(ctx *quickjs.Context, this *quickjs.Value, value *quickjs.Value) *quickjs.Value {
point, _ := this.GetGoObject()
point.(*Point).Y = value.ToFloat64()
return ctx.NewUndefined()
}).
// Properties are bound directly to each instance
Property("version", ctx.NewString("1.0.0")).
Property("type", ctx.NewString("Point")).
// Read-only property
Property("readOnly", ctx.NewBool(true), quickjs.PropertyConfigurable).
// Instance methods
Method("distance", func(ctx *quickjs.Context, this *quickjs.Value, args []*quickjs.Value) *quickjs.Value {
point, _ := this.GetGoObject()
return ctx.NewFloat64(point.(*Point).Distance())
}).
Method("move", func(ctx *quickjs.Context, this *quickjs.Value, args []*quickjs.Value) *quickjs.Value {
point, _ := this.GetGoObject()
dx, dy := 0.0, 0.0
if len(args) > 0 { dx = args[0].ToFloat64() }
if len(args) > 1 { dy = args[1].ToFloat64() }
point.(*Point).Move(dx, dy)
return ctx.NewUndefined()
}).
Method("getName", func(ctx *quickjs.Context, this *quickjs.Value, args []*quickjs.Value) *quickjs.Value {
point, _ := this.GetGoObject()
return ctx.NewString(point.(*Point).Name)
}).
// Static method
StaticMethod("origin", func(ctx *quickjs.Context, this *quickjs.Value, args []*quickjs.Value) *quickjs.Value {
// Create a new Point at origin
origin := &Point{X: 0, Y: 0, Name: "Origin"}
jsVal, _ := ctx.Marshal(origin)
return jsVal
}).
Build(ctx)
// Register the class
ctx.Globals().Set("Point", pointConstructor)
// Use in JavaScript
result := ctx.Eval(`
const p = new Point(3, 4, "My Point");
const dist1 = p.distance();
p.move(1, 1);
const dist2 = p.distance();
// Static method usage
const origin = Point.origin();
({
// Accessor usage
x: p.x,
y: p.y,
// Property usage
version: p.version,
type: p.type,
readOnly: p.readOnly,
hasOwnProperty: p.hasOwnProperty('version'), // true for properties
// Method results
name: p.getName(),
initialDistance: dist1,
finalDistance: dist2,
// Static method result
originDistance: Math.sqrt(origin.x * origin.x + origin.y * origin.y)
});
`)
defer result.Free()
if result.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Result:", result.JSONStringify())
// Demonstrate the difference between accessors and properties
propertyTest := ctx.Eval(`
const p1 = new Point(1, 1);
const p2 = new Point(2, 2);
// Properties are instance-specific values
const sameVersion = p1.version === p2.version; // true, same static value
// Accessors provide dynamic values from Go object
const differentX = p1.x !== p2.x; // true, different values from Go objects
({ sameVersion, differentX });
`)
defer propertyTest.Free()
if propertyTest.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Property vs Accessor:", propertyTest.JSONStringify())
}
Automatically generate JavaScript classes from Go structs using reflection. Go struct fields are automatically converted to JavaScript class accessors, providing getter/setter functionality that directly maps to the underlying Go object fields.
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
type User struct {
ID int64 `js:"id"` // Becomes accessor: user.id
Name string `js:"name"` // Becomes accessor: user.name
Email string `json:"email_address"` // Becomes accessor: user.email_address
Age int `js:"age"` // Becomes accessor: user.age
IsActive bool `js:"is_active"` // Becomes accessor: user.is_active
Scores []float32 `js:"scores"` // Becomes accessor: user.scores (Float32Array)
private string // Not accessible (unexported)
Secret string `js:"-"` // Explicitly ignored
}
func (u *User) GetFullInfo() string {
return fmt.Sprintf("%s (%s) - Age: %d", u.Name, u.Email, u.Age)
}
func (u *User) UpdateEmail(newEmail string) {
u.Email = newEmail
}
func (u *User) AddScore(score float32) {
u.Scores = append(u.Scores, score)
}
func main() {
rt := quickjs.NewRuntime()
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// Automatically create User class from struct
userConstructor, _ := ctx.BindClass(&User{})
ctx.Globals().Set("User", userConstructor)
// Use with positional arguments
result1 := ctx.Eval(`
const user1 = new User(1, "Alice", "alice@example.com", 25, true, [95.5, 87.2]);
user1.GetFullInfo();
`)
defer result1.Free()
if result1.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Positional:", result1.ToString())
// Use with named arguments (object parameter)
result2 := ctx.Eval(`
const user2 = new User({
id: 2,
name: "Bob",
email_address: "bob@example.com",
age: 30,
is_active: true,
scores: [88.0, 92.5, 85.0]
});
// Call methods
user2.UpdateEmail("bob.smith@example.com");
user2.AddScore(95.0);
// Access fields via accessors (directly map to Go struct fields)
user2.age = 31; // Setter: modifies the Go struct field
const newAge = user2.age; // Getter: reads from the Go struct field
({
info: user2.GetFullInfo(),
email: user2.email_address, // Accessor getter
age: newAge, // Modified via accessor setter
scoresType: user2.scores instanceof Float32Array,
scoresLength: user2.scores.length
});
`)
defer result2.Free()
if result2.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Named:", result2.JSONStringify())
// Demonstrate field accessor synchronization
result3 := ctx.Eval(`
const user3 = new User(3, "Charlie", "charlie@example.com", 35, true, []);
// Field accessors provide direct access to Go struct fields
const originalName = user3.name; // Getter: reads Go struct field
user3.name = "Charles"; // Setter: modifies Go struct field
const newName = user3.name; // Getter: reads modified field
// Changes are synchronized with Go object
const info = user3.GetFullInfo(); // Method sees the changed name
// Verify synchronization by changing multiple fields
user3.age = 36;
user3.email_address = "charles.updated@example.com";
const updatedInfo = user3.GetFullInfo();
({
originalName: originalName,
newName: newName,
infoAfterNameChange: info,
finalInfo: updatedInfo,
// Demonstrate that Go object is synchronized
goObjectAge: user3.age,
goObjectEmail: user3.email_address
});
`)
defer result3.Free()
if result3.IsException() {
err := ctx.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Synchronization demonstration:", result3.JSONStringify())
}
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
// Create a new runtime
rt := quickjs.NewRuntime()
defer rt.Close()
// Create a new context
ctx := rt.NewContext()
defer ctx.Close()
jsStr := `
function fib(n)
{
if (n <= 0)
return 0;
else if (n == 1)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
fib(10)
`
// Compile the script to bytecode
buf, err := ctx.Compile(jsStr)
if err != nil {
panic(err)
}
// Create a new runtime
rt2 := quickjs.NewRuntime()
defer rt2.Close()
// Create a new context
ctx2 := rt2.NewContext()
defer ctx2.Close()
//Eval bytecode
result := ctx2.EvalBytecode(buf)
defer result.Free()
if result.IsException() {
err := ctx2.Exception()
fmt.Println("Error:", err.Error())
return
}
fmt.Println(result.ToInt32())
}
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
// Create a new runtime
rt := quickjs.NewRuntime()
defer rt.Close()
// set runtime options
rt.SetExecuteTimeout(30) // Set execute timeout to 30 seconds
rt.SetMemoryLimit(256 * 1024) // Set memory limit to 256KB
rt.SetMaxStackSize(65534) // Set max stack size to 65534
rt.SetGCThreshold(256 * 1024) // Set GC threshold to 256KB
rt.SetCanBlock(true) // Set can block to true
// Create a new context
ctx := rt.NewContext()
defer ctx.Close()
result := ctx.Eval(`var array = []; while (true) { array.push(null) }`)
defer result.Free()
if result.IsException() {
err := ctx.Exception()
fmt.Println("Memory limit exceeded:", err.Error())
}
}
package main
import (
"fmt"
"github.com/buke/quickjs-go"
)
func main() {
// enable module import
rt := quickjs.NewRuntime(quickjs.WithModuleImport(true))
defer rt.Close()
ctx := rt.NewContext()
defer ctx.Close()
// eval module
r1 := ctx.EvalFile("./test/hello_module.js")
defer r1.Free()
if r1.IsException() {
err := ctx.Exception()
panic(err)
}
// load module
r2 := ctx.LoadModuleFile("./test/fib_module.js", "fib_foo")
defer r2.Free()
if r2.IsException() {
err := ctx.Exception()
panic(err)
}
// call module
r3 := ctx.Eval(`
import {fib} from 'fib_foo';
globalThis.result = fib(9);
`)
defer r3.Free()
if r3.IsException() {
err := ctx.Exception()
panic(err)
}
result := ctx.Globals().Get("result")
defer result.Free()
fmt.Println("Fibonacci result:", result.ToInt32())
}