diff --git a/js/initcontext.go b/js/initcontext.go
index 11335e9bd02..93241630519 100644
--- a/js/initcontext.go
+++ b/js/initcontext.go
@@ -34,7 +34,7 @@ import (
"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/js/compiler"
- "github.com/loadimpact/k6/js/modules"
+ "github.com/loadimpact/k6/js/internal/modules"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/loader"
)
@@ -115,8 +115,9 @@ func newBoundInitContext(base *InitContext, ctxPtr *context.Context, rt *goja.Ru
func (i *InitContext) Require(arg string) goja.Value {
switch {
case arg == "k6", strings.HasPrefix(arg, "k6/"):
- // Builtin modules ("k6" or "k6/...") are handled specially, as they don't exist on the
- // filesystem. This intentionally shadows attempts to name your own modules this.
+ // Builtin or external modules ("k6", "k6/*", or "k6/x/*") are handled
+ // specially, as they don't exist on the filesystem. This intentionally
+ // shadows attempts to name your own modules this.
v, err := i.requireModule(arg)
if err != nil {
common.Throw(i.runtime, err)
@@ -133,9 +134,9 @@ func (i *InitContext) Require(arg string) goja.Value {
}
func (i *InitContext) requireModule(name string) (goja.Value, error) {
- mod, ok := modules.Index[name]
- if !ok {
- return nil, errors.Errorf("unknown builtin module: %s", name)
+ mod := modules.Get(name)
+ if mod == nil {
+ return nil, errors.Errorf("unknown module: %s", name)
}
return i.runtime.ToValue(common.Bind(i.runtime, mod, i.ctxPtr)), nil
}
diff --git a/js/initcontext_test.go b/js/initcontext_test.go
index 768aee5e960..e6357c96bcd 100644
--- a/js/initcontext_test.go
+++ b/js/initcontext_test.go
@@ -52,7 +52,7 @@ func TestInitContextRequire(t *testing.T) {
t.Run("Modules", func(t *testing.T) {
t.Run("Nonexistent", func(t *testing.T) {
_, err := getSimpleBundle(t, "/script.js", `import "k6/NONEXISTENT";`)
- assert.Contains(t, err.Error(), "GoError: unknown builtin module: k6/NONEXISTENT")
+ assert.Contains(t, err.Error(), "GoError: unknown module: k6/NONEXISTENT")
})
t.Run("k6", func(t *testing.T) {
diff --git a/js/internal/modules/modules.go b/js/internal/modules/modules.go
new file mode 100644
index 00000000000..058fe459ae0
--- /dev/null
+++ b/js/internal/modules/modules.go
@@ -0,0 +1,52 @@
+/*
+ *
+ * k6 - a next-generation load testing tool
+ * Copyright (C) 2020 Load Impact
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package modules
+
+import (
+ "fmt"
+ "sync"
+)
+
+//nolint:gochecknoglobals
+var (
+ modules = make(map[string]interface{})
+ mx sync.RWMutex
+)
+
+// Get returns the module registered with name.
+func Get(name string) interface{} {
+ mx.RLock()
+ defer mx.RUnlock()
+ return modules[name]
+}
+
+// Register the given mod as a JavaScript module, available
+// for import from JS scripts by name.
+// This function panics if a module with the same name is already registered.
+func Register(name string, mod interface{}) {
+ mx.Lock()
+ defer mx.Unlock()
+
+ if _, ok := modules[name]; ok {
+ panic(fmt.Sprintf("module already registered: %s", name))
+ }
+ modules[name] = mod
+}
diff --git a/js/modules.go b/js/modules.go
new file mode 100644
index 00000000000..28d780b0940
--- /dev/null
+++ b/js/modules.go
@@ -0,0 +1,33 @@
+/*
+ *
+ * k6 - a next-generation load testing tool
+ * Copyright (C) 2020 Load Impact
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package js
+
+import (
+ // Initialize all internal JS modules.
+ _ "github.com/loadimpact/k6/js/modules/k6"
+ _ "github.com/loadimpact/k6/js/modules/k6/crypto"
+ _ "github.com/loadimpact/k6/js/modules/k6/crypto/x509"
+ _ "github.com/loadimpact/k6/js/modules/k6/encoding"
+ _ "github.com/loadimpact/k6/js/modules/k6/grpc"
+ _ "github.com/loadimpact/k6/js/modules/k6/http"
+ _ "github.com/loadimpact/k6/js/modules/k6/metrics"
+ _ "github.com/loadimpact/k6/js/modules/k6/ws"
+)
diff --git a/js/modules/index.go b/js/modules/index.go
deleted file mode 100644
index 981f68b9e22..00000000000
--- a/js/modules/index.go
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- *
- * k6 - a next-generation load testing tool
- * Copyright (C) 2016 Load Impact
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- *
- */
-
-package modules
-
-import (
- "github.com/loadimpact/k6/js/modules/k6"
- "github.com/loadimpact/k6/js/modules/k6/crypto"
- "github.com/loadimpact/k6/js/modules/k6/crypto/x509"
- "github.com/loadimpact/k6/js/modules/k6/encoding"
- "github.com/loadimpact/k6/js/modules/k6/grpc"
- "github.com/loadimpact/k6/js/modules/k6/html"
- "github.com/loadimpact/k6/js/modules/k6/http"
- "github.com/loadimpact/k6/js/modules/k6/metrics"
- "github.com/loadimpact/k6/js/modules/k6/ws"
-)
-
-// Index of module implementations.
-var Index = map[string]interface{}{
- "k6": k6.New(),
- "k6/crypto": crypto.New(),
- "k6/crypto/x509": x509.New(),
- "k6/encoding": encoding.New(),
- "k6/http": http.New(),
- "k6/metrics": metrics.New(),
- "k6/html": html.New(),
- "k6/ws": ws.New(),
- "k6/protocols/grpc": grpc.New(),
-}
diff --git a/js/modules/k6/crypto/crypto.go b/js/modules/k6/crypto/crypto.go
index 354ea16f99d..ae3f8418de8 100644
--- a/js/modules/k6/crypto/crypto.go
+++ b/js/modules/k6/crypto/crypto.go
@@ -37,8 +37,13 @@ import (
"golang.org/x/crypto/ripemd160"
"github.com/loadimpact/k6/js/common"
+ "github.com/loadimpact/k6/js/internal/modules"
)
+func init() {
+ modules.Register("k6/crypto", New())
+}
+
type Crypto struct{}
type Hasher struct {
diff --git a/js/modules/k6/crypto/x509/x509.go b/js/modules/k6/crypto/x509/x509.go
index 28480bd6aa5..cb3201faae0 100644
--- a/js/modules/k6/crypto/x509/x509.go
+++ b/js/modules/k6/crypto/x509/x509.go
@@ -35,8 +35,13 @@ import (
"github.com/pkg/errors"
"github.com/loadimpact/k6/js/common"
+ "github.com/loadimpact/k6/js/internal/modules"
)
+func init() {
+ modules.Register("k6/crypto/x509", New())
+}
+
// X509 certificate functionality
type X509 struct{}
diff --git a/js/modules/k6/encoding/encoding.go b/js/modules/k6/encoding/encoding.go
index a8a417fcc68..2309eef6886 100644
--- a/js/modules/k6/encoding/encoding.go
+++ b/js/modules/k6/encoding/encoding.go
@@ -25,8 +25,13 @@ import (
"encoding/base64"
"github.com/loadimpact/k6/js/common"
+ "github.com/loadimpact/k6/js/internal/modules"
)
+func init() {
+ modules.Register("k6/encoding", New())
+}
+
type Encoding struct{}
func New() *Encoding {
diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go
index 99039fd85ce..bf23679629c 100644
--- a/js/modules/k6/grpc/client_test.go
+++ b/js/modules/k6/grpc/client_test.go
@@ -101,7 +101,7 @@ func TestClient(t *testing.T) {
t.Run("LoadNotFound", func(t *testing.T) {
_, err := common.RunString(rt, `
- client.load([], "./does_not_exist.proto");
+ client.load([], "./does_not_exist.proto");
`)
if !assert.Error(t, err) {
return
@@ -115,7 +115,7 @@ func TestClient(t *testing.T) {
t.Run("Load", func(t *testing.T) {
respV, err := common.RunString(rt, `
- client.load([], "../../../../vendor/google.golang.org/grpc/test/grpc_testing/test.proto");
+ client.load([], "../../../../vendor/google.golang.org/grpc/test/grpc_testing/test.proto");
`)
if !assert.NoError(t, err) {
return
diff --git a/js/modules/k6/grpc/grpc.go b/js/modules/k6/grpc/grpc.go
index 83e41bfaff8..829e2e50e6a 100644
--- a/js/modules/k6/grpc/grpc.go
+++ b/js/modules/k6/grpc/grpc.go
@@ -22,8 +22,14 @@ package grpc
import (
"google.golang.org/grpc/codes"
+
+ "github.com/loadimpact/k6/js/internal/modules"
)
+func init() {
+ modules.Register("k6/protocols/grpc", New())
+}
+
// GRPC represents the gRPC protocol module for k6
type GRPC struct {
StatusOK codes.Code `js:"StatusOK"`
diff --git a/js/modules/k6/html/html.go b/js/modules/k6/html/html.go
index f5efc2e11af..61c597771f2 100644
--- a/js/modules/k6/html/html.go
+++ b/js/modules/k6/html/html.go
@@ -31,8 +31,13 @@ import (
gohtml "golang.org/x/net/html"
"github.com/loadimpact/k6/js/common"
+ "github.com/loadimpact/k6/js/internal/modules"
)
+func init() {
+ modules.Register("k6/html", New())
+}
+
type HTML struct{}
func New() *HTML {
diff --git a/js/modules/k6/http/http.go b/js/modules/k6/http/http.go
index 3b7ea5b17fe..8aa6cdab2dc 100644
--- a/js/modules/k6/http/http.go
+++ b/js/modules/k6/http/http.go
@@ -24,10 +24,15 @@ import (
"context"
"github.com/loadimpact/k6/js/common"
+ "github.com/loadimpact/k6/js/internal/modules"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/lib/netext"
)
+func init() {
+ modules.Register("k6/http", New())
+}
+
const (
HTTP_METHOD_GET = "GET"
HTTP_METHOD_POST = "POST"
diff --git a/js/modules/k6/k6.go b/js/modules/k6/k6.go
index 7bbfddd5e94..32a2cafe58b 100644
--- a/js/modules/k6/k6.go
+++ b/js/modules/k6/k6.go
@@ -30,11 +30,16 @@ import (
"github.com/pkg/errors"
"github.com/loadimpact/k6/js/common"
+ "github.com/loadimpact/k6/js/internal/modules"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/lib/metrics"
"github.com/loadimpact/k6/stats"
)
+func init() {
+ modules.Register("k6", New())
+}
+
type K6 struct{}
// ErrGroupInInitContext is returned when group() are using in the init context
diff --git a/js/modules/k6/metrics/metrics.go b/js/modules/k6/metrics/metrics.go
index 437b9bc0189..9f3d3cc491e 100644
--- a/js/modules/k6/metrics/metrics.go
+++ b/js/modules/k6/metrics/metrics.go
@@ -30,10 +30,15 @@ import (
"github.com/dop251/goja"
"github.com/loadimpact/k6/js/common"
+ "github.com/loadimpact/k6/js/internal/modules"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/stats"
)
+func init() {
+ modules.Register("k6/metrics", New())
+}
+
var nameRegexString = "^[\\p{L}\\p{N}\\._ !\\?/\\(\\)<>%-]{1,128}$"
var compileNameRegex = regexp.MustCompile(nameRegexString)
diff --git a/js/modules/k6/ws/ws.go b/js/modules/k6/ws/ws.go
index 97da421ceaa..09bb9af4d8d 100644
--- a/js/modules/k6/ws/ws.go
+++ b/js/modules/k6/ws/ws.go
@@ -37,11 +37,16 @@ import (
"github.com/gorilla/websocket"
"github.com/loadimpact/k6/js/common"
+ "github.com/loadimpact/k6/js/internal/modules"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/lib/metrics"
"github.com/loadimpact/k6/stats"
)
+func init() {
+ modules.Register("k6/ws", New())
+}
+
// ErrWSInInitContext is returned when websockets are using in the init context
var ErrWSInInitContext = common.NewInitContextError("using websockets in the init context is not supported")
diff --git a/js/modules/modules.go b/js/modules/modules.go
new file mode 100644
index 00000000000..fa7ebdd9fa6
--- /dev/null
+++ b/js/modules/modules.go
@@ -0,0 +1,46 @@
+/*
+ *
+ * k6 - a next-generation load testing tool
+ * Copyright (C) 2020 Load Impact
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package modules
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/loadimpact/k6/js/internal/modules"
+)
+
+const extPrefix string = "k6/x/"
+
+// Get returns the module registered with name.
+func Get(name string) interface{} {
+ return modules.Get(name)
+}
+
+// Register the given mod as an external JavaScript module that can be imported
+// by name. The name must be unique across all registered modules and must be
+// prefixed with "k6/x/", otherwise this function will panic.
+func Register(name string, mod interface{}) {
+ if !strings.HasPrefix(name, extPrefix) {
+ panic(fmt.Errorf("external module names must be prefixed with '%s', tried to register: %s", extPrefix, name))
+ }
+
+ modules.Register(name, mod)
+}