github.com/iosif02/goja_nodejs@v1.0.1/require/module.go (about)

     1  package require
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"io/fs"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"runtime"
    11  	"sync"
    12  	"syscall"
    13  	"text/template"
    14  
    15  	js "github.com/iosif02/goja"
    16  	"github.com/iosif02/goja/parser"
    17  )
    18  
    19  type ModuleLoader func(*js.Runtime, *js.Object)
    20  
    21  // SourceLoader represents a function that returns a file data at a given path.
    22  // The function should return ModuleFileDoesNotExistError if the file either doesn't exist or is a directory.
    23  // This error will be ignored by the resolver and the search will continue. Any other errors will be propagated.
    24  type SourceLoader func(path string) ([]byte, error)
    25  
    26  var (
    27  	InvalidModuleError          = errors.New("Invalid module")
    28  	IllegalModuleNameError      = errors.New("Illegal module name")
    29  	NoSuchBuiltInModuleError    = errors.New("No such built-in module")
    30  	ModuleFileDoesNotExistError = errors.New("module file does not exist")
    31  )
    32  
    33  var native, builtin map[string]ModuleLoader
    34  
    35  // Registry contains a cache of compiled modules which can be used by multiple Runtimes
    36  type Registry struct {
    37  	sync.Mutex
    38  	native   map[string]ModuleLoader
    39  	compiled map[string]*js.Program
    40  
    41  	srcLoader     SourceLoader
    42  	globalFolders []string
    43  }
    44  
    45  type RequireModule struct {
    46  	r           *Registry
    47  	runtime     *js.Runtime
    48  	modules     map[string]*js.Object
    49  	nodeModules map[string]*js.Object
    50  }
    51  
    52  func NewRegistry(opts ...Option) *Registry {
    53  	r := &Registry{}
    54  
    55  	for _, opt := range opts {
    56  		opt(r)
    57  	}
    58  
    59  	return r
    60  }
    61  
    62  func NewRegistryWithLoader(srcLoader SourceLoader) *Registry {
    63  	return NewRegistry(WithLoader(srcLoader))
    64  }
    65  
    66  type Option func(*Registry)
    67  
    68  // WithLoader sets a function which will be called by the require() function in order to get a source code for a
    69  // module at the given path. The same function will be used to get external source maps.
    70  // Note, this only affects the modules loaded by the require() function. If you need to use it as a source map
    71  // loader for code parsed in a different way (such as runtime.RunString() or eval()), use (*Runtime).SetParserOptions()
    72  func WithLoader(srcLoader SourceLoader) Option {
    73  	return func(r *Registry) {
    74  		r.srcLoader = srcLoader
    75  	}
    76  }
    77  
    78  // WithGlobalFolders appends the given paths to the registry's list of
    79  // global folders to search if the requested module is not found
    80  // elsewhere.  By default, a registry's global folders list is empty.
    81  // In the reference Node.js implementation, the default global folders
    82  // list is $NODE_PATH, $HOME/.node_modules, $HOME/.node_libraries and
    83  // $PREFIX/lib/node, see
    84  // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders.
    85  func WithGlobalFolders(globalFolders ...string) Option {
    86  	return func(r *Registry) {
    87  		r.globalFolders = globalFolders
    88  	}
    89  }
    90  
    91  // Enable adds the require() function to the specified runtime.
    92  func (r *Registry) Enable(runtime *js.Runtime) *RequireModule {
    93  	rrt := &RequireModule{
    94  		r:           r,
    95  		runtime:     runtime,
    96  		modules:     make(map[string]*js.Object),
    97  		nodeModules: make(map[string]*js.Object),
    98  	}
    99  
   100  	runtime.Set("require", rrt.require)
   101  	return rrt
   102  }
   103  
   104  func (r *Registry) RegisterNativeModule(name string, loader ModuleLoader) {
   105  	r.Lock()
   106  	defer r.Unlock()
   107  
   108  	if r.native == nil {
   109  		r.native = make(map[string]ModuleLoader)
   110  	}
   111  	name = filepathClean(name)
   112  	r.native[name] = loader
   113  }
   114  
   115  // DefaultSourceLoader is used if none was set (see WithLoader()). It simply loads files from the host's filesystem.
   116  func DefaultSourceLoader(filename string) ([]byte, error) {
   117  	fp := filepath.FromSlash(filename)
   118  	f, err := os.Open(fp)
   119  	if err != nil {
   120  		if errors.Is(err, fs.ErrNotExist) {
   121  			err = ModuleFileDoesNotExistError
   122  		} else if runtime.GOOS == "windows" {
   123  			if errors.Is(err, syscall.Errno(0x7b)) { // ERROR_INVALID_NAME, The filename, directory name, or volume label syntax is incorrect.
   124  				err = ModuleFileDoesNotExistError
   125  			}
   126  		}
   127  		return nil, err
   128  	}
   129  
   130  	defer f.Close()
   131  	// On some systems (e.g. plan9 and FreeBSD) it is possible to use the standard read() call on directories
   132  	// which means we cannot rely on read() returning an error, we have to do stat() instead.
   133  	if fi, err := f.Stat(); err == nil {
   134  		if fi.IsDir() {
   135  			return nil, ModuleFileDoesNotExistError
   136  		}
   137  	} else {
   138  		return nil, err
   139  	}
   140  	return io.ReadAll(f)
   141  }
   142  
   143  func (r *Registry) getSource(p string) ([]byte, error) {
   144  	srcLoader := r.srcLoader
   145  	if srcLoader == nil {
   146  		srcLoader = DefaultSourceLoader
   147  	}
   148  	return srcLoader(p)
   149  }
   150  
   151  func (r *Registry) getCompiledSource(p string) (*js.Program, error) {
   152  	r.Lock()
   153  	defer r.Unlock()
   154  
   155  	prg := r.compiled[p]
   156  	if prg == nil {
   157  		buf, err := r.getSource(p)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  		s := string(buf)
   162  
   163  		if path.Ext(p) == ".json" {
   164  			s = "module.exports = JSON.parse('" + template.JSEscapeString(s) + "')"
   165  		}
   166  
   167  		source := "(function(exports, require, module) {" + s + "\n})"
   168  		parsed, err := js.Parse(p, source, parser.WithSourceMapLoader(r.srcLoader))
   169  		if err != nil {
   170  			return nil, err
   171  		}
   172  		prg, err = js.CompileAST(parsed, false)
   173  		if err == nil {
   174  			if r.compiled == nil {
   175  				r.compiled = make(map[string]*js.Program)
   176  			}
   177  			r.compiled[p] = prg
   178  		}
   179  		return prg, err
   180  	}
   181  	return prg, nil
   182  }
   183  
   184  func (r *RequireModule) require(call js.FunctionCall) js.Value {
   185  	ret, err := r.Require(call.Argument(0).String())
   186  	if err != nil {
   187  		if _, ok := err.(*js.Exception); !ok {
   188  			panic(r.runtime.NewGoError(err))
   189  		}
   190  		panic(err)
   191  	}
   192  	return ret
   193  }
   194  
   195  func filepathClean(p string) string {
   196  	return path.Clean(p)
   197  }
   198  
   199  // Require can be used to import modules from Go source (similar to JS require() function).
   200  func (r *RequireModule) Require(p string) (ret js.Value, err error) {
   201  	module, err := r.resolve(p)
   202  	if err != nil {
   203  		return
   204  	}
   205  	ret = module.Get("exports")
   206  	return
   207  }
   208  
   209  func Require(runtime *js.Runtime, name string) js.Value {
   210  	if r, ok := js.AssertFunction(runtime.Get("require")); ok {
   211  		mod, err := r(js.Undefined(), runtime.ToValue(name))
   212  		if err != nil {
   213  			panic(err)
   214  		}
   215  		return mod
   216  	}
   217  	panic(runtime.NewTypeError("Please enable require for this runtime using new(require.Registry).Enable(runtime)"))
   218  }
   219  
   220  // RegisterNativeModule registers a module that isn't loaded through a SourceLoader, but rather through
   221  // a provided ModuleLoader. Typically, this will be a module implemented in Go (although theoretically
   222  // it can be anything, depending on the ModuleLoader implementation).
   223  // Such modules take precedence over modules loaded through a SourceLoader, i.e. if a module name resolves as
   224  // native, the native module is loaded, and the SourceLoader is not consulted.
   225  // The binding is global and affects all instances of Registry.
   226  // It should be called from a package init() function as it may not be used concurrently with require() calls.
   227  // For registry-specific bindings see Registry.RegisterNativeModule.
   228  func RegisterNativeModule(name string, loader ModuleLoader) {
   229  	if native == nil {
   230  		native = make(map[string]ModuleLoader)
   231  	}
   232  	name = filepathClean(name)
   233  	native[name] = loader
   234  }
   235  
   236  // RegisterCoreModule registers a nodejs core module. If the name does not start with "node:", the module
   237  // will also be loadable as "node:<name>". Hence, for "builtin" modules (such as buffer, console, etc.)
   238  // the name should not include the "node:" prefix, but for prefix-only core modules (such as "node:test")
   239  // it should include the prefix.
   240  func RegisterCoreModule(name string, loader ModuleLoader) {
   241  	if builtin == nil {
   242  		builtin = make(map[string]ModuleLoader)
   243  	}
   244  	name = filepathClean(name)
   245  	builtin[name] = loader
   246  }