aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2023-12-05 18:50:09 -0800
committerMike Crute <mike@crute.us>2023-12-05 18:50:09 -0800
commitf0c1818b9148e56f8e1c0c3fd8a6c4070d9a80d3 (patch)
tree24a21bbec2b84dee3bbc5df0954d4d128cabc5d1
parente01c3ce59a9c01398c7aa27374c882e724896ad4 (diff)
downloadgolib-f0c1818b9148e56f8e1c0c3fd8a6c4070d9a80d3.tar.bz2
golib-f0c1818b9148e56f8e1c0c3fd8a6c4070d9a80d3.tar.xz
golib-f0c1818b9148e56f8e1c0c3fd8a6c4070d9a80d3.zip
echo: add CSS and JS minifiersecho/v0.17.0
-rw-r--r--echo/controller/css_preprocessor.go91
-rw-r--r--echo/controller/js_preprocessor.go110
-rw-r--r--echo/go.mod7
-rw-r--r--echo/go.sum16
4 files changed, 208 insertions, 16 deletions
diff --git a/echo/controller/css_preprocessor.go b/echo/controller/css_preprocessor.go
index a00beb8..b174198 100644
--- a/echo/controller/css_preprocessor.go
+++ b/echo/controller/css_preprocessor.go
@@ -2,40 +2,111 @@ package controller
2 2
3import ( 3import (
4 "bytes" 4 "bytes"
5 "errors"
6 "io"
5 "io/fs" 7 "io/fs"
6 "net/http" 8 "net/http"
9 "path/filepath"
10 "strings"
7 11
12 lru "github.com/hashicorp/golang-lru/v2"
8 "github.com/labstack/echo/v4" 13 "github.com/labstack/echo/v4"
9 14
10 "code.crute.us/mcrute/golib/web/css" 15 "code.crute.us/mcrute/golib/web/css"
11) 16)
12 17
18const defaultCssCacheSize = 100
19
13type CSSMinifierController struct { 20type CSSMinifierController struct {
14 Debug bool 21 Debug bool
22 Skip bool
15 Name string 23 Name string
16 Files fs.FS 24 Files fs.FS
17 cache []byte 25 cache *lru.Cache[string, []byte]
26}
27
28type CSSMinifierConfig struct {
29 Debug bool
30 Skip bool
31 Name string
32 CacheSize int
33 Files fs.FS
18} 34}
19 35
20func (m *CSSMinifierController) Load() error { 36func NewCSSMinifierController(cfg CSSMinifierConfig) *CSSMinifierController {
21 if !m.Debug { 37 s := cfg.CacheSize
22 c := &bytes.Buffer{} 38 if s == 0 {
23 err := css.ParseWriteSheet(m.Files, m.Name, c) 39 s = defaultCssCacheSize
24 m.cache = c.Bytes() 40 }
41
42 c, err := lru.New[string, []byte](s)
43 if err != nil { // Should only happen if size == 0
44 panic(err)
45 }
46
47 return &CSSMinifierController{
48 Debug: cfg.Debug,
49 Skip: cfg.Skip,
50 Name: cfg.Name,
51 Files: cfg.Files,
52 cache: c,
53 }
54}
55
56func (m *CSSMinifierController) minify(name string, w io.Writer) error {
57 fd, err := m.mayOpenFile(name)
58 if err != nil {
25 return err 59 return err
26 } 60 }
27 return nil 61 defer fd.Close()
62 return css.ParseWriteSheet(m.Files, name, w)
63}
64
65// mayOpenFile tries to open a file and translates fs.ErrNotExist into
66// echo.ErrNotFound
67func (m *CSSMinifierController) mayOpenFile(path string) (io.ReadCloser, error) {
68 fd, err := m.Files.Open(path)
69 if err != nil && errors.Is(err, fs.ErrNotExist) {
70 return nil, echo.ErrNotFound
71 }
72 return fd, err
28} 73}
29 74
30func (m *CSSMinifierController) Handle(c echo.Context) error { 75func (m *CSSMinifierController) Handle(c echo.Context) error {
31 r := c.Response() 76 r := c.Response()
32 r.Header().Set("Content-Type", "text/css") 77 r.Header().Set("Content-Type", "text/css")
33 r.WriteHeader(http.StatusOK) 78
79 path := strings.TrimPrefix(filepath.Clean(c.Request().URL.Path), "/")
80 if filepath.Ext(path) != ".css" {
81 return c.NoContent(http.StatusBadRequest)
82 }
34 83
35 if m.Debug { 84 if m.Debug {
36 return css.ParseWriteSheet(m.Files, m.Name, r) 85 if m.Skip {
86 fd, err := m.mayOpenFile(path)
87 if err != nil {
88 return err
89 }
90 defer fd.Close()
91
92 r.WriteHeader(http.StatusOK)
93 _, err = io.Copy(r, fd)
94 return err
95 }
96 return m.minify(path, r)
37 } else { 97 } else {
38 _, err := r.Write(m.cache) 98 val, ok := m.cache.Get(path)
99 if !ok {
100 buf := &bytes.Buffer{}
101 if err := m.minify(path, buf); err != nil {
102 return err
103 }
104 m.cache.Add(path, buf.Bytes())
105 val = buf.Bytes()
106 }
107
108 r.WriteHeader(http.StatusOK)
109 _, err := r.Write(val)
39 return err 110 return err
40 } 111 }
41} 112}
diff --git a/echo/controller/js_preprocessor.go b/echo/controller/js_preprocessor.go
new file mode 100644
index 0000000..377f270
--- /dev/null
+++ b/echo/controller/js_preprocessor.go
@@ -0,0 +1,110 @@
1package controller
2
3import (
4 "bytes"
5 "errors"
6 "io"
7 "io/fs"
8 "net/http"
9 "path/filepath"
10 "strings"
11
12 lru "github.com/hashicorp/golang-lru/v2"
13 "github.com/labstack/echo/v4"
14 "github.com/tdewolff/minify/v2/js"
15)
16
17const defaultJsCacheSize = 100
18
19type JSMinifierController struct {
20 Debug bool
21 Skip bool
22 Files fs.FS
23 cache *lru.Cache[string, []byte]
24 min *js.Minifier
25}
26
27type JSMinifierConfig struct {
28 Debug bool
29 Skip bool
30 CacheSize int
31 Files fs.FS
32}
33
34func NewJSMinifierController(cfg JSMinifierConfig) *JSMinifierController {
35 s := cfg.CacheSize
36 if s == 0 {
37 s = defaultJsCacheSize
38 }
39
40 c, err := lru.New[string, []byte](s)
41 if err != nil { // Should only happen if size == 0
42 panic(err)
43 }
44
45 return &JSMinifierController{
46 Debug: cfg.Debug,
47 Skip: cfg.Skip,
48 Files: cfg.Files,
49 cache: c,
50 min: &js.Minifier{},
51 }
52}
53
54func (m *JSMinifierController) minify(name string, w io.Writer) error {
55 fd, err := m.mayOpenFile(name)
56 if err != nil {
57 return err
58 }
59 defer fd.Close()
60 return m.min.Minify(nil, w, fd, nil)
61}
62
63// mayOpenFile tries to open a file and translates fs.ErrNotExist into
64// echo.ErrNotFound
65func (m *JSMinifierController) mayOpenFile(path string) (io.ReadCloser, error) {
66 fd, err := m.Files.Open(path)
67 if err != nil && errors.Is(err, fs.ErrNotExist) {
68 return nil, echo.ErrNotFound
69 }
70 return fd, err
71}
72
73func (m *JSMinifierController) Handle(c echo.Context) error {
74 r := c.Response()
75 r.Header().Set("Content-Type", "text/javascript")
76
77 path := strings.TrimPrefix(filepath.Clean(c.Request().URL.Path), "/")
78 if filepath.Ext(path) != ".js" {
79 return c.NoContent(http.StatusBadRequest)
80 }
81
82 if m.Debug {
83 if m.Skip {
84 fd, err := m.mayOpenFile(path)
85 if err != nil {
86 return err
87 }
88 defer fd.Close()
89
90 r.WriteHeader(http.StatusOK)
91 _, err = io.Copy(r, fd)
92 return err
93 }
94 return m.minify(path, r)
95 } else {
96 val, ok := m.cache.Get(path)
97 if !ok {
98 buf := &bytes.Buffer{}
99 if err := m.minify(path, buf); err != nil {
100 return err
101 }
102 m.cache.Add(path, buf.Bytes())
103 val = buf.Bytes()
104 }
105
106 r.WriteHeader(http.StatusOK)
107 _, err := r.Write(val)
108 return err
109 }
110}
diff --git a/echo/go.mod b/echo/go.mod
index 23a62be..64877fc 100644
--- a/echo/go.mod
+++ b/echo/go.mod
@@ -7,13 +7,15 @@ require (
7 code.crute.us/mcrute/golib/clients/netbox/v4 v4.1.0 7 code.crute.us/mcrute/golib/clients/netbox/v4 v4.1.0
8 code.crute.us/mcrute/golib/secrets v0.4.0 8 code.crute.us/mcrute/golib/secrets v0.4.0
9 code.crute.us/mcrute/golib/vault v0.2.6 9 code.crute.us/mcrute/golib/vault v0.2.6
10 code.crute.us/mcrute/golib/web/css v0.1.0 10 code.crute.us/mcrute/golib/web/css v0.1.1
11 github.com/elnormous/contenttype v1.0.3 11 github.com/elnormous/contenttype v1.0.3
12 github.com/hashicorp/golang-lru/v2 v2.0.7
12 github.com/labstack/echo/v4 v4.6.1 13 github.com/labstack/echo/v4 v4.6.1
13 github.com/labstack/gommon v0.3.1 14 github.com/labstack/gommon v0.3.1
14 github.com/prometheus/client_golang v1.11.0 15 github.com/prometheus/client_golang v1.11.0
15 github.com/quic-go/quic-go v0.39.0 16 github.com/quic-go/quic-go v0.39.0
16 github.com/stretchr/testify v1.8.1 17 github.com/stretchr/testify v1.8.1
18 github.com/tdewolff/minify/v2 v2.20.9
17 gopkg.in/square/go-jose.v2 v2.5.1 19 gopkg.in/square/go-jose.v2 v2.5.1
18) 20)
19 21
@@ -70,6 +72,7 @@ require (
70 github.com/quic-go/qpack v0.4.0 // indirect 72 github.com/quic-go/qpack v0.4.0 // indirect
71 github.com/quic-go/qtls-go1-20 v0.3.4 // indirect 73 github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
72 github.com/ryanuber/go-glob v1.0.0 // indirect 74 github.com/ryanuber/go-glob v1.0.0 // indirect
75 github.com/tdewolff/parse/v2 v2.7.6 // indirect
73 github.com/valyala/bytebufferpool v1.0.0 // indirect 76 github.com/valyala/bytebufferpool v1.0.0 // indirect
74 github.com/valyala/fasttemplate v1.2.1 // indirect 77 github.com/valyala/fasttemplate v1.2.1 // indirect
75 go.uber.org/atomic v1.9.0 // indirect 78 go.uber.org/atomic v1.9.0 // indirect
@@ -78,7 +81,7 @@ require (
78 golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect 81 golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
79 golang.org/x/mod v0.11.0 // indirect 82 golang.org/x/mod v0.11.0 // indirect
80 golang.org/x/net v0.10.0 // indirect 83 golang.org/x/net v0.10.0 // indirect
81 golang.org/x/sys v0.10.0 // indirect 84 golang.org/x/sys v0.15.0 // indirect
82 golang.org/x/text v0.11.0 // indirect 85 golang.org/x/text v0.11.0 // indirect
83 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect 86 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
84 golang.org/x/tools v0.9.1 // indirect 87 golang.org/x/tools v0.9.1 // indirect
diff --git a/echo/go.sum b/echo/go.sum
index 462635f..05485aa 100644
--- a/echo/go.sum
+++ b/echo/go.sum
@@ -38,8 +38,8 @@ code.crute.us/mcrute/golib/secrets v0.4.0 h1:tZzQEOnJshDGuzvvr0n0BMWZbu3ZMB5QRqI
38code.crute.us/mcrute/golib/secrets v0.4.0/go.mod h1:c40ezKg/NXe5NE3PaCRIUJC6D6XCoPSu9+duZSdKsNY= 38code.crute.us/mcrute/golib/secrets v0.4.0/go.mod h1:c40ezKg/NXe5NE3PaCRIUJC6D6XCoPSu9+duZSdKsNY=
39code.crute.us/mcrute/golib/vault v0.2.6 h1:X+TlEGFPj6pj3OqmrJprv+wJYdo8QTR2IpP3EfVniHU= 39code.crute.us/mcrute/golib/vault v0.2.6 h1:X+TlEGFPj6pj3OqmrJprv+wJYdo8QTR2IpP3EfVniHU=
40code.crute.us/mcrute/golib/vault v0.2.6/go.mod h1:QBgcKiG94tPHAcxeRyNHrfiLGSKtojlRDLGRX5I6LgE= 40code.crute.us/mcrute/golib/vault v0.2.6/go.mod h1:QBgcKiG94tPHAcxeRyNHrfiLGSKtojlRDLGRX5I6LgE=
41code.crute.us/mcrute/golib/web/css v0.1.0 h1:VdP0i2Q+JC+TxiyWTAdqksDcKU4GKwL2Ly02ZRkyDFw= 41code.crute.us/mcrute/golib/web/css v0.1.1 h1:cJ9/fPMPPLzwkMG6Z99SbpAxyknXXEu2ADR5fRG/yTo=
42code.crute.us/mcrute/golib/web/css v0.1.0/go.mod h1:USqoGbYKNDhEVZITLxSxd/vFXBihL8/N3Gg/v01hNWo= 42code.crute.us/mcrute/golib/web/css v0.1.1/go.mod h1:USqoGbYKNDhEVZITLxSxd/vFXBihL8/N3Gg/v01hNWo=
43dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 43dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
44github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 44github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
45github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 45github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -228,6 +228,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
228github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 228github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
229github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 229github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
230github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 230github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
231github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
232github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
231github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 233github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
232github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 234github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
233github.com/hashicorp/vault/api v1.8.0 h1:7765sW1XBt+qf4XKIYE4ebY9qc/yi9V2/egzGSUNMZU= 235github.com/hashicorp/vault/api v1.8.0 h1:7765sW1XBt+qf4XKIYE4ebY9qc/yi9V2/egzGSUNMZU=
@@ -373,6 +375,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
373github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 375github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
374github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 376github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
375github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 377github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
378github.com/tdewolff/minify/v2 v2.20.9 h1:0RGsL+jBpm77obkuNCjNZ2eiN81CZzTnjeVmTqxCmYk=
379github.com/tdewolff/minify/v2 v2.20.9/go.mod h1:hZnNtFqXVQ5QIAR05tdgvS7h6E80jyRwHSGVmM4jbzQ=
380github.com/tdewolff/parse/v2 v2.7.6 h1:PGZH2b/itDSye9RatReRn4GBhsT+KFEMtAMjHRuY1h8=
381github.com/tdewolff/parse/v2 v2.7.6/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
382github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA=
383github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
376github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 384github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
377github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 385github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
378github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 386github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
@@ -530,8 +538,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
530golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 538golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
531golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 539golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
532golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 540golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
533golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 541golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
534golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 542golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
535golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 543golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
536golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 544golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
537golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 545golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=