Help us shape the future of Go! Take the 2026 Go Developer Survey

js

package module
v0.0.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 12 Imported by: 0

README

JavaScript 脚本引擎

基于 goja 封装的 Go 嵌入式 JavaScript 脚本引擎实现,作为 go-scripts 的子模块对外提供。

设计要点

  • 统一接口:实现 script_engine.Engine 接口,可与 Manager、EnginePool、AutoGrowEnginePool 等根模块组件无缝配合。
  • 工厂自动注册:通过 init() 注册到 script_engine 全局工厂表,只需 import _ "github.com/tx7do/go-scripts/javascript" 即可启用。
  • 并发安全:内部用 sync.RWMutex + execMu 双锁保护 runtime / programs / source;Execute* 系列通过 goja.Runtime.Interrupt 支持 ctx.Done() 中断。
  • 脚本源解耦:通过 Source 接口(FileSource / MemSource / MultiSource / 自定义扩展)注入脚本来源,engine 本身不再耦合任何 IO 细节。
  • Node.js 兼容层:内置启用 require / console / process 三个 goja_nodejs 模块,可直接使用 CommonJS 风格的模块加载。
  • ES6 子集支持:goja 支持 ES5 全部 + ES6 部分语法(let / const / 模板字符串 / 箭头函数 / 解构等)。

依赖

快速开始

1. 引入模块
import (
    scriptEngine "github.com/tx7do/go-scripts"
    _ "github.com/tx7do/go-scripts/javascript" // 注册 JavaScript 工厂
)

只要触发 init(),后续所有 scriptEngine.NewScriptEngine(scriptEngine.JavaScriptType, ...) 调用都会返回本模块的 engine 实例。

2. 单实例使用
eng, err := scriptEngine.NewScriptEngine(scriptEngine.JavaScriptType)
if err != nil {
    log.Fatal(err)
}
defer eng.Close()

ctx := context.Background()
if err := eng.Init(ctx); err != nil {
    log.Fatal(err)
}

// 注入变量(host -> JS)
_ = eng.RegisterGlobal("answer", 42)

// 注入函数(host -> JS)
_ = eng.RegisterFunction("sayHello", func(name string) {
    fmt.Println("Hello,", name)
})

// 执行内联脚本
result, err := eng.ExecuteString(ctx, "demo.js", `
    sayHello("world");
    answer + 100;
`)
// result = 142
3. 引擎池(推荐生产用法)
// 固定大小池
pool, err := scriptEngine.NewEnginePool(8, scriptEngine.JavaScriptType)

// 或:可自增池(初始 2,上限 16)
pool, err := scriptEngine.NewAutoGrowEnginePool(2, 16, scriptEngine.JavaScriptType)
if err != nil {
    log.Fatal(err)
}
defer pool.Close()

ctx := context.Background()
_, _ = pool.ExecuteString(ctx, "init.js", `globalThis.appName = "demo";`)
4. 配合 ScriptSource(统一脚本来源)
// 本地文件 + mtime 热更新检测
src := scriptEngine.NewFileSource()
eng.SetSource(src)

// 从 Source 加载并执行
result, err := eng.ExecuteFromKey(ctx, "/path/to/script.js")

// 也可以直接走 engine pool 的 wrapper
pool.SetSource(src)
results, err := pool.ExecuteFromKeys(ctx, []string{"a.js", "b.js"})

也可使用 MemSource(纯内存,零 IO)或 MultiSource(多源聚合 / fallback)。

核心 API

Engine 接口提供的方法如下(节选,完整定义见 engine.go):

生命周期
方法 说明
Init(ctx) 初始化 runtime,必须在任何 Load*/Execute* 之前调用
Close() 释放 runtime,调用后需重新 Init 才能复用
IsInitialized() 查询初始化状态
ScriptSource
方法 说明
SetSource(source) 绑定脚本源(FileSource / S3 / Mem / Multi / ...),传 nil 清除绑定
GetSource() 取回当前绑定的 Source(未绑定返回 nil)
脚本加载
方法 说明
Load(ctx, key) 从绑定的 Source 加载单个脚本(key 为路径 / object key / 脚本 ID)
LoadMulti(ctx, keys) 批量加载,遇到第一个错误即中止
LoadString(ctx, name, code) 直接编译内联脚本,不走 Source。name 用于诊断(出现在 stack trace 里)
脚本执行
方法 说明
Execute(ctx) 执行所有已 Load* 的脚本,按顺序返回结果
ExecuteFromKey(ctx, key) 从 Source 加载 + 立即执行,一步到位
ExecuteFromKeys(ctx, keys) 多 key 版本,结果顺序与 keys 一致
ExecuteString(ctx, name, code) 编译并立即执行内联脚本,不走 Source
全局变量 / 函数 / 模块
方法 说明
RegisterGlobal(name, value) 注册或覆盖 JS 全局变量(任何 Go 值,由 goja 自动转换)
GetGlobal(name) 读取 JS 全局变量,未定义时返回 error
RegisterFunction(name, fn) 注册宿主函数,脚本可按 name 直接调用
CallFunction(ctx, name, args...) 调用脚本中名为 name 的函数,支持 ctx 超时 / 中断
RegisterModule(name, module) 注册模块。module 可为 map[string]any(导出多个成员)或单个值
错误处理
方法 说明
GetLastError() 取回最近一次发生的错误
ClearError() 清除最近错误状态

宿主与脚本互操作

变量

支持三种访问模式:

  • 单向只写:通过 RegisterGlobal 把宿主变量注入脚本。
  • 单向只读:通过 GetGlobal 读取脚本中定义的变量。
  • 双向可读可写:传入指针 / 结构体引用,脚本对其字段的修改会反映到宿主(goja 自动桥接)。
函数
  • 脚本调用宿主:RegisterFunction 后,脚本中直接按名调用。
  • 宿主调用脚本:先 ExecuteString 定义函数(如 function add(a,b){return a+b}),再 CallFunction(ctx, "add", 1, 2)。
模块

RegisterModule(name, module) 用于把一组相关变量 / 函数封装到一个命名空间下,避免污染全局栈:

_ = eng.RegisterModule("mathutil", map[string]any{
    "square": func(x float64) float64 { return x * x },
    "pi":     3.14159,
})
// 脚本侧
mathutil.square(mathutil.pi * 2)
CommonJS 模块(require)

本模块在初始化时启用了 goja_nodejs 的 require / console / process,因此脚本中可以使用标准 Node.js 风格的模块:

// script/m.js
function sayHi(user) {
    console.log(`Js module say Hello, ${user}!`);
}
module.exports = { sayHi: sayHi };
// script/main.js
var m = require("./script/m.js");
m.sayHi("Tom");

require 默认按相对路径(相对于当前工作目录或脚本所在目录)解析,参见 goja_nodejs require。

完整示例

package main

import (
    "context"
    "fmt"
    "log"

    scriptEngine "github.com/tx7do/go-scripts"
    _ "github.com/tx7do/go-scripts/javascript"
)

type User struct {
    Name  string
    Token string
}

func main() {
    pool, err := scriptEngine.NewEnginePool(4, scriptEngine.JavaScriptType)
    if err != nil {
        log.Fatal(err)
    }
    defer pool.Close()

    ctx := context.Background()

    // 1. 注入结构体(脚本可修改其字段)
    u := &User{Name: "Tim"}
    _ = pool.RegisterGlobal("u", u)

    // 2. 注入宿主函数
    _ = pool.RegisterFunction("sayHello", func(name string) {
        fmt.Println("Hello,", name)
    })

    // 3. 注入模块
    _ = pool.RegisterModule("config", map[string]any{
        "env":  "prod",
        "port": 8080,
    })

    // 4. 执行内联脚本
    _, err = pool.ExecuteString(ctx, "app.js", `
        sayHello(u.Name);
        u.Token = "abcd-1234";
        console.log("env:", config.env, "port:", config.port);
    `)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("user token:", u.Token) // abcd-1234
}

测试

cd javascript
go test -v ./...

涵盖:

  • 基础执行 + 全局变量读写 + 宿主函数注入
  • 并发 ExecuteString / CallFunction 压测
  • 并发 Init / Close / Execute 压测
  • Source 注入 + Load / LoadMulti / ExecuteFromKey / ExecuteFromKeys
  • FileSource 端到端(t.TempDir() + 临时脚本文件)

相关文档

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrJavascriptEngineNotInitialized is returned when an operation is invoked
	// before Init or after Close.
	ErrJavascriptEngineNotInitialized = errors.New("javascript engine not initialized")

	// ErrJavascriptEngineAlreadyInitialized is returned when Init is called on an
	// already-initialized engine.
	ErrJavascriptEngineAlreadyInitialized = errors.New("javascript engine already initialized")

	// ErrJavascriptVMNotInitialized is returned when the underlying VM is nil.
	ErrJavascriptVMNotInitialized = errors.New("javascript VM not initialized")

	// ErrJavascriptCompileFailed is returned when goja fails to compile a script.
	ErrJavascriptCompileFailed = errors.New("javascript compile failed")

	// ErrJavascriptRuntimeNotInitialized is returned when the goja runtime is nil.
	ErrJavascriptRuntimeNotInitialized = errors.New("javascript runtime not initialized")

	// ErrJavascriptExecutionFailed is returned when running a program fails.
	ErrJavascriptExecutionFailed = errors.New("javascript execution failed")

	// ErrJavascriptNoProgramLoaded is returned when ExecuteLoaded is called but
	// no program has been loaded yet.
	ErrJavascriptNoProgramLoaded = errors.New("javascript no program loaded")
)

Sentinel errors returned by the JavaScript engine.

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL