From 6202e3cafac3f7bab3969e80ed6c4f72b50c62ad Mon Sep 17 00:00:00 2001 From: Aveline <352441+ym@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:17:15 +0200 Subject: [PATCH] chore: serve pre-compressed static files (#793) --- Makefile | 14 +++++++++++++- go.mod | 1 + go.sum | 2 ++ ui/vite.config.ts | 33 ++++++++++++++++++++++++--------- web.go | 24 ++++++++++++++++++++++-- 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 0da630a..178e6da 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,19 @@ build_dev_test: build_test2json build_gotestsum tar czfv device-tests.tar.gz -C $(BIN_DIR)/tests . frontend: - cd ui && npm ci && npm run build:device + cd ui && npm ci && npm run build:device && \ + find ../static/assets \ + -type f \ + \( -name '*.js' \ + -o -name '*.css' \ + -o -name '*.png' \ + -o -name '*.jpg' \ + -o -name '*.jpeg' \ + -o -name '*.gif' \ + -o -name '*.webp' \ + -o -name '*.woff2' \ + \) \ + -exec sh -c 'gzip -9 -kfv {}' \; dev_release: frontend build_dev @echo "Uploading release..." diff --git a/go.mod b/go.mod index 72e57cd..962c3a1 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,7 @@ require ( github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect + github.com/vearutop/statigz v1.5.0 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/wlynxg/anet v0.0.5 // indirect golang.org/x/arch v0.18.0 // indirect diff --git a/go.sum b/go.sum index 36087a2..e19fa9e 100644 --- a/go.sum +++ b/go.sum @@ -171,6 +171,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/vearutop/statigz v1.5.0 h1:FuWwZiT82yBw4xbWdWIawiP2XFTyEPhIo8upRxiKLqk= +github.com/vearutop/statigz v1.5.0/go.mod h1:oHmjFf3izfCO804Di1ZjB666P3fAlVzJEx2k6jNt/Gk= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 44eec3a..e227f67 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -31,20 +31,35 @@ export default defineConfig(({ mode, command }) => { esbuild: { pure: ["console.debug"], }, - build: { outDir: isCloud ? "dist" : "../static" }, + build: { + outDir: isCloud ? "dist" : "../static", + rollupOptions: { + output: { + manualChunks: (id) => { + if (id.includes("node_modules")) { + return "vendor"; + } + return null; + }, + assetFileNames: "assets/immutable/[name]-[hash][extname]", + chunkFileNames: "assets/immutable/[name]-[hash].js", + entryFileNames: "assets/immutable/[name]-[hash].js", + }, + }, + }, server: { host: "0.0.0.0", https: useSSL, proxy: JETKVM_PROXY_URL ? { - "/me": JETKVM_PROXY_URL, - "/device": JETKVM_PROXY_URL, - "/webrtc": JETKVM_PROXY_URL, - "/auth": JETKVM_PROXY_URL, - "/storage": JETKVM_PROXY_URL, - "/cloud": JETKVM_PROXY_URL, - "/developer": JETKVM_PROXY_URL, - } + "/me": JETKVM_PROXY_URL, + "/device": JETKVM_PROXY_URL, + "/webrtc": JETKVM_PROXY_URL, + "/auth": JETKVM_PROXY_URL, + "/storage": JETKVM_PROXY_URL, + "/cloud": JETKVM_PROXY_URL, + "/developer": JETKVM_PROXY_URL, + } : undefined, }, base: onDevice && command === "build" ? "/static" : "/", diff --git a/web.go b/web.go index 21e17e7..883ebb7 100644 --- a/web.go +++ b/web.go @@ -11,6 +11,7 @@ import ( "net/http" "net/http/pprof" "path/filepath" + "slices" "strings" "time" @@ -24,6 +25,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog" + "github.com/vearutop/statigz" "golang.org/x/crypto/bcrypt" ) @@ -66,6 +68,11 @@ type SetupRequest struct { Password string `json:"password,omitempty"` } +var cachableFileExtensions = []string{ + ".jpg", ".jpeg", ".png", ".gif", ".webp", ".woff2", + ".ico", +} + func setupRouter() *gin.Engine { gin.SetMode(gin.ReleaseMode) gin.DisableConsoleColor() @@ -75,23 +82,36 @@ func setupRouter() *gin.Engine { return *ginLogger }), )) + staticFS, _ := fs.Sub(staticFiles, "static") + staticFileServer := http.StripPrefix("/static", statigz.FileServer( + staticFS.(fs.ReadDirFS), + )) // Add a custom middleware to set cache headers for images // This is crucial for optimizing the initial welcome screen load time // By enabling caching, we ensure that pre-loaded images are stored in the browser cache // This allows for a smoother enter animation and improved user experience on the welcome screen r.Use(func(c *gin.Context) { + if strings.HasPrefix(c.Request.URL.Path, "/static/assets/immutable/") { + c.Header("Cache-Control", "public, max-age=31536000, immutable") // Cache for 1 year + c.Next() + return + } + if strings.HasPrefix(c.Request.URL.Path, "/static/") { ext := filepath.Ext(c.Request.URL.Path) - if ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" || ext == ".webp" { + if slices.Contains(cachableFileExtensions, ext) { c.Header("Cache-Control", "public, max-age=300") // Cache for 5 minutes } } + c.Next() }) - r.StaticFS("/static", http.FS(staticFS)) + r.Any("/static/*w", func(c *gin.Context) { + staticFileServer.ServeHTTP(c.Writer, c.Request) + }) r.POST("/auth/login-local", handleLogin) // We use this to determine if the device is setup