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  }