gojaを使ってGoでJavaScriptの実行を試してみた

[第2のドワンゴ Advent Calendar 2016](http://qiita.com/advent-calendar/2016/dwango2)の３日目の記事です。

来年使い倒そうと思ってるgojaについて紹介しようと思います。

# goja とは

https://github.com/dop251/goja

gojaはEcmaScript5.1をGoで実装しているライブラリです。
GoでJavaScriptを実行するメリットとしてプラグインにJavaScriptを使えたりサーバーサイドレンダリングに使えるのではないかなと思います。

# インストール

Goを入れていれば`go get github.com/dop251/goja`でインストールできます。

# Hello World

gojaのHello Worldです。
スクリプトで最後に実行した行の戻り値が`RunString`の戻り値として取得できます。

```go
package main

import (
	"fmt"

	"github.com/dop251/goja"
)

func main() {
	vm := goja.New()
	v, err := vm.RunString(`2+2`)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%T, %#v\n", v, v) // Output: goja.valueInt, 4
}
```

# Goとの値のやり取り

`Set`を使うことでスクリプト実行前に変数を定義することができます。

```go
package main

import (
	"fmt"

	"github.com/dop251/goja"
)

func main() {
	vm := goja.New()
	x, y := 5, 7
	vm.Set("x", x)
	vm.Set("y", y)
	v, err := vm.RunString(`x+y`)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%T, %#v\n", v, v) // Output: goja.valueInt, 7
}
```

関数も同じ様にセットすることができます。

```go
package main

import (
	"fmt"

	"github.com/dop251/goja"
)

func Sum(x, y int) int {
	return x + y
}

func main() {
	vm := goja.New()
	vm.Set("sum", Sum)
	v, err := vm.RunString(`sum(9, 4)`)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%T, %#v\n", v, v) // Output: goja.valueInt, 13
}
```

逆にJavaScriptで定義した変数をGoに渡すこともできます。
その場合は`Get`を使います。

```go
package main

import (
	"fmt"

	"github.com/dop251/goja"
)

func main() {
	vm := goja.New()
	_, err := vm.RunString(`
        var result = 8 + 7
    `)
	if err != nil {
		panic(err)
	}

	v := vm.Get("result")

	fmt.Printf("%T, %#v\n", v, v) // Output: goja.valueInt, 15
}
```

変数だけではなく関数もJavaScriptで定義してGoで呼び出すこともできます。

```go
package main

import (
	"fmt"

	"github.com/dop251/goja"
)

func main() {
	vm := goja.New()
	_, err := vm.RunString(`
        function sum(x, y) {
            return x + y
        }
    `)
	if err != nil {
		panic(err)
	}

	var v goja.Value
	if sum, ok := goja.AssertFunction(vm.Get("sum")); ok {
		v, err = sum(goja.Undefined(), vm.ToValue(4), vm.ToValue(10))
		if err != nil {
			panic(err)
		}
	}

	fmt.Printf("%T, %#v\n", v, v) // Output: goja.valueInt, 14
}
```

# モジュールの読み込み

サイドプロジェクトとしてNode.jsのrequireとconsoleを実装しているパッケージがあります。

https://github.com/dop251/goja_nodejs

このパッケージの`require`を使うことでモジュール読み込みができます。
インストールは`go get github.com/dop251/goja_nodejs/...`でできます。

```js:sum.js
module.exports = function sum(x, y) {
    return x + y
}
```

モジュールをJavaScriptから呼び出すなら以下のように。

```go
package main

import (
	"fmt"

	"github.com/dop251/goja"
	"github.com/dop251/goja_nodejs/require"
)

func main() {
	registry := new(require.Registry)

	vm := goja.New()
	registry.Enable(vm)
	v, err := vm.RunString(`
        var sum = require('./sum.js')
        sum(10, 5)
    `)

	if err != nil {
		panic(err)
	}

	fmt.Printf("%T, %#v\n", v, v) // Output: goja.valueInt, 15
}
```

モジュールをGoで呼び出すなら以下のようになります。

```go
package main

import (
	"fmt"

	"github.com/dop251/goja"
	"github.com/dop251/goja_nodejs/require"
)

func main() {
	registry := new(require.Registry)

	vm := goja.New()
	rm := registry.Enable(vm)

	var v goja.Value
	if sum, err := rm.Require("sum.js"); err != nil {
		panic(err)
	} else {
		if sum, ok := goja.AssertFunction(sum); ok {
			v, err = sum(goja.Undefined(), vm.ToValue(6), vm.ToValue(7))
			if err != nil {
				panic(err)
			}
		}
	}

	fmt.Printf("%T, %#v\n", v, v) // Output: goja.valueInt, 13
}
```

# まとめ

gojaを使えば手軽にJavaScriptを組み込むことができました。
ただsetTimeoutなどvue.jsでサーバーサイドレンダリングをしようと思うと
実装しなければならないものが多くてちょっと間に合わなかったので
来年こそはGoでSSRしたいと思います。

あと僕がGoを触るキッカケにもなった[dwanGo](https://dwgo.connpass.com/)が復活してくれたらいいなぁ(チラチラ