-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
js: refactor how modules are loaded (#2881)
This refactor tries to simplify the implementation of `require` and connected code by splitting it heavily into different parts. These changes are similar to what will be needed for native ESM support as shown in #2563 , but without any of the native parts and without doing anything that isn't currently needed. This will hopefully make ESM PR much smaller and less intrusive. This includes still keeping the wrong relativity of `require` as explained in #2674. It also tries to simplify connected code, but due to this being very sensitive code and the changes already being quite big, this is done only to an extent. The lack of new tests is mostly due to there not being really any new code and the tests that were created along these changes already being merged months ago with #2782. Future changes will try to address the above as well as potentially moving the whole module types and logic in separate package to be reused in tests.
- Loading branch information
Showing
8 changed files
with
519 additions
and
394 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
||
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 | ||
} |
Oops, something went wrong.