Files
jscriptx/console.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

42 lines
1.0 KiB
Go

package jscriptx
import (
"context"
"fmt"
"log/slog"
"strings"
)
// newConsole 造一个绑定到指定脚本的 console 对象,脚本里的 console.log 等
// 直接落到结构化日志里,带上 script 字段,方便按脚本名检索。
func newConsole(logger *slog.Logger, script string) map[string]any {
at := func(level slog.Level) func(args ...any) {
return func(args ...any) {
if !logger.Enabled(context.Background(), level) {
return
}
logger.LogAttrs(context.Background(), level, joinArgs(args),
slog.String("script", script), slog.String("source", "console"))
}
}
return map[string]any{
"log": at(slog.LevelInfo),
"info": at(slog.LevelInfo),
"debug": at(slog.LevelDebug),
"warn": at(slog.LevelWarn),
"error": at(slog.LevelError),
}
}
// joinArgs 按 console 的习惯用空格拼接参数。
func joinArgs(args []any) string {
if len(args) == 0 {
return ""
}
parts := make([]string, 0, len(args))
for _, a := range args {
parts = append(parts, fmt.Sprintf("%v", a))
}
return strings.Join(parts, " ")
}