k6 集成goja 的部分集成说明

k6 对于goja 的集成还是比较强大的,支持了es6(基于babel 的编译能力),同时对于默认的js engine 进行了扩展(基于core-js)
同时对于require以及module ,exports 也是支持的,只是对于exports 是自己定义了变量,同时对于一些内置的模块(k6 开头的进行了
特殊处理,直接内置),对于require 的支持是基于了 github.com/spf13/afero 一个很不错的通用文件系统抽象以及自定义了一个require
函数,同时暴露到goja js engine 的全局对象中,同时k6 扩展了console的支持

es6编译处理

会将babel 的Transform 映射为一个golang 函数,编译就是利用了bable 的能力,为了防止多起加载,使用了sync.Once
同时会通过goja 的parser 解析代码为ast然后在编译(为了兼容,支持了多种模式的编译)

 
func (c *Compiler) Compile(src, filename, pre, post string,
    strict bool, compatMode lib.CompatibilityMode) (*goja.Program, string, error) {
    code := pre + src + post
    ast, err := parser.ParseFile(nil, filename, code, 0)
    if err != nil {
        if compatMode == lib.CompatibilityModeExtended {
            code, _, err = c.Transform(src, filename)
            if err != nil {
                return nil, code, err
            }
            // the compatibility mode "decreases" here as we shouldn't transform twice
            return c.Compile(code, filename, pre, post, strict, lib.CompatibilityModeBase)
        }
        return nil, code, err
    }
    pgm, err := goja.CompileAST(ast, strict)
    return pgm, code, err
}

babel 浏览器集成方法(一个参考)

var output = Babel.transform(content, { presets: ['env'] }).code;

require 支持

实际上就是利用了js hack 的模式
https://github.com/loadimpact/k6/blob/master/js/initcontext.go#L117

 
func (i *InitContext) Require(arg string) goja.Value {
    switch {
    case arg == "k6", strings.HasPrefix(arg, "k6/"):
        // Builtin or external modules ("k6", "k6/*", or "k6/x/*") are handled
        // specially, as they don't exist on the filesystem. This intentionally
        // shadows attempts to name your own modules this.
        v, err := i.requireModule(arg)
        if err != nil {
            common.Throw(i.runtime, err)
        }
        return v
    default:
        // Fall back to loading from the filesystem.
        v, err := i.requireFile(arg)
        if err != nil {
            common.Throw(i.runtime, err)
        }
        return v
    }
}

spf13/afero 参考使用

package main
import (
    "io/ioutil"
    "log"
    "net/http"
    "github.com/spf13/afero"
)
func httpFs() {
    var appFs = afero.NewMemMapFs()
    appFs.Mkdir("/", 0755)
    fh, _ := appFs.Create("/demoapp.js")
    fh.WriteString("This is a test")
    fh.Close()
    httpFs := afero.NewHttpFs(appFs)
    fileserver := http.FileServer(httpFs.Dir("/"))
    http.Handle("/", fileserver)
    http.ListenAndServe(":8090", nil)
}
func main() {
    httpFs()
}

为了方便跨平台的支持,提供了一个通用的基于afero 的文件系统实现,同时支持网络文件系统

func CreateFilesystems() map[string]afero.Fs {
    // We want to eliminate disk access at runtime, so we set up a memory mapped cache that's
    // written every time something is read from the real filesystem. This cache is then used for
    // successive spawns to read from (they have no access to the real disk).
    // Also initialize the same for `https` but the caching is handled manually in the loader package
    osfs := afero.NewOsFs()
    if runtime.GOOS == "windows" {
        // This is done so that we can continue to use paths with /|"\" through the code but also to
        // be easier to traverse the cachedFs later as it doesn't work very well if you have windows
        // volumes
        osfs = fsext.NewTrimFilePathSeparatorFs(osfs)
    }
    return map[string]afero.Fs{
        "file":  fsext.NewCacheOnReadFs(osfs, afero.NewMemMapFs(), 0),
        "https": afero.NewMemMapFs(),
    }
} 

网络文件加载模式也是支持的,首先第一次是进行http 请求缓存,然后进行读取(基于NewMemMapFs)

if scheme == "https" {
        var finalModuleSpecifierURL = &url.URL{}
        switch {
        case moduleSpecifier.Opaque != "": // This is loader
            finalModuleSpecifierURL, err = resolveUsingLoaders(logger, moduleSpecifier.Opaque)
            if err != nil {
                return nil, err
            }
        case moduleSpecifier.Scheme == "":
            logger.Warningf(`The moduleSpecifier "%s" has no scheme but we will try to resolve it as remote module. `+
                `This will be deprecated in the future and all remote modules will `+
                `need to explicitly use "https" as scheme.`, originalModuleSpecifier)
            *finalModuleSpecifierURL = *moduleSpecifier
            finalModuleSpecifierURL.Scheme = scheme
        default:
            finalModuleSpecifierURL = moduleSpecifier
        }
        var result *SourceData
        result, err = loadRemoteURL(logger, finalModuleSpecifierURL)
        if err == nil {
            result.URL = moduleSpecifier
            // TODO maybe make an afero.Fs which makes request directly and than use CacheOnReadFs
            // on top of as with the `file` scheme fs
            _ = afero.WriteFile(filesystems[scheme], pathOnFs, result.Data, 0644)
            return result, nil
        }
        if moduleSpecifier.Scheme == "" || moduleSpecifier.Opaque == "" {
            // we have an error and we did remote module resolution without a scheme
            // let's write the coolest error message to try to help the lost soul who got to here
            return nil, noSchemeRemoteModuleResolutionError{err: err, moduleSpecifier: originalModuleSpecifier}
        }
        return nil, errors.Errorf(httpsSchemeCouldntBeLoadedMsg, originalModuleSpecifier, finalModuleSpecifierURL, err)
    }

自定义模块的开发

k6已经内置了一些以k6开头的模块,使用了类似databasedriver 的开发模式,一个简单参考

func init() {
    modules.Register("k6", New())
}
type K6 struct{}
// ErrGroupInInitContext is returned when group() are using in the init context
var ErrGroupInInitContext = common.NewInitContextError("Using group() in the init context is not supported")
// ErrCheckInInitContext is returned when check() are using in the init context
var ErrCheckInInitContext = common.NewInitContextError("Using check() in the init context is not supported")
func New() *K6 {
    return &K6{}
}

说明

通过学习k6集成goja 的模式,发现比goja 社区的模块模式简单点(但是核心没变),提供k6支持es6 的模式设计的挺不错的
(以前自己有基于browserify +babel 编译模式集成的模式,发现不是很好,而且没有k6集成模式支持的特性多,而且简单)

参考资料

https://github.com/loadimpact/k6/blob/master/js/common/bridge.go
https://github.com/loadimpact/k6/blob/master/js/initcontext.go#L117
https://godoc.org/github.com/dop251/goja#Runtime.ToValue
https://github.com/spf13/afero
https://github.com/loadimpact/k6/blob/master/js/compiler/compiler.go#L74
https://github.com/zloirock/core-js
https://babeljs.io/docs/en/babel-standalone.html
https://github.com/loadimpact/k6/blob/master/loader/loader.go#L205

posted on 2021-01-05 16:30  荣锋亮  阅读(965)  评论(0编辑  收藏  举报

导航