gitee.com/quant1x/pkg@v0.2.8/goja_nodejs/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 "gitee.com/quant1x/pkg/goja"
    14  	"gitee.com/quant1x/pkg/goja/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  }