goja 對於es6 的module 模式是不支持的,但是我們可以通過擴展模式支持
基本原理
k6 是利用了goja 的js 能力,但是為了支持es6,使用了babel (standalone),同時為了方便擴展ls 的能力,使用了core.js
同時利用了js可以直接轉換為golang 方法的模式,直接使用了golang 方法進行es6 編譯處理
- 核心代碼
使用babel 的transform 方法,轉換es6 為es6,同時暴露為一個golang 方法
func newBabel() (*babel, error) {
var err error
once.Do(func() {
conf := rice.Config{
LocateOrder: []rice.LocateMethod{rice.LocateEmbedded},
}
babelSrc := conf.MustFindBox("lib").MustString("babel.min.js")
vm := goja.New()
if _, err = vm.RunString(babelSrc); err != nil {
return
}
this := vm.Get("Babel")
bObj := this.ToObject(vm)
globalBabel = &babel{vm: vm, this: this}
if err = vm.ExportTo(bObj.Get("transform"), &globalBabel.transform); err != nil {
return
}
})
return globalBabel, err
}
globalBabel.transform 的定義
使用了goja 的Callable
transform goja.Callable
- 進行es6代碼編譯處理
func (b *babel) Transform(logger logrus.FieldLogger, src, filename string) (string, *SourceMap, error) {
b.mutex.Lock()
defer b.mutex.Unlock()
opts := make(map[string]interface{})
for k, v := range DefaultOpts {
opts[k] = v
}
opts["filename"] = filename
startTime := time.Now()
v, err := b.transform(b.this, b.vm.ToValue(src), b.vm.ToValue(opts))
if err != nil {
return "", nil, err
}
logger.WithField("t", time.Since(startTime)).Debug("Babel: Transformed")
vO := v.ToObject(b.vm)
var code string
if err = b.vm.ExportTo(vO.Get("code"), &code); err != nil {
return code, nil, err
}
var rawMap map[string]interface{}
if err = b.vm.ExportTo(vO.Get("map"), &rawMap); err != nil {
return code, nil, err
}
var srcMap SourceMap
if err = mapstructure.Decode(rawMap, &srcMap); err != nil {
return code, &srcMap, err
}
return code, &srcMap, err
}
一個參考集成babel 的demo
- 參考代碼
package main
import (
"demoapp-goja/pkg"
"log"
"github.com/dop251/goja"
)
const (
bablename = "node_modules/babel.min.js"
corejsname = "node_modules/index.js"
demomodule = "node_modules/app.js"
)
var convert func(string) string
var jsCompilerVM *goja.Runtime = goja.New()
func main() {
jsCompilerVM = goja.New()
// 使用go-bindata
bableContent, _ := pkg.Asset(bablename)
corejsContent, _ := pkg.Asset(corejsname)
mydemomodule, _ := pkg.Asset(demomodule)
corejs, _ := goja.Compile(corejsname, string(corejsContent), false)
bable, _ := goja.Compile(bablename, string(bableContent), false)
// 預加載babel 以及core-js
jsCompilerVM.RunProgram(bable)
jsCompilerVM.RunProgram(corejs)
// es6 編譯es5 轉換為golang 代碼
jsCompilerVM.RunString(`var convert = function(es6code) {
return Babel.transform(es6code, { presets: ['env'] }).code;
}
`)
err := jsCompilerVM.ExportTo(jsCompilerVM.Get("convert"), &convert)
if err != nil {
panic(err)
}
// 調用轉換
log.Println(convert(string(mydemomodule)))
}
node_modules/app.js 內容
import rong from "./rong.js"
var demo = new rong("dalong",222)
console.log(demo.printInfo())
效果
說明
實踐方法參考自k6一個很不錯的基於golang 開發的壓力測試工具,k6集成goja js 引擎還是比較巧妙的,后邊說明下方法
參考資料
https://github.com/loadimpact/k6/blob/master/js/lib/lib.go
https://github.com/loadimpact/k6/blob/master/js/compiler/compiler.go#L85
https://github.com/dop251/goja
https://github.com/dop251/goja/blob/master/runtime.go#L2017
https://github.com/loadimpact/k6/blob/master/js/compiler/compiler.go#L116
https://k6.io/docs/using-k6/modules