Files
jscriptx/extension.go
T
what 0627d49425 feat: 嵌入式 JS 脚本引擎核心
用 goja 承载业务回调,让业务逻辑变更不必重新编译发布 Go 程序。脚本用
ESM + TypeScript 写,Go 侧按名字把它们当普通对象实例化并调用方法。

主要组成:

  - Engine    编译脚本、管配置,公开 API 不暴露任何 goja 类型
  - Script    一份编译好的脚本 + 它的 VM 池,热更新时整体顶替
  - Instance  独占一个 VM 的实例,状态留在 JS 侧
  - Caller    自定义调用约定,把脚本函数适配成 Go 侧要的签名
  - Scope     让同一个 ctx 下的多个脚本共享 Go 侧对象
  - Extension 扩展接口:给脚本添全局对象,配套 TS 类型
  - Overlay   多层 Loader 叠加,后面的盖前面的

几个关键取舍:

  - 源码一律先过 esbuild 打包成 ESM,再改写成立即执行函数。goja 不认
    import/export,而业务脚本要能拆文件、用 TypeScript。
  - VM 池化复用,但每个 VM 单线程。goja 的 Runtime 不是 goroutine 安全的。
  - Go 侧函数返回的 error 在脚本里表现为抛异常,不占返回值位置。
  - 脚本能看见的全局只有白名单放行的那些,且注入是惰性的——没读到的
    全局根本不会被转换。
2026-09-05 22:11:55 +08:00

49 lines
2.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package jscriptx
// Extension 是一个作用域级扩展:给脚本添一个全局对象。
//
// 扩展实例由调用方自己创建,跟着 ctx 走:
//
// st := jscriptx.NewStore()
// tx := myTx(db)
// ctx = jscriptx.WithScope(ctx, jscriptx.ScopeExtensions(st, tx))
//
// obj, err := ctrl.New(ctx) // 脚本里能用 store 和 tx
// defer obj.Close()
//
// st.Get("count") // Go 侧拿的就是同一份,不用再取回
//
// 同一个 ctx 下 New 出来的所有实例共享同一份扩展对象——这就是多个 controller
// 共享数据的方式:共享的是 Go 侧对象,不是共用 Runtime。
//
// 业务写自己的扩展只要实现这三个方法:
//
// type tx struct{ conn *sql.Tx }
//
// func (t *tx) Name() string { return "tx" }
// func (t *tx) Bindings() map[string]any {
// return map[string]any{"commit": t.conn.Commit, "rollback": t.conn.Rollback}
// }
// func (t *tx) Module() string { return txTypings }
type Extension interface {
// Name 是它在脚本里的全局名,必须是合法的 JS 标识符。
// 跟白名单(WithGlobals)同名时以白名单为准。
Name() string
// Bindings 是暴露给脚本的方法集。方法名**大写开头**,跟脚本里能碰到的
// 其它东西保持一致——传进来的 Go 对象(m.GetCode()、res.GetDBTable()
// 用的都是 Go 的方法名,扩展再用小写的话,同一行代码里两种风格混着写。
Bindings() map[string]any
// Module 返回配套 TypeScript 模块的 import 路径和源码,让脚本能拿到类型:
//
// import store from "@jscriptx/store"
//
// 路径要加 scope 前缀,免得跟 node_modules 里的包撞名。用什么 scope 自己定,
// 框架层那套用的是 @jscriptx/,业务自己的可以用 @fsdpf/ 之类。
// 路径返回空字符串表示不提供模块,脚本只能用全局变量的写法。
//
// 模块本身只是个门面:把全局对象转发出来,附上类型声明。真正的实现在 Go 侧。
Module() (path, source string)
}