diff --git a/require/module.go b/require/module.go index b9fb9b8..bc249d1 100644 --- a/require/module.go +++ b/require/module.go @@ -10,6 +10,7 @@ import ( "text/template" js "github.com/dop251/goja" + "github.com/dop251/goja/parser" ) type ModuleLoader func(*js.Runtime, *js.Object) @@ -61,6 +62,10 @@ func NewRegistryWithLoader(srcLoader SourceLoader) *Registry { type Option func(*Registry) +// WithLoader sets a function which will be called by the require() function in order to get a source code for a +// module at the given path. The same function will be used to get external source maps. +// Note, this only affects the modules loaded by the require() function. If you need to use it as a source map +// loader for code parsed in a different way (such as runtime.RunString() or eval()), use (*Runtime).SetParserOptions() func WithLoader(srcLoader SourceLoader) Option { return func(r *Registry) { r.srcLoader = srcLoader @@ -123,32 +128,37 @@ func (r *Registry) getSource(p string) ([]byte, error) { return srcLoader(p) } -func (r *Registry) getCompiledSource(p string) (prg *js.Program, err error) { +func (r *Registry) getCompiledSource(p string) (*js.Program, error) { r.Lock() defer r.Unlock() - prg = r.compiled[p] + prg := r.compiled[p] if prg == nil { - if buf, err1 := r.getSource(p); err1 == nil { - s := string(buf) + buf, err := r.getSource(p) + if err != nil { + return nil, err + } + s := string(buf) - if filepath.Ext(p) == ".json" { - s = "module.exports = JSON.parse('" + template.JSEscapeString(s) + "')" - } + if filepath.Ext(p) == ".json" { + s = "module.exports = JSON.parse('" + template.JSEscapeString(s) + "')" + } - source := "(function(exports, require, module) {" + s + "\n})" - prg, err = js.Compile(p, source, false) - if err == nil { - if r.compiled == nil { - r.compiled = make(map[string]*js.Program) - } - r.compiled[p] = prg + source := "(function(exports, require, module) {" + s + "\n})" + parsed, err := js.Parse(p, source, parser.WithSourceMapLoader(r.srcLoader)) + if err != nil { + return nil, err + } + prg, err = js.CompileAST(parsed, false) + if err == nil { + if r.compiled == nil { + r.compiled = make(map[string]*js.Program) } - } else { - err = err1 + r.compiled[p] = prg } + return prg, err } - return + return prg, nil } func (r *RequireModule) require(call js.FunctionCall) js.Value { diff --git a/require/module_test.go b/require/module_test.go index 80d4068..140615c 100644 --- a/require/module_test.go +++ b/require/module_test.go @@ -335,3 +335,31 @@ func TestErrorPropagation(t *testing.T) { t.Fatal(err) } } + +func TestSourceMapLoader(t *testing.T) { + vm := js.New() + r := NewRegistry(WithLoader(func(p string) ([]byte, error) { + switch p { + case "dir/m.js": + return []byte(`throw 'test passed'; +//# sourceMappingURL=m.js.map`), nil + case "dir/m.js.map": + return []byte(`{"version":3,"file":"m.js","sourceRoot":"","sources":["m.ts"],"names":[],"mappings":";AAAA"} +`), nil + } + return nil, ModuleFileDoesNotExistError + })) + + rr := r.Enable(vm) + _, err := rr.Require("./dir/m") + if err == nil { + t.Fatal("Expected an error") + } + if ex, ok := err.(*js.Exception); ok { + if !ex.Value().StrictEquals(vm.ToValue("test passed")) { + t.Fatalf("Unexpected Exception: %v", ex) + } + } else { + t.Fatal(err) + } +}