gojs/gojs.go
2025-07-18 22:40:17 +08:00

1089 lines
26 KiB
Go
Raw 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"
"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有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 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 调试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.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")
}