diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 64d660d6..da754bc5 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -156,16 +156,16 @@ func Parse() { slog.Info("The default value for DATABASE_URL is used") } - if err := static.CalculateBinaryFileChecksums(); err != nil { - printErrorAndExit(fmt.Errorf("unable to calculate binary file checksums: %v", err)) + if err := static.GenerateBinaryBundles(); err != nil { + printErrorAndExit(fmt.Errorf("unable to generate binary files bundle: %v", err)) } if err := static.GenerateStylesheetsBundles(); err != nil { - printErrorAndExit(fmt.Errorf("unable to generate stylesheets bundles: %v", err)) + printErrorAndExit(fmt.Errorf("unable to generate stylesheets bundle: %v", err)) } if err := static.GenerateJavascriptBundles(); err != nil { - printErrorAndExit(fmt.Errorf("unable to generate javascript bundles: %v", err)) + printErrorAndExit(fmt.Errorf("unable to generate javascript bundle: %v", err)) } db, err := database.NewConnectionPool( diff --git a/internal/ui/static/bin/sprite.svg b/internal/ui/static/bin/sprite.svg index 7ff7b1c7..e96364db 100644 --- a/internal/ui/static/bin/sprite.svg +++ b/internal/ui/static/bin/sprite.svg @@ -1,29 +1,7 @@ diff --git a/internal/ui/static/static.go b/internal/ui/static/static.go index 72c2df36..74a5af2e 100644 --- a/internal/ui/static/static.go +++ b/internal/ui/static/static.go @@ -6,13 +6,15 @@ package static // import "miniflux.app/v2/internal/ui/static" import ( "bytes" "embed" - "fmt" + "log/slog" + "strings" "miniflux.app/v2/internal/crypto" "github.com/tdewolff/minify/v2" "github.com/tdewolff/minify/v2/css" "github.com/tdewolff/minify/v2/js" + "github.com/tdewolff/minify/v2/svg" ) type asset struct { @@ -22,9 +24,9 @@ type asset struct { // Static assets. var ( - StylesheetBundles map[string]asset - JavascriptBundles map[string]asset - binaryFileChecksums map[string]string + StylesheetBundles map[string]asset + JavascriptBundles map[string]asset + BinaryBundles map[string]asset ) //go:embed bin/* @@ -36,40 +38,42 @@ var stylesheetFiles embed.FS //go:embed js/*.js var javascriptFiles embed.FS -// CalculateBinaryFileChecksums generates hash of embed binary files. -func CalculateBinaryFileChecksums() error { +func GenerateBinaryBundles() error { dirEntries, err := binaryFiles.ReadDir("bin") if err != nil { return err } - binaryFileChecksums = make(map[string]string, len(dirEntries)) + BinaryBundles = make(map[string]asset, len(dirEntries)) + + minifier := minify.New() + minifier.Add("image/svg+xml", &svg.Minifier{ + KeepComments: true, // needed to keep the license + }) for _, dirEntry := range dirEntries { - data, err := LoadBinaryFile(dirEntry.Name()) + name := dirEntry.Name() + data, err := binaryFiles.ReadFile("bin/" + name) if err != nil { return err } - binaryFileChecksums[dirEntry.Name()] = crypto.HashFromBytes(data) + if strings.HasSuffix(name, ".svg") { + // minifier.Bytes returns the data unchanged in case of error. + data, err = minifier.Bytes("image/svg+xml", data) + if err != nil { + slog.Error("Unable to minimize the svg file", slog.String("filename", name), slog.Any("error", err)) + } + } + + BinaryBundles[name] = asset{ + Data: data, + Checksum: crypto.HashFromBytes(data), + } } return nil } -// LoadBinaryFile loads an embed binary file. -func LoadBinaryFile(filename string) ([]byte, error) { - return binaryFiles.ReadFile("bin/" + filename) -} - -// GetBinaryFileChecksum returns a binary file checksum. -func GetBinaryFileChecksum(filename string) (string, error) { - data, found := binaryFileChecksums[filename] - if !found { - return "", fmt.Errorf(`static: unable to find checksum for %q`, filename) - } - return data, nil -} - // GenerateStylesheetsBundles creates CSS bundles. func GenerateStylesheetsBundles() error { var bundles = map[string][]string{ diff --git a/internal/ui/static_app_icon.go b/internal/ui/static_app_icon.go index 8a99fb62..92dff2fa 100644 --- a/internal/ui/static_app_icon.go +++ b/internal/ui/static_app_icon.go @@ -16,19 +16,13 @@ import ( func (h *handler) showAppIcon(w http.ResponseWriter, r *http.Request) { filename := request.RouteStringParam(r, "filename") - etag, err := static.GetBinaryFileChecksum(filename) - if err != nil { + value, ok := static.BinaryBundles[filename] + if !ok { html.NotFound(w, r) return } - response.New(w, r).WithCaching(etag, 72*time.Hour, func(b *response.Builder) { - blob, err := static.LoadBinaryFile(filename) - if err != nil { - html.ServerError(w, r, err) - return - } - + response.New(w, r).WithCaching(value.Checksum, 72*time.Hour, func(b *response.Builder) { switch filepath.Ext(filename) { case ".png": b.WithoutCompression() @@ -36,8 +30,7 @@ func (h *handler) showAppIcon(w http.ResponseWriter, r *http.Request) { case ".svg": b.WithHeader("Content-Type", "image/svg+xml") } - - b.WithBody(blob) + b.WithBody(value.Data) b.Write() }) } diff --git a/internal/ui/static_favicon.go b/internal/ui/static_favicon.go index 2775ca81..e2618a88 100644 --- a/internal/ui/static_favicon.go +++ b/internal/ui/static_favicon.go @@ -13,22 +13,16 @@ import ( ) func (h *handler) showFavicon(w http.ResponseWriter, r *http.Request) { - etag, err := static.GetBinaryFileChecksum("favicon.ico") - if err != nil { + value, ok := static.BinaryBundles["favicon.ico"] + if !ok { html.NotFound(w, r) return } - response.New(w, r).WithCaching(etag, 48*time.Hour, func(b *response.Builder) { - blob, err := static.LoadBinaryFile("favicon.ico") - if err != nil { - html.ServerError(w, r, err) - return - } - + response.New(w, r).WithCaching(value.Checksum, 48*time.Hour, func(b *response.Builder) { b.WithHeader("Content-Type", "image/x-icon") b.WithoutCompression() - b.WithBody(blob) + b.WithBody(value.Data) b.Write() }) }