service/web.go

100 lines
2.3 KiB
Go
Raw Normal View History

2026-05-07 21:11:06 +08:00
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"
}
}