100 lines
2.3 KiB
Go
100 lines
2.3 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/ssgo/u"
|
|
"github.com/tdewolff/minify/v2"
|
|
"github.com/tdewolff/minify/v2/css"
|
|
"github.com/tdewolff/minify/v2/html"
|
|
"github.com/tdewolff/minify/v2/js"
|
|
"github.com/tdewolff/minify/v2/json"
|
|
"github.com/tdewolff/minify/v2/svg"
|
|
"github.com/tdewolff/minify/v2/xml"
|
|
)
|
|
|
|
var m *minify.M
|
|
|
|
func init() {
|
|
m = minify.New()
|
|
m.AddFunc("text/html", html.Minify)
|
|
m.AddFunc("text/css", css.Minify)
|
|
m.AddFunc("image/svg+xml", svg.Minify)
|
|
m.AddFuncRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), js.Minify)
|
|
m.AddFuncRegexp(regexp.MustCompile("[/+]json$"), json.Minify)
|
|
m.AddFuncRegexp(regexp.MustCompile("[/+]xml$"), xml.Minify)
|
|
}
|
|
|
|
var reJSBacktickContent = regexp.MustCompile("(?s)`([^`]*)`")
|
|
var reHTMLBetweenTags = regexp.MustCompile(`>\s+<`)
|
|
|
|
func Minify(from, to string) error {
|
|
srcStat := u.GetFileInfo(from)
|
|
if srcStat == nil {
|
|
return errors.New("file not found: " + from)
|
|
}
|
|
dstStat := u.GetFileInfo(to)
|
|
if dstStat != nil && !srcStat.ModTime.After(dstStat.ModTime) {
|
|
return nil
|
|
}
|
|
|
|
tmpTo := filepath.Join(filepath.Dir(to), "."+filepath.Base(to))
|
|
dst, err := os.OpenFile(tmpTo, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.Remove(tmpTo)
|
|
defer dst.Close()
|
|
|
|
if syscall.Flock(int(dst.Fd()), syscall.LOCK_EX) == nil {
|
|
defer syscall.Flock(int(dst.Fd()), syscall.LOCK_UN)
|
|
}
|
|
|
|
dstStat = u.GetFileInfo(to)
|
|
if dstStat != nil && !srcStat.ModTime.After(dstStat.ModTime) {
|
|
return nil
|
|
}
|
|
|
|
input := u.ReadFileN(from)
|
|
processedInput := reJSBacktickContent.ReplaceAllStringFunc(input, func(s string) string {
|
|
return "`" + reHTMLBetweenTags.ReplaceAllString(strings.TrimSpace(s[1:len(s)-1]), "><") + "`"
|
|
})
|
|
|
|
minifiedOutput, err := m.String(getMimeType(from), processedInput)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err = dst.WriteString(minifiedOutput); err != nil {
|
|
return err
|
|
}
|
|
if err = dst.Close(); err == nil {
|
|
err = os.Rename(tmpTo, to)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func getMimeType(filename string) string {
|
|
switch filepath.Ext(filename) {
|
|
case ".html", ".htm":
|
|
return "text/html"
|
|
case ".css":
|
|
return "text/css"
|
|
case ".js":
|
|
return "application/javascript"
|
|
case ".json":
|
|
return "application/json"
|
|
case ".svg":
|
|
return "image/svg+xml"
|
|
case ".xml":
|
|
return "text/xml"
|
|
default:
|
|
return "text/plain"
|
|
}
|
|
}
|