# quickjs-go [English](README.md) | 简体中文 源自 https://github.com/buke/quickjs-go, 修复了函数回调时的性能问题 [![Test](https://apigo.cc/apigo/quickjs-go/workflows/Test/badge.svg)](https://apigo.cc/apigo/quickjs-go/actions?query=workflow%3ATest) [![codecov](https://codecov.io/gh/buke/quickjs-go/branch/main/graph/badge.svg?token=DW5RGD01AG)](https://codecov.io/gh/buke/quickjs-go) [![Go Report Card](https://goreportcard.com/badge/apigo.cc/apigo/quickjs-go)](https://goreportcard.com/report/apigo.cc/apigo/quickjs-go) [![GoDoc](https://pkg.go.dev/badge/apigo.cc/apigo/quickjs-go?status.svg)](https://pkg.go.dev/apigo.cc/apigo/quickjs-go?tab=doc) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go?ref=badge_shield) Go 语言的QuickJS绑定库:快速、小型、可嵌入的ES2020 JavaScript解释器。 ## 平台支持 使用预编译的quickjs静态库,支持以下平台: | 平台 | 架构 | 静态库 | | -------- | ------------ | --------------------------------------------------- | | Linux | x64 | [libquickjs.a](deps/libs/linux_amd64/libquickjs.a) | | Linux | arm64 | [libquickjs.a](deps/libs/linux_arm64/libquickjs.a) | | Windows | x64 | [libquickjs.a](deps/libs/windows_amd64/libquickjs.a) | | Windows | x86 | [libquickjs.a](deps/libs/windows_386/libquickjs.a) | | MacOS | x64 | [libquickjs.a](deps/libs/darwin_amd64/libquickjs.a) | | MacOS | arm64 | [libquickjs.a](deps/libs/darwin_arm64/libquickjs.a) | \* windows 静态库基于 mingw 12.2.0 编译, 请确认 go version > 1.20.0 ## 版本说明 | quickjs-go | QuickJS | | ---------- | ------- | | v0.1.x | v2021-03-27 | | v0.2.x | v2023-12-09 | | v0.3.x | v2024-01-13 | | v0.4.x | v2024-02-14 | ## 功能 * 执行javascript脚本 * 编译javascript脚本到字节码并执行字节码 * 在 Go 中操作 JavaScript 值和对象 * 绑定 Go 函数到 JavaScript 同步函数和异步函数 * 简单的异常抛出和捕获 ## 指南 1. 在使用完毕后,请记得关闭 `quickjs.Runtime` 和 `quickjs.Context`。 2. 请记得关闭由 `Eval()` 和 `EvalFile()` 返回的 `quickjs.Value`。其他值不需要关闭,因为它们会被垃圾回收。 3. 如果你使用了promise 或 async function,请使用 `ctx.Loop()` 等待所有的promise/job结果。 4. 如果`Eval()` 或 `EvalFile()`返回了错误,可强制转换为`*quickjs.Error`以读取错误的堆栈信息。 5. 如果你想在函数中返回参数,请在函数中复制参数。 ## 用法 ```go import "apigo.cc/apigo/quickjs-go" ``` ### 执行javascript脚本 ```go package main import ( "fmt" "apigo.cc/apigo/quickjs-go" ) func main() { // Create a new runtime rt := quickjs.NewRuntime( quickjs.WithExecuteTimeout(30), quickjs.WithMemoryLimit(128*1024), quickjs.WithGCThreshold(256*1024), quickjs.WithMaxStackSize(65534), quickjs.WithCanBlock(true), ) defer rt.Close() // Create a new context ctx := rt.NewContext() defer ctx.Close() ret, err := ctx.Eval("'Hello ' + 'QuickJS!'") if err != nil { println(err.Error()) } fmt.Println(ret.String()) } ``` ### 读取/设置 JavaScript 对象 ```go package main import ( "fmt" "apigo.cc/apigo/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.Object() test.Set("A", ctx.String("String A")) test.Set("B", ctx.String("String B")) test.Set("C", ctx.String("String C")) ctx.Globals().Set("test", test) ret, _ := ctx.Eval(`Object.keys(test).map(key => test[key]).join(" ")`) defer ret.Free() fmt.Println(ret.String()) } ``` ### 函数绑定 ```go package main import "apigo.cc/apigo/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.Object() defer test.Free() // bind properties to the object test.Set("A", test.Context().String("String A")) test.Set("B", ctx.Int32(0)) test.Set("C", ctx.Bool(false)) // bind go function to js object test.Set("hello", ctx.Function(func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { return ctx.String("Hello " + args[0].String()) })) // bind "test" object to global object ctx.Globals().Set("test", test) // call js function by js js_ret, _ := ctx.Eval(`test.hello("Javascript!")`) fmt.Println(js_ret.String()) // call js function by go go_ret := ctx.Globals().Get("test").Call("hello", ctx.String("Golang!")) fmt.Println(go_ret.String()) //bind go function to Javascript async function ctx.Globals().Set("testAsync", ctx.AsyncFunction(func(ctx *quickjs.Context, this quickjs.Value, promise quickjs.Value, args []quickjs.Value) { promise.Call("resolve", ctx.String("Hello Async Function!")) })) ret, _ := ctx.Eval(` var ret; testAsync().then(v => ret = v) `) defer ret.Free() // wait for promise resolve ctx.Loop() //get promise result asyncRet, _ := ctx.Eval("ret") defer asyncRet.Free() fmt.Println(asyncRet.String()) // Output: // Hello Javascript! // Hello Golang! // Hello Async Function! } ``` ### 异常抛出和捕获 ```go package main import ( "fmt" "apigo.cc/apigo/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 *Context, this Value, args []Value) Value { // raise error return ctx.ThrowError(expected) }) _, actual := ctx.Eval("A()") fmt.Println(actual.Error()) } ``` ### Bytecode编译和执行 ```go package main import ( "fmt" "apigo.cc/apigo/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, _ := ctx.Compile(jsStr) // 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) fmt.Println(result.Int32()) } ``` ### 设置内存、栈、GC等等 ```go package main import ( "fmt" "apigo.cc/apigo/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, err := ctx.Eval(`var array = []; while (true) { array.push(null) }`) defer result.Free() } ``` ### ES6 模块支持 ```go package main import ( "fmt" "apigo.cc/apigo/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, err := ctx.EvalFile("./test/hello_module.js") defer r1.Free() require.NoError(t, err) require.EqualValues(t, 55, ctx.Globals().Get("result").Int32()) // load module r2, err := ctx.LoadModuleFile("./test/fib_module.js", "fib_foo") defer r2.Free() require.NoError(t, err) // call module r3, err := ctx.Eval(` import {fib} from 'fib_foo'; globalThis.result = fib(9); `) defer r3.Free() require.NoError(t, err) require.EqualValues(t, 34, ctx.Globals().Get("result").Int32()) } ``` ## 文档 Go 语言文档和示例: https://pkg.go.dev/apigo.cc/apigo/quickjs-go ## 协议 [MIT](./LICENSE) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go?ref=badge_large) ## 相关项目 * https://apigo.cc/apigo/quickjs-go-polyfill