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" } }