gojs/gojs.go

1290 lines
31 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package gojs
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/signal"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strings"
"sync"
"syscall"
"time"
"apigo.cc/gojs/common"
"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()
WaitForStop2 func()
OnStart func()
OnSignal func(os.Signal)
OnKill func()
OnKill2 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 JsError struct {
Message string
Stack []string
fullMessage string
err error
exceprion *goja.Exception
}
func (e *JsError) Error() string {
return e.fullMessage
}
func (e *JsError) GetError() error {
return e.err
}
func (e *JsError) GetException() error {
return e.exceprion
}
func GetJsError(err error) *JsError {
if err == nil {
return nil
}
return MakeJsError(err).(*JsError)
}
func MakeJsError(err error) error {
if err == nil {
return nil
}
var newErr *JsError
if gojsErr, ok := err.(*JsError); 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 = &JsError{Message: msg, fullMessage: fullMsg, Stack: stack1, err: err, exceprion: gojaErr}
} else {
msg, fullMsg, stack := parseMessageAndStack(err.Error(), []string{})
newErr = &JsError{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 Errf(format string, args ...any) *common.GoErr {
return Err(fmt.Sprintf(format, args...))
}
func Err(err any) *common.GoErr {
if err == nil {
return nil
}
if e, ok := err.(*common.GoErr); ok {
return e
}
errStr := ""
callStacks := make([]string, 0)
if gojaErr, ok := err.(*goja.Exception); ok {
errStr = gojaErr.String()
for _, v := range gojaErr.Stack() {
callStacks = append(callStacks, fmt.Sprintf("%s @%s", v.Position().String(), v.FuncName()))
}
} else if e, ok := err.(error); ok {
errStr = e.Error()
} else {
errStr = u.String(err)
}
for i := 1; i < 50; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
if strings.Contains(file, "/go/src/") {
continue
}
if strings.Contains(file, "/ssgo/log") {
continue
}
if strings.Contains(file, "/ssgo") {
file = "**" + file[strings.LastIndex(file, "/ssgo"):]
}
callStacks = append(callStacks, fmt.Sprintf("%s:%d", file, line))
}
return &common.GoErr{
Message: errStr,
Stack: callStacks,
}
}
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
}
fullMsg := msg
if len(stack) > 0 {
fullMsg += "\n" + strings.Join(stack, "\n")
}
if curDir, err := os.Getwd(); err == nil && curDir != "" {
fullMsg = strings.ReplaceAll(fullMsg, curDir, ".")
}
return msg, fullMsg, stack
}
// 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() {
time.Sleep(time.Millisecond * 1000)
waits := []func(){}
waits2 := []func(){}
onSignals := []func(os.Signal){}
onKills := []func(){}
onKills2 := []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.WaitForStop2 != nil {
waits2 = append(waits2, mod.WaitForStop2)
}
if mod.OnSignal != nil {
onSignals = append(onSignals, mod.OnSignal)
}
if mod.OnKill != nil {
onKills = append(onKills, mod.OnKill)
}
if mod.OnKill2 != nil {
onKills2 = append(onKills, mod.OnKill2)
}
}
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 {
wg := sync.WaitGroup{}
wg.Add(len(waits))
for _, fn := range waits {
go func(fn func()) {
fn()
wg.Done()
}(fn)
}
wg.Wait()
}
for _, fn := range onKills2 {
fn()
}
if len(waits2) > 0 {
wg := sync.WaitGroup{}
wg.Add(len(waits2))
for _, fn := range waits2 {
go func(fn func()) {
fn()
wg.Done()
}(fn)
}
wg.Wait()
}
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, MakeJsError(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, MakeJsError(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, MakeJsError(err)
}
func CompileFile(file string) (*Program, error) {
if code, err := u.ReadFile(file); err == nil {
return CompileMain(code, file)
} else {
return nil, MakeJsError(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}, MakeJsError(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}, MakeJsError(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有bugvm已经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 MakeJsError(err)
}
if mod.OnStart != nil {
mod.OnStart()
}
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 MakeJsError(err)
}
if mod.OnStart != nil {
mod.OnStart()
}
rt.required[modName] = true
}
}
return nil
}
func MakeImports(code *string) {
makeImports(code)
}
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
// fmt.Println(u.BYellow(modName), ok, varName, 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.SetData("logger", log.New(u.ShortUniqueId()))
vm.Set("getLastError", func(call goja.FunctionCall) goja.Value {
return rt.vm.ToValue(rt.vm.GetData("_lastError"))
})
// 处理模块引用
// TODO 调试gojarequire.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.vm.SetData("vmId", u.String(vmId1))
rt.vm.SetData("startFile", refFile)
rt.vm.SetData("startPath", refPath)
rt.vm.Set("__startFile", refFile)
if rt.srcCode == "" {
rt.srcCode = code
}
rt.code = code
// 按需加载引用
//var importCount int
var modErr error
// modErr = rt.requireAllMod()
// 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)
}
// makeImports(&rt.code)
// modErr = rt.requireAllMod()
//// 如果没有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 MakeJsError(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 := GetJsError(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 := GetJsError(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)
}
var vmIdRunTimes = map[string]int{}
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 := GetJsError(err); e2 != nil {
err = e2
GetLogger(rt.vm).Error(e2.Error(), "stack", e2.Stack)
}
} else {
err = errors.New(u.String(x))
}
}
}()
// vmId := u.String(rt.vm.GetData("vmId"))
// vmIdRunTimes[vmId]++
// if vmIdRunTimes[vmId] > 1 {
// fmt.Println(u.BMagenta("start "), u.Magenta(rt.vm.GetData("vmId")), str)
// }
r, err := rt.vm.RunString(str)
// if vmIdRunTimes[vmId] > 1 {
// fmt.Println(u.BYellow("stop "), u.Yellow(rt.vm.GetData("vmId")))
// }
return r, err
}
func (rt *Runtime) StartFromProgram(prg *Program) error {
var modErr error
// modErr = rt.requireAllMod()
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.vm.SetData("vmId", u.String(vmId1))
rt.vm.SetData("vmId", u.String(vmId1))
rt.vm.SetData("startFile", prg.startFile)
rt.vm.SetData("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 MakeJsError(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 any
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, MakeJsError(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, MakeJsError(err)
}
func (rt *Runtime) SetGoData(name string, value any) {
rt.vm.SetData(name, value)
}
func (rt *Runtime) GetGoData(name string) any {
return rt.vm.GetData(name)
}
func (rt *Runtime) Set(name string, value any) error {
rt.lock()
defer rt.unlock()
return MakeJsError(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 MakeJsError(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, MakeJsError(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(){}
waits2 := []func(){}
onKills := []func(){}
onKills2 := []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)
}
if mod.WaitForStop2 != nil {
waits2 = append(waits2, mod.WaitForStop2)
}
if mod.OnKill2 != nil {
onKills2 = append(onKills2, mod.OnKill2)
}
}
modulesLock.RUnlock()
for _, fn := range onKills {
fn()
}
if len(waits) > 0 {
wg := sync.WaitGroup{}
// waitChan := make(chan bool, len(waits))
for _, fn := range waits {
wg.Add(1)
go func(fn func()) {
fn()
// waitChan <- true
wg.Done()
}(fn)
}
wg.Wait()
}
for _, fn := range onKills2 {
fn()
}
if len(waits2) > 0 {
wg := sync.WaitGroup{}
// waitChan := make(chan bool, len(waits))
for _, fn := range waits2 {
wg.Add(1)
go func(fn func()) {
fn()
// waitChan <- true
wg.Done()
}(fn)
}
wg.Wait()
}
}
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.vm.SetData("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")
}
// SetUserPath 设置用户路径,确保在沙盒中从 userPath 中查找文件
func (rt *Runtime) SetUserPath(filename string) string {
rt.SetGoData("userPath", u.GetAbsFilename(filename))
return filename
}
// FixPath 修复文件路径,确保在沙盒中从 userPath 中查找文件
func FixPath(vm *goja.Runtime, filename string) string {
// 使用 userPath 作为文件系统沙盒
rootPath := u.String(vm.GetData("userPath"))
if rootPath != "" {
absRootPath := u.GetAbsFilename(rootPath)
// 沙盒中必须从 userPath 中查找文件
if !strings.HasSuffix(absRootPath, string(os.PathSeparator)) {
absRootPath += string(os.PathSeparator)
}
if !strings.HasPrefix(u.GetAbsFilename(filename)+string(os.PathSeparator), absRootPath) {
filename = filepath.Join(absRootPath, filename)
}
} else {
// 非沙盒从 startPath 中查找文件
if !filepath.IsAbs(filename) {
startPath := u.String(vm.GetData("startPath"))
if startPath != "" {
tryFilename := filepath.Join(startPath, filename)
if u.FileExists(tryFilename) {
filename = tryFilename
}
}
}
}
return filename
}
// FixObject 修复对象,不显示 gojsObjectId
var GojsObjectIdKeyRef = reflect.ValueOf("gojsObjectId")
func FixObject(o any) any {
v := reflect.ValueOf(o)
if v.Kind() != reflect.Map {
return o
}
gojsObjectIdV := v.MapIndex(GojsObjectIdKeyRef)
if !gojsObjectIdV.IsValid() {
return o
}
// 创建新的map类型与原map相同
newMap := reflect.MakeMapWithSize(v.Type(), v.Len()-1)
// 遍历原map的所有键值对
iter := v.MapRange()
for iter.Next() {
key := iter.Key()
// 跳过 gojsObjectId 键
if key.Interface() == "gojsObjectId" {
continue
}
// 将其他键值对复制到新map中
newMap.SetMapIndex(key, iter.Value())
}
return newMap.Interface()
}