QJS 是一个不依赖 CGO 的现代 JavaScript 运行时,用于 Go 语言,它将 QuickJS 引擎嵌入到 WebAssembly 模块中,并使用 Wazero 运行它,为 Go 应用程序提供了一个带有 async/await 和和紧密 Go-JS 互操作性的沙箱化 ES2023 环境。
QJS 的目标是那些希望在 Go 进程中运行现代 JavaScript 而不链接本地 C 库的 Go 开发人员。它没有直接通过 CGO 绑定 QuickJS,而是将 QuickJS-NG 编译为 WebAssembly,并在 Wazero 下执行,提供:
完整的 ES2023 支持(模块、async/await、BigInt 等)。
一个完全沙箱化、内存安全的执行模型。
无需 CGO 工具链或 C 运行时依赖。
该运行时与 Go 1.22+兼容,并作为常规 Go 模块分发:
go get github.com/fastschema/qjs
然后:
import "github.com/fastschema/qjs"
QJS 公开了一个 Runtime 和 Context API,允许 Go 代码评估 JavaScript、绑定函数和交换数据结构。一个最简单的示例创建了一个运行时,计算脚本,并将结构化结果读回到 Go 中:
rt, err := qjs.New() if err != nil { log.Fatal(err) } defer rt.Close()ctx := rt.Context()result, err := ctx.Eval("test.js", qjs.Code(` const person = { name: "Alice", age: 30, city: "New York" };const info = Object.keys(person).map(key => key + ": " + person[key]).join(", ");({ person: person, info: info });)) if err != nil { log.Fatal("Eval error:", err) } defer result.Free()log.Println(result.GetPropertyStr("info").String()) log.Println(result.GetPropertyStr("person").GetPropertyStr("name").String()) log.Println(result.GetPropertyStr("person").GetPropertyStr("age").Int32())
Go 函数可以暴露给 JavaScript,JS 函数可以转换回类型化的 Go 可调用函数。例如,绑定一个 Go 函数:
ctx.SetFunc("goFunction", func(this qjs.This) (qjs.Value, error) { return this.Context().NewString("Hello from Go!"), nil })result, err := ctx.Eval("test.js", qjs.Code( const message = goFunction(); message; )) if err != nil { log.Fatal("Eval error:", err) } defer result.Free()log.Println(result.String()) // Hello from Go!
QJS 还支持将更丰富的 Go 结构体转换为 JS 值,包括可以从 JavaScript 调用的方法,然后反序列化为类型化的 Go 值。
为了避免重复序列化大型或不透明的 Go 对象,QJS 引入了Proxy,这是一个轻量级的 JavaScript 包装器,只保存对 Go 值的引用。这对于上下文、数据库句柄或 JS 不需要检查就可以通过的大型结构体来说很有用:
ctx.SetFunc("$context", func(this qjs.This) (qjs.Value, error) { passContext := context.WithValue(context.Background(), "key", "value123") val := ctx.NewProxyValue(passContext) return val, nil })goFuncWithContext := func(c context.Context, num int) int { log.Println("Context value:", c.Value("key")) return num * 2 }
JavaScript 接收代理并将其传回 Go,其中 JsValueToGo 恢复底层值和类型。QJS 通过允许 Go 异步解决 JS 承诺来支持 async/await。一个 Go 异步函数可以调度工作并解决一个承诺:
ctx.SetAsyncFunc("asyncFunction", func(this *qjs.This) { go func() { time.Sleep(100 * time.Millisecond) result := this.Context().NewString("Async result from Go!") this.Promise().Resolve(result) }()})
然后 JS await 它:
async function main() { const result = await asyncFunction(); return result; }({ main: main() });
该运行时可用于在 JavaScript 中实现 HTTP 处理程序,同时保持服务器在 Go 中。例如, /about 和 /contact 路由在 JS 中定义,预编译为字节码,并从运行时池中执行:
byteCode := must(ctx.Compile("script.js", qjs.Code(script), qjs.TypeModule())) pool := qjs.NewPool(3, &qjs.Option{}, func(r *qjs.Runtime) error { results := must(r.Context().Eval("script.js", qjs.Bytecode(byteCode), qjs.TypeModule())) r.Context().Global().SetPropertyStr("handlers", results) return nil })http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) { runtime := must(pool.Get()) defer pool.Put(runtime)handlers := runtime.Context().Global().GetPropertyStr("handlers")result := must(handlers.InvokeJS("about"))fmt.Fprint(w, result.String())result.Free()})
计算阶乘(10)1,000,000 次
AreWeFastYet V8-V7
通过这种设计,QJS 的目标是那些需要安全的插件系统、用户提供的脚本或用 JavaScript 编写的嵌入式业务逻辑,而不需要将 C 工具链或 CGO 引入构建和部署流水线的 Go 开发人员。
原文链接:





