From 7f7e17e8ee31379d61effb48c4760a13a406d2e7 Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Thu, 27 Feb 2025 00:53:19 +0000 Subject: [PATCH] add minification --- app/app.go | 130 +++++++++++++++++++++++++++++++++++++++------------ app/utils.go | 98 ++++++++++++++++++++++++++++++++++++++ go.mod | 8 ++++ web/embed.go | 22 +-------- 4 files changed, 208 insertions(+), 50 deletions(-) diff --git a/app/app.go b/app/app.go index 39e98cb..49e3310 100644 --- a/app/app.go +++ b/app/app.go @@ -1,61 +1,89 @@ package app import ( + "bytes" "flag" "fmt" "io/fs" "log" "net/http" - embed "proxmoxaas-dashboard/dist/web" // go will complain here until the first build + "proxmoxaas-dashboard/dist/web" // go will complain here until the first build + "strings" "text/template" + + "github.com/tdewolff/minify" ) -var html map[string]*template.Template - -func ParseTemplates() { - html = make(map[string]*template.Template) - fs.WalkDir(embed.HTML, ".", func(path string, d fs.DirEntry, err error) error { +func ParseTemplates() map[string]*template.Template { + // create html map which stores html files to parsed templates + html := make(map[string]*template.Template) + fs.WalkDir(web.HTML, ".", func(path string, html_entry fs.DirEntry, err error) error { // walk the html directory if err != nil { return err } - if !d.IsDir() { // if it is a html file, parse with all the template files - v, err := fs.ReadFile(embed.HTML, path) + if !html_entry.IsDir() { // if it is an html file, parse with all the template files + v, err := fs.ReadFile(web.HTML, path) if err != nil { log.Fatalf("error reading html file %s: %s", path, err.Error()) } - t := template.New(d.Name()) + t := template.New(html_entry.Name()) // parse the html file t, err = t.Parse(string(v)) if err != nil { log.Fatalf("error parsing html file %s: %s", path, err.Error()) } - fs.WalkDir(embed.Templates, ".", func(path string, e fs.DirEntry, err error) error { + // parse the html with every template file + fs.WalkDir(web.Templates, ".", func(path string, templates_entry fs.DirEntry, err error) error { if err != nil { return err } - if !e.IsDir() { // if it is a template file, parse it - v, err = fs.ReadFile(embed.Templates, path) + if !templates_entry.IsDir() { // if it is a template file, parse it + v, err = fs.ReadFile(web.Templates, path) if err != nil { log.Fatalf("error reading template file %s: %s", path, err.Error()) } - t, err = t.Parse(string(v)) + t, err = t.Parse(string(v)) // parse the template file if err != nil { log.Fatalf("error parsing template file %s: %s", path, err.Error()) } } return nil }) - html[d.Name()] = t + html[html_entry.Name()] = t } return nil }) - + return html } -func ServeStatic() { - http.Handle("/css/", http.FileServerFS(embed.CSS_fs)) - http.Handle("/images/", http.FileServerFS(embed.Images_fs)) - http.Handle("/modules/", http.FileServerFS(embed.Modules_fs)) - http.Handle("/scripts/", http.FileServerFS(embed.Scripts_fs)) +func ServeStatic(m *minify.M) { + css := MinifyStatic(m, web.CSS_fs) + http.HandleFunc("/css/", func(w http.ResponseWriter, r *http.Request) { + path := strings.TrimPrefix(r.URL.Path, "/") + data := css[path] + w.Header().Add("Content-Type", data.MimeType.Type) + w.Write([]byte(data.Data)) + }) + images := MinifyStatic(m, web.Images_fs) + http.HandleFunc("/images/", func(w http.ResponseWriter, r *http.Request) { + path := strings.TrimPrefix(r.URL.Path, "/") + data := images[path] + w.Header().Add("Content-Type", data.MimeType.Type) + w.Write([]byte(data.Data)) + }) + modules := MinifyStatic(m, web.Modules_fs) + http.HandleFunc("/modules/", func(w http.ResponseWriter, r *http.Request) { + path := strings.TrimPrefix(r.URL.Path, "/") + data := modules[path] + w.Header().Add("Content-Type", data.MimeType.Type) + w.Write([]byte(data.Data)) + }) + scripts := MinifyStatic(m, web.Scripts_fs) + http.HandleFunc("/scripts/", func(w http.ResponseWriter, r *http.Request) { + path := strings.TrimPrefix(r.URL.Path, "/") + data := scripts[path] + w.Header().Add("Content-Type", data.MimeType.Type) + w.Write([]byte(data.Data)) + }) } func Run() { @@ -64,58 +92,102 @@ func Run() { global := GetConfig(*configPath) - ParseTemplates() + m := InitMinify() + + ServeStatic(m) + + html := ParseTemplates() http.HandleFunc("/account.html", func(w http.ResponseWriter, r *http.Request) { global.Page = "account" - err := html["account.html"].Execute(w, global) + page := bytes.Buffer{} + err := html["account.html"].Execute(&page, global) if err != nil { log.Fatal(err.Error()) } + minified := bytes.Buffer{} + err = m.Minify("text/html", &minified, &page) + if err != nil { + log.Fatal(err.Error()) + } + w.Write(minified.Bytes()) }) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { global.Page = "index" - err := html["index.html"].Execute(w, global) + page := bytes.Buffer{} + err := html["index.html"].Execute(&page, global) if err != nil { log.Fatal(err.Error()) } + minified := bytes.Buffer{} + err = m.Minify("text/html", &minified, &page) + if err != nil { + log.Fatal(err.Error()) + } + w.Write(minified.Bytes()) }) http.HandleFunc("/index.html", func(w http.ResponseWriter, r *http.Request) { global.Page = "index" - err := html["index.html"].Execute(w, global) + page := bytes.Buffer{} + err := html["index.html"].Execute(&page, global) if err != nil { log.Fatal(err.Error()) } + minified := bytes.Buffer{} + err = m.Minify("text/html", &minified, &page) + if err != nil { + log.Fatal(err.Error()) + } + w.Write(minified.Bytes()) }) http.HandleFunc("/instance.html", func(w http.ResponseWriter, r *http.Request) { global.Page = "instance" - err := html["instance.html"].Execute(w, global) + page := bytes.Buffer{} + err := html["instance.html"].Execute(&page, global) if err != nil { log.Fatal(err.Error()) } + minified := bytes.Buffer{} + err = m.Minify("text/html", &minified, &page) + if err != nil { + log.Fatal(err.Error()) + } + w.Write(minified.Bytes()) }) http.HandleFunc("/login.html", func(w http.ResponseWriter, r *http.Request) { global.Page = "login" - err := html["login.html"].Execute(w, global) + page := bytes.Buffer{} + err := html["login.html"].Execute(&page, global) if err != nil { log.Fatal(err.Error()) } + minified := bytes.Buffer{} + err = m.Minify("text/html", &minified, &page) + if err != nil { + log.Fatal(err.Error()) + } + w.Write(minified.Bytes()) }) http.HandleFunc("/settings.html", func(w http.ResponseWriter, r *http.Request) { global.Page = "settings" - err := html["settings.html"].Execute(w, global) + page := bytes.Buffer{} + err := html["settings.html"].Execute(&page, global) if err != nil { log.Fatal(err.Error()) } + minified := bytes.Buffer{} + err = m.Minify("text/html", &minified, &page) + if err != nil { + log.Fatal(err.Error()) + } + w.Write(minified.Bytes()) }) - ServeStatic() - log.Printf("Starting HTTP server at port: %d\n", global.Port) err := http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", global.Port), nil) if err != nil { diff --git a/app/utils.go b/app/utils.go index 4eda294..b6cfbd2 100644 --- a/app/utils.go +++ b/app/utils.go @@ -1,9 +1,18 @@ package app import ( + "embed" "encoding/json" + "io" + "io/fs" "log" "os" + "strings" + + "github.com/tdewolff/minify" + "github.com/tdewolff/minify/css" + "github.com/tdewolff/minify/html" + "github.com/tdewolff/minify/js" ) type Config struct { @@ -26,3 +35,92 @@ func GetConfig(configPath string) Config { } return config } + +type MimeType struct { + Type string + Minifier func(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error +} + +var PlainTextMimeType = MimeType{ + Type: "text/plain", + Minifier: nil, +} + +var MimeTypes = map[string]MimeType{ + "css": { + Type: "text/css", + Minifier: css.Minify, + }, + "html": { + Type: "text/html", + Minifier: html.Minify, + }, + "svg": { + Type: "image/svg+xml", + Minifier: nil, + }, + "js": { + Type: "application/javascript", + Minifier: js.Minify, + }, + "wasm": { + Type: "application/wasm", + Minifier: nil, + }, +} + +func InitMinify() *minify.M { + m := minify.New() + for _, v := range MimeTypes { + if v.Minifier != nil { + m.AddFunc(v.Type, v.Minifier) + } + } + return m +} + +type StaticFile struct { + Data string + MimeType MimeType +} + +func MinifyStatic(m *minify.M, files embed.FS) map[string]StaticFile { + minified := make(map[string]StaticFile) + fs.WalkDir(files, ".", func(path string, entry fs.DirEntry, err error) error { + if err != nil { + return err + } + if !entry.IsDir() { + v, err := files.ReadFile(path) + if err != nil { + log.Fatalf("error parsing template file %s: %s", path, err.Error()) + } + x := strings.Split(entry.Name(), ".") + if len(x) >= 2 { // file has extension + mimetype, ok := MimeTypes[x[len(x)-1]] + if ok && mimetype.Minifier != nil { // if the extension is mapped in MimeTypes and has a minifier + min, err := m.String(mimetype.Type, string(v)) // try to minify + if err != nil { + log.Fatalf("error minifying file %s: %s", path, err.Error()) + } + minified[path] = StaticFile{ + Data: min, + MimeType: mimetype, + } + } else { // if extension is not in MimeTypes and does not have minifier, skip minify + minified[path] = StaticFile{ + Data: string(v), + MimeType: mimetype, + } + } + } else { // if the file has no extension, skip minify + minified[path] = StaticFile{ + Data: string(v), + MimeType: PlainTextMimeType, + } + } + } + return nil + }) + return minified +} diff --git a/go.mod b/go.mod index e6c2959..f885a54 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module proxmoxaas-dashboard go 1.23.2 + +require github.com/tdewolff/minify v2.3.6+incompatible + +require ( + github.com/tdewolff/minify/v2 v2.21.3 // indirect + github.com/tdewolff/parse v2.3.4+incompatible // indirect + github.com/tdewolff/parse/v2 v2.7.20 // indirect +) diff --git a/web/embed.go b/web/embed.go index 028277d..4f86da4 100644 --- a/web/embed.go +++ b/web/embed.go @@ -1,4 +1,4 @@ -package embed +package web import ( "embed" @@ -21,23 +21,3 @@ var HTML embed.FS //go:embed templates/* var Templates embed.FS - -/* -//go:embed html/account.html -var Account string - -//go:embed html/index.html -var Index string - -//go:embed html/instance.html -var Instance string - -//go:embed html/login.html -var Login string - -//go:embed html/settings.html -var Settings string - -//go:embed templates/base.html -var Base string -*/