From 2753196abee3c39dd633fd178e2406707f138d53 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov <312246+mstoykov@users.noreply.github.com> Date: Wed, 23 Nov 2022 15:35:39 +0200 Subject: [PATCH] js: make certain only one copy of the exports is used per each module (#2773) This is inline with ESM and works for source code modules, but not for modules written in go as the code was never made to work with it. This also lets (as the test suggests) change what `k6/http`.get is across the VU just as it should be possible. --- js/initcontext.go | 20 ++++++++++++++------ js/initcontext_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/js/initcontext.go b/js/initcontext.go index 34c4a65ec5c..cad1e15a339 100644 --- a/js/initcontext.go +++ b/js/initcontext.go @@ -45,13 +45,14 @@ type InitContext struct { pwd *url.URL // Cache of loaded programs and files. - programs map[string]programWithSource + programs map[string]programWithSource + exportsCache map[string]goja.Value compatibilityMode lib.CompatibilityMode logger logrus.FieldLogger - modules map[string]interface{} + moduleRegistry map[string]interface{} } // NewInitContext creates a new initcontext with the provided arguments @@ -66,7 +67,8 @@ func NewInitContext( programs: make(map[string]programWithSource), compatibilityMode: compatMode, logger: logger, - modules: getJSModules(), + moduleRegistry: getJSModules(), + exportsCache: make(map[string]goja.Value), moduleVUImpl: &moduleVUImpl{ ctx: context.Background(), runtime: rt, @@ -92,14 +94,20 @@ func newBoundInitContext(base *InitContext, vuImpl *moduleVUImpl) *InitContext { programs: programs, compatibilityMode: base.compatibilityMode, + exportsCache: make(map[string]goja.Value), logger: base.logger, - modules: base.modules, + moduleRegistry: base.moduleRegistry, moduleVUImpl: vuImpl, } } // Require is called when a module/file needs to be loaded by a script -func (i *InitContext) Require(arg string) goja.Value { +func (i *InitContext) Require(arg string) (export goja.Value) { + var ok bool + if export, ok = i.exportsCache[arg]; ok { + return export + } + defer func() { i.exportsCache[arg] = export }() switch { case arg == "k6", strings.HasPrefix(arg, "k6/"): // Builtin or external modules ("k6", "k6/*", or "k6/x/*") are handled @@ -144,7 +152,7 @@ func toESModuleExports(exp modules.Exports) interface{} { } func (i *InitContext) requireModule(name string) (goja.Value, error) { - mod, ok := i.modules[name] + mod, ok := i.moduleRegistry[name] if !ok { return nil, fmt.Errorf("unknown module: %s", name) } diff --git a/js/initcontext_test.go b/js/initcontext_test.go index b1a4a0023a5..30190db82bd 100644 --- a/js/initcontext_test.go +++ b/js/initcontext_test.go @@ -687,3 +687,34 @@ export default function () { // likely settings in the transpilers require.Equal(t, "cool is cool\n\tat webpack:///./test1.ts:2:4(2)\n\tat r (webpack:///./test1.ts:5:4(3))\n\tat file:///script.js:4:2(4)\n\tat native\n", exception.String()) } + +func TestImportModificationsAreConsistentBetweenFiles(t *testing.T) { + t.Parallel() + logger := testutils.NewLogger(t) + fs := afero.NewMemMapFs() + require.NoError(t, afero.WriteFile(fs, "/notk6.js", []byte(`export default {group}; function group() {}`), 0o644)) + require.NoError(t, afero.WriteFile(fs, "/instrument.js", []byte(` + import k6 from "k6"; + k6.newKey = 5; + k6.group = 3; + + import notk6 from "./notk6.js"; + notk6.group = 3; + notk6.newKey = 5; + `), 0o644)) + + b, err := getSimpleBundle(t, "/script.js", ` + import k6 from "k6"; + import notk6 from "./notk6.js"; + import "./instrument.js"; + if (k6.newKey != 5) { throw "k6.newKey is wrong "+ k6.newKey} + if (k6.group != 3) { throw "k6.group is wrong "+ k6.group} + if (notk6.newKey != 5) { throw "notk6.newKey is wrong "+ notk6.newKey} + if (notk6.group != 3) { throw "notk6.group is wrong "+ notk6.group} + export default () => { throw "this shouldn't be ran" } +`, fs) + require.NoError(t, err, "bundle error") + + _, err = b.Instantiate(logger, 0) + require.NoError(t, err) +}