go.ketch.com/lib/goja_nodejs@v0.0.1/require/module.go (about) 1 package require 2 3 import ( 4 "errors" 5 "os" 6 "path" 7 "path/filepath" 8 "runtime" 9 "sync" 10 "syscall" 11 "text/template" 12 13 js "go.ketch.com/lib/goja" 14 "go.ketch.com/lib/goja/parser" 15 ) 16 17 type ModuleLoader func(*js.Runtime, *js.Object) 18 19 // SourceLoader represents a function that returns a file data at a given path. 20 // The function should return ModuleFileDoesNotExistError if the file either doesn't exist or is a directory. 21 // This error will be ignored by the resolver and the search will continue. Any other errors will be propagated. 22 type SourceLoader func(path string) ([]byte, error) 23 24 var ( 25 InvalidModuleError = errors.New("Invalid module") 26 IllegalModuleNameError = errors.New("Illegal module name") 27 28 ModuleFileDoesNotExistError = errors.New("module file does not exist") 29 ) 30 31 var native map[string]ModuleLoader 32 33 // Registry contains a cache of compiled modules which can be used by multiple Runtimes 34 type Registry struct { 35 sync.Mutex 36 native map[string]ModuleLoader 37 compiled map[string]*js.Program 38 39 srcLoader SourceLoader 40 globalFolders []string 41 } 42 43 type RequireModule struct { 44 r *Registry 45 runtime *js.Runtime 46 modules map[string]*js.Object 47 nodeModules map[string]*js.Object 48 } 49 50 func NewRegistry(opts ...Option) *Registry { 51 r := &Registry{} 52 53 for _, opt := range opts { 54 opt(r) 55 } 56 57 return r 58 } 59 60 func NewRegistryWithLoader(srcLoader SourceLoader) *Registry { 61 return NewRegistry(WithLoader(srcLoader)) 62 } 63 64 type Option func(*Registry) 65 66 // WithLoader sets a function which will be called by the require() function in order to get a source code for a 67 // module at the given path. The same function will be used to get external source maps. 68 // Note, this only affects the modules loaded by the require() function. If you need to use it as a source map 69 // loader for code parsed in a different way (such as runtime.RunString() or eval()), use (*Runtime).SetParserOptions() 70 func WithLoader(srcLoader SourceLoader) Option { 71 return func(r *Registry) { 72 r.srcLoader = srcLoader 73 } 74 } 75 76 // WithGlobalFolders appends the given paths to the registry's list of 77 // global folders to search if the requested module is not found 78 // elsewhere. By default, a registry's global folders list is empty. 79 // In the reference Node.js implementation, the default global folders 80 // list is $NODE_PATH, $HOME/.node_modules, $HOME/.node_libraries and 81 // $PREFIX/lib/node, see 82 // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders. 83 func WithGlobalFolders(globalFolders ...string) Option { 84 return func(r *Registry) { 85 r.globalFolders = globalFolders 86 } 87 } 88 89 // Enable adds the require() function to the specified runtime. 90 func (r *Registry) Enable(runtime *js.Runtime) *RequireModule { 91 rrt := &RequireModule{ 92 r: r, 93 runtime: runtime, 94 modules: make(map[string]*js.Object), 95 nodeModules: make(map[string]*js.Object), 96 } 97 98 runtime.Set("require", rrt.require) 99 return rrt 100 } 101 102 func (r *Registry) RegisterNativeModule(name string, loader ModuleLoader) { 103 r.Lock() 104 defer r.Unlock() 105 106 if r.native == nil { 107 r.native = make(map[string]ModuleLoader) 108 } 109 name = filepathClean(name) 110 r.native[name] = loader 111 } 112 113 // DefaultSourceLoader is used if none was set (see WithLoader()). It simply loads files from the host's filesystem. 114 func DefaultSourceLoader(filename string) ([]byte, error) { 115 fp := filepath.FromSlash(filename) 116 data, err := os.ReadFile(fp) 117 if err != nil { 118 if os.IsNotExist(err) || errors.Is(err, syscall.EISDIR) { 119 err = ModuleFileDoesNotExistError 120 } else if runtime.GOOS == "windows" { 121 if errors.Is(err, syscall.Errno(0x7b)) { // ERROR_INVALID_NAME, The filename, directory name, or volume label syntax is incorrect. 122 err = ModuleFileDoesNotExistError 123 } else { 124 // temporary workaround for https://go.ketch.com/lib/goja_nodejs/issues/21 125 fi, err1 := os.Stat(fp) 126 if err1 == nil && fi.IsDir() { 127 err = ModuleFileDoesNotExistError 128 } 129 } 130 } 131 } 132 return data, err 133 } 134 135 func (r *Registry) getSource(p string) ([]byte, error) { 136 srcLoader := r.srcLoader 137 if srcLoader == nil { 138 srcLoader = DefaultSourceLoader 139 } 140 return srcLoader(p) 141 } 142 143 func (r *Registry) getCompiledSource(p string) (*js.Program, error) { 144 r.Lock() 145 defer r.Unlock() 146 147 prg := r.compiled[p] 148 if prg == nil { 149 buf, err := r.getSource(p) 150 if err != nil { 151 return nil, err 152 } 153 s := string(buf) 154 155 if path.Ext(p) == ".json" { 156 s = "module.exports = JSON.parse('" + template.JSEscapeString(s) + "')" 157 } 158 159 source := "(function(exports, require, module) {" + s + "\n})" 160 parsed, err := js.Parse(p, source, parser.WithSourceMapLoader(r.srcLoader)) 161 if err != nil { 162 return nil, err 163 } 164 prg, err = js.CompileAST(parsed, false) 165 if err == nil { 166 if r.compiled == nil { 167 r.compiled = make(map[string]*js.Program) 168 } 169 r.compiled[p] = prg 170 } 171 return prg, err 172 } 173 return prg, nil 174 } 175 176 func (r *RequireModule) require(call js.FunctionCall) js.Value { 177 ret, err := r.Require(call.Argument(0).String()) 178 if err != nil { 179 if _, ok := err.(*js.Exception); !ok { 180 panic(r.runtime.NewGoError(err)) 181 } 182 panic(err) 183 } 184 return ret 185 } 186 187 func filepathClean(p string) string { 188 return path.Clean(p) 189 } 190 191 // Require can be used to import modules from Go source (similar to JS require() function). 192 func (r *RequireModule) Require(p string) (ret js.Value, err error) { 193 module, err := r.resolve(p) 194 if err != nil { 195 return 196 } 197 ret = module.Get("exports") 198 return 199 } 200 201 func Require(runtime *js.Runtime, name string) js.Value { 202 if r, ok := js.AssertFunction(runtime.Get("require")); ok { 203 mod, err := r(js.Undefined(), runtime.ToValue(name)) 204 if err != nil { 205 panic(err) 206 } 207 return mod 208 } 209 panic(runtime.NewTypeError("Please enable require for this runtime using new(require.Registry).Enable(runtime)")) 210 } 211 212 func RegisterNativeModule(name string, loader ModuleLoader) { 213 if native == nil { 214 native = make(map[string]ModuleLoader) 215 } 216 name = filepathClean(name) 217 native[name] = loader 218 }