[feat] SUI support BeforeRender hook for dataset processing, and deprecating the s:set method.
This commit is contained in:
parent
8b93c3b219
commit
276f29d777
11 changed files with 174 additions and 59 deletions
|
|
@ -168,7 +168,8 @@ func (r *Request) Render() (string, int, error) {
|
|||
Route: r.Request.URL.Path,
|
||||
Root: c.Root,
|
||||
Script: c.Script,
|
||||
Request: true,
|
||||
Imports: c.Imports,
|
||||
Request: r.Request,
|
||||
}
|
||||
|
||||
// Parse the template
|
||||
|
|
@ -250,6 +251,17 @@ func (r *Request) MakeCache() (*core.Cache, int, error) {
|
|||
globalDataSel.Remove()
|
||||
}
|
||||
|
||||
var imports map[string]string
|
||||
importsSel := doc.Find("script[name=imports]")
|
||||
if importsSel != nil && importsSel.Length() > 0 {
|
||||
importsRaw := importsSel.Text()
|
||||
importsSel.Remove()
|
||||
err := jsoniter.UnmarshalFromString(importsRaw, &imports)
|
||||
if err != nil {
|
||||
return nil, 500, fmt.Errorf("imports error, please re-complie the page %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
html, err := doc.Html()
|
||||
if err != nil {
|
||||
return nil, 500, fmt.Errorf("parse error, please re-complie the page %s", err.Error())
|
||||
|
|
@ -274,6 +286,7 @@ func (r *Request) MakeCache() (*core.Cache, int, error) {
|
|||
CacheTime: time.Duration(cacheTime) * time.Second,
|
||||
DataCacheTime: time.Duration(dataCacheTime) * time.Second,
|
||||
Script: script,
|
||||
Imports: imports,
|
||||
}
|
||||
|
||||
go core.SetCache(r.File, cache)
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ func makeParser(route string, t *testing.T) (*core.TemplateParser, string, core.
|
|||
Debug: r.Request.DebugMode(),
|
||||
DisableCache: r.Request.DisableCache(),
|
||||
Route: r.Request.URL.Path,
|
||||
Request: true,
|
||||
Request: r.Request,
|
||||
}
|
||||
|
||||
// Parse the template
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ func (page *Page) BuildAsComponent(sel *goquery.Selection, ctx *BuildContext, op
|
|||
ctx.styles = append(ctx.styles, styles...)
|
||||
|
||||
sel.ReplaceWithSelection(body.Contents())
|
||||
ctx.components[page.Route] = true
|
||||
ctx.components[component] = page.Route
|
||||
return source, nil
|
||||
}
|
||||
|
||||
|
|
@ -456,7 +456,6 @@ func (page *Page) buildComponents(doc *goquery.Document, ctx *BuildContext, opti
|
|||
return warnings, fmt.Errorf("SUI %s not found", page.SuiID)
|
||||
}
|
||||
|
||||
public := sui.GetPublic()
|
||||
tmpl, err := sui.GetTemplate(page.TemplateID)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
|
|
@ -480,7 +479,6 @@ func (page *Page) buildComponents(doc *goquery.Document, ctx *BuildContext, opti
|
|||
if ctx.isJitComponent(name) {
|
||||
sel.SetAttr("s:jit", "true")
|
||||
sel.SetAttr("s:parent", page.namespace)
|
||||
sel.SetAttr("s:root", public.Root)
|
||||
ctx.addJitComponent(name)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ type Cache struct {
|
|||
CacheTime time.Duration
|
||||
DataCacheTime time.Duration
|
||||
Script *Script
|
||||
Imports map[string]string
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -93,13 +93,7 @@ func (page *Page) Compile(ctx *BuildContext, option *BuildOption) (string, []str
|
|||
}
|
||||
|
||||
// Page Components
|
||||
components := []string{}
|
||||
if ctx != nil && ctx.components != nil && len(ctx.components) > 0 {
|
||||
for route := range ctx.components {
|
||||
components = append(components, route)
|
||||
}
|
||||
}
|
||||
rawComponents, _ := jsoniter.MarshalToString(components)
|
||||
rawComponents, _ := jsoniter.MarshalToString(ctx.components)
|
||||
body.AppendHtml("\n\n" + `<script name="imports" type="json">` + "\n" + rawComponents + "\n</script>\n\n")
|
||||
|
||||
page.ReplaceDocument(doc)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package core
|
|||
// NewBuildContext create a new build context
|
||||
func NewBuildContext(global *GlobalBuildContext) *BuildContext {
|
||||
return &BuildContext{
|
||||
components: map[string]bool{},
|
||||
components: map[string]string{},
|
||||
sequence: 1,
|
||||
scripts: []ScriptNode{},
|
||||
scriptUnique: map[string]bool{},
|
||||
|
|
|
|||
|
|
@ -191,16 +191,11 @@ 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ func init() {
|
|||
}
|
||||
|
||||
// parseComponent parse the component
|
||||
func (parser *TemplateParser) parseComponent(sel *goquery.Selection) {
|
||||
func (parser *TemplateParser) parseJitComponent(sel *goquery.Selection) {
|
||||
parser.parsed(sel)
|
||||
comp, props, slots, children, err := parser.getComponent(sel)
|
||||
if err != nil {
|
||||
|
|
@ -218,8 +218,8 @@ func (parser *TemplateParser) getComponent(sel *goquery.Selection) (*JitComponen
|
|||
return comp, props, slots, children, nil
|
||||
}
|
||||
|
||||
// isComponent check if the selection is a component
|
||||
func (parser *TemplateParser) isComponent(sel *goquery.Selection) bool {
|
||||
// isJitComponent check if the selection is a component
|
||||
func (parser *TemplateParser) isJitComponent(sel *goquery.Selection) bool {
|
||||
_, exist := sel.Attr("s:jit")
|
||||
is := sel.AttrOr("is", "")
|
||||
return exist && is != ""
|
||||
|
|
@ -314,8 +314,7 @@ func (parser *TemplateParser) componentFile(sel *goquery.Selection, props map[st
|
|||
data := Data{"$props": props}
|
||||
route, _ = data.ReplaceUse(slotRe, route)
|
||||
route, _ = parser.data.Replace(route)
|
||||
root := sel.AttrOr("s:root", "/")
|
||||
file := filepath.Join(string(os.PathSeparator), "public", root, route+".jit")
|
||||
file := filepath.Join(string(os.PathSeparator), "public", parser.option.Root, route+".jit")
|
||||
return file, route, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package core
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
|
|
@ -40,17 +42,18 @@ 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"`
|
||||
Script *Script `json:"-"` // backend script
|
||||
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"`
|
||||
Route string `json:"route,omitempty"`
|
||||
Theme any `json:"theme,omitempty"`
|
||||
Locale any `json:"locale,omitempty"`
|
||||
Root string `json:"root,omitempty"`
|
||||
Imports map[string]string `json:"imports,omitempty"`
|
||||
Script *Script `json:"-"` // backend script
|
||||
Request *Request `json:"request,omitempty"`
|
||||
}
|
||||
|
||||
var keepWords = map[string]bool{
|
||||
|
|
@ -99,9 +102,6 @@ func NewTemplateParser(data Data, option *ParserOption) *TemplateParser {
|
|||
// Render parses and renders the HTML template
|
||||
func (parser *TemplateParser) Render(html string) (string, error) {
|
||||
|
||||
// Set the locale
|
||||
parser.locale = parser.Locale()
|
||||
|
||||
if !strings.Contains(html, "<html") {
|
||||
html = fmt.Sprintf(`<!DOCTYPE html><html lang="en-us">%s</html>`, html)
|
||||
}
|
||||
|
|
@ -111,13 +111,11 @@ func (parser *TemplateParser) Render(html string) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
root := doc.Selection.Find("html")
|
||||
parser.parseNode(root.Nodes[0])
|
||||
|
||||
// Replace the nodes
|
||||
for sel, nodes := range parser.replace {
|
||||
sel.ReplaceWithNodes(nodes...)
|
||||
delete(parser.replace, sel)
|
||||
// Set the locale
|
||||
parser.locale = parser.Locale()
|
||||
err = parser.RenderSelection(doc.Selection)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Append the head
|
||||
|
|
@ -150,16 +148,13 @@ func (parser *TemplateParser) Render(html string) (string, error) {
|
|||
parser.addScripts(body, parser.filterScripts("body", parser.scripts))
|
||||
}
|
||||
|
||||
// Fmt
|
||||
parser.Fmt(doc)
|
||||
|
||||
// For editor
|
||||
if parser.option != nil && parser.option.Editor {
|
||||
return doc.Find("body").Html()
|
||||
}
|
||||
|
||||
// For Request
|
||||
if parser.option != nil && (parser.option.Request || parser.option.Preview) {
|
||||
if parser.option != nil && (parser.option.Request != nil || parser.option.Preview) {
|
||||
// Remove the sui-hide attribute
|
||||
doc.Find("[sui-hide]").Remove()
|
||||
parser.tidy(doc.Selection)
|
||||
|
|
@ -170,8 +165,26 @@ func (parser *TemplateParser) Render(html string) (string, error) {
|
|||
return doc.Html()
|
||||
}
|
||||
|
||||
// RenderSelection parses and renders the HTML template
|
||||
func (parser *TemplateParser) RenderSelection(section *goquery.Selection) error {
|
||||
|
||||
if len(section.Nodes) == 0 {
|
||||
return fmt.Errorf("No nodes found")
|
||||
}
|
||||
|
||||
parser.parseNode(section.Nodes[0])
|
||||
// Replace the nodes
|
||||
for sel, nodes := range parser.replace {
|
||||
sel.ReplaceWithNodes(nodes...)
|
||||
delete(parser.replace, sel)
|
||||
}
|
||||
|
||||
parser.Fmt(section)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fmt formats the HTML template
|
||||
func (parser *TemplateParser) Fmt(doc *goquery.Document) {
|
||||
func (parser *TemplateParser) Fmt(doc *goquery.Selection) {
|
||||
if parser.locale != nil {
|
||||
sels := doc.Find(`[s\:trans-fmt]`)
|
||||
sels.Each(func(i int, sel *goquery.Selection) {
|
||||
|
|
@ -194,10 +207,8 @@ func (parser *TemplateParser) parseNode(node *html.Node) {
|
|||
}
|
||||
parser.parseElementNode(sel)
|
||||
|
||||
// Skip children if the node is a loop node
|
||||
if _, exist := sel.Attr("s:for"); exist {
|
||||
skipChildren = true
|
||||
}
|
||||
// Skip children if the node is a loop node、element component or JIT component
|
||||
skipChildren = parser.hasForStatement(sel) || parser.isElementComponent(sel) || parser.isJitComponent(sel)
|
||||
|
||||
case html.TextNode:
|
||||
parser.parseTextNode(node)
|
||||
|
|
@ -229,13 +240,86 @@ func (parser *TemplateParser) parseElementNode(sel *goquery.Selection) {
|
|||
parser.setStatementNode(sel)
|
||||
}
|
||||
|
||||
// JIT Compile the element
|
||||
if parser.isComponent(sel) {
|
||||
parser.parseComponent(sel)
|
||||
}
|
||||
|
||||
// Parse the attributes
|
||||
parser.parseElementAttrs(sel)
|
||||
|
||||
// if the element is a component
|
||||
if parser.isElementComponent(sel) {
|
||||
parser.parseElementComponent(sel)
|
||||
}
|
||||
|
||||
// JIT Compile the element
|
||||
if parser.isJitComponent(sel) {
|
||||
parser.parseJitComponent(sel)
|
||||
}
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) parseElementComponent(sel *goquery.Selection) {
|
||||
|
||||
sel.SetAttr("parsed", "true")
|
||||
com := sel.AttrOr("s:cn", "")
|
||||
props := map[string]string{}
|
||||
for _, attr := range sel.Nodes[0].Attr {
|
||||
if !strings.HasPrefix(attr.Key, "s:") && attr.Key != "parsed" {
|
||||
props[attr.Key] = attr.Val
|
||||
}
|
||||
}
|
||||
|
||||
// load the component based on the route
|
||||
var script *Script
|
||||
var err error
|
||||
if parser.option.Imports != nil {
|
||||
if route, has := parser.option.Imports[com]; has {
|
||||
file := filepath.Join(string(os.PathSeparator), "public", parser.option.Root, route)
|
||||
script, err = LoadScript(file)
|
||||
if err != nil {
|
||||
parser.errors = append(parser.errors, err)
|
||||
setError(sel, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compParser := parser.clone(script)
|
||||
|
||||
// Call the BeforeRender Hook
|
||||
if script != nil {
|
||||
data, err := script.BeforeRender(parser.option.Request, props)
|
||||
if err != nil {
|
||||
parser.errors = append(parser.errors, err)
|
||||
setError(sel, err)
|
||||
return
|
||||
}
|
||||
if data != nil {
|
||||
for k, v := range data {
|
||||
compParser.data[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = compParser.RenderSelection(sel)
|
||||
if err != nil {
|
||||
parser.errors = append(parser.errors, err)
|
||||
setError(sel, err)
|
||||
}
|
||||
parser.sequence = parser.sequence + 1
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) clone(script *Script) *TemplateParser {
|
||||
var new = *parser
|
||||
new.data = Data{}
|
||||
for k, v := range parser.data {
|
||||
new.data[k] = v
|
||||
}
|
||||
new.option.Script = script
|
||||
return &new
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) isElementComponent(sel *goquery.Selection) bool {
|
||||
if comp, exist := sel.Attr("s:cn"); exist && comp != "" && sel.Nodes[0].Data != "script" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) transTextNode(node *html.Node) {
|
||||
|
|
@ -486,6 +570,12 @@ func (parser *TemplateParser) parseTextNode(node *html.Node) {
|
|||
}
|
||||
node.Data = res
|
||||
}
|
||||
func (parser *TemplateParser) hasForStatement(sel *goquery.Selection) bool {
|
||||
if _, exist := sel.Attr("s:for"); exist {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) forStatementNode(sel *goquery.Selection) {
|
||||
|
||||
|
|
|
|||
|
|
@ -99,6 +99,31 @@ func (script *Script) Call(r *Request, method string, args ...any) (interface{},
|
|||
return res, nil
|
||||
}
|
||||
|
||||
// BeforeRender the script method
|
||||
func (script *Script) BeforeRender(r *Request, props map[string]string) (Data, error) {
|
||||
|
||||
ctx, err := script.NewContext(r.Sid, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ctx.Close()
|
||||
|
||||
if !ctx.Global().Has("BeforeRender") {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res, err := ctx.Call("BeforeRender", r, props)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if data, ok := res.(map[string]interface{}); ok {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("BeforeRender return %v should be Record<string, any>", res)
|
||||
}
|
||||
|
||||
// ConstantsToString get the constants from the script
|
||||
func (script *Script) ConstantsToString() (string, error) {
|
||||
constants, err := script.Constants()
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ type PageProp struct {
|
|||
|
||||
// BuildContext is the struct for the build context
|
||||
type BuildContext struct {
|
||||
components map[string]bool
|
||||
components map[string]string
|
||||
jitComponents map[string]bool
|
||||
sequence int
|
||||
doc *goquery.Document
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue