module.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. package require
  2. import (
  3. "errors"
  4. "io"
  5. "io/fs"
  6. "os"
  7. "path"
  8. "path/filepath"
  9. "runtime"
  10. "sync"
  11. "syscall"
  12. "text/template"
  13. js "github.com/dop251/goja"
  14. "github.com/dop251/goja/parser"
  15. )
  16. type ModuleLoader func(*js.Runtime, *js.Object)
  17. // SourceLoader represents a function that returns a file data at a given path.
  18. // The function should return ModuleFileDoesNotExistError if the file either doesn't exist or is a directory.
  19. // This error will be ignored by the resolver and the search will continue. Any other errors will be propagated.
  20. type SourceLoader func(path string) ([]byte, error)
  21. var (
  22. InvalidModuleError = errors.New("Invalid module")
  23. IllegalModuleNameError = errors.New("Illegal module name")
  24. NoSuchBuiltInModuleError = errors.New("No such built-in module")
  25. ModuleFileDoesNotExistError = errors.New("module file does not exist")
  26. )
  27. // Registry contains a cache of compiled modules which can be used by multiple Runtimes
  28. type Registry struct {
  29. sync.Mutex
  30. native map[string]ModuleLoader
  31. builtin map[string]ModuleLoader
  32. compiled map[string]*js.Program
  33. srcLoader SourceLoader
  34. globalFolders []string
  35. fsEnabled bool
  36. }
  37. type RequireModule struct {
  38. r *Registry
  39. runtime *js.Runtime
  40. modules map[string]*js.Object
  41. nodeModules map[string]*js.Object
  42. }
  43. func NewRegistry(opts ...Option) *Registry {
  44. r := &Registry{}
  45. for _, opt := range opts {
  46. opt(r)
  47. }
  48. return r
  49. }
  50. type Option func(*Registry)
  51. // WithLoader sets a function which will be called by the require() function in order to get a source code for a
  52. // module at the given path. The same function will be used to get external source maps.
  53. // Note, this only affects the modules loaded by the require() function. If you need to use it as a source map
  54. // loader for code parsed in a different way (such as runtime.RunString() or eval()), use (*Runtime).SetParserOptions()
  55. func WithLoader(srcLoader SourceLoader) Option {
  56. return func(r *Registry) {
  57. r.srcLoader = srcLoader
  58. }
  59. }
  60. // WithGlobalFolders appends the given paths to the registry's list of
  61. // global folders to search if the requested module is not found
  62. // elsewhere. By default, a registry's global folders list is empty.
  63. // In the reference Node.js implementation, the default global folders
  64. // list is $NODE_PATH, $HOME/.node_modules, $HOME/.node_libraries and
  65. // $PREFIX/lib/node, see
  66. // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders.
  67. func WithGlobalFolders(globalFolders ...string) Option {
  68. return func(r *Registry) {
  69. r.globalFolders = globalFolders
  70. }
  71. }
  72. func WithFsEnable(enabled bool) Option {
  73. return func(r *Registry) {
  74. r.fsEnabled = enabled
  75. }
  76. }
  77. // Enable adds the require() function to the specified runtime.
  78. func (r *Registry) Enable(runtime *js.Runtime) *RequireModule {
  79. rrt := &RequireModule{
  80. r: r,
  81. runtime: runtime,
  82. modules: make(map[string]*js.Object),
  83. nodeModules: make(map[string]*js.Object),
  84. }
  85. runtime.Set("require", rrt.require)
  86. return rrt
  87. }
  88. func (r *Registry) RegisterNodeModule(name string, loader ModuleLoader) {
  89. r.Lock()
  90. defer r.Unlock()
  91. if r.builtin == nil {
  92. r.builtin = make(map[string]ModuleLoader)
  93. }
  94. name = filepathClean(name)
  95. r.builtin[name] = loader
  96. }
  97. func (r *Registry) RegisterNativeModule(name string, loader ModuleLoader) {
  98. r.Lock()
  99. defer r.Unlock()
  100. if r.native == nil {
  101. r.native = make(map[string]ModuleLoader)
  102. }
  103. name = filepathClean(name)
  104. r.native[name] = loader
  105. }
  106. // DefaultSourceLoader is used if none was set (see WithLoader()). It simply loads files from the host's filesystem.
  107. func DefaultSourceLoader(filename string) ([]byte, error) {
  108. fp := filepath.FromSlash(filename)
  109. f, err := os.Open(fp)
  110. if err != nil {
  111. if errors.Is(err, fs.ErrNotExist) {
  112. err = ModuleFileDoesNotExistError
  113. } else if runtime.GOOS == "windows" {
  114. if errors.Is(err, syscall.Errno(0x7b)) { // ERROR_INVALID_NAME, The filename, directory name, or volume label syntax is incorrect.
  115. err = ModuleFileDoesNotExistError
  116. }
  117. }
  118. return nil, err
  119. }
  120. defer f.Close()
  121. // On some systems (e.g. plan9 and FreeBSD) it is possible to use the standard read() call on directories
  122. // which means we cannot rely on read() returning an error, we have to do stat() instead.
  123. if fi, err := f.Stat(); err == nil {
  124. if fi.IsDir() {
  125. return nil, ModuleFileDoesNotExistError
  126. }
  127. } else {
  128. return nil, err
  129. }
  130. return io.ReadAll(f)
  131. }
  132. func (r *Registry) getSource(p string) ([]byte, error) {
  133. srcLoader := r.srcLoader
  134. if srcLoader == nil {
  135. srcLoader = DefaultSourceLoader
  136. }
  137. return srcLoader(p)
  138. }
  139. func (r *Registry) getCompiledSource(p string) (*js.Program, error) {
  140. r.Lock()
  141. defer r.Unlock()
  142. prg := r.compiled[p]
  143. if prg == nil {
  144. buf, err := r.getSource(p)
  145. if err != nil {
  146. return nil, err
  147. }
  148. s := string(buf)
  149. if path.Ext(p) == ".json" {
  150. s = "module.exports = JSON.parse('" + template.JSEscapeString(s) + "')"
  151. }
  152. source := "(function(exports, require, module) {" + s + "\n})"
  153. parsed, err := js.Parse(p, source, parser.WithSourceMapLoader(r.srcLoader))
  154. if err != nil {
  155. return nil, err
  156. }
  157. prg, err = js.CompileAST(parsed, false)
  158. if err == nil {
  159. if r.compiled == nil {
  160. r.compiled = make(map[string]*js.Program)
  161. }
  162. r.compiled[p] = prg
  163. }
  164. return prg, err
  165. }
  166. return prg, nil
  167. }
  168. func (r *RequireModule) require(call js.FunctionCall) js.Value {
  169. ret, err := r.Require(call.Argument(0).String())
  170. if err != nil {
  171. if _, ok := err.(*js.Exception); !ok {
  172. panic(r.runtime.NewGoError(err))
  173. }
  174. panic(err)
  175. }
  176. return ret
  177. }
  178. func filepathClean(p string) string {
  179. return path.Clean(p)
  180. }
  181. // Require can be used to import modules from Go source (similar to JS require() function).
  182. func (r *RequireModule) Require(p string) (ret js.Value, err error) {
  183. module, err := r.resolve(p)
  184. if err != nil {
  185. return
  186. }
  187. ret = module.Get("exports")
  188. return
  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.Registry).Enable(runtime)"))
  199. }