github.com/dop251/goja_nodejs@v0.0.0-20240418154818-2aae10d4cbcf/require/resolve.go (about) 1 package require 2 3 import ( 4 "encoding/json" 5 "errors" 6 "path" 7 "path/filepath" 8 "runtime" 9 "strings" 10 11 js "github.com/dop251/goja" 12 ) 13 14 const NodePrefix = "node:" 15 16 // NodeJS module search algorithm described by 17 // https://nodejs.org/api/modules.html#modules_all_together 18 func (r *RequireModule) resolve(modpath string) (module *js.Object, err error) { 19 origPath, modpath := modpath, filepathClean(modpath) 20 if modpath == "" { 21 return nil, IllegalModuleNameError 22 } 23 24 var start string 25 err = nil 26 if path.IsAbs(origPath) { 27 start = "/" 28 } else { 29 start = r.getCurrentModulePath() 30 } 31 32 p := path.Join(start, modpath) 33 if isFileOrDirectoryPath(origPath) { 34 if module = r.modules[p]; module != nil { 35 return 36 } 37 module, err = r.loadAsFileOrDirectory(p) 38 if err == nil && module != nil { 39 r.modules[p] = module 40 } 41 } else { 42 module, err = r.loadNative(origPath) 43 if err == nil { 44 return 45 } else { 46 if err == InvalidModuleError { 47 err = nil 48 } else { 49 return 50 } 51 } 52 if module = r.nodeModules[p]; module != nil { 53 return 54 } 55 module, err = r.loadNodeModules(modpath, start) 56 if err == nil && module != nil { 57 r.nodeModules[p] = module 58 } 59 } 60 61 if module == nil && err == nil { 62 err = InvalidModuleError 63 } 64 return 65 } 66 67 func (r *RequireModule) loadNative(path string) (*js.Object, error) { 68 module := r.modules[path] 69 if module != nil { 70 return module, nil 71 } 72 73 var ldr ModuleLoader 74 if ldr = r.r.native[path]; ldr == nil { 75 ldr = native[path] 76 } 77 78 var isBuiltIn, withPrefix bool 79 if ldr == nil { 80 ldr = builtin[path] 81 if ldr == nil && strings.HasPrefix(path, NodePrefix) { 82 ldr = builtin[path[len(NodePrefix):]] 83 if ldr == nil { 84 return nil, NoSuchBuiltInModuleError 85 } 86 withPrefix = true 87 } 88 isBuiltIn = true 89 } 90 91 if ldr != nil { 92 module = r.createModuleObject() 93 r.modules[path] = module 94 if isBuiltIn { 95 if withPrefix { 96 r.modules[path[len(NodePrefix):]] = module 97 } else { 98 if !strings.HasPrefix(path, NodePrefix) { 99 r.modules[NodePrefix+path] = module 100 } 101 } 102 } 103 ldr(r.runtime, module) 104 return module, nil 105 } 106 107 return nil, InvalidModuleError 108 } 109 110 func (r *RequireModule) loadAsFileOrDirectory(path string) (module *js.Object, err error) { 111 if module, err = r.loadAsFile(path); module != nil || err != nil { 112 return 113 } 114 115 return r.loadAsDirectory(path) 116 } 117 118 func (r *RequireModule) loadAsFile(path string) (module *js.Object, err error) { 119 if module, err = r.loadModule(path); module != nil || err != nil { 120 return 121 } 122 123 p := path + ".js" 124 if module, err = r.loadModule(p); module != nil || err != nil { 125 return 126 } 127 128 p = path + ".json" 129 return r.loadModule(p) 130 } 131 132 func (r *RequireModule) loadIndex(modpath string) (module *js.Object, err error) { 133 p := path.Join(modpath, "index.js") 134 if module, err = r.loadModule(p); module != nil || err != nil { 135 return 136 } 137 138 p = path.Join(modpath, "index.json") 139 return r.loadModule(p) 140 } 141 142 func (r *RequireModule) loadAsDirectory(modpath string) (module *js.Object, err error) { 143 p := path.Join(modpath, "package.json") 144 buf, err := r.r.getSource(p) 145 if err != nil { 146 return r.loadIndex(modpath) 147 } 148 var pkg struct { 149 Main string 150 } 151 err = json.Unmarshal(buf, &pkg) 152 if err != nil || len(pkg.Main) == 0 { 153 return r.loadIndex(modpath) 154 } 155 156 m := path.Join(modpath, pkg.Main) 157 if module, err = r.loadAsFile(m); module != nil || err != nil { 158 return 159 } 160 161 return r.loadIndex(m) 162 } 163 164 func (r *RequireModule) loadNodeModule(modpath, start string) (*js.Object, error) { 165 return r.loadAsFileOrDirectory(path.Join(start, modpath)) 166 } 167 168 func (r *RequireModule) loadNodeModules(modpath, start string) (module *js.Object, err error) { 169 for _, dir := range r.r.globalFolders { 170 if module, err = r.loadNodeModule(modpath, dir); module != nil || err != nil { 171 return 172 } 173 } 174 for { 175 var p string 176 if path.Base(start) != "node_modules" { 177 p = path.Join(start, "node_modules") 178 } else { 179 p = start 180 } 181 if module, err = r.loadNodeModule(modpath, p); module != nil || err != nil { 182 return 183 } 184 if start == ".." { // Dir('..') is '.' 185 break 186 } 187 parent := path.Dir(start) 188 if parent == start { 189 break 190 } 191 start = parent 192 } 193 194 return 195 } 196 197 func (r *RequireModule) getCurrentModulePath() string { 198 var buf [2]js.StackFrame 199 frames := r.runtime.CaptureCallStack(2, buf[:0]) 200 if len(frames) < 2 { 201 return "." 202 } 203 return path.Dir(frames[1].SrcName()) 204 } 205 206 func (r *RequireModule) createModuleObject() *js.Object { 207 module := r.runtime.NewObject() 208 module.Set("exports", r.runtime.NewObject()) 209 return module 210 } 211 212 func (r *RequireModule) loadModule(path string) (*js.Object, error) { 213 module := r.modules[path] 214 if module == nil { 215 module = r.createModuleObject() 216 r.modules[path] = module 217 err := r.loadModuleFile(path, module) 218 if err != nil { 219 module = nil 220 delete(r.modules, path) 221 if errors.Is(err, ModuleFileDoesNotExistError) { 222 err = nil 223 } 224 } 225 return module, err 226 } 227 return module, nil 228 } 229 230 func (r *RequireModule) loadModuleFile(path string, jsModule *js.Object) error { 231 232 prg, err := r.r.getCompiledSource(path) 233 234 if err != nil { 235 return err 236 } 237 238 f, err := r.runtime.RunProgram(prg) 239 if err != nil { 240 return err 241 } 242 243 if call, ok := js.AssertFunction(f); ok { 244 jsExports := jsModule.Get("exports") 245 jsRequire := r.runtime.Get("require") 246 247 // Run the module source, with "jsExports" as "this", 248 // "jsExports" as the "exports" variable, "jsRequire" 249 // as the "require" variable and "jsModule" as the 250 // "module" variable (Nodejs capable). 251 _, err = call(jsExports, jsExports, jsRequire, jsModule) 252 if err != nil { 253 return err 254 } 255 } else { 256 return InvalidModuleError 257 } 258 259 return nil 260 } 261 262 func isFileOrDirectoryPath(path string) bool { 263 result := path == "." || path == ".." || 264 strings.HasPrefix(path, "/") || 265 strings.HasPrefix(path, "./") || 266 strings.HasPrefix(path, "../") 267 268 if runtime.GOOS == "windows" { 269 result = result || 270 strings.HasPrefix(path, `.\`) || 271 strings.HasPrefix(path, `..\`) || 272 filepath.IsAbs(path) 273 } 274 275 return result 276 }