Skip to content

buke/quickjs-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

quickjs-go

English | 简体中文

Test codecov Go Report Card GoDoc FOSSA Status

Go bindings to QuickJS: a fast, small, and embeddable ES2020 JavaScript interpreter.

⚠️ This project is not ready for production use yet. Use at your own risk. APIs may change without notice.

Features

  • 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))

Guidelines

Error Handling

  • Use Value.IsException() or Context.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

Memory Management

  • 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.

Performance Tips

  • 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.

Best Practices

  • Use appropriate EvalOptions for different script types.
  • Handle both JavaScript exceptions and Go errors appropriately.
  • Test memory usage under load to prevent leaks.

Usage

import "github.com/buke/quickjs-go"

Run a script

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()) }

Get/Set Javascript Object

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()) }

Bind Go Funtion to Javascript async/sync function

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! }

Error Handling

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()) } }

TypedArray Support

QuickJS-Go provides support for JavaScript TypedArrays, enabling binary data processing between Go and JavaScript.

Creating TypedArrays from Go

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()) }

Converting JavaScript TypedArrays to Go

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) } } }

TypedArray Types Support

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()

TypedArray Detection

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()) }

Binary Data Processing Example

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) } } }

Marshal/Unmarshal Go Values

QuickJS-Go provides conversion between Go and JavaScript values through the Marshal and Unmarshal methods.

Basic Types

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) }

Struct Marshaling with Tags

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) }

Custom Marshal/Unmarshal

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} }

Type Mappings

Go to JavaScript:

  • bool → JavaScript boolean
  • int, int8, int16, int32 → JavaScript number (32-bit)
  • int64 → JavaScript number (64-bit)
  • uint, uint8, uint16, uint32 → JavaScript number (32-bit unsigned)
  • uint64 → JavaScript BigInt
  • float32, float64 → JavaScript number
  • string → 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 BigUint64Array
  • slice/array → JavaScript Array (for non-typed arrays)
  • map → JavaScript Object
  • struct → JavaScript Object
  • pointer → 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/undefined
  • bool for boolean
  • int64 for integer numbers
  • float64 for floating-point numbers
  • string for string
  • []interface{} for Array
  • map[string]interface{} for Object
  • *big.Int for BigInt
  • []byte for ArrayBuffer

Create JavaScript Modules from Go with ModuleBuilder

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.

Basic Module Creation

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"} }

Advanced Module Features

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()) }

Multiple Module Integration

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 }

ModuleBuilder API Reference

Core Methods:

  • NewModuleBuilder(name) - Create a new module builder with the specified name
  • Export(name, value) - Add a named export to the module (chainable method)
  • Build(ctx) - Register the module in the JavaScript context

Create JavaScript Classes from Go with ClassBuilder

The ClassBuilder API allows you to create JavaScript classes from Go code.

Manual Class Creation

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()) }

Automatic Class Creation with Reflection

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()) }

Bytecode Compiler

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()) }

Runtime Options: memory, stack, GC, ...

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()) } }

ES6 Module Support

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()) }

License

MIT License

About

Go bindings to QuickJS

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 6