mirror of https://github.com/jlelse/GoBlog
Asset handling with minification, fingerprinting and Cache-Control headers
This commit is contained in:
parent
54acbc5808
commit
377bf496c2
|
@ -2,4 +2,5 @@
|
|||
/config/
|
||||
/config.yaml
|
||||
/data
|
||||
/GoBlog
|
||||
/GoBlog
|
||||
/tmp_assets
|
7
go.mod
7
go.mod
|
@ -6,14 +6,15 @@ require (
|
|||
github.com/PuerkitoBio/goquery v1.5.1
|
||||
github.com/andybalholm/cascadia v1.2.0 // indirect
|
||||
github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1
|
||||
github.com/bep/golibsass v0.7.0
|
||||
github.com/caddyserver/certmagic v0.12.0
|
||||
github.com/frankban/quicktest v1.11.0 // indirect
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
|
||||
github.com/gorilla/feeds v1.1.1
|
||||
github.com/jeremywohl/flatten v1.0.1
|
||||
github.com/jinzhu/gorm v1.9.16 // indirect
|
||||
github.com/klauspost/cpuid v1.3.1 // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/lib/pq v1.8.0 // indirect
|
||||
|
@ -40,8 +41,8 @@ require (
|
|||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
|
||||
golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 // indirect
|
||||
golang.org/x/tools v0.0.0-20200917221617-d56e4e40bc9d // indirect
|
||||
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff // indirect
|
||||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
gopkg.in/ini.v1 v1.61.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
|
|
19
go.sum
19
go.sum
|
@ -30,6 +30,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
|
|||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bep/golibsass v0.7.0 h1:/ocxgtPZ5rgp7FA+mktzyent+fAg82tJq4iMsTMBAtA=
|
||||
github.com/bep/golibsass v0.7.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/caddyserver/certmagic v0.12.0 h1:1f7kxykaJkOVVpXJ8ZrC6RAO5F6+kKm9U7dBFbLNeug=
|
||||
|
@ -54,6 +56,10 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
|
|||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk=
|
||||
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/frankban/quicktest v1.11.0 h1:Yyrghcw93e1jKo4DTZkRFTTFvBsVhzbblBUPNU1vW6Q=
|
||||
github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
|
@ -86,6 +92,10 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
|
|||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
|
@ -389,8 +399,8 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=
|
||||
golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff h1:1CPUrky56AcgSpxz/KfgzQWzfG09u5YOL8MvPYBlrL8=
|
||||
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
|
@ -422,10 +432,11 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200917221617-d56e4e40bc9d h1:y39d97JVttj+rkTXITl1nf9Vsk+VoRuNzIDLFldUSB4=
|
||||
golang.org/x/tools v0.0.0-20200917221617-d56e4e40bc9d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266 h1:k7tVuG0g1JwmD3Jh8oAl1vQ1C3jb4Hi/dUl1wWDBJpQ=
|
||||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
|
|
12
http.go
12
http.go
|
@ -58,6 +58,7 @@ func buildHandler() (http.Handler, error) {
|
|||
}
|
||||
r.Use(middleware.Recoverer, middleware.StripSlashes, middleware.GetHead)
|
||||
|
||||
// API
|
||||
r.Route("/api", func(apiRouter chi.Router) {
|
||||
apiRouter.Use(middleware.BasicAuth("API", map[string]string{
|
||||
appConfig.User.Nick: appConfig.User.Password,
|
||||
|
@ -68,6 +69,7 @@ func buildHandler() (http.Handler, error) {
|
|||
apiRouter.Post("/hugo", apiPostCreateHugo)
|
||||
})
|
||||
|
||||
// Posts
|
||||
allPostPaths, err := allPostPaths()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -78,6 +80,7 @@ func buildHandler() (http.Handler, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Redirects
|
||||
allRedirectPaths, err := allRedirectPaths()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -88,11 +91,19 @@ func buildHandler() (http.Handler, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Assets
|
||||
for _, path := range allAssetPaths() {
|
||||
if path != "" {
|
||||
r.Get(path, serveAsset(path))
|
||||
}
|
||||
}
|
||||
|
||||
paginationPath := "/page/{page}"
|
||||
rssPath := ".rss"
|
||||
jsonPath := ".json"
|
||||
atomPath := ".atom"
|
||||
|
||||
// Indexes, Feeds
|
||||
for _, section := range appConfig.Blog.Sections {
|
||||
if section.Name != "" {
|
||||
path := "/" + section.Name
|
||||
|
@ -122,6 +133,7 @@ func buildHandler() (http.Handler, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Blog
|
||||
rootPath := "/"
|
||||
blogPath := "/blog"
|
||||
if routePatterns := routesToStringSlice(r.Routes()); !routePatterns.has(rootPath) {
|
||||
|
|
5
main.go
5
main.go
|
@ -21,6 +21,11 @@ func main() {
|
|||
initMarkdown()
|
||||
initRendering()
|
||||
initMinify()
|
||||
err = initTemplateAssets()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare graceful shutdown
|
||||
quit := make(chan os.Signal, 1)
|
||||
|
|
|
@ -54,6 +54,7 @@ func initRendering() {
|
|||
}
|
||||
return d.Format(format)
|
||||
},
|
||||
"asset": assetFile,
|
||||
"include": func(templateName string, data interface{}) (template.HTML, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
err := templates[templateName].ExecuteTemplate(buf, templateName, data)
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"github.com/bep/golibsass/libsass"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const assetsFolder = "templates/assets"
|
||||
const compiledAssetsFolder = "tmp_assets"
|
||||
|
||||
var assetFiles map[string]string
|
||||
|
||||
func initTemplateAssets() error {
|
||||
err := os.RemoveAll(compiledAssetsFolder)
|
||||
err = os.MkdirAll(compiledAssetsFolder, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assetFiles = map[string]string{}
|
||||
err = filepath.Walk(assetsFolder, func(path string, info os.FileInfo, err error) error {
|
||||
if info.Mode().IsRegular() {
|
||||
compiled, err := compileAssets(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if compiled != "" {
|
||||
assetFiles[path] = compiled
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compileAssets(name string) (compiledFileName string, err error) {
|
||||
originalContent, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ext := path.Ext(name)
|
||||
var compiledContent []byte
|
||||
compiledExt := ext
|
||||
switch ext {
|
||||
case ".js":
|
||||
compiledContent, err = minifier.Bytes("application/javascript", originalContent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case ".css":
|
||||
compiledContent, err = minifier.Bytes("text/css", originalContent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case ".scss":
|
||||
transpiler, err := libsass.New(libsass.Options{OutputStyle: libsass.CompressedStyle})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result, err := transpiler.Execute(string(originalContent))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
compiledContent, err = minifier.Bytes("text/css", []byte(result.CSS))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
compiledExt = ".css"
|
||||
default:
|
||||
// Just copy the file
|
||||
compiledContent = originalContent
|
||||
}
|
||||
sha := sha1.New()
|
||||
sha.Write(compiledContent)
|
||||
hash := fmt.Sprintf("%x", sha.Sum(nil))
|
||||
compiledFileName = hash + compiledExt
|
||||
err = ioutil.WriteFile(path.Join(compiledAssetsFolder, compiledFileName), compiledContent, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Function for templates
|
||||
func assetFile(fileName string) string {
|
||||
return appConfig.Server.PublicAddress + "/" + assetFiles[fileName]
|
||||
}
|
||||
|
||||
func allAssetPaths() []string {
|
||||
var paths []string
|
||||
for _, name := range assetFiles {
|
||||
paths = append(paths, "/"+name)
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
// Gets only called by registered paths
|
||||
func serveAsset(path string) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Cache-Control", "public,max-age=31536000,immutable")
|
||||
http.ServeFile(w, r, compiledAssetsFolder+path)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue