github.com/kosmosJS/engine-node@v0.0.0-20220426040216-d53e2a72192e/require/module.go (about) 1 package require 2 3 import ( 4 "errors" 5 "io/ioutil" 6 "os" 7 "path" 8 "path/filepath" 9 "sync" 10 "syscall" 11 "text/template" 12 13 js "github.com/kosmosJS/engine" 14 "github.com/kosmosJS/engine/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 data, err := ioutil.ReadFile(filepath.FromSlash(filename)) 116 if err != nil { 117 if os.IsNotExist(err) || errors.Is(err, syscall.EISDIR) { 118 err = ModuleFileDoesNotExistError 119 } 120 } 121 return data, err 122 } 123 124 func (r *Registry) getSource(p string) ([]byte, error) { 125 srcLoader := r.srcLoader 126 if srcLoader == nil { 127 srcLoader = DefaultSourceLoader 128 } 129 return srcLoader(p) 130 } 131 132 func (r *Registry) getCompiledSource(p string) (*js.Program, error) { 133 r.Lock() 134 defer r.Unlock() 135 136 prg := r.compiled[p] 137 if prg == nil { 138 buf, err := r.getSource(p) 139 if err != nil { 140 return nil, err 141 } 142 s := string(buf) 143 144 if path.Ext(p) == ".json" { 145 s = "module.exports = JSON.parse('" + template.JSEscapeString(s) + "')" 146 } 147 148 source := "(function(exports, require, module) {" + s + "\n})" 149 parsed, err := js.Parse(p, source, parser.WithSourceMapLoader(r.srcLoader)) 150 if err != nil { 151 return nil, err 152 } 153 prg, err = js.CompileAST(parsed, false) 154 if err == nil { 155 if r.compiled == nil { 156 r.compiled = make(map[string]*js.Program) 157 } 158 r.compiled[p] = prg 159 } 160 return prg, err 161 } 162 return prg, nil 163 } 164 165 func (r *RequireModule) require(call js.FunctionCall) js.Value { 166 ret, err := r.Require(call.Argument(0).String()) 167 if err != nil { 168 if _, ok := err.(*js.Exception); !ok { 169 panic(r.runtime.NewGoError(err)) 170 } 171 panic(err) 172 } 173 return ret 174 } 175 176 func filepathClean(p string) string { 177 return path.Clean(p) 178 } 179 180 // Require can be used to import modules from Go source (similar to JS require() function). 181 func (r *RequireModule) Require(p string) (ret js.Value, err error) { 182 module, err := r.resolve(p) 183 if err != nil { 184 return 185 } 186 ret = module.Get("exports") 187 return 188 } 189 190 func Require(runtime *js.Runtime, name string) js.Value { 191 if r, ok := js.AssertFunction(runtime.Get("require")); ok { 192 mod, err := r(js.Undefined(), runtime.ToValue(name)) 193 if err != nil { 194 panic(err) 195 } 196 return mod 197 } 198 panic(runtime.NewTypeError("Please enable require for this runtime using new(require.Require).Enable(runtime)")) 199 } 200 201 func RegisterNativeModule(name string, loader ModuleLoader) { 202 if native == nil { 203 native = make(map[string]ModuleLoader) 204 } 205 name = filepathClean(name) 206 native[name] = loader 207 }