Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js: refactor how modules are loaded #2881

Merged
merged 6 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
293 changes: 154 additions & 139 deletions js/bundle.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func TestNewBundle(t *testing.T) {
b, err := getSimpleBundle(t, "-", `export default function() {};`)
require.NoError(t, err)
assert.Equal(t, "file://-", b.Filename.String())
assert.Equal(t, "file:///", b.BaseInitContext.pwd.String())
assert.Equal(t, "file:///", b.pwd.String())
})
t.Run("CompatibilityMode", func(t *testing.T) {
t.Parallel()
Expand Down
70 changes: 70 additions & 0 deletions js/cjsmodule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package js

import (
"fmt"
"net/url"

"github.com/dop251/goja"
"go.k6.io/k6/js/compiler"
"go.k6.io/k6/js/modules"
)

// cjsModule represents a commonJS module
type cjsModule struct {
prg *goja.Program
url *url.URL
}

var _ module = &cjsModule{}

type cjsModuleInstance struct {
mod *cjsModule
moduleObj *goja.Object
vu modules.VU
}

func (c *cjsModule) Instantiate(vu modules.VU) moduleInstance {
return &cjsModuleInstance{vu: vu, mod: c}
}

func (c *cjsModuleInstance) execute() error {
rt := c.vu.Runtime()
exports := rt.NewObject()
c.moduleObj = rt.NewObject()
err := c.moduleObj.Set("exports", exports)
if err != nil {
return fmt.Errorf("error while getting ready to import commonJS, couldn't set exports property of module: %w",
err)
}

// Run the program.
f, err := rt.RunProgram(c.mod.prg)
if err != nil {
return err
}
if call, ok := goja.AssertFunction(f); ok {
if _, err = call(exports, c.moduleObj, exports); err != nil {
return err
}
}

return nil
}

func (c *cjsModuleInstance) exports() *goja.Object {
exportsV := c.moduleObj.Get("exports")
if goja.IsNull(exportsV) || goja.IsUndefined(exportsV) {
return nil
}
return exportsV.ToObject(c.vu.Runtime())
}

type cjsModuleLoader func(specifier *url.URL, name string) (*cjsModule, error)

func cjsmoduleFromString(fileURL *url.URL, data []byte, c *compiler.Compiler) (*cjsModule, error) {
pgm, _, err := c.Compile(string(data), fileURL.String(), false)
if err != nil {
return nil, err
}
return &cjsModule{prg: pgm, url: fileURL}, nil
}
93 changes: 93 additions & 0 deletions js/gomodule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package js

import (
"github.com/dop251/goja"
"go.k6.io/k6/js/modules"
)

// baseGoModule is a go module that does not implement modules.Module interface
// TODO maybe depracate those in the future
type baseGoModule struct {
mod interface{}
}

var _ module = &baseGoModule{}

func (b *baseGoModule) Instantiate(vu modules.VU) moduleInstance {
return &baseGoModuleInstance{mod: b.mod, vu: vu}
}

type baseGoModuleInstance struct {
mod interface{}
vu modules.VU
exportsO *goja.Object // this is so we only initialize the exports once per instance
na-- marked this conversation as resolved.
Show resolved Hide resolved
}

func (b *baseGoModuleInstance) execute() error {
return nil
}

func (b *baseGoModuleInstance) exports() *goja.Object {
if b.exportsO == nil {
// TODO check this does not panic a lot
rt := b.vu.Runtime()
b.exportsO = rt.ToValue(b.mod).ToObject(rt)
}
return b.exportsO
}

// goModule is a go module which implements modules.Module
type goModule struct {
modules.Module
}

var _ module = &goModule{}

func (g *goModule) Instantiate(vu modules.VU) moduleInstance {
return &goModuleInstance{vu: vu, module: g}
}

type goModuleInstance struct {
modules.Instance
module *goModule
vu modules.VU
exportsO *goja.Object // this is so we only initialize the exports once per instance
}

var _ moduleInstance = &goModuleInstance{}

func (gi *goModuleInstance) execute() error {
gi.Instance = gi.module.NewModuleInstance(gi.vu)
return nil
}

func (gi *goModuleInstance) exports() *goja.Object {
if gi.exportsO == nil {
rt := gi.vu.Runtime()
gi.exportsO = rt.ToValue(toESModuleExports(gi.Instance.Exports())).ToObject(rt)
}
return gi.exportsO
}

func toESModuleExports(exp modules.Exports) interface{} {
if exp.Named == nil {
return exp.Default
}
if exp.Default == nil {
return exp.Named
}

result := make(map[string]interface{}, len(exp.Named)+2)

for k, v := range exp.Named {
result[k] = v
}
// Maybe check that those weren't set
result["default"] = exp.Default
// this so babel works with the `default` when it transpiles from ESM to commonjs.
// This should probably be removed once we have support for ESM directly. So that require doesn't get support for
// that while ESM has.
result["__esModule"] = true

return result
}
Loading