diff --git a/js/bundle.go b/js/bundle.go index fad8fd2595d..3fe036b3a43 100644 --- a/js/bundle.go +++ b/js/bundle.go @@ -283,6 +283,7 @@ func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (*goja.Object, e modSys := modules.NewModuleSystem(b.ModuleResolver, vuImpl) b.setInitGlobals(rt, vuImpl, modSys) + modules.ExportGloballyModule(rt, modSys, "k6/timers") vuImpl.initEnv = initenv defer func() { vuImpl.initEnv = nil diff --git a/js/bundle_test.go b/js/bundle_test.go index e25ad76a8ac..3ccd607892a 100644 --- a/js/bundle_test.go +++ b/js/bundle_test.go @@ -925,3 +925,40 @@ func TestBundleMakeArchive(t *testing.T) { }) } } + +func TestGlobalTimers(t *testing.T) { + t.Parallel() + data := ` + import timers from "k6/timers"; + if (setTimeout != timers.setTimeout) { + throw "setTimeout doesn't match"; + } + if (clearTimeout != timers.clearTimeout) { + throw "clearTimeout doesn't match"; + } + if (setInterval != timers.setInterval) { + throw "setInterval doesn't match"; + } + if (clearInterval != timers.clearInterval) { + throw "clearInterval doesn't match"; + } + export default function() {} + ` + + b1, err := getSimpleBundle(t, "/script.js", data) + require.NoError(t, err) + logger := testutils.NewLogger(t) + + b2, err := NewBundleFromArchive(getTestPreInitState(t, logger, nil), b1.makeArchive()) + require.NoError(t, err) + + bundles := map[string]*Bundle{"Source": b1, "Archive": b2} + for name, b := range bundles { + b := b + t.Run(name, func(t *testing.T) { + t.Parallel() + _, err := b.Instantiate(context.Background(), 1) + require.NoError(t, err) + }) + } +} diff --git a/js/modules/k6/timers/timers.go b/js/modules/k6/timers/timers.go index 01c438a2d5e..fc4b0bae7f0 100644 --- a/js/modules/k6/timers/timers.go +++ b/js/modules/k6/timers/timers.go @@ -62,10 +62,12 @@ func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance { func (e *Timers) Exports() modules.Exports { return modules.Exports{ Named: map[string]interface{}{ - "setTimeout": e.setTimeout, - "clearTimeout": e.clearTimeout, - "setInterval": e.setInterval, - "clearInterval": e.clearInterval, + // TODO the usage of `ToValue` here is so that goja doesn't do it automatically later + // which will effectively create new instance each time it is accessed. + "setTimeout": e.vu.Runtime().ToValue(e.setTimeout), + "clearTimeout": e.vu.Runtime().ToValue(e.clearTimeout), + "setInterval": e.vu.Runtime().ToValue(e.setInterval), + "clearInterval": e.vu.Runtime().ToValue(e.clearInterval), }, } } diff --git a/js/modules/resolution.go b/js/modules/resolution.go index 47118d64398..bc7efa34710 100644 --- a/js/modules/resolution.go +++ b/js/modules/resolution.go @@ -194,3 +194,15 @@ func (ms *ModuleSystem) RunSourceData(source *loader.SourceData) (goja.Value, er } return ms.Require(pwd, specifier) } + +// 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) + + for _, key := range t.Keys() { + if err := rt.Set(key, t.Get(key)); err != nil { + panic(fmt.Errorf("failed to set '%s' global object: %w", key, err)) + } + } +}