23時に寝て2時に起きてだらだらして寝て7時に起きた。テンカイチ 日本最強武芸者決定戦 という漫画を読んでた。

pure go の javascript 処理系

ユーザー定義スクリプトを実行する要件がある。システム管理者がスクリプトを書くなら javascript がいいかなと次のリファレンスを調べたりしていた。

これらを読んだ結果として dop251/goja がよいだろうと判断した。README だけ読めばすぐに実装できてめっちゃ簡単で感心した。こういうライブラリを私も会社のプロダクトとして作ってみたい。万人が使うものではなくても、必要なときがたまにあって、すごく使いやすいみたいな。こんな感じで goja の機能を使ってライブラリ実装してみた。

type FunctionCaller func(funcName string, args ...interface{}) (interface{}, error)

func NewFunctionCaller(script string) (FunctionCaller, error) {
	vm := goja.New()
	_, err := vm.RunString(script)
	if err != nil {
		return nil, err
	}
	return func(funcName string, args ...interface{}) (interface{}, error) {
		f, ok := goja.AssertFunction(vm.Get(funcName))
		if !ok {
			return nil, fmt.Errorf("failed to get function: %s", funcName)
		}

		funcArgs := make([]goja.Value, 0, len(args))
		for _, arg := range args {
			funcArgs = append(funcArgs, vm.ToValue(arg))
		}
		value, err := f(goja.Undefined(), funcArgs...)
		if err != nil {
			return nil, fmt.Errorf("failed to call javascript function: %w", err)
		}
		return value.Export(), nil
	}, nil
}

Is it goroutine-safe? によると、goroutine-safe ではないので毎回 js の runtime を生成する必要がある。用途によっては、実行したいスクリプトが大きくなるとオーバーヘッドを無視できない状況もあるかもしれない。うちの要件は小さいユーザー定義スクリプトを実行するだけなのでおそらくそれほど問題にはならないだろうという想定。