optimize the SUI compiler & add http server gzip support
This commit is contained in:
parent
536621d72e
commit
e4c8eeb180
11 changed files with 169 additions and 44 deletions
|
|
@ -81,13 +81,25 @@ var BuildCmd = &cobra.Command{
|
|||
|
||||
// Timecost
|
||||
start := time.Now()
|
||||
err = tmpl.Build(&core.BuildOption{SSR: true, AssetRoot: assetRoot, ExecScripts: true})
|
||||
minify := true
|
||||
mode := "production"
|
||||
if debug {
|
||||
minify = false
|
||||
mode = "development"
|
||||
}
|
||||
|
||||
err = tmpl.Build(&core.BuildOption{SSR: true, AssetRoot: assetRoot, ExecScripts: true, ScriptMinify: minify, StyleMinify: minify})
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, color.RedString(err.Error()))
|
||||
return
|
||||
}
|
||||
end := time.Now()
|
||||
timecost := end.Sub(start).Truncate(time.Millisecond)
|
||||
fmt.Println(color.GreenString("Build success in %s", timecost))
|
||||
if debug {
|
||||
fmt.Println(color.YellowString("Build succeeded for %s in %s", mode, timecost))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(color.GreenString("Build succeeded for %s in %s", mode, timecost))
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package sui
|
||||
|
||||
var data string
|
||||
var debug bool
|
||||
|
||||
func init() {
|
||||
WatchCmd.PersistentFlags().StringVarP(&data, "data", "d", "::{}", L("Session Data"))
|
||||
BuildCmd.PersistentFlags().StringVarP(&data, "data", "d", "::{}", L("Session Data"))
|
||||
BuildCmd.PersistentFlags().BoolVarP(&debug, "debug", "D", false, L("Debug mode"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ import (
|
|||
"github.com/yaoapp/yao/sui/core"
|
||||
)
|
||||
|
||||
var data string
|
||||
|
||||
var watched sync.Map
|
||||
|
||||
// WatchCmd command
|
||||
|
|
|
|||
8
go.mod
8
go.mod
|
|
@ -3,7 +3,7 @@ module github.com/yaoapp/yao
|
|||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/PuerkitoBio/goquery v1.9.2
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/caarlos0/env/v6 v6.10.1
|
||||
github.com/dchest/captcha v1.0.0
|
||||
|
|
@ -26,8 +26,8 @@ require (
|
|||
github.com/yaoapp/gou v0.10.3
|
||||
github.com/yaoapp/kun v0.9.0
|
||||
github.com/yaoapp/xun v0.9.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/crypto v0.25.0
|
||||
golang.org/x/net v0.27.0
|
||||
golang.org/x/text v0.16.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
|
@ -113,7 +113,7 @@ require (
|
|||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/oauth2 v0.14.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect
|
||||
|
|
|
|||
8
go.sum
8
go.sum
|
|
@ -1,5 +1,7 @@
|
|||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
|
||||
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
|
||||
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4=
|
||||
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
|
|
@ -261,6 +263,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
|
|||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
|
|
@ -284,6 +288,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
|
||||
|
|
@ -317,6 +323,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
|
|
|||
46
service/gzip.go
Normal file
46
service/gzip.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// gzipHandler
|
||||
func gzipHandler(h http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
gz := gzip.NewWriter(w)
|
||||
defer gz.Close()
|
||||
|
||||
gzWriter := gzipResponseWriter{ResponseWriter: w, Writer: gz}
|
||||
h.ServeHTTP(gzWriter, r)
|
||||
}
|
||||
}
|
||||
|
||||
type gzipResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
*gzip.Writer
|
||||
}
|
||||
|
||||
func (w gzipResponseWriter) WriteHeader(code int) {
|
||||
w.ResponseWriter.Header().Del("Content-Length")
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
func (w gzipResponseWriter) Flush() {
|
||||
w.Writer.Flush()
|
||||
}
|
||||
|
||||
func (w gzipResponseWriter) Header() http.Header {
|
||||
return w.ResponseWriter.Header()
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
|
|
@ -42,15 +45,6 @@ func withStaticFileServer(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// Yao Builder
|
||||
// URL /yao/builder
|
||||
if length >= 12 && c.Request.URL.Path[0:12] == "/yao/builder" {
|
||||
c.Request.URL.Path = strings.TrimPrefix(c.Request.URL.Path, "/yao/builder")
|
||||
BuilderFileServer.ServeHTTP(c.Writer, c.Request)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Rewrite
|
||||
for _, rewrite := range rewriteRules {
|
||||
// log.Debug("Rewrite: %s => %s", c.Request.URL.Path, rewrite.Replacement)
|
||||
|
|
@ -67,8 +61,6 @@ func withStaticFileServer(c *gin.Context) {
|
|||
// Sui file server
|
||||
if strings.HasSuffix(c.Request.URL.Path, ".sui") {
|
||||
|
||||
log.Debug("Sui File: %s", c.Request.URL.Path)
|
||||
|
||||
// Default index.sui
|
||||
if filepath.Base(c.Request.URL.Path) == ".sui" {
|
||||
c.Request.URL.Path = strings.TrimSuffix(c.Request.URL.Path, ".sui") + "index.sui"
|
||||
|
|
@ -96,9 +88,30 @@ func withStaticFileServer(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// Gzip Compression
|
||||
if strings.Contains(c.GetHeader("Accept-Encoding"), "gzip") {
|
||||
var buf bytes.Buffer
|
||||
gz := gzip.NewWriter(&buf)
|
||||
if _, err := gz.Write([]byte(html)); err != nil {
|
||||
log.Error("GZIP Compression Error: %s", err.Error())
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err := gz.Close(); err != nil {
|
||||
log.Error("GZIP Close Error: %s", err.Error())
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.Header("Content-Encoding", "gzip")
|
||||
c.Data(http.StatusOK, "text/html", buf.Bytes())
|
||||
c.Done()
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.String(200, html)
|
||||
c.Done()
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ type rewriteRule struct {
|
|||
func SetupStatic() error {
|
||||
setupAdminRoot()
|
||||
setupRewrite()
|
||||
AppFileServer = http.FileServer(fs.Dir("public"))
|
||||
AppFileServer = gzipHandler(http.FileServer(fs.Dir("public")))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Docume
|
|||
}
|
||||
|
||||
// Scripts
|
||||
namespace := Namespace(page.Name, ctx.sequence)
|
||||
namespace := Namespace(page.Name, ctx.sequence, option.ScriptMinify)
|
||||
scripts, err := page.BuildScripts(ctx, option, "__page", namespace)
|
||||
if err != nil {
|
||||
return nil, ctx.warnings, err
|
||||
|
|
@ -99,8 +99,8 @@ func (page *Page) BuildAsComponent(sel *goquery.Selection, ctx *BuildContext, op
|
|||
return "", fmt.Errorf("The component tag must have an is attribute")
|
||||
}
|
||||
|
||||
namespace := Namespace(name, ctx.sequence)
|
||||
component := ComponentName(name)
|
||||
namespace := Namespace(name, ctx.sequence, option.ScriptMinify)
|
||||
component := ComponentName(name, option.ScriptMinify)
|
||||
attrs := []html.Attribute{
|
||||
{Key: "s:ns", Val: namespace},
|
||||
{Key: "s:cn", Val: component},
|
||||
|
|
|
|||
|
|
@ -64,6 +64,13 @@ var keepWords = map[string]bool{
|
|||
"s:bind": true,
|
||||
}
|
||||
|
||||
var keepAttrs = map[string]bool{
|
||||
"s:ns": true,
|
||||
"s:cn": true,
|
||||
"s:ready": true,
|
||||
"s:click": true,
|
||||
}
|
||||
|
||||
// Locales the locales
|
||||
var Locales = map[string]map[string]*Locale{}
|
||||
|
||||
|
|
@ -229,8 +236,6 @@ func (parser *TemplateParser) Render(html string) (string, error) {
|
|||
if parser.option != nil && (parser.option.Request || parser.option.Preview) {
|
||||
// Remove the sui-hide attribute
|
||||
doc.Find("[sui-hide]").Remove()
|
||||
|
||||
// Remove All comments and used nodes
|
||||
parser.tidy(doc.Selection)
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +276,8 @@ func (parser *TemplateParser) parseNode(node *html.Node) {
|
|||
|
||||
func (parser *TemplateParser) parseElementNode(sel *goquery.Selection) {
|
||||
|
||||
node := sel.Get(0)
|
||||
|
||||
if _, exist := sel.Attr("s:for"); exist {
|
||||
parser.forStatementNode(sel)
|
||||
}
|
||||
|
|
@ -279,7 +286,7 @@ func (parser *TemplateParser) parseElementNode(sel *goquery.Selection) {
|
|||
parser.ifStatementNode(sel)
|
||||
}
|
||||
|
||||
if _, exist := sel.Attr("s:set"); exist || sel.Get(0).Data == "s:set" {
|
||||
if _, exist := sel.Attr("s:set"); exist || node.Data == "s:set" {
|
||||
parser.setStatementNode(sel)
|
||||
}
|
||||
|
||||
|
|
@ -288,6 +295,11 @@ func (parser *TemplateParser) parseElementNode(sel *goquery.Selection) {
|
|||
parser.parseJitElementNode(sel)
|
||||
}
|
||||
|
||||
// Slot Node
|
||||
if node.Data == "slot" {
|
||||
parser.slotNode(sel)
|
||||
}
|
||||
|
||||
// Parse the attributes
|
||||
parser.parseElementAttrs(sel)
|
||||
|
||||
|
|
@ -361,6 +373,16 @@ func (parser *TemplateParser) transElementNode(sel *goquery.Selection) {
|
|||
}
|
||||
}
|
||||
|
||||
// Remove the slot tag and replace it with the children
|
||||
func (parser *TemplateParser) slotNode(sel *goquery.Selection) {
|
||||
children := sel.Children()
|
||||
if children.Length() == 0 {
|
||||
sel.Remove()
|
||||
return
|
||||
}
|
||||
sel.ReplaceWithSelection(children)
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) setStatementNode(sel *goquery.Selection) {
|
||||
|
||||
sel.SetAttr("parsed", "true")
|
||||
|
|
@ -396,6 +418,12 @@ func (parser *TemplateParser) parseElementAttrs(sel *goquery.Selection) {
|
|||
|
||||
attrs := sel.Nodes[0].Attr
|
||||
for _, attr := range attrs {
|
||||
|
||||
// Ignore the s: attributes
|
||||
if strings.HasPrefix(attr.Key, "s:") {
|
||||
continue
|
||||
}
|
||||
|
||||
parser.sequence = parser.sequence + 1
|
||||
res, hasStmt := parser.data.Replace(attr.Val)
|
||||
if hasStmt {
|
||||
|
|
@ -672,30 +700,33 @@ func (parser *TemplateParser) show(sel *goquery.Selection) {
|
|||
// sel.SetAttr("style", style)
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) tidy(selection *goquery.Selection) {
|
||||
selection.Contents().Each(func(i int, s *goquery.Selection) {
|
||||
func (parser *TemplateParser) tidy(s *goquery.Selection) {
|
||||
|
||||
if s.Nodes[0].Type == html.CommentNode {
|
||||
s.Remove()
|
||||
s.Contents().Each(func(i int, child *goquery.Selection) {
|
||||
|
||||
node := child.Get(0)
|
||||
if node.Type == html.CommentNode {
|
||||
child = child.Remove()
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the s:key-* attributes
|
||||
if s.Nodes[0].Type == html.ElementNode {
|
||||
for _, attr := range s.Nodes[0].Attr {
|
||||
if attr.Key == "parsed" || keepWords[attr.Key] || strings.HasPrefix(attr.Key, "s:key") || strings.HasPrefix(attr.Key, "s:bind") {
|
||||
s.RemoveAttr(attr.Key)
|
||||
}
|
||||
// Remove the parsed attribute
|
||||
attrs := []html.Attribute{}
|
||||
for _, attr := range node.Attr {
|
||||
if strings.HasPrefix(attr.Key, "s:") && !keepAttrs[attr.Key] {
|
||||
continue
|
||||
}
|
||||
|
||||
if s.Nodes[0].Data == "s:set" {
|
||||
s.Remove()
|
||||
return
|
||||
if attr.Key == "parsed" || attr.Key == "is" || strings.HasPrefix(attr.Key, "...") {
|
||||
continue
|
||||
}
|
||||
attrs = append(attrs, attr)
|
||||
}
|
||||
|
||||
parser.tidy(s)
|
||||
node.Attr = attrs
|
||||
parser.tidy(child)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) key(prefix string, sel *goquery.Selection) string {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package core
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
|
|
@ -50,17 +51,29 @@ func NewDocumentStringWithWrapper(htmlContent string) (*goquery.Document, error)
|
|||
}
|
||||
|
||||
// Namespace convert the name to namespace
|
||||
func Namespace(name string, idx int) string {
|
||||
func Namespace(name string, idx int, hash ...bool) string {
|
||||
name = strings.ReplaceAll(name, "/", "_")
|
||||
name = strings.ReplaceAll(name, "[", "_")
|
||||
name = strings.ReplaceAll(name, "]", "_")
|
||||
ns := fmt.Sprintf("__namespace_%s", name)
|
||||
if len(hash) > 0 && hash[0] {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(ns))
|
||||
return fmt.Sprintf("ns_%x", h.Sum64())
|
||||
}
|
||||
return fmt.Sprintf("__page_%s_%d", name, idx)
|
||||
}
|
||||
|
||||
// ComponentName convert the name to component name
|
||||
func ComponentName(name string) string {
|
||||
func ComponentName(name string, hash ...bool) string {
|
||||
name = strings.ReplaceAll(name, "/", "_")
|
||||
name = strings.ReplaceAll(name, "[", "_")
|
||||
name = strings.ReplaceAll(name, "]", "_")
|
||||
return fmt.Sprintf("__component_%s", name)
|
||||
cn := fmt.Sprintf("__component_%s", name)
|
||||
if len(hash) > 0 && hash[0] {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(cn))
|
||||
return fmt.Sprintf("cn_%x", h.Sum64())
|
||||
}
|
||||
return cn
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue