120 lines
2.8 KiB
Go
120 lines
2.8 KiB
Go
// Copyright 2022 E99p1ant. All rights reserved.
|
||
// Use of this source code is governed by a MIT-style
|
||
// license that can be found in the LICENSE file.
|
||
|
||
package template
|
||
|
||
import (
|
||
"bytes"
|
||
"embed"
|
||
"io/fs"
|
||
"path/filepath"
|
||
"regexp"
|
||
|
||
"github.com/flamego/template"
|
||
"github.com/pkg/errors"
|
||
"github.com/sirupsen/logrus"
|
||
"github.com/tdewolff/minify/v2"
|
||
"github.com/tdewolff/minify/v2/css"
|
||
"github.com/tdewolff/minify/v2/html"
|
||
"github.com/tdewolff/minify/v2/js"
|
||
)
|
||
|
||
var _ template.FileSystem = (*FileSystem)(nil)
|
||
|
||
type file struct {
|
||
name string
|
||
data []byte
|
||
ext string
|
||
}
|
||
|
||
func (f *file) Name() string { return f.name }
|
||
func (f *file) Data() ([]byte, error) { return f.data, nil }
|
||
func (f *file) Ext() string { return f.ext }
|
||
|
||
type FileSystem struct {
|
||
files []template.File
|
||
}
|
||
|
||
func (fs *FileSystem) Files() []template.File { return fs.files }
|
||
|
||
// getExt returns the extension of given name, prefixed with the dot (".").
|
||
// 必须用 filepath.Ext:取最后一个点之后的部分。
|
||
// 旧的 strings.Index 实现会把 "foo.bar.html" 的扩展名误判为 ".bar.html",
|
||
// 导致模板名截取错误(如 "foo" 丢失子目录结构)。
|
||
func getExt(name string) string {
|
||
return filepath.Ext(name)
|
||
}
|
||
|
||
func Minify(embedFS embed.FS, dir string, allowedExtensions []string) (*FileSystem, error) {
|
||
var beforeSize, afterSize int64
|
||
|
||
var files []template.File
|
||
m := minify.New()
|
||
m.AddFunc("text/html", html.Minify)
|
||
m.AddFunc("text/css", css.Minify)
|
||
m.AddFuncRegexp(regexp.MustCompile(`^(application|text)/(x-)?(java|ecma)script$`), js.Minify)
|
||
|
||
err := fs.WalkDir(embedFS, dir, func(path string, d fs.DirEntry, err error) error {
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if d.IsDir() {
|
||
return nil
|
||
}
|
||
|
||
relpath, err := filepath.Rel(dir, path)
|
||
if err != nil {
|
||
return errors.Wrap(err, "get relative path")
|
||
}
|
||
|
||
ext := getExt(relpath)
|
||
for _, allowed := range allowedExtensions {
|
||
if ext != allowed {
|
||
continue
|
||
}
|
||
|
||
data, err := embedFS.ReadFile(path)
|
||
if err != nil {
|
||
return errors.Wrap(err, "read")
|
||
}
|
||
beforeSize += int64(len(data))
|
||
|
||
var buf bytes.Buffer
|
||
switch filepath.Ext(path) {
|
||
case ".html":
|
||
if err := html.Minify(m, &buf, bytes.NewReader(data), nil); err != nil {
|
||
return errors.Wrap(err, "minify html")
|
||
}
|
||
default:
|
||
buf = *bytes.NewBuffer(data)
|
||
}
|
||
afterSize += int64(buf.Len())
|
||
|
||
name := filepath.ToSlash(relpath[:len(relpath)-len(ext)])
|
||
files = append(files,
|
||
&file{
|
||
name: name,
|
||
data: buf.Bytes(),
|
||
ext: ext,
|
||
},
|
||
)
|
||
}
|
||
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "walk")
|
||
}
|
||
|
||
logrus.WithFields(logrus.Fields{
|
||
"before_size": beforeSize,
|
||
"after_size": afterSize,
|
||
"ratio": float64(afterSize) / float64(beforeSize),
|
||
}).Info("Minify succeeded")
|
||
|
||
return &FileSystem{
|
||
files: files,
|
||
}, nil
|
||
}
|