diff --git a/go.mod b/go.mod index 7e31a580..0d86ec76 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/dchest/captcha v1.0.0 github.com/elazarl/go-bindata-assetfs v1.0.1 github.com/evanw/esbuild v0.19.5 - github.com/expr-lang/expr v1.15.8 + github.com/expr-lang/expr v1.16.9 github.com/fatih/color v1.16.0 github.com/fsnotify/fsnotify v1.7.0 github.com/gin-gonic/gin v1.9.1 diff --git a/go.sum b/go.sum index 3e781616..5cc00b72 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/evanw/esbuild v0.19.5 h1:9ildZqajUJzDAwNf9MyQsLh2RdDRKTq3kcyyzhE39us= github.com/evanw/esbuild v0.19.5/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48= github.com/expr-lang/expr v1.15.8 h1:FL8+d3rSSP4tmK9o+vKfSMqqpGL8n15pEPiHcnBpxoI= github.com/expr-lang/expr v1.15.8/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= +github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= +github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= diff --git a/sui/core/build.go b/sui/core/build.go index 4121331d..912a88dc 100644 --- a/sui/core/build.go +++ b/sui/core/build.go @@ -140,6 +140,18 @@ func (page *Page) BuildForImport(ctx *BuildContext, option *BuildOption, slots m func (page *Page) parse(ctx *BuildContext, doc *goquery.Document, option *BuildOption, warnings []string) error { + sui := SUIs[page.SuiID] + if sui == nil { + return fmt.Errorf("SUI %s not found", page.SuiID) + } + + tmpl, err := sui.GetTemplate(page.TemplateID) + if err != nil { + return err + } + + public := sui.GetPublic() + // Find the import pages pages := doc.Find("*").FilterFunction(func(i int, sel *goquery.Selection) bool { @@ -158,23 +170,18 @@ func (page *Page) parse(ctx *BuildContext, doc *goquery.Document, option *BuildO } name, has := sel.Attr("is") - _, jit := sel.Attr("s:jit") // Just in time rendering - if has && jit { - ctx.addJitComponent(name) + if has { + // Just in time component + if ctx.isJitComponent(name) { + sel.SetAttr("s:jit", "true") + sel.SetAttr("s:root", public.Root) + ctx.addJitComponent(name) + return false + } } - return has && !jit + return has }) - sui := SUIs[page.SuiID] - if sui == nil { - return fmt.Errorf("SUI %s not found", page.SuiID) - } - - tmpl, err := sui.GetTemplate(page.TemplateID) - if err != nil { - return err - } - for _, node := range pages.Nodes { sel := goquery.NewDocumentFromNode(node) name, has := sel.Attr("is") diff --git a/sui/core/context.go b/sui/core/context.go index 6e75f0d8..eaa74c48 100644 --- a/sui/core/context.go +++ b/sui/core/context.go @@ -52,3 +52,9 @@ func (ctx *BuildContext) addJitComponent(name string) { ctx.global.jitComponents[name] = true } } + +func (ctx *BuildContext) isJitComponent(name string) bool { + hasStmt := stmtRe.MatchString(name) + hasProp := propRe.MatchString(name) + return hasStmt || hasProp +} diff --git a/sui/core/parser.go b/sui/core/parser.go index 94f47942..45c6e3fe 100644 --- a/sui/core/parser.go +++ b/sui/core/parser.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "os" "path/filepath" "strings" @@ -13,6 +14,9 @@ import ( "gopkg.in/yaml.v3" ) +// Load the jit components +var components = map[string]string{} + // TemplateParser parser for the template type TemplateParser struct { data Data @@ -22,6 +26,11 @@ type TemplateParser struct { replace map[*goquery.Selection][]*html.Node // replace nodes option *ParserOption // parser option locale *Locale // locale + context *ParserContext // parser context +} + +// ParserContext parser context for the template +type ParserContext struct { } // Mapping mapping for the template @@ -33,6 +42,7 @@ 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"` @@ -190,7 +200,7 @@ func (parser *TemplateParser) Render(html string) (string, error) { // Append the data to the body body := doc.Find("body") - if body.Length() > 0 { + if body.Length() > 0 && !parser.option.Component { data, err := jsoniter.MarshalToString(parser.data) if err != nil { data, _ = jsoniter.MarshalToString(map[string]string{"error": err.Error()}) @@ -273,6 +283,11 @@ func (parser *TemplateParser) parseElementNode(sel *goquery.Selection) { parser.setStatementNode(sel) } + // JIT Compile the element + if _, exist := sel.Attr("s:jit"); exist { + parser.parseJitElementNode(sel) + } + // Parse the attributes parser.parseElementAttrs(sel) @@ -280,6 +295,42 @@ func (parser *TemplateParser) parseElementNode(sel *goquery.Selection) { parser.transElementNode(sel) } +func (parser *TemplateParser) parseJitElementNode(sel *goquery.Selection) { + + is := sel.AttrOr("is", "") + if is == "" { + sel.Remove() + return + } + + // Render the JIT component Data + is, _ = parser.data.Replace(is) + root := sel.AttrOr("s:root", "/") + file := filepath.Join(string(os.PathSeparator), "public", root, is+".jit") + + // Should be cached to reduce the file read and unnecessary parsing + content, err := application.App.Read(file) + if err != nil { + log.Error("[parser] %s JIT %s", file, err.Error()) + return + } + + // With Properties + + // copy options + option := *parser.option + option.Component = true + p := NewTemplateParser(parser.data, &option) + html, err := p.Render(string(content)) + if err != nil { + log.Error("[parser] %s JIT %s", file, err.Error()) + return + } + + // Replace the node + sel.ReplaceWithHtml(html) +} + func (parser *TemplateParser) transElementNode(sel *goquery.Selection) { if parser.locale == nil { return diff --git a/sui/storages/local/build.go b/sui/storages/local/build.go index 90d1c54d..bfccb013 100644 --- a/sui/storages/local/build.go +++ b/sui/storages/local/build.go @@ -229,7 +229,7 @@ func (page *Page) Build(globalCtx *core.GlobalBuildContext, option *core.BuildOp // BuildAsComponent build the page as component func (page *Page) BuildAsComponent(globalCtx *core.GlobalBuildContext, option *core.BuildOption) error { - return page.writeJitHTML([]byte("
"+page.Route+"
"), option.Data) + return page.writeJitHTML([]byte("
"+page.Route+" {{ type }} {{ bind }}
"), option.Data) } func (page *Page) publicFile(data map[string]interface{}) string {