diff --git a/sui/api/request.go b/sui/api/request.go index 2cac757ed0..2ad2d7249b 100644 --- a/sui/api/request.go +++ b/sui/api/request.go @@ -86,88 +86,16 @@ func (r *Request) Render() (string, int, error) { if c == nil { - // The page is not cached message := fmt.Sprintf("[SUI] The page %s is not cached. file=%s DisableCache=%v", r.Request.URL.Path, r.File, r.Request.DisableCache()) go fmt.Println(color.YellowString(message)) go log.Warn(message) - // Read the file - content, err := application.App.Read(r.File) + var status int + var err error + c, status, err = r.MakeCache() if err != nil { - return "", 404, err + return "", status, err } - - doc, err := core.NewDocument(content) - if err != nil { - return "", 500, err - } - - guard := "" - guardRedirect := "" - configText := "" - cacheStore := "" - cacheTime := 0 - dateCacheTime := 0 - - configSel := doc.Find("script[name=config]") - if configSel != nil && configSel.Length() > 0 { - configText = configSel.Text() - configSel.Remove() - - var conf core.PageConfig - err := jsoniter.UnmarshalFromString(configText, &conf) - if err != nil { - return "", 500, fmt.Errorf("config error, please re-complie the page %s", err.Error()) - } - - // Redirect the page (should refector before release) - // guard=cookie-jwt:redirect-url redirect to the url if not authorized - // guard=cookie-jwt return {code: 403, message: "Not Authorized"} - guard = conf.Guard - if strings.Contains(conf.Guard, ":") { - parts := strings.Split(conf.Guard, ":") - guard = parts[0] - guardRedirect = parts[1] - } - - // Cache store - cacheStore = conf.CacheStore - cacheTime = conf.Cache - dateCacheTime = conf.DataCache - } - - dataText := "" - dataSel := doc.Find("script[name=data]") - if dataSel != nil && dataSel.Length() > 0 { - dataText = dataSel.Text() - dataSel.Remove() - } - - globalDataText := "" - globalDataSel := doc.Find("script[name=global]") - if globalDataSel != nil && globalDataSel.Length() > 0 { - globalDataText = globalDataSel.Text() - globalDataSel.Remove() - } - - html, err := doc.Html() - if err != nil { - return "", 500, fmt.Errorf("parse error, please re-complie the page %s", err.Error()) - } - - // Save to The Cache - c = &core.Cache{ - Data: dataText, - Global: globalDataText, - HTML: html, - Guard: guard, - GuardRedirect: guardRedirect, - Config: configText, - CacheStore: cacheStore, - CacheTime: time.Duration(cacheTime) * time.Second, - DataCacheTime: time.Duration(dateCacheTime) * time.Second, - } - go core.SetCache(r.File, c) go log.Trace("[SUI] The page %s is cached file=%s", r.Request.URL.Path, r.File) } @@ -249,6 +177,90 @@ func (r *Request) Render() (string, int, error) { return html, 200, nil } +// MakeCache is the cache for the page API. +func (r *Request) MakeCache() (*core.Cache, int, error) { + + // Read the file + content, err := application.App.Read(r.File) + if err != nil { + return nil, 404, err + } + + doc, err := core.NewDocument(content) + if err != nil { + return nil, 500, err + } + + guard := "" + guardRedirect := "" + configText := "" + cacheStore := "" + cacheTime := 0 + dateCacheTime := 0 + + configSel := doc.Find("script[name=config]") + if configSel != nil && configSel.Length() > 0 { + configText = configSel.Text() + configSel.Remove() + + var conf core.PageConfig + err := jsoniter.UnmarshalFromString(configText, &conf) + if err != nil { + return nil, 500, fmt.Errorf("config error, please re-complie the page %s", err.Error()) + } + + // Redirect the page (should refector before release) + // guard=cookie-jwt:redirect-url redirect to the url if not authorized + // guard=cookie-jwt return {code: 403, message: "Not Authorized"} + guard = conf.Guard + if strings.Contains(conf.Guard, ":") { + parts := strings.Split(conf.Guard, ":") + guard = parts[0] + guardRedirect = parts[1] + } + + // Cache store + cacheStore = conf.CacheStore + cacheTime = conf.Cache + dateCacheTime = conf.DataCache + } + + dataText := "" + dataSel := doc.Find("script[name=data]") + if dataSel != nil && dataSel.Length() > 0 { + dataText = dataSel.Text() + dataSel.Remove() + } + + globalDataText := "" + globalDataSel := doc.Find("script[name=global]") + if globalDataSel != nil && globalDataSel.Length() > 0 { + globalDataText = globalDataSel.Text() + globalDataSel.Remove() + } + + html, err := doc.Html() + if err != nil { + return nil, 500, fmt.Errorf("parse error, please re-complie the page %s", err.Error()) + } + + // Save to The Cache + cache := &core.Cache{ + Data: dataText, + Global: globalDataText, + HTML: html, + Guard: guard, + GuardRedirect: guardRedirect, + Config: configText, + CacheStore: cacheStore, + CacheTime: time.Duration(cacheTime) * time.Second, + DataCacheTime: time.Duration(dateCacheTime) * time.Second, + } + + go core.SetCache(r.File, cache) + return cache, 200, nil +} + // Guard the page func (r *Request) Guard(c *core.Cache) (int, error) { diff --git a/sui/api/request_test.go b/sui/api/request_test.go new file mode 100644 index 0000000000..ea8e093655 --- /dev/null +++ b/sui/api/request_test.go @@ -0,0 +1,100 @@ +package api + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/sui/core" +) + +func TestMakeCache(t *testing.T) { + prepare(t) + defer clean() + r := makeRequest("/unit-test/index.sui", t) + c, status, err := r.MakeCache() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, http.StatusOK, status) + assert.Contains(t, c.HTML, "The advanced test cases") +} + +func TestRender(t *testing.T) { + prepare(t) + defer clean() + + parser, html, data := makeParser("/unit-test/index.sui", t) + result, err := parser.Render(html) + if err != nil { + t.Fatal(err) + } + + assert.Contains(t, result, "The advanced test cases") + assert.NotNil(t, data["$global"]) + assert.NotNil(t, data["foo"]) + assert.NotNil(t, data["items"]) + + // fmt.Println(result) +} + +func makeParser(route string, t *testing.T) (*core.TemplateParser, string, core.Data) { + r := makeRequest(route, t) + c, _, err := r.MakeCache() + if err != nil { + t.Fatal(err) + } + + data := r.Request.NewData() + if c.Data != "" { + err = r.Request.ExecStringMerge(data, c.Data) + if err != nil { + t.Fatal(err) + } + } + + if c.Global != "" { + global, err := r.Request.ExecString(c.Global) + if err != nil { + t.Fatal(err) + } + data["$global"] = global + } + + // Set the page request data + option := core.ParserOption{ + Theme: r.Request.Theme, + Locale: r.Request.Locale, + Debug: r.Request.DebugMode(), + DisableCache: r.Request.DisableCache(), + Route: r.Request.URL.Path, + Request: true, + } + + // Parse the template + return core.NewTemplateParser(data, &option), c.HTML, data +} + +func makeRequest(path string, t *testing.T) *Request { + req, err := http.NewRequest(http.MethodGet, path, nil) + if err != nil { + t.Fatal(err) + } + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = req + + r, status, err := NewRequestContext(c) + if err != nil { + t.Fatal(err) + } + + if status != http.StatusOK { + t.Fatalf("Status: %d", status) + } + return r +} diff --git a/sui/api/sui_test.go b/sui/api/sui_test.go index 318d8dda22..66b8046772 100644 --- a/sui/api/sui_test.go +++ b/sui/api/sui_test.go @@ -31,6 +31,16 @@ func prepare(t *testing.T) { if err != nil { t.Fatal(err) } + + advanced, err := core.SUIs["test"].GetTemplate("advanced") + if err != nil { + t.Fatal(err) + } + + err = advanced.Build(&core.BuildOption{SSR: true}) + if err != nil { + t.Fatal(err) + } } func clean() { diff --git a/sui/core/build.go b/sui/core/build.go index 8dfbf19f5d..ec8523c3df 100644 --- a/sui/core/build.go +++ b/sui/core/build.go @@ -62,7 +62,11 @@ func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Docume doc.Selection.Find("body").AppendHtml("\n" + `` + "\n") } } - doc.Selection.Find("body").AppendHtml(fmt.Sprintf("\n"+``+"\n%s\n", strings.Join(ctx.scripts, "\n"), code)) + componentScripts := "" + if len(ctx.scripts) > 0 { + componentScripts = fmt.Sprintf("\n"+``+"\n", strings.Join(ctx.scripts, "\n")) + } + doc.Selection.Find("body").AppendHtml(fmt.Sprintf("\n%s\n%s\n", componentScripts, code)) return doc, warnings, nil } @@ -373,7 +377,7 @@ func (page *Page) BuildScript(ctx *BuildContext, option *BuildOption, namespace // if the script is a component and not the first import if option.ComponentName != "" && ctx.components[option.ComponentName] { ctx.scripts = append(ctx.scripts, instanceCode) - return fmt.Sprintf("\n", instanceCode), []string{}, nil + return fmt.Sprintf("\n", option.ComponentName, instanceCode), []string{}, nil } // TypeScript @@ -402,12 +406,15 @@ func (page *Page) BuildScript(ctx *BuildContext, option *BuildOption, namespace } if option.Namespace == "" { - return fmt.Sprintf("\n", code), scripts, nil + if strings.TrimSpace(string(code)) == "" { + return "", scripts, nil + } + return fmt.Sprintf("\n", code), scripts, nil } componentCode := fmt.Sprintf("function %s(){\n%s\n}\n%s\n", option.ComponentName, addTabToEachLine(string(code)), instanceCode) ctx.scripts = append(ctx.scripts, componentCode) - return fmt.Sprintf("\n", componentCode), scripts, nil + return fmt.Sprintf("\n", option.ComponentName, componentCode), scripts, nil } // JavaScript @@ -434,12 +441,15 @@ func (page *Page) BuildScript(ctx *BuildContext, option *BuildOption, namespace } if option.Namespace == "" { - return fmt.Sprintf("\n", code), scripts, nil + if strings.TrimSpace(string(code)) == "" { + return "", scripts, nil + } + return fmt.Sprintf("\n", code), scripts, nil } componentCode := fmt.Sprintf("function %s(){\n%s\n}\n%s\n", option.ComponentName, addTabToEachLine(string(code)), instanceCode) ctx.scripts = append(ctx.scripts, componentCode) - return fmt.Sprintf("\n", componentCode), scripts, nil + return fmt.Sprintf("\n", option.ComponentName, componentCode), scripts, nil } func addTabToEachLine(input string, prefix ...string) string { diff --git a/sui/core/fs_test.go b/sui/core/fs_test.go deleted file mode 100644 index 9a8bc9592b..0000000000 --- a/sui/core/fs_test.go +++ /dev/null @@ -1 +0,0 @@ -package core