From 58ca39a32b5469efc2658a7bf1c592bfddccad35 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 25 Jul 2024 15:37:23 +0800 Subject: [PATCH] [feat] Sharing the Constants variable between frontend and backend scripts --- sui/api/request.go | 8 ++ sui/core/build.go | 15 ++- sui/core/cache.go | 4 +- sui/core/injections.go | 25 +++++ sui/core/parser.go | 21 ++-- sui/core/script.go | 191 +++++++++++++++++++++++++++++++++ sui/core/types.go | 7 +- sui/storages/local/build.go | 33 +++++- sui/storages/local/template.go | 2 +- 9 files changed, 287 insertions(+), 19 deletions(-) create mode 100644 sui/core/script.go diff --git a/sui/api/request.go b/sui/api/request.go index 90d3f1c7..e9b27743 100644 --- a/sui/api/request.go +++ b/sui/api/request.go @@ -167,6 +167,7 @@ func (r *Request) Render() (string, int, error) { DisableCache: r.Request.DisableCache(), Route: r.Request.URL.Path, Root: c.Root, + Script: c.Script, Request: true, } @@ -254,6 +255,12 @@ func (r *Request) MakeCache() (*core.Cache, int, error) { return nil, 500, fmt.Errorf("parse error, please re-complie the page %s", err.Error()) } + // Backend script + script, err := core.LoadScript(r.File) + if err != nil { + return nil, 500, fmt.Errorf("script error, please re-complie the page %s", err.Error()) + } + // Save to The Cache cache := &core.Cache{ Data: dataText, @@ -266,6 +273,7 @@ func (r *Request) MakeCache() (*core.Cache, int, error) { Root: root, CacheTime: time.Duration(cacheTime) * time.Second, DataCacheTime: time.Duration(dataCacheTime) * time.Second, + Script: script, } go core.SetCache(r.File, cache) diff --git a/sui/core/build.go b/sui/core/build.go index c8136361..55dc7245 100644 --- a/sui/core/build.go +++ b/sui/core/build.go @@ -589,6 +589,16 @@ func (page *Page) BuildScripts(ctx *BuildContext, option *BuildOption, component } injectScript := componentInitScript(arguments) + // Get the Constants and Helpers + var err error = nil + constants := "" + if page.Script != nil { + constants, err = page.Script.ConstantsToString() + if err != nil { + return nil, err + } + } + scripts := []ScriptNode{} if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" { return scripts, nil @@ -599,7 +609,6 @@ func (page *Page) BuildScripts(ctx *BuildContext, option *BuildOption, component ctx.scriptUnique[component] = true - var err error = nil var imports []string = nil var source []byte = nil if page.Codes.TS.Code != "" { @@ -639,6 +648,10 @@ func (page *Page) BuildScripts(ctx *BuildContext, option *BuildOption, component }) code := string(source) + if constants != "" { + code = fmt.Sprintf("this.Constants = %s\n%s", constants, code) + } + parent := "body" if !ispage { parent = "head" diff --git a/sui/core/cache.go b/sui/core/cache.go index 544ee3aa..c3cc176d 100644 --- a/sui/core/cache.go +++ b/sui/core/cache.go @@ -4,7 +4,6 @@ import ( "time" jsoniter "github.com/json-iterator/go" - v8 "github.com/yaoapp/gou/runtime/v8" "github.com/yaoapp/gou/store" "github.com/yaoapp/kun/log" ) @@ -21,7 +20,7 @@ type Cache struct { CacheStore string CacheTime time.Duration DataCacheTime time.Duration - Script *v8.Script // the backend script + Script *Script } const ( @@ -73,6 +72,7 @@ func GetCache(file string) *Cache { // RemoveCache remove the cache func RemoveCache(file string) { ch <- &cacheData{file, nil, removeCache} + chScript <- &scriptData{file, nil, removeScript} } // CleanCache clean the cache diff --git a/sui/core/injections.go b/sui/core/injections.go index afd08b2c..8eadefc4 100644 --- a/sui/core/injections.go +++ b/sui/core/injections.go @@ -186,6 +186,26 @@ const componentInitScriptTmpl = ` this.store = new __sui_store(this.root); ` +// Inject code +const backendScriptTmpl = ` +this.__sui_page = '%s'; +this.__sui_constants = {}; +this.__sui_helpers = []; +this.__sui_hooks = null; + +if (typeof Helpers === 'object') { + this.__sui_helpers = Object.keys(Helpers); +} + +if (typeof Hooks === 'function') { + this.__sui_hooks = new Hooks(); +} + +if (typeof Constants === 'object') { + this.__sui_constants = Constants; +} +` + func bodyInjectionScript(jsonRaw string, debug bool) string { jsPrintData := "" if debug { @@ -209,3 +229,8 @@ func compEventInjectScript(eventID, eventName, component, dataKeys, jsonKeys, ha func componentInitScript(root string) string { return fmt.Sprintf(componentInitScriptTmpl, root) } + +// BackendScript inject the backend script +func BackendScript(route string) string { + return fmt.Sprintf(backendScriptTmpl, route) +} diff --git a/sui/core/parser.go b/sui/core/parser.go index 3b6be196..bfef4b03 100644 --- a/sui/core/parser.go +++ b/sui/core/parser.go @@ -40,16 +40,17 @@ type Mapping struct { // ParserOption parser option type ParserOption struct { - Component bool `json:"component,omitempty"` - Editor bool `json:"editor,omitempty"` - Preview bool `json:"preview,omitempty"` - Debug bool `json:"debug,omitempty"` - DisableCache bool `json:"disableCache,omitempty"` - Request bool `json:"request,omitempty"` - Route string `json:"route,omitempty"` - Theme any `json:"theme,omitempty"` - Locale any `json:"locale,omitempty"` - Root string `json:"root,omitempty"` + Component bool `json:"component,omitempty"` + Editor bool `json:"editor,omitempty"` + Preview bool `json:"preview,omitempty"` + Debug bool `json:"debug,omitempty"` + DisableCache bool `json:"disableCache,omitempty"` + Request bool `json:"request,omitempty"` + Route string `json:"route,omitempty"` + Theme any `json:"theme,omitempty"` + Locale any `json:"locale,omitempty"` + Root string `json:"root,omitempty"` + Script *Script `json:"-"` // backend script } var keepWords = map[string]bool{ diff --git a/sui/core/script.go b/sui/core/script.go new file mode 100644 index 00000000..1e9f60ce --- /dev/null +++ b/sui/core/script.go @@ -0,0 +1,191 @@ +package core + +import ( + "fmt" + "strings" + "time" + + "github.com/google/uuid" + jsoniter "github.com/json-iterator/go" + "github.com/yaoapp/gou/application" + v8 "github.com/yaoapp/gou/runtime/v8" + "github.com/yaoapp/gou/runtime/v8/bridge" +) + +// Scripts loaded scripts +var Scripts = map[string]*Script{} + +const ( + saveScript uint8 = iota + removeScript +) + +// Script the script +type Script struct { + *v8.Script +} + +type scriptData struct { + file string + script *Script + cmd uint8 +} + +var chScript = make(chan *scriptData, 1) + +func init() { + go scriptWriter() +} + +func scriptWriter() { + for { + select { + case data := <-chScript: + switch data.cmd { + case saveScript: + Scripts[data.file] = data.script + case removeScript: + delete(Scripts, data.file) + } + } + } +} + +// LoadScript load the script +func LoadScript(file string) (*Script, error) { + + if script, has := Scripts[file]; has { + return script, nil + } + + base := strings.TrimSuffix(file, ".sui") + file = base + ".ts" + if exist, _ := application.App.Exists(file); !exist { + file = base + ".js" + } + + if exist, _ := application.App.Exists(file); !exist { + return nil, nil + } + + source, err := application.App.Read(file) + if err != nil { + return nil, err + } + + v8script, err := v8.MakeScript(source, file, 5*time.Second) + if err != nil { + return nil, err + } + + script := &Script{Script: v8script} + chScript <- &scriptData{file, script, saveScript} + return script, nil +} + +// Call the script method +// This will be refactored to improve the performance +func (script *Script) Call(r *Request, method string, args ...any) (interface{}, error) { + ctx, err := script.NewContext(r.Sid, nil) + if err != nil { + return nil, err + } + defer ctx.Close() + + res, err := ctx.Call(method, args...) + if err != nil { + return nil, err + } + return res, nil +} + +// ConstantsToString get the constants from the script +func (script *Script) ConstantsToString() (string, error) { + constants, err := script.Constants() + if err != nil { + return "", err + } + raw, err := jsoniter.MarshalToString(constants) + if err != nil { + return "", err + } + return raw, nil +} + +// Constants get the constants from the script +// This will be refactored to improve the performance +func (script *Script) Constants() (map[string]interface{}, error) { + uuid := uuid.New().String() + ctx, err := script.NewContext(uuid, nil) + if err != nil { + return nil, err + } + defer ctx.Close() + + global := ctx.Global() + if global == nil { + return nil, fmt.Errorf("global is nil") + } + + if !global.Has("__sui_constants") { + return nil, nil + } + + res, err := global.Get("__sui_constants") + if err != nil { + return nil, err + } + defer res.Release() + + goValues, err := bridge.GoValue(res, ctx.Context) + if err != nil { + return nil, err + } + + if constants, ok := goValues.(map[string]interface{}); ok { + return constants, nil + } + + return nil, fmt.Errorf("constants is %v should be Record", goValues) +} + +// Helpers get the helpers from the script +// This will be refactored to improve the performance +func (script *Script) Helpers() ([]string, error) { + uuid := uuid.New().String() + ctx, err := script.NewContext(uuid, nil) + if err != nil { + return nil, err + } + defer ctx.Close() + + global := ctx.Global() + if global == nil { + return nil, fmt.Errorf("global is nil") + } + + if !global.Has("__sui_helpers") { + return nil, nil + } + + res, err := global.Get("__sui_helpers") + if err != nil { + return nil, err + } + defer res.Release() + + goValues, err := bridge.GoValue(res, ctx.Context) + if err != nil { + return nil, err + } + + if helpers, ok := goValues.([]interface{}); ok { + methods := []string{} + for _, key := range helpers { + methods = append(methods, fmt.Sprintf("%v", key)) + } + return methods, nil + } + + return nil, fmt.Errorf("helpers is %v should be []string", goValues) +} diff --git a/sui/core/types.go b/sui/core/types.go index fbc61d9c..a5f45a6a 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -5,7 +5,6 @@ import ( "regexp" "github.com/PuerkitoBio/goquery" - v8 "github.com/yaoapp/gou/runtime/v8" "golang.org/x/net/html" ) @@ -39,7 +38,7 @@ type Page struct { Path string `json:"-"` Root string `json:"-"` Codes SourceCodes `json:"-"` - Script *v8.Script `json:"-"` // The backend script name.backend.ts / name.backend.js + Script *Script `json:"-"` // The backend script name.backend.ts / name.backend.js Document []byte `json:"-"` GlobalData []byte `json:"-"` Attrs map[string]string `json:"-"` @@ -179,8 +178,8 @@ type Template struct { GlobalData []byte `json:"-"` Scripts *TemplateScirpts `json:"scripts,omitempty"` Translator string `json:"translator,omitempty"` - BuildScript *v8.Script `json:"-"` // __build.backend.ts / __build.backend.js - GlobalScript *v8.Script `json:"-"` // __global.backend.ts / __global.backend.js + BuildScript *Script `json:"-"` // __build.backend.ts / __build.backend.js + GlobalScript *Script `json:"-"` // __global.backend.ts / __global.backend.js } // TemplateScirpts is the struct for the template scripts diff --git a/sui/storages/local/build.go b/sui/storages/local/build.go index 009280bc..36c02d89 100644 --- a/sui/storages/local/build.go +++ b/sui/storages/local/build.go @@ -5,10 +5,12 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/hashicorp/go-multierror" "github.com/yaoapp/gou/application" "github.com/yaoapp/gou/process" + v8 "github.com/yaoapp/gou/runtime/v8" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/sui/core" "golang.org/x/text/language" @@ -338,7 +340,6 @@ func (tmpl *Template) getLocale(name string, route string, pageOnly ...bool) cor func (page *Page) Build(globalCtx *core.GlobalBuildContext, option *core.BuildOption) ([]string, error) { ctx := core.NewBuildContext(globalCtx) - var err error = nil root := option.PublicRoot if root == "" { @@ -353,6 +354,11 @@ func (page *Page) Build(globalCtx *core.GlobalBuildContext, option *core.BuildOp } page.Root = root + err = page.loadBackendScript() + if err != nil { + return nil, err + } + html, warnings, err := page.Page.Compile(ctx, option) if err != nil { return warnings, fmt.Errorf("Compile the page %s error: %s", page.Route, err.Error()) @@ -425,6 +431,11 @@ func (page *Page) BuildAsComponent(globalCtx *core.GlobalBuildContext, option *c option.AssetRoot = filepath.Join(root, "assets") } + err := page.loadBackendScript() + if err != nil { + return nil, err + } + html, messages, err := page.Page.CompileAsComponent(ctx, option) if err != nil { return warnings, err @@ -656,9 +667,29 @@ func (page *Page) backendScriptSource() (string, []byte, error) { return "", nil, err } + source = []byte(fmt.Sprintf("%s\n%s", source, core.BackendScript(page.Route))) return backendFile, source, nil } +func (page *Page) loadBackendScript() error { + file, source, err := page.backendScriptSource() + if err != nil { + return err + } + + if source == nil { + return nil + } + approot := page.tmpl.local.AppRoot() + file = filepath.Join(approot, file) + script, err := v8.MakeScript(source, file, 5*time.Second) + if err != nil { + return err + } + page.Script = &core.Script{Script: script} + return nil +} + func (page *Page) writeBackendScript(data map[string]interface{}) error { file, source, err := page.backendScriptSource() diff --git a/sui/storages/local/template.go b/sui/storages/local/template.go index 5d2ef00f..52894d72 100644 --- a/sui/storages/local/template.go +++ b/sui/storages/local/template.go @@ -103,7 +103,7 @@ func (tmpl *Template) loadBuildScript() error { if err != nil { return err } - tmpl.BuildScript = script + tmpl.BuildScript = &core.Script{Script: script} return nil }