Merge pull request #709 from trheyi/main
[feat] SUI backend script support
This commit is contained in:
commit
cd08013bfe
14 changed files with 443 additions and 61 deletions
|
|
@ -167,7 +167,9 @@ func (r *Request) Render() (string, int, error) {
|
||||||
DisableCache: r.Request.DisableCache(),
|
DisableCache: r.Request.DisableCache(),
|
||||||
Route: r.Request.URL.Path,
|
Route: r.Request.URL.Path,
|
||||||
Root: c.Root,
|
Root: c.Root,
|
||||||
Request: true,
|
Script: c.Script,
|
||||||
|
Imports: c.Imports,
|
||||||
|
Request: r.Request,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the template
|
// Parse the template
|
||||||
|
|
@ -249,11 +251,28 @@ func (r *Request) MakeCache() (*core.Cache, int, error) {
|
||||||
globalDataSel.Remove()
|
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()
|
html, err := doc.Html()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 500, fmt.Errorf("parse error, please re-complie the page %s", err.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
|
// Save to The Cache
|
||||||
cache := &core.Cache{
|
cache := &core.Cache{
|
||||||
Data: dataText,
|
Data: dataText,
|
||||||
|
|
@ -266,6 +285,8 @@ func (r *Request) MakeCache() (*core.Cache, int, error) {
|
||||||
Root: root,
|
Root: root,
|
||||||
CacheTime: time.Duration(cacheTime) * time.Second,
|
CacheTime: time.Duration(cacheTime) * time.Second,
|
||||||
DataCacheTime: time.Duration(dataCacheTime) * time.Second,
|
DataCacheTime: time.Duration(dataCacheTime) * time.Second,
|
||||||
|
Script: script,
|
||||||
|
Imports: imports,
|
||||||
}
|
}
|
||||||
|
|
||||||
go core.SetCache(r.File, cache)
|
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(),
|
Debug: r.Request.DebugMode(),
|
||||||
DisableCache: r.Request.DisableCache(),
|
DisableCache: r.Request.DisableCache(),
|
||||||
Route: r.Request.URL.Path,
|
Route: r.Request.URL.Path,
|
||||||
Request: true,
|
Request: r.Request,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the template
|
// Parse the template
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,7 @@ func (page *Page) BuildAsComponent(sel *goquery.Selection, ctx *BuildContext, op
|
||||||
ctx.styles = append(ctx.styles, styles...)
|
ctx.styles = append(ctx.styles, styles...)
|
||||||
|
|
||||||
sel.ReplaceWithSelection(body.Contents())
|
sel.ReplaceWithSelection(body.Contents())
|
||||||
ctx.components[page.Route] = true
|
ctx.components[component] = page.Route
|
||||||
return source, nil
|
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)
|
return warnings, fmt.Errorf("SUI %s not found", page.SuiID)
|
||||||
}
|
}
|
||||||
|
|
||||||
public := sui.GetPublic()
|
|
||||||
tmpl, err := sui.GetTemplate(page.TemplateID)
|
tmpl, err := sui.GetTemplate(page.TemplateID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return warnings, err
|
return warnings, err
|
||||||
|
|
@ -480,7 +479,6 @@ func (page *Page) buildComponents(doc *goquery.Document, ctx *BuildContext, opti
|
||||||
if ctx.isJitComponent(name) {
|
if ctx.isJitComponent(name) {
|
||||||
sel.SetAttr("s:jit", "true")
|
sel.SetAttr("s:jit", "true")
|
||||||
sel.SetAttr("s:parent", page.namespace)
|
sel.SetAttr("s:parent", page.namespace)
|
||||||
sel.SetAttr("s:root", public.Root)
|
|
||||||
ctx.addJitComponent(name)
|
ctx.addJitComponent(name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -589,6 +587,16 @@ func (page *Page) BuildScripts(ctx *BuildContext, option *BuildOption, component
|
||||||
}
|
}
|
||||||
injectScript := componentInitScript(arguments)
|
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{}
|
scripts := []ScriptNode{}
|
||||||
if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" {
|
if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" {
|
||||||
return scripts, nil
|
return scripts, nil
|
||||||
|
|
@ -599,7 +607,6 @@ func (page *Page) BuildScripts(ctx *BuildContext, option *BuildOption, component
|
||||||
|
|
||||||
ctx.scriptUnique[component] = true
|
ctx.scriptUnique[component] = true
|
||||||
|
|
||||||
var err error = nil
|
|
||||||
var imports []string = nil
|
var imports []string = nil
|
||||||
var source []byte = nil
|
var source []byte = nil
|
||||||
if page.Codes.TS.Code != "" {
|
if page.Codes.TS.Code != "" {
|
||||||
|
|
@ -639,6 +646,10 @@ func (page *Page) BuildScripts(ctx *BuildContext, option *BuildOption, component
|
||||||
})
|
})
|
||||||
|
|
||||||
code := string(source)
|
code := string(source)
|
||||||
|
if constants != "" {
|
||||||
|
code = fmt.Sprintf("this.Constants = %s\n%s", constants, code)
|
||||||
|
}
|
||||||
|
|
||||||
parent := "body"
|
parent := "body"
|
||||||
if !ispage {
|
if !ispage {
|
||||||
parent = "head"
|
parent = "head"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
v8 "github.com/yaoapp/gou/runtime/v8"
|
|
||||||
"github.com/yaoapp/gou/store"
|
"github.com/yaoapp/gou/store"
|
||||||
"github.com/yaoapp/kun/log"
|
"github.com/yaoapp/kun/log"
|
||||||
)
|
)
|
||||||
|
|
@ -21,7 +20,8 @@ type Cache struct {
|
||||||
CacheStore string
|
CacheStore string
|
||||||
CacheTime time.Duration
|
CacheTime time.Duration
|
||||||
DataCacheTime time.Duration
|
DataCacheTime time.Duration
|
||||||
Script *v8.Script // the backend script
|
Script *Script
|
||||||
|
Imports map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -73,6 +73,7 @@ func GetCache(file string) *Cache {
|
||||||
// RemoveCache remove the cache
|
// RemoveCache remove the cache
|
||||||
func RemoveCache(file string) {
|
func RemoveCache(file string) {
|
||||||
ch <- &cacheData{file, nil, removeCache}
|
ch <- &cacheData{file, nil, removeCache}
|
||||||
|
chScript <- &scriptData{file, nil, removeScript}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanCache clean the cache
|
// CleanCache clean the cache
|
||||||
|
|
|
||||||
|
|
@ -93,14 +93,10 @@ func (page *Page) Compile(ctx *BuildContext, option *BuildOption) (string, []str
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page Components
|
// Page Components
|
||||||
components := []string{}
|
|
||||||
if ctx != nil && ctx.components != nil && len(ctx.components) > 0 {
|
if ctx != nil && ctx.components != nil && len(ctx.components) > 0 {
|
||||||
for route := range ctx.components {
|
rawComponents, _ := jsoniter.MarshalToString(ctx.components)
|
||||||
components = append(components, route)
|
body.AppendHtml("\n\n" + `<script name="imports" type="json">` + "\n" + rawComponents + "\n</script>\n\n")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
rawComponents, _ := jsoniter.MarshalToString(components)
|
|
||||||
body.AppendHtml("\n\n" + `<script name="imports" type="json">` + "\n" + rawComponents + "\n</script>\n\n")
|
|
||||||
|
|
||||||
page.ReplaceDocument(doc)
|
page.ReplaceDocument(doc)
|
||||||
html, err := doc.Html()
|
html, err := doc.Html()
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package core
|
||||||
// NewBuildContext create a new build context
|
// NewBuildContext create a new build context
|
||||||
func NewBuildContext(global *GlobalBuildContext) *BuildContext {
|
func NewBuildContext(global *GlobalBuildContext) *BuildContext {
|
||||||
return &BuildContext{
|
return &BuildContext{
|
||||||
components: map[string]bool{},
|
components: map[string]string{},
|
||||||
sequence: 1,
|
sequence: 1,
|
||||||
scripts: []ScriptNode{},
|
scripts: []ScriptNode{},
|
||||||
scriptUnique: map[string]bool{},
|
scriptUnique: map[string]bool{},
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,21 @@ const componentInitScriptTmpl = `
|
||||||
this.store = new __sui_store(this.root);
|
this.store = new __sui_store(this.root);
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// Inject code
|
||||||
|
const backendScriptTmpl = `
|
||||||
|
this.__sui_page = '%s';
|
||||||
|
this.__sui_constants = {};
|
||||||
|
this.__sui_helpers = [];
|
||||||
|
|
||||||
|
if (typeof Helpers === 'object') {
|
||||||
|
this.__sui_helpers = Object.keys(Helpers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof Constants === 'object') {
|
||||||
|
this.__sui_constants = Constants;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
func bodyInjectionScript(jsonRaw string, debug bool) string {
|
func bodyInjectionScript(jsonRaw string, debug bool) string {
|
||||||
jsPrintData := ""
|
jsPrintData := ""
|
||||||
if debug {
|
if debug {
|
||||||
|
|
@ -209,3 +224,8 @@ func compEventInjectScript(eventID, eventName, component, dataKeys, jsonKeys, ha
|
||||||
func componentInitScript(root string) string {
|
func componentInitScript(root string) string {
|
||||||
return fmt.Sprintf(componentInitScriptTmpl, root)
|
return fmt.Sprintf(componentInitScriptTmpl, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BackendScript inject the backend script
|
||||||
|
func BackendScript(route string) string {
|
||||||
|
return fmt.Sprintf(backendScriptTmpl, route)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseComponent parse the component
|
// parseComponent parse the component
|
||||||
func (parser *TemplateParser) parseComponent(sel *goquery.Selection) {
|
func (parser *TemplateParser) parseJitComponent(sel *goquery.Selection) {
|
||||||
parser.parsed(sel)
|
parser.parsed(sel)
|
||||||
comp, props, slots, children, err := parser.getComponent(sel)
|
comp, props, slots, children, err := parser.getComponent(sel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -218,8 +218,8 @@ func (parser *TemplateParser) getComponent(sel *goquery.Selection) (*JitComponen
|
||||||
return comp, props, slots, children, nil
|
return comp, props, slots, children, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isComponent check if the selection is a component
|
// isJitComponent check if the selection is a component
|
||||||
func (parser *TemplateParser) isComponent(sel *goquery.Selection) bool {
|
func (parser *TemplateParser) isJitComponent(sel *goquery.Selection) bool {
|
||||||
_, exist := sel.Attr("s:jit")
|
_, exist := sel.Attr("s:jit")
|
||||||
is := sel.AttrOr("is", "")
|
is := sel.AttrOr("is", "")
|
||||||
return exist && is != ""
|
return exist && is != ""
|
||||||
|
|
@ -314,8 +314,7 @@ func (parser *TemplateParser) componentFile(sel *goquery.Selection, props map[st
|
||||||
data := Data{"$props": props}
|
data := Data{"$props": props}
|
||||||
route, _ = data.ReplaceUse(slotRe, route)
|
route, _ = data.ReplaceUse(slotRe, route)
|
||||||
route, _ = parser.data.Replace(route)
|
route, _ = parser.data.Replace(route)
|
||||||
root := sel.AttrOr("s:root", "/")
|
file := filepath.Join(string(os.PathSeparator), "public", parser.option.Root, route+".jit")
|
||||||
file := filepath.Join(string(os.PathSeparator), "public", root, route+".jit")
|
|
||||||
return file, route, nil
|
return file, route, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
|
@ -40,16 +42,18 @@ type Mapping struct {
|
||||||
|
|
||||||
// ParserOption parser option
|
// ParserOption parser option
|
||||||
type ParserOption struct {
|
type ParserOption struct {
|
||||||
Component bool `json:"component,omitempty"`
|
Component bool `json:"component,omitempty"`
|
||||||
Editor bool `json:"editor,omitempty"`
|
Editor bool `json:"editor,omitempty"`
|
||||||
Preview bool `json:"preview,omitempty"`
|
Preview bool `json:"preview,omitempty"`
|
||||||
Debug bool `json:"debug,omitempty"`
|
Debug bool `json:"debug,omitempty"`
|
||||||
DisableCache bool `json:"disableCache,omitempty"`
|
DisableCache bool `json:"disableCache,omitempty"`
|
||||||
Request bool `json:"request,omitempty"`
|
Route string `json:"route,omitempty"`
|
||||||
Route string `json:"route,omitempty"`
|
Theme any `json:"theme,omitempty"`
|
||||||
Theme any `json:"theme,omitempty"`
|
Locale any `json:"locale,omitempty"`
|
||||||
Locale any `json:"locale,omitempty"`
|
Root string `json:"root,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{
|
var keepWords = map[string]bool{
|
||||||
|
|
@ -98,9 +102,6 @@ func NewTemplateParser(data Data, option *ParserOption) *TemplateParser {
|
||||||
// Render parses and renders the HTML template
|
// Render parses and renders the HTML template
|
||||||
func (parser *TemplateParser) Render(html string) (string, error) {
|
func (parser *TemplateParser) Render(html string) (string, error) {
|
||||||
|
|
||||||
// Set the locale
|
|
||||||
parser.locale = parser.Locale()
|
|
||||||
|
|
||||||
if !strings.Contains(html, "<html") {
|
if !strings.Contains(html, "<html") {
|
||||||
html = fmt.Sprintf(`<!DOCTYPE html><html lang="en-us">%s</html>`, html)
|
html = fmt.Sprintf(`<!DOCTYPE html><html lang="en-us">%s</html>`, html)
|
||||||
}
|
}
|
||||||
|
|
@ -110,13 +111,11 @@ func (parser *TemplateParser) Render(html string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
root := doc.Selection.Find("html")
|
// Set the locale
|
||||||
parser.parseNode(root.Nodes[0])
|
parser.locale = parser.Locale()
|
||||||
|
err = parser.RenderSelection(doc.Selection)
|
||||||
// Replace the nodes
|
if err != nil {
|
||||||
for sel, nodes := range parser.replace {
|
return "", err
|
||||||
sel.ReplaceWithNodes(nodes...)
|
|
||||||
delete(parser.replace, sel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append the head
|
// Append the head
|
||||||
|
|
@ -149,16 +148,13 @@ func (parser *TemplateParser) Render(html string) (string, error) {
|
||||||
parser.addScripts(body, parser.filterScripts("body", parser.scripts))
|
parser.addScripts(body, parser.filterScripts("body", parser.scripts))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fmt
|
|
||||||
parser.Fmt(doc)
|
|
||||||
|
|
||||||
// For editor
|
// For editor
|
||||||
if parser.option != nil && parser.option.Editor {
|
if parser.option != nil && parser.option.Editor {
|
||||||
return doc.Find("body").Html()
|
return doc.Find("body").Html()
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Request
|
// 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
|
// Remove the sui-hide attribute
|
||||||
doc.Find("[sui-hide]").Remove()
|
doc.Find("[sui-hide]").Remove()
|
||||||
parser.tidy(doc.Selection)
|
parser.tidy(doc.Selection)
|
||||||
|
|
@ -169,8 +165,26 @@ func (parser *TemplateParser) Render(html string) (string, error) {
|
||||||
return doc.Html()
|
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
|
// Fmt formats the HTML template
|
||||||
func (parser *TemplateParser) Fmt(doc *goquery.Document) {
|
func (parser *TemplateParser) Fmt(doc *goquery.Selection) {
|
||||||
if parser.locale != nil {
|
if parser.locale != nil {
|
||||||
sels := doc.Find(`[s\:trans-fmt]`)
|
sels := doc.Find(`[s\:trans-fmt]`)
|
||||||
sels.Each(func(i int, sel *goquery.Selection) {
|
sels.Each(func(i int, sel *goquery.Selection) {
|
||||||
|
|
@ -193,10 +207,8 @@ func (parser *TemplateParser) parseNode(node *html.Node) {
|
||||||
}
|
}
|
||||||
parser.parseElementNode(sel)
|
parser.parseElementNode(sel)
|
||||||
|
|
||||||
// Skip children if the node is a loop node
|
// Skip children if the node is a loop node、element component or JIT component
|
||||||
if _, exist := sel.Attr("s:for"); exist {
|
skipChildren = parser.hasForStatement(sel) || parser.isElementComponent(sel) || parser.isJitComponent(sel)
|
||||||
skipChildren = true
|
|
||||||
}
|
|
||||||
|
|
||||||
case html.TextNode:
|
case html.TextNode:
|
||||||
parser.parseTextNode(node)
|
parser.parseTextNode(node)
|
||||||
|
|
@ -228,13 +240,86 @@ func (parser *TemplateParser) parseElementNode(sel *goquery.Selection) {
|
||||||
parser.setStatementNode(sel)
|
parser.setStatementNode(sel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JIT Compile the element
|
|
||||||
if parser.isComponent(sel) {
|
|
||||||
parser.parseComponent(sel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the attributes
|
// Parse the attributes
|
||||||
parser.parseElementAttrs(sel)
|
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) {
|
func (parser *TemplateParser) transTextNode(node *html.Node) {
|
||||||
|
|
@ -485,6 +570,12 @@ func (parser *TemplateParser) parseTextNode(node *html.Node) {
|
||||||
}
|
}
|
||||||
node.Data = res
|
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) {
|
func (parser *TemplateParser) forStatementNode(sel *goquery.Selection) {
|
||||||
|
|
||||||
|
|
|
||||||
216
sui/core/script.go
Normal file
216
sui/core/script.go
Normal file
|
|
@ -0,0 +1,216 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
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<string, any>", 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)
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
v8 "github.com/yaoapp/gou/runtime/v8"
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -39,7 +38,7 @@ type Page struct {
|
||||||
Path string `json:"-"`
|
Path string `json:"-"`
|
||||||
Root string `json:"-"`
|
Root string `json:"-"`
|
||||||
Codes SourceCodes `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:"-"`
|
Document []byte `json:"-"`
|
||||||
GlobalData []byte `json:"-"`
|
GlobalData []byte `json:"-"`
|
||||||
Attrs map[string]string `json:"-"`
|
Attrs map[string]string `json:"-"`
|
||||||
|
|
@ -60,7 +59,7 @@ type PageProp struct {
|
||||||
|
|
||||||
// BuildContext is the struct for the build context
|
// BuildContext is the struct for the build context
|
||||||
type BuildContext struct {
|
type BuildContext struct {
|
||||||
components map[string]bool
|
components map[string]string
|
||||||
jitComponents map[string]bool
|
jitComponents map[string]bool
|
||||||
sequence int
|
sequence int
|
||||||
doc *goquery.Document
|
doc *goquery.Document
|
||||||
|
|
@ -179,8 +178,8 @@ type Template struct {
|
||||||
GlobalData []byte `json:"-"`
|
GlobalData []byte `json:"-"`
|
||||||
Scripts *TemplateScirpts `json:"scripts,omitempty"`
|
Scripts *TemplateScirpts `json:"scripts,omitempty"`
|
||||||
Translator string `json:"translator,omitempty"`
|
Translator string `json:"translator,omitempty"`
|
||||||
BuildScript *v8.Script `json:"-"` // __build.backend.ts / __build.backend.js
|
BuildScript *Script `json:"-"` // __build.backend.ts / __build.backend.js
|
||||||
GlobalScript *v8.Script `json:"-"` // __global.backend.ts / __global.backend.js
|
GlobalScript *Script `json:"-"` // __global.backend.ts / __global.backend.js
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateScirpts is the struct for the template scripts
|
// TemplateScirpts is the struct for the template scripts
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/yaoapp/gou/application"
|
"github.com/yaoapp/gou/application"
|
||||||
"github.com/yaoapp/gou/process"
|
"github.com/yaoapp/gou/process"
|
||||||
|
v8 "github.com/yaoapp/gou/runtime/v8"
|
||||||
"github.com/yaoapp/kun/log"
|
"github.com/yaoapp/kun/log"
|
||||||
"github.com/yaoapp/yao/sui/core"
|
"github.com/yaoapp/yao/sui/core"
|
||||||
"golang.org/x/text/language"
|
"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) {
|
func (page *Page) Build(globalCtx *core.GlobalBuildContext, option *core.BuildOption) ([]string, error) {
|
||||||
|
|
||||||
ctx := core.NewBuildContext(globalCtx)
|
ctx := core.NewBuildContext(globalCtx)
|
||||||
|
|
||||||
var err error = nil
|
var err error = nil
|
||||||
root := option.PublicRoot
|
root := option.PublicRoot
|
||||||
if root == "" {
|
if root == "" {
|
||||||
|
|
@ -656,9 +657,29 @@ func (page *Page) backendScriptSource() (string, []byte, error) {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
source = []byte(fmt.Sprintf("%s\n%s", source, core.BackendScript(page.Route)))
|
||||||
return backendFile, source, nil
|
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 {
|
func (page *Page) writeBackendScript(data map[string]interface{}) error {
|
||||||
|
|
||||||
file, source, err := page.backendScriptSource()
|
file, source, err := page.backendScriptSource()
|
||||||
|
|
|
||||||
|
|
@ -453,6 +453,13 @@ func (page *Page) Load() error {
|
||||||
|
|
||||||
// Set the page global data
|
// Set the page global data
|
||||||
page.GlobalData = page.tmpl.GlobalData
|
page.GlobalData = page.tmpl.GlobalData
|
||||||
|
|
||||||
|
// Load the backend script
|
||||||
|
err := page.loadBackendScript()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ func (tmpl *Template) loadBuildScript() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tmpl.BuildScript = script
|
tmpl.BuildScript = &core.Script{Script: script}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue