Skip to content

Commit

Permalink
Allow core modules to be loaded both with and without the 'node:' pre…
Browse files Browse the repository at this point in the history
…fix.
  • Loading branch information
dop251 committed Jul 27, 2023
1 parent 804a845 commit 0788764
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 17 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v3
- name: Check formatting
run: diff -u <(echo -n) <(gofmt -d .)
if: runner.os != 'Windows'
- name: Run go vet
env:
GOARCH: ${{ matrix.arch }}
run: go vet ./...
- name: Run tests
env:
GOARCH: ${{ matrix.arch }}
run: go test ./...
run: go test -vet=off ./...
4 changes: 2 additions & 2 deletions buffer/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"golang.org/x/text/encoding/unicode"
)

const ModuleName = "node:buffer"
const ModuleName = "buffer"

type Buffer struct {
r *goja.Runtime
Expand Down Expand Up @@ -442,5 +442,5 @@ func Require(runtime *goja.Runtime, module *goja.Object) {
}

func init() {
require.RegisterNativeModule(ModuleName, Require)
require.RegisterCoreModule(ModuleName, Require)
}
4 changes: 2 additions & 2 deletions console/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/dop251/goja_nodejs/util"
)

const ModuleName = "node:console"
const ModuleName = "console"

type Console struct {
runtime *goja.Runtime
Expand Down Expand Up @@ -78,5 +78,5 @@ func Enable(runtime *goja.Runtime) {
}

func init() {
require.RegisterNativeModule(ModuleName, Require)
require.RegisterCoreModule(ModuleName, Require)
}
6 changes: 4 additions & 2 deletions process/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/dop251/goja_nodejs/require"
)

const ModuleName = "process"

type Process struct {
env map[string]string
}
Expand All @@ -27,9 +29,9 @@ func Require(runtime *goja.Runtime, module *goja.Object) {
}

func Enable(runtime *goja.Runtime) {
runtime.Set("process", require.Require(runtime, "process"))
runtime.Set("process", require.Require(runtime, ModuleName))
}

func init() {
require.RegisterNativeModule("process", Require)
require.RegisterCoreModule(ModuleName, Require)
}
28 changes: 24 additions & 4 deletions require/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ type ModuleLoader func(*js.Runtime, *js.Object)
type SourceLoader func(path string) ([]byte, error)

var (
InvalidModuleError = errors.New("Invalid module")
IllegalModuleNameError = errors.New("Illegal module name")

InvalidModuleError = errors.New("Invalid module")
IllegalModuleNameError = errors.New("Illegal module name")
NoSuchBuiltInModuleError = errors.New("No such built-in module")
ModuleFileDoesNotExistError = errors.New("module file does not exist")
)

var native map[string]ModuleLoader
var native, builtin map[string]ModuleLoader

// Registry contains a cache of compiled modules which can be used by multiple Runtimes
type Registry struct {
Expand Down Expand Up @@ -217,10 +217,30 @@ func Require(runtime *js.Runtime, name string) js.Value {
panic(runtime.NewTypeError("Please enable require for this runtime using new(require.Registry).Enable(runtime)"))
}

// RegisterNativeModule registers a module that isn't loaded through a SourceLoader, but rather through
// a provided ModuleLoader. Typically, this will be a module implemented in Go (although theoretically
// it can be anything, depending on the ModuleLoader implementation).
// Such modules take precedence over modules loaded through a SourceLoader, i.e. if a module name resolves as
// native, the native module is loaded, and the SourceLoader is not consulted.
// The binding is global and affects all instances of Registry.
// It should be called from a package init() function as it may not be used concurrently with require() calls.
// For registry-specific bindings see Registry.RegisterNativeModule.
func RegisterNativeModule(name string, loader ModuleLoader) {
if native == nil {
native = make(map[string]ModuleLoader)
}
name = filepathClean(name)
native[name] = loader
}

// RegisterCoreModule registers a nodejs core module. If the name does not start with "node:", the module
// will also be loadable as "node:<name>". Hence, for "builtin" modules (such as buffer, console, etc.)
// the name should not include the "node:" prefix, but for prefix-only core modules (such as "node:test")
// it should include the prefix.
func RegisterCoreModule(name string, loader ModuleLoader) {
if builtin == nil {
builtin = make(map[string]ModuleLoader)
}
name = filepathClean(name)
builtin[name] = loader
}
78 changes: 78 additions & 0 deletions require/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,84 @@ func TestRequireNativeModule(t *testing.T) {
}
}

func TestRegisterCoreModule(t *testing.T) {
vm := js.New()

registry := new(Registry)
registry.Enable(vm)

RegisterCoreModule("coremod", func(runtime *js.Runtime, module *js.Object) {
o := module.Get("exports").(*js.Object)
o.Set("test", func(call js.FunctionCall) js.Value {
return runtime.ToValue("passed")
})
})

RegisterCoreModule("coremod1", func(runtime *js.Runtime, module *js.Object) {
o := module.Get("exports").(*js.Object)
o.Set("test", func(call js.FunctionCall) js.Value {
return runtime.ToValue("passed1")
})
})

RegisterCoreModule("node:test1", func(runtime *js.Runtime, module *js.Object) {
o := module.Get("exports").(*js.Object)
o.Set("test", func(call js.FunctionCall) js.Value {
return runtime.ToValue("test1 passed")
})
})

registry.RegisterNativeModule("bob", func(runtime *js.Runtime, module *js.Object) {

})

_, err := vm.RunString(`
const m1 = require("coremod");
const m2 = require("node:coremod");
if (m1 !== m2) {
throw new Error("Modules are not equal");
}
if (m1.test() !== "passed") {
throw new Error("m1.test() has failed");
}
const m3 = require("node:coremod1");
const m4 = require("coremod1");
if (m3 !== m4) {
throw new Error("Modules are not equal (1)");
}
if (m3.test() !== "passed1") {
throw new Error("m3.test() has failed");
}
try {
require("node:bob");
} catch (e) {
if (!e.message.includes("No such built-in module")) {
throw e;
}
}
require("bob");
try {
require("test1");
throw new Error("Expected exception");
} catch (e) {
if (!e.message.includes("Invalid module")) {
throw e;
}
}
if (require("node:test1").test() !== "test1 passed") {
throw new Error("test1.test() has failed");
}
`)

if err != nil {
t.Fatal(err)
}
}

func TestRequireRegistryNativeModule(t *testing.T) {
const SCRIPT = `
var log = require("test/log");
Expand Down
30 changes: 29 additions & 1 deletion require/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
js "github.com/dop251/goja"
)

const NodePrefix = "node:"

// NodeJS module search algorithm described by
// https://nodejs.org/api/modules.html#modules_all_together
func (r *RequireModule) resolve(modpath string) (module *js.Object, err error) {
Expand Down Expand Up @@ -41,7 +43,11 @@ func (r *RequireModule) resolve(modpath string) (module *js.Object, err error) {
if err == nil {
return
} else {
err = nil
if err == InvalidModuleError {
err = nil
} else {
return
}
}
if module = r.nodeModules[p]; module != nil {
return
Expand Down Expand Up @@ -69,9 +75,31 @@ func (r *RequireModule) loadNative(path string) (*js.Object, error) {
ldr = native[path]
}

var isBuiltIn, withPrefix bool
if ldr == nil {
ldr = builtin[path]
if ldr == nil && strings.HasPrefix(path, NodePrefix) {
ldr = builtin[path[len(NodePrefix):]]
if ldr == nil {
return nil, NoSuchBuiltInModuleError
}
withPrefix = true
}
isBuiltIn = true
}

if ldr != nil {
module = r.createModuleObject()
r.modules[path] = module
if isBuiltIn {
if withPrefix {
r.modules[path[len(NodePrefix):]] = module
} else {
if !strings.HasPrefix(path, NodePrefix) {
r.modules[NodePrefix+path] = module
}
}
}
ldr(r.runtime, module)
return module, nil
}
Expand Down
4 changes: 2 additions & 2 deletions url/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/dop251/goja_nodejs/require"
)

const ModuleName = "node:url"
const ModuleName = "url"

var (
reflectTypeURL = reflect.TypeOf((*url.URL)(nil))
Expand Down Expand Up @@ -360,5 +360,5 @@ func Enable(runtime *goja.Runtime) {
}

func init() {
require.RegisterNativeModule(ModuleName, Require)
require.RegisterCoreModule(ModuleName, Require)
}
4 changes: 2 additions & 2 deletions util/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/dop251/goja_nodejs/require"
)

const ModuleName = "node:util"
const ModuleName = "util"

type Util struct {
runtime *goja.Runtime
Expand Down Expand Up @@ -100,5 +100,5 @@ func New(runtime *goja.Runtime) *Util {
}

func init() {
require.RegisterNativeModule(ModuleName, Require)
require.RegisterCoreModule(ModuleName, Require)
}

1 comment on commit 0788764

@gbl08ma
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't Registry also have a RegisterCoreModule function? 😕

Please sign in to comment.