Skip to content

Commit

Permalink
Merge af21b6f into 93649df
Browse files Browse the repository at this point in the history
  • Loading branch information
mstoykov authored May 31, 2024
2 parents 93649df + af21b6f commit 2961d5a
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 230 deletions.
46 changes: 37 additions & 9 deletions js/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (*goja.Object, e
FileSystems: b.filesystems,
CWD: b.pwd,
}
vuImpl.initEnv = initenv

modSys := modules.NewModuleSystem(b.ModuleResolver, vuImpl)
b.setInitGlobals(rt, vuImpl, modSys)
Expand Down Expand Up @@ -369,17 +370,40 @@ func (b *Bundle) setupJSRuntime(rt *goja.Runtime, vuID int64, logger logrus.Fiel
return nil
}

// this exists only to make the check in the init context.
// this exists to support `open` current behaviour and to check init context conformity
type requireImpl struct {
inInitContext func() bool
internal *modules.LegacyRequireImpl
vu *moduleVUImpl
modSys *modules.ModuleSystem
}

func (r *requireImpl) require(specifier string) (*goja.Object, error) {
if !r.inInitContext() {
if r.vu.state != nil {
return nil, fmt.Errorf(cantBeUsedOutsideInitContextMsg, "require")
}
return r.internal.Require(specifier)
return r.modSys.Require(specifier)
}

// getPreviousRequiringFile is a helper that is currently need for the implemnetation of `open`.
// it depends on the `require` method above
func (r *requireImpl) getPreviousRequiringFile() (*url.URL, error) {
var buf [1000]goja.StackFrame

frames := r.vu.Runtime().CaptureCallStack(1000, buf[:0])

for i, frame := range frames[1:] { // first one should be the current require
// TODO have this precalculated automatically
if frame.FuncName() == "go.k6.io/k6/js.(*requireImpl).require-fm" {
// we need to get the one *before* but as we skip the first one the index matches ;)
return url.Parse(frames[i].SrcName())
}
}
// hopefully nobody is calling `require` with 1000 big stack :crossedfingers:
if len(frames) == 1000 {
return nil, errors.New("stack too big")
}

// fallback
return url.Parse(frames[len(frames)-1].SrcName())
}

func (b *Bundle) setInitGlobals(rt *goja.Runtime, vu *moduleVUImpl, modSys *modules.ModuleSystem) {
Expand All @@ -390,8 +414,8 @@ func (b *Bundle) setInitGlobals(rt *goja.Runtime, vu *moduleVUImpl, modSys *modu
}

impl := requireImpl{
inInitContext: func() bool { return vu.state == nil },
internal: modules.NewLegacyRequireImpl(vu, modSys, *b.pwd),
vu: vu,
modSys: modSys,
}

mustSet("require", impl.require)
Expand All @@ -406,8 +430,12 @@ func (b *Bundle) setInitGlobals(rt *goja.Runtime, vu *moduleVUImpl, modSys *modu
return nil, errors.New("open() can't be used with an empty filename")
}
// This uses the pwd from the requireImpl
pwd := impl.internal.CurrentlyRequiredModule()
return openImpl(rt, b.filesystems["file"], &pwd, filename, args...)
requiringFile, err := impl.getPreviousRequiringFile()
if err != nil {
return nil, err // TODO:wrap
}
pwd := loader.Dir(requiringFile)
return openImpl(rt, b.filesystems["file"], pwd, filename, args...)
})
}

Expand Down
154 changes: 0 additions & 154 deletions js/modules/require_impl.go

This file was deleted.

46 changes: 40 additions & 6 deletions js/modules/resolution.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package modules

import (
"errors"
"fmt"
"net/url"
"strings"
Expand Down Expand Up @@ -150,6 +151,7 @@ type ModuleSystem struct {
vu VU
instanceCache map[module]moduleInstance
resolver *ModuleResolver
backupCWD *url.URL
}

// NewModuleSystem returns a new ModuleSystem for the provide VU using the provided resoluter
Expand All @@ -158,28 +160,59 @@ func NewModuleSystem(resolver *ModuleResolver, vu VU) *ModuleSystem {
resolver: resolver,
instanceCache: make(map[module]moduleInstance),
vu: vu,
backupCWD: vu.InitEnv().CWD,
}
}

// Require is called when a module/file needs to be loaded by a script
func (ms *ModuleSystem) Require(pwd *url.URL, arg string) (*goja.Object, error) {
mod, err := ms.resolver.resolve(pwd, arg)
func (ms *ModuleSystem) Require(specifier string) (*goja.Object, error) {
if specifier == "" {
return nil, errors.New("require() can't be used with an empty specifier")
}

currentModuleURL, err := ms.getCurrentModuleScript()
if err != nil {
return nil, err
}

dir := loader.Dir(currentModuleURL)
if currentModuleURL.String() == "file://-" {
dir = ms.backupCWD
}

mod, err := ms.resolver.resolve(dir, specifier)
if err != nil {
return nil, err
}
return ms.instantiate(mod)
}

// Require is called when a module/file needs to be loaded by a script
func (ms *ModuleSystem) instantiate(mod module) (*goja.Object, error) {
if instance, ok := ms.instanceCache[mod]; ok {
return instance.exports(), nil
}

instance := mod.instantiate(ms.vu)
ms.instanceCache[mod] = instance
if err = instance.execute(); err != nil {
if err := instance.execute(); err != nil {
return nil, err
}

return instance.exports(), nil
}

func (ms *ModuleSystem) getCurrentModuleScript() (*url.URL, error) {
var parent string
var buf [2]goja.StackFrame
frames := ms.vu.Runtime().CaptureCallStack(2, buf[:0])
if len(frames) == 0 {
return &url.URL{Scheme: "file", Path: "/-"}, nil
}
parent = frames[1].SrcName()
return url.Parse(parent)
}

// RunSourceData runs the provided sourceData and adds it to the cache.
// If a module with the same specifier as the source is already cached
// it will be used instead of reevaluating the source from the provided SourceData.
Expand All @@ -189,16 +222,17 @@ func (ms *ModuleSystem) Require(pwd *url.URL, arg string) (*goja.Object, error)
func (ms *ModuleSystem) RunSourceData(source *loader.SourceData) (goja.Value, error) {
specifier := source.URL.String()
pwd := source.URL.JoinPath("../")
if _, err := ms.resolver.resolveLoaded(pwd, specifier, source.Data); err != nil {
mod, err := ms.resolver.resolveLoaded(pwd, specifier, source.Data)
if err != nil {
return nil, err // TODO wrap as this should never happen
}
return ms.Require(pwd, specifier)
return ms.instantiate(mod)
}

// ExportGloballyModule sets all exports of the provided module name on the globalThis.
// effectively making them globally available
func ExportGloballyModule(rt *goja.Runtime, modSys *ModuleSystem, moduleName string) {
t, _ := modSys.Require(nil, moduleName)
t, _ := modSys.Require(moduleName)

for _, key := range t.Keys() {
if err := rt.Set(key, t.Get(key)); err != nil {
Expand Down
4 changes: 1 addition & 3 deletions js/modulestest/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package modulestest

import (
"context"
"net/url"
"testing"

"github.com/dop251/goja"
Expand Down Expand Up @@ -113,7 +112,6 @@ func (r *Runtime) RunOnEventLoop(code string) (value goja.Value, err error) {

func (r *Runtime) innerSetupModuleSystem() error {
ms := modules.NewModuleSystem(r.mr, r.VU)
impl := modules.NewLegacyRequireImpl(r.VU, ms, url.URL{})
modules.ExportGloballyModule(r.VU.RuntimeField, ms, "k6/timers")
return r.VU.RuntimeField.Set("require", impl.Require)
return r.VU.RuntimeField.Set("require", ms.Require)
}
Loading

0 comments on commit 2961d5a

Please sign in to comment.