277 lines
5.8 KiB
Go
277 lines
5.8 KiB
Go
package require
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"path"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
js "apigo.cc/ai/ai/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) {
|
|
origPath, modpath := modpath, filepathClean(modpath)
|
|
if modpath == "" {
|
|
return nil, IllegalModuleNameError
|
|
}
|
|
|
|
var start string
|
|
err = nil
|
|
if path.IsAbs(origPath) {
|
|
start = "/"
|
|
} else {
|
|
start = r.getCurrentModulePath()
|
|
}
|
|
|
|
p := path.Join(start, modpath)
|
|
if isFileOrDirectoryPath(origPath) {
|
|
if module = r.modules[p]; module != nil {
|
|
return
|
|
}
|
|
module, err = r.loadAsFileOrDirectory(p)
|
|
if err == nil && module != nil {
|
|
r.modules[p] = module
|
|
}
|
|
} else {
|
|
module, err = r.loadNative(origPath)
|
|
if err == nil {
|
|
return
|
|
} else {
|
|
if err == InvalidModuleError {
|
|
err = nil
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
if module = r.nodeModules[p]; module != nil {
|
|
return
|
|
}
|
|
module, err = r.loadNodeModules(modpath, start)
|
|
if err == nil && module != nil {
|
|
r.nodeModules[p] = module
|
|
}
|
|
}
|
|
|
|
if module == nil && err == nil {
|
|
err = InvalidModuleError
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *RequireModule) loadNative(path string) (*js.Object, error) {
|
|
module := r.modules[path]
|
|
if module != nil {
|
|
return module, nil
|
|
}
|
|
|
|
var ldr ModuleLoader
|
|
if ldr = r.r.native[path]; ldr == nil {
|
|
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
|
|
}
|
|
|
|
return nil, InvalidModuleError
|
|
}
|
|
|
|
func (r *RequireModule) loadAsFileOrDirectory(path string) (module *js.Object, err error) {
|
|
if module, err = r.loadAsFile(path); module != nil || err != nil {
|
|
return
|
|
}
|
|
|
|
return r.loadAsDirectory(path)
|
|
}
|
|
|
|
func (r *RequireModule) loadAsFile(path string) (module *js.Object, err error) {
|
|
if module, err = r.loadModule(path); module != nil || err != nil {
|
|
return
|
|
}
|
|
|
|
p := path + ".js"
|
|
if module, err = r.loadModule(p); module != nil || err != nil {
|
|
return
|
|
}
|
|
|
|
p = path + ".json"
|
|
return r.loadModule(p)
|
|
}
|
|
|
|
func (r *RequireModule) loadIndex(modpath string) (module *js.Object, err error) {
|
|
p := path.Join(modpath, "index.js")
|
|
if module, err = r.loadModule(p); module != nil || err != nil {
|
|
return
|
|
}
|
|
|
|
p = path.Join(modpath, "index.json")
|
|
return r.loadModule(p)
|
|
}
|
|
|
|
func (r *RequireModule) loadAsDirectory(modpath string) (module *js.Object, err error) {
|
|
p := path.Join(modpath, "package.json")
|
|
buf, err := r.r.getSource(p)
|
|
if err != nil {
|
|
return r.loadIndex(modpath)
|
|
}
|
|
var pkg struct {
|
|
Main string
|
|
}
|
|
err = json.Unmarshal(buf, &pkg)
|
|
if err != nil || len(pkg.Main) == 0 {
|
|
return r.loadIndex(modpath)
|
|
}
|
|
|
|
m := path.Join(modpath, pkg.Main)
|
|
if module, err = r.loadAsFile(m); module != nil || err != nil {
|
|
return
|
|
}
|
|
|
|
return r.loadIndex(m)
|
|
}
|
|
|
|
func (r *RequireModule) loadNodeModule(modpath, start string) (*js.Object, error) {
|
|
return r.loadAsFileOrDirectory(path.Join(start, modpath))
|
|
}
|
|
|
|
func (r *RequireModule) loadNodeModules(modpath, start string) (module *js.Object, err error) {
|
|
for _, dir := range r.r.globalFolders {
|
|
if module, err = r.loadNodeModule(modpath, dir); module != nil || err != nil {
|
|
return
|
|
}
|
|
}
|
|
for {
|
|
var p string
|
|
if path.Base(start) != "node_modules" {
|
|
p = path.Join(start, "node_modules")
|
|
} else {
|
|
p = start
|
|
}
|
|
if module, err = r.loadNodeModule(modpath, p); module != nil || err != nil {
|
|
return
|
|
}
|
|
if start == ".." { // Dir('..') is '.'
|
|
break
|
|
}
|
|
parent := path.Dir(start)
|
|
if parent == start {
|
|
break
|
|
}
|
|
start = parent
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (r *RequireModule) getCurrentModulePath() string {
|
|
var buf [2]js.StackFrame
|
|
frames := r.runtime.CaptureCallStack(2, buf[:0])
|
|
if len(frames) < 2 {
|
|
return "."
|
|
}
|
|
return path.Dir(frames[1].SrcName())
|
|
}
|
|
|
|
func (r *RequireModule) createModuleObject() *js.Object {
|
|
module := r.runtime.NewObject()
|
|
module.Set("exports", r.runtime.NewObject())
|
|
return module
|
|
}
|
|
|
|
func (r *RequireModule) loadModule(path string) (*js.Object, error) {
|
|
module := r.modules[path]
|
|
if module == nil {
|
|
module = r.createModuleObject()
|
|
r.modules[path] = module
|
|
err := r.loadModuleFile(path, module)
|
|
if err != nil {
|
|
module = nil
|
|
delete(r.modules, path)
|
|
if errors.Is(err, ModuleFileDoesNotExistError) {
|
|
err = nil
|
|
}
|
|
}
|
|
return module, err
|
|
}
|
|
return module, nil
|
|
}
|
|
|
|
func (r *RequireModule) loadModuleFile(path string, jsModule *js.Object) error {
|
|
|
|
prg, err := r.r.getCompiledSource(path)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := r.runtime.RunProgram(prg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if call, ok := js.AssertFunction(f); ok {
|
|
jsExports := jsModule.Get("exports")
|
|
jsRequire := r.runtime.Get("require")
|
|
|
|
// Run the module source, with "jsExports" as "this",
|
|
// "jsExports" as the "exports" variable, "jsRequire"
|
|
// as the "require" variable and "jsModule" as the
|
|
// "module" variable (Nodejs capable).
|
|
_, err = call(jsExports, jsExports, jsRequire, jsModule)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
return InvalidModuleError
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func isFileOrDirectoryPath(path string) bool {
|
|
result := path == "." || path == ".." ||
|
|
strings.HasPrefix(path, "/") ||
|
|
strings.HasPrefix(path, "./") ||
|
|
strings.HasPrefix(path, "../")
|
|
|
|
if runtime.GOOS == "windows" {
|
|
result = result ||
|
|
strings.HasPrefix(path, `.\`) ||
|
|
strings.HasPrefix(path, `..\`) ||
|
|
filepath.IsAbs(path)
|
|
}
|
|
|
|
return result
|
|
}
|