From fbfd3f270afd8e032e240f001ffd23303026e461 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 20 Jul 2024 10:47:46 +0800 Subject: [PATCH 1/3] Refactor event binding logic in SUI core --- sui/core/build.go | 15 ++++++++--- sui/core/compile.go | 3 +++ sui/core/event.go | 25 +++++++++++++++--- sui/core/injections.go | 58 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 88 insertions(+), 13 deletions(-) diff --git a/sui/core/build.go b/sui/core/build.go index 0009c90c..b1b415c4 100644 --- a/sui/core/build.go +++ b/sui/core/build.go @@ -170,6 +170,9 @@ func (page *Page) BuildAsComponent(sel *goquery.Selection, ctx *BuildContext, op return "", err } + // Bind the component events + page.BindEvent(ctx, doc.Selection, component) + body := doc.Selection.Find("body") if body.Children().Length() == 0 { @@ -553,6 +556,11 @@ func (page *Page) BuildScripts(ctx *BuildContext, option *BuildOption, component component = ComponentName(page.Route, option.ScriptMinify) } + arguments := "" + if !ispage { + arguments = "arguments[0]" + } + scripts := []ScriptNode{} if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" { return scripts, nil @@ -566,17 +574,18 @@ func (page *Page) BuildScripts(ctx *BuildContext, option *BuildOption, component var imports []string = nil var source []byte = nil if page.Codes.TS.Code != "" { - source, imports, err = page.CompileTS([]byte(page.Codes.TS.Code), option.ScriptMinify) + code := fmt.Sprintf("this.store = new __sui_store(%s);\n%s", arguments, page.Codes.TS.Code) + source, imports, err = page.CompileTS([]byte(code), option.ScriptMinify) if err != nil { return nil, err } } else if page.Codes.JS.Code != "" { - source, imports, err = page.CompileJS([]byte(page.Codes.JS.Code), option.ScriptMinify) + code := fmt.Sprintf("this.store = new __sui_store(%s);\n%s", arguments, page.Codes.JS.Code) + source, imports, err = page.CompileJS([]byte(code), option.ScriptMinify) if err != nil { return nil, err } - } // Add the script diff --git a/sui/core/compile.go b/sui/core/compile.go index 27cd6735..fb5a0693 100644 --- a/sui/core/compile.go +++ b/sui/core/compile.go @@ -58,6 +58,9 @@ func (page *Page) Compile(ctx *BuildContext, option *BuildOption) (string, []str } + // SUI lib + head.AppendHtml("\n\n" + `` + "\n\n") + // Page Config page.Config = page.GetConfig() diff --git a/sui/core/event.go b/sui/core/event.go index c959da48..dcdc6890 100644 --- a/sui/core/event.go +++ b/sui/core/event.go @@ -10,14 +10,26 @@ import ( ) // BindEvent is a method that binds events to the page. -func (page *Page) BindEvent(ctx *BuildContext, sel *goquery.Selection) { +func (page *Page) BindEvent(ctx *BuildContext, sel *goquery.Selection, component ...string) { + + // Component events + if len(component) > 0 && component[0] != "" { + fmt.Println("component events:", page.Route, component[0]) + matcher := NewAttrPrefixMatcher(`s:on-`) + sel.FindMatcher(matcher).Each(func(i int, s *goquery.Selection) { + fmt.Println("\tcomponent event", i, s.Nodes[0].Data) + page.appendEventScript(ctx, s, component[0]) + }) + return + } + matcher := NewAttrPrefixMatcher(`s:on-`) sel.FindMatcher(matcher).Each(func(i int, s *goquery.Selection) { - page.appendEventScript(ctx, s) + page.appendEventScript(ctx, s, "") }) } -func (page *Page) appendEventScript(ctx *BuildContext, sel *goquery.Selection) { +func (page *Page) appendEventScript(ctx *BuildContext, sel *goquery.Selection, component string) { if len(sel.Nodes) == 0 { return @@ -71,13 +83,18 @@ func (page *Page) appendEventScript(ctx *BuildContext, sel *goquery.Selection) { source := "" for name, handler := range events { - source += pageEventInjectScript(id, name, dataRaw, jsonRaw, handler) + "\n" + if component == "" { + source += pageEventInjectScript(id, name, dataRaw, jsonRaw, handler) + "\n" + } else { + source += compEventInjectScript(id, name, component, dataRaw, jsonRaw, handler) + "\n" + } sel.RemoveAttr(fmt.Sprintf("s:on-%s", name)) } ctx.scripts = append(ctx.scripts, ScriptNode{ Source: source, Namespace: page.namespace, + Component: component, Attrs: []html.Attribute{{Key: "event", Val: id}}, }) diff --git a/sui/core/injections.go b/sui/core/injections.go index 14eb5447..70375ad8 100644 --- a/sui/core/injections.go +++ b/sui/core/injections.go @@ -2,11 +2,7 @@ package core import "fmt" -const initScriptTmpl = ` - try { - var __sui_data = %s; - } catch (e) { console.log('init data error:', e); } - +const suiLibScript = ` function __sui_event_handler(event, dataKeys, jsonKeys, elm, handler) { const data = {}; @@ -26,10 +22,47 @@ const initScriptTmpl = ` } } }) - + handler && handler(event, data, elm); }; + function __sui_store(elm) { + elm = elm || document.body; + + this.Get = function (key) { + return elm.getAttribute("data:" + key); + } + + this.Set = function (key, value) { + elm.setAttribute("data:" + key, value); + } + + this.GetJSON = function (key) { + const value = elm.getAttribute("json:" + key); + if (value && value != "") { + try { + const res = JSON.parse(value); + return res; + } catch (e) { + const message = e.message || e || "An error occurred"; + console.error(` + "`[SUI] Event Handler Error: ${message}`" + `, elm); + return null; + } + } + return null; + } + + this.SetJSON = function (key, value) { + elm.setAttribute("json:" + key, JSON.stringify(value)); + } + } +` + +const initScriptTmpl = ` + try { + var __sui_data = %s; + } catch (e) { console.log('init data error:', e); } + document.addEventListener("DOMContentLoaded", function () { try { document.querySelectorAll("[s\\:ready]").forEach(function (element) { @@ -71,6 +104,15 @@ const pageEventScriptTmpl = ` }); ` +const compEventScriptTmpl = ` + document.querySelector("[s\\:event=%s]").addEventListener("%s", function (event) { + const dataKeys = %s; + const jsonKeys = %s; + const handler = new %s(this).%s; + __sui_event_handler(event, dataKeys, jsonKeys, this, handler); + }); +` + func bodyInjectionScript(jsonRaw string, debug bool) string { jsPrintData := "" if debug { @@ -86,3 +128,7 @@ func headInjectionScript(jsonRaw string) string { func pageEventInjectScript(eventID, eventName, dataKeys, jsonKeys, handler string) string { return fmt.Sprintf(pageEventScriptTmpl, eventID, eventName, dataKeys, jsonKeys, handler) } + +func compEventInjectScript(eventID, eventName, component, dataKeys, jsonKeys, handler string) string { + return fmt.Sprintf(compEventScriptTmpl, eventID, eventName, dataKeys, jsonKeys, component, handler) +} From 258163497a9b7127eada7b48c23d2c4796cdf6aa Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 20 Jul 2024 15:42:08 +0800 Subject: [PATCH 2/3] Refactor event binding logic in SUI core --- sui/core/build.go | 6 ++++-- sui/core/compile.go | 1 + sui/core/event.go | 49 ++++++++++++++++++++------------------------- sui/core/jit.go | 1 + sui/core/types.go | 1 + 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/sui/core/build.go b/sui/core/build.go index b1b415c4..f1b32eb8 100644 --- a/sui/core/build.go +++ b/sui/core/build.go @@ -55,7 +55,9 @@ func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Docume doc.Find("body").SetAttr("s:ns", namespace) // Bind the Page events - page.BindEvent(ctx, doc.Selection) + if !option.JitMode { + page.BindEvent(ctx, doc.Selection, "__page", true) + } warnings, err := page.buildComponents(doc, ctx, option) if err != nil { @@ -171,7 +173,7 @@ func (page *Page) BuildAsComponent(sel *goquery.Selection, ctx *BuildContext, op } // Bind the component events - page.BindEvent(ctx, doc.Selection, component) + page.BindEvent(ctx, doc.Selection, component, false) body := doc.Selection.Find("body") diff --git a/sui/core/compile.go b/sui/core/compile.go index fb5a0693..84b94981 100644 --- a/sui/core/compile.go +++ b/sui/core/compile.go @@ -114,6 +114,7 @@ func (page *Page) CompileAsComponent(ctx *BuildContext, option *BuildOption) (st opt := *option opt.IgnoreDocument = true opt.WithWrapper = true + opt.JitMode = true doc, warnings, err := page.Build(ctx, &opt) if err != nil { return "", warnings, err diff --git a/sui/core/event.go b/sui/core/event.go index dcdc6890..ab4b7345 100644 --- a/sui/core/event.go +++ b/sui/core/event.go @@ -10,37 +10,32 @@ import ( ) // BindEvent is a method that binds events to the page. -func (page *Page) BindEvent(ctx *BuildContext, sel *goquery.Selection, component ...string) { - - // Component events - if len(component) > 0 && component[0] != "" { - fmt.Println("component events:", page.Route, component[0]) - matcher := NewAttrPrefixMatcher(`s:on-`) - sel.FindMatcher(matcher).Each(func(i int, s *goquery.Selection) { - fmt.Println("\tcomponent event", i, s.Nodes[0].Data) - page.appendEventScript(ctx, s, component[0]) - }) - return - } - +func (page *Page) BindEvent(ctx *BuildContext, sel *goquery.Selection, component string, ispage bool) { matcher := NewAttrPrefixMatcher(`s:on-`) sel.FindMatcher(matcher).Each(func(i int, s *goquery.Selection) { - page.appendEventScript(ctx, s, "") + if comp, has := s.Attr("is"); has && ctx.isJitComponent(comp) { + return + } + script := GetEventScript(&ctx.sequence, s, page.namespace, component, ispage) + if script != nil { + ctx.scripts = append(ctx.scripts, *script) + } }) } -func (page *Page) appendEventScript(ctx *BuildContext, sel *goquery.Selection, component string) { +// GetEventScript the event script +func GetEventScript(sequence *int, sel *goquery.Selection, ns string, cn string, ispage bool) *ScriptNode { if len(sel.Nodes) == 0 { - return + return nil } // Page events events := map[string]string{} dataUnique := map[string]string{} jsonUnique := map[string]string{} - id := fmt.Sprintf("event-%d", ctx.sequence) - ctx.sequence++ + id := fmt.Sprintf("event-%d", *sequence) + *sequence = *sequence + 1 for _, attr := range sel.Nodes[0].Attr { @@ -83,20 +78,20 @@ func (page *Page) appendEventScript(ctx *BuildContext, sel *goquery.Selection, c source := "" for name, handler := range events { - if component == "" { + if ispage { source += pageEventInjectScript(id, name, dataRaw, jsonRaw, handler) + "\n" } else { - source += compEventInjectScript(id, name, component, dataRaw, jsonRaw, handler) + "\n" + source += compEventInjectScript(id, name, cn, dataRaw, jsonRaw, handler) + "\n" } sel.RemoveAttr(fmt.Sprintf("s:on-%s", name)) } - ctx.scripts = append(ctx.scripts, ScriptNode{ - Source: source, - Namespace: page.namespace, - Component: component, - Attrs: []html.Attribute{{Key: "event", Val: id}}, - }) - sel.SetAttr("s:event", id) + + return &ScriptNode{ + Source: source, + Namespace: ns, + Component: cn, + Attrs: []html.Attribute{{Key: "event", Val: id}}, + } } diff --git a/sui/core/jit.go b/sui/core/jit.go index 73d8e7b6..9803d4ae 100644 --- a/sui/core/jit.go +++ b/sui/core/jit.go @@ -62,6 +62,7 @@ func (parser *TemplateParser) parseComponent(sel *goquery.Selection) { return } + // fmt.Println(sel.Nodes[0].Attr) sel.SetHtml(html) } diff --git a/sui/core/types.go b/sui/core/types.go index 7c81197b..6e21a859 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -253,6 +253,7 @@ type BuildOption struct { AssetRoot string `json:"asset_root,omitempty"` IgnoreAssetRoot bool `json:"ignore_asset_root,omitempty"` IgnoreDocument bool `json:"ignore_document,omitempty"` + JitMode bool `json:"jit_mode,omitempty"` WithWrapper bool `json:"with_wrapper,omitempty"` KeepPageTag bool `json:"keep_page_tag,omitempty"` Namespace string `json:"namespace,omitempty"` From f32a6772654842932fbb3e203b5e844b959dab24 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 20 Jul 2024 17:45:19 +0800 Subject: [PATCH 3/3] Refactor event binding logic in SUI core --- sui/core/compile.go | 3 ++ sui/core/event.go | 30 ++++++++---- sui/core/jit.go | 108 ++++++++++++++++++++++++++------------------ sui/core/parser.go | 3 +- 4 files changed, 92 insertions(+), 52 deletions(-) diff --git a/sui/core/compile.go b/sui/core/compile.go index 84b94981..a83bbf9d 100644 --- a/sui/core/compile.go +++ b/sui/core/compile.go @@ -266,6 +266,9 @@ func (script ScriptNode) ComponentHTML(ns string) string { } source := fmt.Sprintf(`function %s(){%s};`, script.Component, script.Source) + if script.Component == "" { + return "" + } return "" } diff --git a/sui/core/event.go b/sui/core/event.go index ab4b7345..b231de48 100644 --- a/sui/core/event.go +++ b/sui/core/event.go @@ -9,22 +9,38 @@ import ( "golang.org/x/net/html" ) +var eventMatcher = NewAttrPrefixMatcher(`s:on-`) + // BindEvent is a method that binds events to the page. -func (page *Page) BindEvent(ctx *BuildContext, sel *goquery.Selection, component string, ispage bool) { - matcher := NewAttrPrefixMatcher(`s:on-`) - sel.FindMatcher(matcher).Each(func(i int, s *goquery.Selection) { +func (page *Page) BindEvent(ctx *BuildContext, sel *goquery.Selection, cn string, ispage bool) { + + sel.FindMatcher(eventMatcher).Each(func(i int, s *goquery.Selection) { if comp, has := s.Attr("is"); has && ctx.isJitComponent(comp) { return } - script := GetEventScript(&ctx.sequence, s, page.namespace, component, ispage) + script := GetEventScript(ctx.sequence, s, page.namespace, cn, "event", ispage) if script != nil { ctx.scripts = append(ctx.scripts, *script) + ctx.sequence++ + } + }) +} + +// BindEvent is a method that binds events to the component in just-in-time mode. +func (parser *TemplateParser) BindEvent(sel *goquery.Selection, ns string, cn string) { + sel.FindMatcher(eventMatcher).Each(func(i int, s *goquery.Selection) { + script := GetEventScript(parser.sequence, s, ns, cn, "event-jit", false) + if script != nil { + script.Component = "" + script.Parent = "body" + parser.scripts = append(parser.scripts, *script) + parser.sequence++ } }) } // GetEventScript the event script -func GetEventScript(sequence *int, sel *goquery.Selection, ns string, cn string, ispage bool) *ScriptNode { +func GetEventScript(sequence int, sel *goquery.Selection, ns string, cn string, prefix string, ispage bool) *ScriptNode { if len(sel.Nodes) == 0 { return nil @@ -34,9 +50,7 @@ func GetEventScript(sequence *int, sel *goquery.Selection, ns string, cn string, events := map[string]string{} dataUnique := map[string]string{} jsonUnique := map[string]string{} - id := fmt.Sprintf("event-%d", *sequence) - *sequence = *sequence + 1 - + id := fmt.Sprintf("%s-%d", prefix, sequence) for _, attr := range sel.Nodes[0].Attr { if strings.HasPrefix(attr.Key, "s:on-") { diff --git a/sui/core/jit.go b/sui/core/jit.go index 9803d4ae..64737c86 100644 --- a/sui/core/jit.go +++ b/sui/core/jit.go @@ -6,7 +6,6 @@ import ( "path/filepath" "regexp" "strings" - "time" "github.com/PuerkitoBio/goquery" jsoniter "github.com/json-iterator/go" @@ -69,15 +68,17 @@ func (parser *TemplateParser) parseComponent(sel *goquery.Selection) { // RenderComponent render the component func (parser *TemplateParser) RenderComponent(comp *JitComponent, props map[string]interface{}, slots *goquery.Selection, children *goquery.Selection) (string, string, error) { html := comp.html - randvar := fmt.Sprintf("__%s_$props", time.Now().Format("20060102150405")) - html = replaceRandVar(html, randvar) - data := Data{} - data[randvar] = props - if parser.data != nil { - for key, val := range parser.data { - data[key] = val - } - } + + html = replaceRandVar(html, Data(props)) + option := *parser.option + option.Route = comp.route + compParser := NewTemplateParser(parser.data, &option) + compParser.sequence = parser.sequence + 1 + locale := compParser.Locale() + + // Parse the node + ns := Namespace(comp.route, compParser.sequence, comp.buildOption.ScriptMinify) + cn := ComponentName(comp.route, comp.buildOption.ScriptMinify) sel, err := NewDocumentString(`` + html + ``) if err != nil { @@ -107,24 +108,26 @@ func (parser *TemplateParser) RenderComponent(comp *JitComponent, props map[stri children.Find("slot").Remove() root.Find("children").ReplaceWithSelection(children) - option := *parser.option - option.Route = comp.route - compParser := NewTemplateParser(data, &option) - locale := compParser.Locale() + // Replace the props if locale != nil { - locale.replaceVars(randvar) + locale.replaceVars(Data(props)) } - compParser.locale = locale - // Parse the node - ns := Namespace(comp.route, compParser.sequence, comp.buildOption.ScriptMinify) - cn := ComponentName(comp.route, comp.buildOption.ScriptMinify) + compParser.locale = locale + compParser.BindEvent(root, ns, cn) compParser.parseNode(root.Get(0)) for sel, nodes := range compParser.replace { sel.ReplaceWithNodes(nodes...) delete(parser.replace, sel) } + if compParser.scripts != nil { + for _, script := range compParser.scripts { + script.Namespace = ns + parser.scripts = append(parser.scripts, script) + } + } + if comp.scripts != nil { for _, script := range comp.scripts { script.Namespace = ns @@ -145,6 +148,7 @@ func (parser *TemplateParser) RenderComponent(comp *JitComponent, props map[stri first.SetAttr("s:ns", ns) first.SetAttr("s:cn", cn) first.SetAttr("s:ready", cn+"()") + html, err = root.Html() if err != nil { return "", "", err @@ -152,15 +156,28 @@ func (parser *TemplateParser) RenderComponent(comp *JitComponent, props map[stri return html, ns, nil } -func (parser *TemplateParser) addScripts(sel *goquery.Selection, scripts []ScriptNode) { +func (parser *TemplateParser) filterScripts(parent string, scripts []ScriptNode) []ScriptNode { if scripts == nil { - return + return []ScriptNode{} } + filtered := []ScriptNode{} for _, script := range scripts { - query := fmt.Sprintf(`script[s\:cn="%s"]`, script.Component) - if sel.Find(query).Length() > 0 { + if script.Parent != parent { continue } + filtered = append(filtered, script) + } + return filtered +} + +func (parser *TemplateParser) addScripts(sel *goquery.Selection, scripts []ScriptNode) { + for _, script := range scripts { + if script.Component != "" { + query := fmt.Sprintf(`script[s\:cn="%s"]`, script.Component) + if sel.Find(query).Length() > 0 { + continue + } + } sel.AppendHtml(script.ComponentHTML(script.Namespace)) } } @@ -227,6 +244,13 @@ func (parser *TemplateParser) componentProps(sel *goquery.Selection) (map[string } for _, attr := range sel.Nodes[0].Attr { + + // s:on , s:data , s:json + if strings.HasPrefix(attr.Key, "s:on") || strings.HasPrefix(attr.Key, "s:data") || strings.HasPrefix(attr.Key, "s:json") { + props[attr.Key] = attr.Val + continue + } + if strings.HasPrefix(attr.Key, "s:") || attr.Key == "is" { continue } @@ -269,22 +293,13 @@ func (parser *TemplateParser) parseComponentProps(props map[string]string) (map[ } if _, ok := values.(map[string]interface{}); ok { - for k, v := range values.(map[string]interface{}) { - result[k] = v + for k := range values.(map[string]interface{}) { + result[k] = k } } continue } - if strings.HasPrefix(val, "{{") && strings.HasSuffix(val, "}}") { - value, err := parser.data.Exec(val) - if err != nil { - return map[string]interface{}{}, err - } - result[key] = value - continue - } - result[key] = val } return result, nil @@ -304,16 +319,16 @@ func (parser *TemplateParser) componentFile(sel *goquery.Selection, props map[st return file, route, nil } -func (locale *Locale) replaceVars(randvar string) { +func (locale *Locale) replaceVars(data Data) { if locale.Keys != nil { for key, val := range locale.Keys { - locale.Keys[key] = replaceRandVar(val, randvar) + locale.Keys[key] = replaceRandVar(val, data) } } if locale.Messages != nil { for key, val := range locale.Messages { - locale.Messages[key] = replaceRandVar(val, randvar) + locale.Messages[key] = replaceRandVar(val, data) } } } @@ -415,11 +430,18 @@ func readComponent(route string, file string) (*JitComponent, error) { }, nil } -func replaceRandVar(html string, randvar string) string { - return slotRe.ReplaceAllStringFunc(html, func(exp string) string { - exp = strings.ReplaceAll(exp, "[{", "{{") - exp = strings.ReplaceAll(exp, "}]", "}}") - exp = strings.ReplaceAll(exp, "$props", randvar) - return exp +func replaceRandVar(value string, data Data) string { + + value = propNewRe.ReplaceAllStringFunc(value, func(exp string) string { + exp = strings.TrimPrefix(exp, "{%") + exp = strings.TrimSuffix(exp, "%}") + res, _ := data.ExecString(fmt.Sprintf("{{ %s }}", exp)) + return res + }) + + data = Data{"$props": data} + return slotRe.ReplaceAllStringFunc(value, func(exp string) string { + res, _ := data.ExecString(exp) + return res }) } diff --git a/sui/core/parser.go b/sui/core/parser.go index 175f987e..a3db3bed 100644 --- a/sui/core/parser.go +++ b/sui/core/parser.go @@ -134,7 +134,7 @@ func (parser *TemplateParser) Render(html string) (string, error) { } head.AppendHtml(headInjectionScript(data)) - parser.addScripts(head, parser.scripts) + parser.addScripts(head, parser.filterScripts("head", parser.scripts)) parser.addStyles(head, parser.styles) } @@ -146,6 +146,7 @@ func (parser *TemplateParser) Render(html string) (string, error) { data, _ = jsoniter.MarshalToString(map[string]string{"error": err.Error()}) } body.AppendHtml(bodyInjectionScript(data, parser.debug())) + parser.addScripts(body, parser.filterScripts("body", parser.scripts)) } // Fmt