1089 lines
26 KiB
Go
1089 lines
26 KiB
Go
package gojs
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"os"
|
||
"os/signal"
|
||
"path/filepath"
|
||
"regexp"
|
||
"strings"
|
||
"sync"
|
||
"syscall"
|
||
"time"
|
||
|
||
"apigo.cc/gojs/goja"
|
||
"apigo.cc/gojs/goja_nodejs/require"
|
||
"github.com/ssgo/log"
|
||
"github.com/ssgo/tool/watcher"
|
||
"github.com/ssgo/u"
|
||
)
|
||
|
||
type Map = map[string]any
|
||
|
||
type Module struct {
|
||
Object Map
|
||
ObjectMaker func(vm *goja.Runtime) Map
|
||
JsCode string
|
||
TsCode string
|
||
TsCodeMaker func() string
|
||
Desc string
|
||
Example string
|
||
WaitForStop func()
|
||
OnSignal func(os.Signal)
|
||
OnKill func()
|
||
SetSSKey func([]byte, []byte)
|
||
}
|
||
|
||
type Program struct {
|
||
prg *goja.Program
|
||
startFile string
|
||
imports map[string]string
|
||
}
|
||
|
||
var modules = map[string]Module{}
|
||
var moduleAlias = map[string]string{}
|
||
var modulesLock = sync.RWMutex{}
|
||
var runInMains = []func(){}
|
||
var runInMainsLock = sync.RWMutex{}
|
||
|
||
type Error struct {
|
||
Message string
|
||
Stack []string
|
||
fullMessage string
|
||
err error
|
||
exceprion *goja.Exception
|
||
}
|
||
|
||
func (e *Error) Error() string {
|
||
return e.fullMessage
|
||
}
|
||
|
||
func (e *Error) GetError() error {
|
||
return e.err
|
||
}
|
||
|
||
func (e *Error) GetException() error {
|
||
return e.exceprion
|
||
}
|
||
|
||
func GetError(err error) *Error {
|
||
if err == nil {
|
||
return nil
|
||
}
|
||
return MakeError(err).(*Error)
|
||
}
|
||
|
||
func MakeError(err error) error {
|
||
if err == nil {
|
||
return nil
|
||
}
|
||
var newErr *Error
|
||
if gojsErr, ok := err.(*Error); ok {
|
||
newErr = gojsErr
|
||
} else if gojaErr, ok := err.(*goja.Exception); ok {
|
||
stack := make([]string, len(gojaErr.Stack()))
|
||
for i, v := range gojaErr.Stack() {
|
||
stack[i] = fmt.Sprintf("%s @%s", v.Position().String(), v.FuncName())
|
||
}
|
||
msg, fullMsg, stack1 := parseMessageAndStack(gojaErr.Value().String(), stack)
|
||
newErr = &Error{Message: msg, fullMessage: fullMsg, Stack: stack1, err: err, exceprion: gojaErr}
|
||
} else {
|
||
msg, fullMsg, stack := parseMessageAndStack(err.Error(), []string{})
|
||
newErr = &Error{Message: msg, fullMessage: fullMsg, Stack: stack, err: err, exceprion: nil}
|
||
}
|
||
// newErr.Message = strings.ReplaceAll(newErr.Message, "GoError: ", "")
|
||
newErr.fullMessage = strings.ReplaceAll(newErr.fullMessage, "GoError: ", "")
|
||
return newErr
|
||
}
|
||
|
||
func parseMessageAndStack(msg string, stack []string) (string, string, []string) {
|
||
if strings.Contains(msg, "|:|") {
|
||
a := strings.Split(msg, "|:|")
|
||
for i := 1; i < len(a); i++ {
|
||
stack1 := []string{}
|
||
u.UnJson(a[i], &stack1)
|
||
stack = append(stack, stack1...)
|
||
}
|
||
fullMsg := a[0]
|
||
if len(stack) > 0 {
|
||
fullMsg += "\n" + strings.Join(stack, "\n")
|
||
}
|
||
if curDir, err := os.Getwd(); err == nil && curDir != "" {
|
||
fullMsg = strings.ReplaceAll(fullMsg, curDir, ".")
|
||
}
|
||
// fullMsg = strings.ReplaceAll(fullMsg, "SyntaxError: SyntaxError:", "SyntaxError:")
|
||
return a[0], fullMsg, stack
|
||
}
|
||
return msg, msg, []string{}
|
||
}
|
||
|
||
// func GetErrorString(err error) string {
|
||
// if gojaErr, ok := err.(*goja.Exception); ok {
|
||
// return gojaErr.String()
|
||
// } else {
|
||
// return err.Error()
|
||
// }
|
||
// }
|
||
|
||
// func GetError(err error) (string, []string) {
|
||
// if gojaErr, ok := err.(*goja.Exception); ok {
|
||
// stack := make([]string, len(gojaErr.Stack()))
|
||
// for i, v := range gojaErr.Stack() {
|
||
// stack[i] = fmt.Sprintf("%s @%s", v.Position().String(), v.FuncName())
|
||
// }
|
||
// return gojaErr.Value().String(), stack
|
||
// } else {
|
||
// return err.Error(), []string{}
|
||
// }
|
||
// }
|
||
|
||
func Register(name string, mod Module) {
|
||
modulesLock.Lock()
|
||
modules[name] = mod
|
||
modulesLock.Unlock()
|
||
}
|
||
|
||
func Alias(aliasName string, modName string) {
|
||
modulesLock.Lock()
|
||
moduleAlias[aliasName] = modName
|
||
modulesLock.Unlock()
|
||
}
|
||
|
||
func SetSSKey(key, iv []byte) {
|
||
modulesLock.RLock()
|
||
for _, mod := range modules {
|
||
if mod.SetSSKey != nil {
|
||
mod.SetSSKey(key, iv)
|
||
}
|
||
}
|
||
modulesLock.RUnlock()
|
||
}
|
||
|
||
func RunInMain(fn func()) {
|
||
runInMainsLock.Lock()
|
||
defer runInMainsLock.Unlock()
|
||
runInMains = append(runInMains, fn)
|
||
}
|
||
|
||
func WaitAll() {
|
||
waits := []func(){}
|
||
onSignals := []func(os.Signal){}
|
||
onKills := []func(){}
|
||
runInMainFuncs := []func(){}
|
||
runInMainsLock.RLock()
|
||
runInMainFuncs = append(runInMainFuncs, runInMains...)
|
||
runInMainsLock.RUnlock()
|
||
modulesLock.RLock()
|
||
for _, mod := range modules {
|
||
if mod.WaitForStop != nil {
|
||
waits = append(waits, mod.WaitForStop)
|
||
}
|
||
if mod.OnSignal != nil {
|
||
onSignals = append(onSignals, mod.OnSignal)
|
||
}
|
||
if mod.OnKill != nil {
|
||
onKills = append(onKills, mod.OnKill)
|
||
}
|
||
}
|
||
modulesLock.RUnlock()
|
||
stopped := false
|
||
sigCh := make(chan os.Signal, 1)
|
||
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)
|
||
go func() {
|
||
for sig := range sigCh {
|
||
if stopped {
|
||
break
|
||
}
|
||
stopped = true
|
||
// send signal
|
||
for _, fn := range onSignals {
|
||
fn(sig)
|
||
}
|
||
// kill
|
||
if sig == syscall.SIGINT || sig == syscall.SIGTERM {
|
||
if wr != nil {
|
||
wr.Stop()
|
||
}
|
||
for _, fn := range onKills {
|
||
fn()
|
||
}
|
||
break
|
||
}
|
||
}
|
||
}()
|
||
// 一些必须在主线程运行的代码在这运行(例如 apigo.cc/gojs/client)
|
||
for _, fn := range runInMainFuncs {
|
||
fn()
|
||
}
|
||
if wr != nil {
|
||
wr.Wait()
|
||
}
|
||
if len(waits) > 0 {
|
||
waitChan := make(chan bool, len(waits))
|
||
for _, fn := range waits {
|
||
go func(fn func()) {
|
||
fn()
|
||
waitChan <- true
|
||
}(fn)
|
||
}
|
||
<-waitChan
|
||
if !stopped {
|
||
// 优雅的关闭信号等待线程
|
||
stopped = true
|
||
sigCh <- syscall.SIGTERM
|
||
}
|
||
}
|
||
}
|
||
|
||
func RunFile(file string, args ...any) (any, error) {
|
||
if code, err := u.ReadFile(file); err == nil {
|
||
return Run(code, file, args...)
|
||
} else {
|
||
return nil, MakeError(err)
|
||
}
|
||
}
|
||
|
||
func Run(code string, refFile string, args ...any) (any, error) {
|
||
rt := New()
|
||
var r any
|
||
err := rt.StartFromCode(code, refFile)
|
||
if err == nil {
|
||
r, err = rt.RunMain(args...)
|
||
}
|
||
return r, MakeError(err)
|
||
}
|
||
|
||
func RunProgram(prg *Program, args ...any) (any, error) {
|
||
rt := New()
|
||
var r any
|
||
err := rt.StartFromProgram(prg)
|
||
if err == nil {
|
||
r, err = rt.RunMain(args...)
|
||
}
|
||
return r, MakeError(err)
|
||
}
|
||
|
||
func CompileFile(file string) (*Program, error) {
|
||
if code, err := u.ReadFile(file); err == nil {
|
||
return CompileMain(code, file)
|
||
} else {
|
||
return nil, MakeError(err)
|
||
}
|
||
}
|
||
|
||
func CompileMain(code string, refFile string) (*Program, error) {
|
||
imports := makeImports(&code)
|
||
if !checkMainMatcher.MatchString(code) {
|
||
code = "function main(...Args){" + code + "}"
|
||
}
|
||
p, err := goja.Compile(refFile, code, false)
|
||
return &Program{prg: p, imports: imports, startFile: refFile}, MakeError(err)
|
||
}
|
||
|
||
func CompileCode(code string, refFile string) (*Program, error) {
|
||
imports := makeImports(&code)
|
||
p, err := goja.Compile(refFile, code, false)
|
||
return &Program{prg: p, imports: imports, startFile: refFile}, MakeError(err)
|
||
}
|
||
|
||
var importModMatcher = regexp.MustCompile(`(?im)^\s*import\s+(.+?)\s+from\s+['"](.+?)['"]`)
|
||
var requireModMatcher = regexp.MustCompile(`(?im)^\s*(const|let|var)\s+(.+?)\s*=\s*require\s*\(\s*['"](.+?)['"]\s*\)`)
|
||
var exportModMatcher = regexp.MustCompile(`(?im)^\s*export(\s+default)?\s+`)
|
||
|
||
// var importLibMatcher = regexp.MustCompile(`(?im)^\s*(import)\s+(.+?)\s+from\s+['"][./\\\w:]+lib[/\\](.+?)(\.ts)?['"]`)
|
||
// var requireLibMatcher = regexp.MustCompile(`(?im)^\s*(const|let|var)\s+(.+?)\s*=\s*require\s*\(\s*['"][./\\\w:]+lib[/\\](.+?)(\.ts)?['"]\s*\)`)
|
||
var checkMainMatcher = regexp.MustCompile(`(?im)^\s*function\s+main\s*\(`)
|
||
|
||
type Runtime struct {
|
||
vm *goja.Runtime
|
||
required map[string]bool
|
||
file string
|
||
srcCode string
|
||
code string
|
||
moduleLoader func(string) string
|
||
started bool
|
||
dataLock sync.RWMutex
|
||
}
|
||
|
||
func (rt *Runtime) lock() {
|
||
// fmt.Println(u.Cyan(">>>>Lock"), u.BCyan(u.String(rt.GetGoData("vmId"))))
|
||
rt.vm.Locker.Lock()
|
||
}
|
||
|
||
func (rt *Runtime) unlock() {
|
||
// fmt.Println(u.Green("\u200B <<<<UnLock"), u.BGreen(u.String(rt.GetGoData("vmId"))))
|
||
rt.vm.Locker.Unlock()
|
||
}
|
||
|
||
//func (rt *Runtime) Free() {
|
||
// rt.vm.GoData = nil
|
||
// rt.vm = nil
|
||
//}
|
||
|
||
func (rt *Runtime) SetModuleLoader(fn func(filename string) string) {
|
||
rt.moduleLoader = fn
|
||
}
|
||
|
||
func (rt *Runtime) GetCallStack() []string {
|
||
callStacks := make([]string, 0)
|
||
rt.lock()
|
||
stacks := rt.vm.CaptureCallStack(0, nil)
|
||
rt.unlock()
|
||
for _, stack := range stacks {
|
||
callStacks = append(callStacks, stack.Position().String())
|
||
}
|
||
return callStacks
|
||
}
|
||
|
||
var jsCodeLock = sync.Mutex{} // goja有bug,vm已经lock了,当还是会报错 fatal error: concurrent map read and map write @object_gomap.go:45 vm.go:2225
|
||
|
||
func (rt *Runtime) requireMod(modName, realModName string, inVM bool) error {
|
||
if rt.required[modName] {
|
||
return nil
|
||
}
|
||
modulesLock.RLock()
|
||
fullModName := moduleAlias[modName]
|
||
if fullModName == "" {
|
||
fullModName = modName
|
||
}
|
||
mod, ok := modules[fullModName]
|
||
modulesLock.RUnlock()
|
||
if ok {
|
||
var err error
|
||
if !inVM {
|
||
rt.lock()
|
||
}
|
||
if mod.ObjectMaker != nil {
|
||
err = rt.vm.Set(realModName, mod.ObjectMaker(rt.vm))
|
||
} else {
|
||
err = rt.vm.Set(realModName, mod.Object)
|
||
}
|
||
if mod.JsCode != "" {
|
||
jsCodeLock.Lock()
|
||
_, err = rt.SafeRunString(strings.ReplaceAll(mod.JsCode, "$MOD$.", realModName+"."))
|
||
jsCodeLock.Unlock()
|
||
}
|
||
if !inVM {
|
||
rt.unlock()
|
||
}
|
||
if err != nil {
|
||
return MakeError(err)
|
||
}
|
||
rt.required[modName] = true
|
||
}
|
||
return nil
|
||
}
|
||
|
||
var modNameMatcher = regexp.MustCompile(`[^\w]`)
|
||
|
||
func (rt *Runtime) requireAllMod() error {
|
||
var allModules = map[string]Module{}
|
||
modulesLock.RLock()
|
||
for k, v := range modules {
|
||
allModules[k] = v
|
||
}
|
||
modulesLock.RUnlock()
|
||
|
||
for modName, mod := range allModules {
|
||
if !rt.required[modName] {
|
||
var err error
|
||
realModName := modNameMatcher.ReplaceAllString(modName, "_")
|
||
rt.lock()
|
||
if mod.ObjectMaker != nil {
|
||
err = rt.vm.Set(realModName, mod.ObjectMaker(rt.vm))
|
||
} else {
|
||
err = rt.vm.Set(realModName, mod.Object)
|
||
}
|
||
if mod.JsCode != "" {
|
||
_, err = rt.SafeRunString(strings.ReplaceAll(mod.JsCode, "$MOD$.", realModName+"."))
|
||
}
|
||
rt.unlock()
|
||
if err != nil {
|
||
return MakeError(err)
|
||
}
|
||
rt.required[modName] = true
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func makeImports(code *string) map[string]string {
|
||
importModules := map[string]string{}
|
||
*code = importModMatcher.ReplaceAllString(*code, "let $1 = require('$2')")
|
||
*code = exportModMatcher.ReplaceAllString(*code, "module.exports = ")
|
||
*code = requireModMatcher.ReplaceAllStringFunc(*code, func(str string) string {
|
||
if m := requireModMatcher.FindStringSubmatch(str); len(m) > 3 {
|
||
optName := m[1]
|
||
varName := m[2]
|
||
modName := m[3]
|
||
realModName := modNameMatcher.ReplaceAllString(modName, "_")
|
||
modulesLock.RLock()
|
||
_, ok := modules[modName]
|
||
if !ok {
|
||
_, ok = moduleAlias[modName]
|
||
}
|
||
modulesLock.RUnlock()
|
||
if !ok {
|
||
return str
|
||
}
|
||
importModules[modName] = realModName
|
||
if varName != realModName {
|
||
return fmt.Sprintf("%s %s = %s", optName, varName, realModName)
|
||
} else {
|
||
return ""
|
||
}
|
||
}
|
||
return str
|
||
})
|
||
|
||
if strings.Contains(*code, "setTimeout") {
|
||
importModules["setTimeout"] = "setTimeout"
|
||
}
|
||
return importModules
|
||
}
|
||
|
||
func (rt *Runtime) setImports(importModules map[string]string, inVM bool) error {
|
||
var modErr error
|
||
for k, v := range importModules {
|
||
if modErr == nil {
|
||
if err := rt.requireMod(k, v, inVM); err != nil {
|
||
modErr = err
|
||
}
|
||
}
|
||
}
|
||
return modErr
|
||
}
|
||
|
||
func New() *Runtime {
|
||
vm := goja.New()
|
||
|
||
rt := &Runtime{
|
||
vm: vm,
|
||
required: map[string]bool{},
|
||
}
|
||
vm.Set("__startExec", os.Args[0])
|
||
|
||
vm.GoData = map[string]any{
|
||
"logger": log.New(u.ShortUniqueId()),
|
||
}
|
||
|
||
// 处理模块引用
|
||
// TODO 调试goja,require.NewRegistryWithLoader 引用到第三层就不在执行了,是有什么限制吗?代码会卡住没有反应也不结束。
|
||
require.NewRegistryWithLoader(func(path string) ([]byte, error) {
|
||
modFile := path
|
||
if !filepath.IsAbs(modFile) {
|
||
modFile = filepath.Join(filepath.Dir(rt.file), modFile)
|
||
}
|
||
if !strings.HasSuffix(modFile, ".js") && !u.FileExists(modFile) {
|
||
modFile += ".js"
|
||
}
|
||
modCode := ""
|
||
if rt.moduleLoader != nil {
|
||
modCode = rt.moduleLoader(modFile)
|
||
}
|
||
if modCode == "" {
|
||
var err error
|
||
modCode, err = u.ReadFile(modFile)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
imports := makeImports(&modCode)
|
||
if err := rt.setImports(imports, true); err != nil {
|
||
return nil, err
|
||
}
|
||
return []byte(modCode), nil
|
||
}).Enable(rt.vm)
|
||
|
||
return rt
|
||
}
|
||
|
||
func (rt *Runtime) StartFromFile(file string) error {
|
||
if code, err := u.ReadFile(file); err == nil {
|
||
return rt.StartFromCode(code, file)
|
||
} else {
|
||
return err
|
||
}
|
||
}
|
||
|
||
var vmId = 0
|
||
var vmIdLock = sync.Mutex{}
|
||
|
||
func (rt *Runtime) StartFromCode(code, refFile string) error {
|
||
if refFile != "" {
|
||
rt.file = refFile
|
||
}
|
||
if rt.file == "" {
|
||
rt.file = "main.js"
|
||
}
|
||
|
||
if absFile, err := filepath.Abs(rt.file); err == nil {
|
||
rt.file = absFile
|
||
}
|
||
refPath := filepath.Dir(refFile)
|
||
|
||
vmIdLock.Lock()
|
||
vmId++
|
||
vmId1 := vmId
|
||
vmIdLock.Unlock()
|
||
rt.SetGoData("vmId", u.String(vmId1))
|
||
rt.SetGoData("startFile", refFile)
|
||
rt.SetGoData("startPath", refPath)
|
||
rt.vm.Set("__startFile", refFile)
|
||
|
||
if rt.srcCode == "" {
|
||
rt.srcCode = code
|
||
}
|
||
rt.code = code
|
||
|
||
// 按需加载引用
|
||
//var importCount int
|
||
var modErr error
|
||
//rt.code, importCount, modErr = rt.makeImport(rt.code)
|
||
imports := makeImports(&rt.code)
|
||
if len(imports) == 0 {
|
||
modErr = rt.requireAllMod()
|
||
} else {
|
||
modErr = rt.setImports(imports, false)
|
||
}
|
||
|
||
//// 如果没有import,默认import所有
|
||
//if modErr == nil && importCount == 0 {
|
||
// modErr = rt.requireMod("", "")
|
||
//}
|
||
if modErr != nil {
|
||
return modErr
|
||
}
|
||
|
||
//fmt.Println(u.BCyan(rt.code))
|
||
|
||
// 初始化主函数
|
||
if !checkMainMatcher.MatchString(rt.code) {
|
||
rt.code = "function main(...Args){" + rt.code + "}"
|
||
}
|
||
rt.lock()
|
||
rt.vm.Set("__runFile", rt.file)
|
||
_, err := rt.SafeRunScript(rt.file, rt.code)
|
||
rt.unlock()
|
||
if err != nil {
|
||
return MakeError(err)
|
||
} else {
|
||
rt.started = true
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func (rt *Runtime) SafeRunProgram(prg *Program) (v goja.Value, err error) {
|
||
defer func() {
|
||
if x := recover(); x != nil {
|
||
if e, ok := x.(error); ok {
|
||
err = e
|
||
if e2 := GetError(err); e2 != nil {
|
||
err = e2
|
||
GetLogger(rt.vm).Error(e2.Error(), "stack", e2.Stack)
|
||
}
|
||
} else {
|
||
err = errors.New(u.String(x))
|
||
}
|
||
}
|
||
}()
|
||
return rt.vm.RunProgram(prg.prg)
|
||
}
|
||
|
||
func (rt *Runtime) SafeRunScript(name, src string) (v goja.Value, err error) {
|
||
defer func() {
|
||
if x := recover(); x != nil {
|
||
if e, ok := x.(error); ok {
|
||
err = e
|
||
if e2 := GetError(err); e2 != nil {
|
||
err = e2
|
||
GetLogger(rt.vm).Error(e2.Error(), "stack", e2.Stack)
|
||
}
|
||
} else {
|
||
err = errors.New(u.String(x))
|
||
}
|
||
}
|
||
}()
|
||
return rt.vm.RunScript(name, src)
|
||
}
|
||
|
||
func (rt *Runtime) SafeRunString(str string) (v goja.Value, err error) {
|
||
defer func() {
|
||
if x := recover(); x != nil {
|
||
if e, ok := x.(error); ok {
|
||
err = e
|
||
if e2 := GetError(err); e2 != nil {
|
||
err = e2
|
||
GetLogger(rt.vm).Error(e2.Error(), "stack", e2.Stack)
|
||
}
|
||
} else {
|
||
err = errors.New(u.String(x))
|
||
}
|
||
}
|
||
}()
|
||
return rt.vm.RunString(str)
|
||
}
|
||
|
||
func (rt *Runtime) StartFromProgram(prg *Program) error {
|
||
var modErr error
|
||
if len(prg.imports) == 0 {
|
||
modErr = rt.requireAllMod()
|
||
} else {
|
||
modErr = rt.setImports(prg.imports, false)
|
||
}
|
||
if modErr != nil {
|
||
return modErr
|
||
}
|
||
vmIdLock.Lock()
|
||
vmId++
|
||
vmId1 := vmId
|
||
vmIdLock.Unlock()
|
||
rt.SetGoData("vmId", u.String(vmId1))
|
||
|
||
rt.SetGoData("vmId", u.String(vmId1))
|
||
rt.SetGoData("startFile", prg.startFile)
|
||
rt.SetGoData("startPath", filepath.Dir(prg.startFile))
|
||
rt.lock()
|
||
rt.vm.Set("__startFile", prg.startFile)
|
||
rt.vm.Set("__runFile", prg.startFile)
|
||
_, err := rt.SafeRunProgram(prg)
|
||
rt.unlock()
|
||
if err != nil {
|
||
return MakeError(err)
|
||
} else {
|
||
rt.started = true
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func (rt *Runtime) RunMain(args ...any) (any, error) {
|
||
if !rt.started {
|
||
return nil, errors.New("runtime not started")
|
||
}
|
||
|
||
// 解析参数
|
||
for i, arg := range args {
|
||
if str, ok := arg.(string); ok {
|
||
var v interface{}
|
||
if err := json.Unmarshal([]byte(str), &v); err == nil {
|
||
args[i] = v
|
||
}
|
||
}
|
||
}
|
||
|
||
rt.lock()
|
||
err := rt.vm.Set("__args", args)
|
||
rt.unlock()
|
||
if err != nil {
|
||
return nil, MakeError(err)
|
||
}
|
||
rt.lock()
|
||
r, err := rt.SafeRunScript("main", "main(...__args)")
|
||
rt.unlock()
|
||
return makeResult(r, err)
|
||
}
|
||
|
||
func (rt *Runtime) RunVM(callback func(vm *goja.Runtime) (any, error)) (any, error) {
|
||
rt.lock()
|
||
r, err := callback(rt.vm)
|
||
rt.unlock()
|
||
return r, MakeError(err)
|
||
}
|
||
|
||
func (rt *Runtime) SetGoData(name string, value any) {
|
||
rt.dataLock.Lock()
|
||
rt.vm.GoData[name] = value
|
||
rt.dataLock.Unlock()
|
||
}
|
||
|
||
func (rt *Runtime) GetGoData(name string) any {
|
||
rt.dataLock.RLock()
|
||
defer rt.dataLock.RUnlock()
|
||
return rt.vm.GoData[name]
|
||
}
|
||
|
||
func (rt *Runtime) Set(name string, value any) error {
|
||
rt.lock()
|
||
defer rt.unlock()
|
||
return MakeError(rt.vm.Set(name, value))
|
||
}
|
||
|
||
func (rt *Runtime) SetGlobal(global Map) error {
|
||
var err error
|
||
rt.lock()
|
||
defer rt.unlock()
|
||
for k, v := range global {
|
||
if err = rt.vm.Set(k, v); err != nil {
|
||
return MakeError(err)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (rt *Runtime) RunFile(file string) (any, error) {
|
||
if code, err := u.ReadFile(file); err == nil {
|
||
imports := makeImports(&code)
|
||
if err := rt.setImports(imports, false); err != nil {
|
||
return nil, err
|
||
}
|
||
rt.lock()
|
||
rt.vm.Set("__runFile", file)
|
||
r, err := rt.SafeRunScript(file, code)
|
||
rt.unlock()
|
||
return makeResult(r, err)
|
||
} else {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
func (rt *Runtime) RunCode(code string) (any, error) {
|
||
imports := makeImports(&code)
|
||
if err := rt.setImports(imports, false); err != nil {
|
||
return nil, err
|
||
}
|
||
rt.lock()
|
||
r, err := rt.SafeRunScript("anonymous", code)
|
||
rt.unlock()
|
||
return makeResult(r, err)
|
||
}
|
||
|
||
func (rt *Runtime) RunProgram(prg *Program) (any, error) {
|
||
var r goja.Value
|
||
err := rt.setImports(prg.imports, false)
|
||
if err == nil {
|
||
rt.lock()
|
||
rt.vm.Set("__runFile", prg.startFile)
|
||
r, err = rt.SafeRunProgram(prg)
|
||
rt.unlock()
|
||
}
|
||
return makeResult(r, err)
|
||
}
|
||
|
||
func makeResult(r goja.Value, err error) (any, error) {
|
||
var result any
|
||
if err == nil {
|
||
if r != nil && !r.Equals(goja.Undefined()) {
|
||
result = r.Export()
|
||
}
|
||
}
|
||
return result, MakeError(err)
|
||
}
|
||
|
||
type WatchRunner struct {
|
||
w *watcher.Watcher
|
||
stopCh chan bool
|
||
}
|
||
|
||
// func (wr *WatchRunner) WaitForKill() {
|
||
// exitCh := make(chan os.Signal, 1)
|
||
// closeCh := make(chan bool, 1)
|
||
// signal.Notify(exitCh, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)
|
||
// go func() {
|
||
// <-exitCh
|
||
// closeCh <- true
|
||
// }()
|
||
// <-closeCh
|
||
// wr.w.Stop()
|
||
// }
|
||
|
||
func (wr *WatchRunner) Wait() {
|
||
<-wr.stopCh
|
||
}
|
||
|
||
func (wr *WatchRunner) Stop() {
|
||
wr.w.Stop()
|
||
wr.stopCh <- true
|
||
wr = nil
|
||
}
|
||
|
||
func (wr *WatchRunner) SetModuleLoader(rt *Runtime) {
|
||
rt.SetModuleLoader(func(filename string) string {
|
||
filePath := filepath.Dir(filename)
|
||
needWatch := true
|
||
for _, v := range wr.w.WatchList() {
|
||
if v == filePath {
|
||
needWatch = false
|
||
break
|
||
}
|
||
}
|
||
if needWatch {
|
||
fmt.Println(u.BMagenta("[watching module path]"), filePath)
|
||
_ = wr.w.Add(filePath)
|
||
}
|
||
return u.ReadFileN(filename)
|
||
})
|
||
}
|
||
|
||
var wr *WatchRunner
|
||
|
||
func Watch(files, types, ignores []string, onRun func([]string)) (*WatchRunner, error) {
|
||
wr = &WatchRunner{}
|
||
var isWaitingRun = false
|
||
changes := make([]string, 0)
|
||
changesLock := sync.Mutex{}
|
||
onChange := func(filename string, event string) {
|
||
if strings.Contains(filename, string(os.PathSeparator)+".") {
|
||
return
|
||
}
|
||
// ignore := false
|
||
// for _, ignorePath := range ignores {
|
||
// if strings.HasPrefix(filename, ignorePath) {
|
||
// ignore = true
|
||
// break
|
||
// }
|
||
// }
|
||
// if !ignore {
|
||
changesLock.Lock()
|
||
changes = append(changes, filename)
|
||
changesLock.Unlock()
|
||
if !isWaitingRun {
|
||
// _, _ = os.Stdout.WriteString("\x1b[3;J\x1b[H\x1b[2J")
|
||
isWaitingRun = true
|
||
go func() {
|
||
time.Sleep(time.Millisecond * 10)
|
||
isWaitingRun = false
|
||
changesLock.Lock()
|
||
changes2 := make([]string, len(changes))
|
||
copy(changes2, changes)
|
||
changes = make([]string, 0)
|
||
changesLock.Unlock()
|
||
fmt.Println(u.BYellow("[run]"), changes2)
|
||
onRun(changes2)
|
||
}()
|
||
}
|
||
fmt.Println(u.BYellow("[changed]"), filename)
|
||
// }
|
||
}
|
||
// _, _ = os.Stdout.WriteString("\x1b[3;J\x1b[H\x1b[2J")
|
||
if w, err := watcher.Start(files, types, ignores, onChange); err == nil {
|
||
wr.w = w
|
||
go func() {
|
||
onRun(changes)
|
||
}()
|
||
wr.stopCh = make(chan bool, 1)
|
||
return wr, nil
|
||
} else {
|
||
wr = nil
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
func StopForWatch() {
|
||
// 等待所有模块停止
|
||
waits := []func(){}
|
||
onKills := []func(){}
|
||
modulesLock.RLock()
|
||
for _, mod := range modules {
|
||
if mod.WaitForStop != nil {
|
||
waits = append(waits, mod.WaitForStop)
|
||
}
|
||
if mod.OnKill != nil {
|
||
onKills = append(onKills, mod.OnKill)
|
||
}
|
||
}
|
||
modulesLock.RUnlock()
|
||
for _, fn := range onKills {
|
||
fn()
|
||
}
|
||
if len(waits) > 0 {
|
||
waitChan := make(chan bool, len(waits))
|
||
for _, fn := range waits {
|
||
go func(fn func()) {
|
||
fn()
|
||
waitChan <- true
|
||
}(fn)
|
||
}
|
||
<-waitChan
|
||
}
|
||
}
|
||
|
||
func WatchRun(file string, args ...any) (*WatchRunner, error) {
|
||
return WatchRunWith([]string{file}, []string{"js", "json", "yml"}, file, args...)
|
||
}
|
||
|
||
func WatchRunWith(watchPath []string, watchFileType []string, file string, args ...any) (*WatchRunner, error) {
|
||
return Watch(watchPath, watchFileType, nil, func(files []string) {
|
||
StopForWatch()
|
||
|
||
// 启动新的程序
|
||
rt := New()
|
||
rt.SetGoData("inWatch", true)
|
||
wr.SetModuleLoader(rt)
|
||
err := rt.StartFromFile(file)
|
||
if err != nil {
|
||
fmt.Println(u.BRed(err.Error()))
|
||
fmt.Println(u.Red(" " + strings.Join(rt.GetCallStack(), "\n ")))
|
||
return
|
||
}
|
||
result, err := rt.RunMain(args...)
|
||
if err != nil {
|
||
fmt.Println(u.BRed(err.Error()))
|
||
fmt.Println(u.Red(" " + strings.Join(rt.GetCallStack(), "\n ")))
|
||
} else if result != nil {
|
||
fmt.Println(u.Cyan(u.JsonP(result)))
|
||
}
|
||
})
|
||
// wr = &WatchRunner{}
|
||
// run := func() {
|
||
// rt := New()
|
||
// if wr.w != nil {
|
||
// rt.SetModuleLoader(func(filename string) string {
|
||
// filePath := filepath.Dir(filename)
|
||
// needWatch := true
|
||
// for _, v := range wr.w.WatchList() {
|
||
// if v == filePath {
|
||
// needWatch = false
|
||
// break
|
||
// }
|
||
// }
|
||
// if needWatch {
|
||
// fmt.Println(u.BMagenta("[watching module path]"), filePath)
|
||
// _ = wr.w.Add(filePath)
|
||
// }
|
||
// return u.ReadFileN(filename)
|
||
// })
|
||
// }
|
||
// err := rt.StartFromFile(file)
|
||
// result, err := rt.RunMain(args...)
|
||
// if err != nil {
|
||
// fmt.Println(u.BRed(err.Error()))
|
||
// fmt.Println(u.Red(" " + strings.Join(rt.GetCallStack(), "\n ")))
|
||
// } else if result != nil {
|
||
// fmt.Println(u.Cyan(u.JsonP(result)))
|
||
// }
|
||
// }
|
||
|
||
// var isWaitingRun = false
|
||
// onChange := func(filename string, event string) {
|
||
// if !isWaitingRun {
|
||
// _, _ = os.Stdout.WriteString("\x1b[3;J\x1b[H\x1b[2J")
|
||
// isWaitingRun = true
|
||
// go func() {
|
||
// time.Sleep(time.Millisecond * 10)
|
||
// isWaitingRun = false
|
||
// run()
|
||
// }()
|
||
// }
|
||
// fmt.Println(u.BYellow("[changed]"), filename)
|
||
// }
|
||
// _, _ = os.Stdout.WriteString("\x1b[3;J\x1b[H\x1b[2J")
|
||
// watchStartPath := filepath.Dir(file)
|
||
// fmt.Println(u.BMagenta("[watching root path]"), watchStartPath)
|
||
// watchDirs := []string{watchStartPath}
|
||
// watchTypes := []string{"js", "json", "yml"}
|
||
// if extDirs != nil {
|
||
// for _, v := range extDirs {
|
||
// watchDirs = append(watchDirs, v)
|
||
// }
|
||
// }
|
||
// if extTypes != nil {
|
||
// for _, v := range extTypes {
|
||
// watchTypes = append(watchTypes, v)
|
||
// }
|
||
// }
|
||
// if w, err := watcher.Start(watchDirs, watchTypes, onChange); err == nil {
|
||
// wr.w = w
|
||
// go func() {
|
||
// run()
|
||
// }()
|
||
// return wr, nil
|
||
// } else {
|
||
// return nil, err
|
||
// }
|
||
}
|
||
|
||
func ExportForDev() string {
|
||
var allModules = map[string]Module{}
|
||
var allModuleAlias = map[string]string{}
|
||
modulesLock.RLock()
|
||
for k, v := range modules {
|
||
allModules[k] = v
|
||
}
|
||
for k, v := range moduleAlias {
|
||
allModuleAlias[k] = v
|
||
}
|
||
modulesLock.RUnlock()
|
||
|
||
dir, _ := os.Getwd()
|
||
nodeModulesPath := "node_modules"
|
||
packageJsonFile := "package.json"
|
||
for i := 0; i < 20; i++ {
|
||
nodePath := filepath.Join(dir, "node_modules")
|
||
if u.FileExists(nodePath) {
|
||
nodeModulesPath = nodePath
|
||
packageJsonFile = filepath.Join(dir, "package.json")
|
||
break
|
||
}
|
||
parentDir := filepath.Dir(dir)
|
||
if parentDir == dir {
|
||
break
|
||
}
|
||
dir = parentDir
|
||
}
|
||
|
||
oldPackageJson := u.ReadFileN(packageJsonFile)
|
||
insertModulesPostfix := ""
|
||
if oldPackageJson == "" {
|
||
oldPackageJson = "{\n \"devDependencies\": {\n\n }\n}"
|
||
} else if !strings.Contains(oldPackageJson, "\"devDependencies\": {") {
|
||
oldPackageJson = strings.Replace(oldPackageJson, "{", "{\n \"devDependencies\": {\n\n },", 1)
|
||
} else {
|
||
insertModulesPostfix = ","
|
||
}
|
||
|
||
insertModules := make([]string, 0)
|
||
imports := make([]string, len(allModules))
|
||
i := 0
|
||
for k, v := range allModules {
|
||
varName := k
|
||
tsPath := k
|
||
if strings.ContainsRune(k, '/') {
|
||
varName = varName[strings.LastIndex(varName, "/")+1:]
|
||
if os.PathSeparator != '/' {
|
||
tsPath = strings.ReplaceAll(tsPath, "/", string(os.PathSeparator))
|
||
}
|
||
}
|
||
tsCode := v.TsCode
|
||
if v.TsCodeMaker != nil {
|
||
tsCode = v.TsCodeMaker()
|
||
}
|
||
_ = u.WriteFile(filepath.Join(nodeModulesPath, tsPath, "index.ts"), tsCode)
|
||
_ = u.WriteFile(filepath.Join(nodeModulesPath, tsPath, "package.json"), "{\n \"name\": \""+k+"\",\n \"version\": \"0.0.0\"\n}")
|
||
imports[i] = fmt.Sprintf("import %s from '%s'", varName, k)
|
||
insertModuleKey := "\"" + k + "\":"
|
||
insertModuleLine := fmt.Sprint("\"", k, "\": \"v0.0.0\"")
|
||
|
||
for aliasName, module := range allModuleAlias {
|
||
if module == k {
|
||
aliasVarName := aliasName
|
||
aliasPath := aliasName
|
||
if strings.ContainsRune(aliasName, '/') {
|
||
aliasVarName = aliasVarName[strings.LastIndex(aliasVarName, "/")+1:]
|
||
if os.PathSeparator != '/' {
|
||
aliasPath = strings.ReplaceAll(aliasPath, "/", string(os.PathSeparator))
|
||
}
|
||
}
|
||
u.CopyFile(filepath.Join(nodeModulesPath, tsPath, "index.ts"), filepath.Join(nodeModulesPath, aliasPath, "index.ts"))
|
||
_ = u.WriteFile(filepath.Join(nodeModulesPath, aliasPath, "package.json"), "{\n \"name\": \""+aliasName+"\",\n \"version\": \"0.0.0\"\n}")
|
||
imports[i] = fmt.Sprintf("import %s from '%s'", aliasVarName, aliasName)
|
||
insertModuleKey = "\"" + aliasName + "\":"
|
||
insertModuleLine = fmt.Sprint("\"", aliasName, "\": \"v0.0.0\"")
|
||
}
|
||
}
|
||
|
||
if !strings.Contains(oldPackageJson, insertModuleKey) {
|
||
insertModules = u.AppendUniqueString(insertModules, insertModuleLine)
|
||
}
|
||
|
||
i++
|
||
}
|
||
|
||
if len(insertModules) > 0 {
|
||
newPackageJson := strings.Replace(oldPackageJson, "\"devDependencies\": {", "\"devDependencies\": {\n "+strings.Join(insertModules, ",\n ")+insertModulesPostfix, 1)
|
||
_ = u.WriteFile(packageJsonFile, newPackageJson)
|
||
}
|
||
return strings.Join(imports, "\n")
|
||
}
|