This was hardcoded to "app-" because that's what I wanted mine to use. No real reason it has to be that way, though. Only offering a prefix is still pretty limiting, but better than hardcoding.
129 lines
2.7 KiB
Go
129 lines
2.7 KiB
Go
package precompiler
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/tdewolff/minify/v2"
|
|
"github.com/tdewolff/minify/v2/css"
|
|
"github.com/tdewolff/minify/v2/js"
|
|
)
|
|
|
|
// FileType represents a supported file extension (without the .)
|
|
type FileType string
|
|
|
|
// supported file types
|
|
const (
|
|
CSS FileType = "css"
|
|
JS FileType = "js"
|
|
)
|
|
|
|
func initMinify() *minify.M {
|
|
minifier := minify.New()
|
|
minifier.AddFunc(string(CSS), css.Minify)
|
|
minifier.AddFunc(string(JS), js.Minify)
|
|
return minifier
|
|
}
|
|
|
|
func supportedFileType(t FileType) bool {
|
|
switch t {
|
|
case CSS, JS:
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func getBytes(config Config, minifier *minify.M) (map[FileType]*bytes.Buffer, error) {
|
|
buf := make(map[FileType]*bytes.Buffer)
|
|
|
|
for _, pattern := range config.Files {
|
|
files, _ := filepath.Glob(pattern)
|
|
for _, file := range files {
|
|
if file, err := os.Open(file); err == nil {
|
|
ext := FileType(filepath.Ext(file.Name())[1:])
|
|
if !supportedFileType(ext) {
|
|
fmt.Println("Unsupported file type:", file.Name())
|
|
continue
|
|
}
|
|
|
|
if buf[ext] == nil {
|
|
buf[ext] = &bytes.Buffer{}
|
|
}
|
|
|
|
if config.Minify {
|
|
if err = minifier.Minify(string(ext), buf[ext], file); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
if _, err = buf[ext].ReadFrom(file); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return buf, nil
|
|
}
|
|
|
|
// CompileResult holds the results of compilation
|
|
type CompileResult struct {
|
|
Bytes []byte
|
|
Hash string
|
|
OutputPath string
|
|
}
|
|
|
|
func finalize(config Config, buf map[FileType]*bytes.Buffer) (map[FileType]*CompileResult, error) {
|
|
ret := make(map[FileType]*CompileResult, len(buf))
|
|
|
|
for key, b := range buf {
|
|
if b.Len() > 0 {
|
|
bytes := b.Bytes()
|
|
hash := sha256.Sum256(bytes)
|
|
ret[key] = &CompileResult{
|
|
Bytes: bytes,
|
|
Hash: hex.EncodeToString(hash[:]),
|
|
}
|
|
|
|
if len(config.OutputDir) > 0 {
|
|
ext := "." + string(key)
|
|
if config.Minify {
|
|
ext = ".min" + ext
|
|
}
|
|
|
|
dir := filepath.Join(config.OutputDir, string(key))
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
destFile := filepath.Join(dir, config.FilePrefix+ret[key].Hash+ext)
|
|
if err := ioutil.WriteFile(destFile, bytes, 0644); err != nil {
|
|
return nil, err
|
|
}
|
|
ret[key].OutputPath = destFile
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// Compile compiles files indicated by the config into a single file per type
|
|
func Compile(config Config) (map[FileType]*CompileResult, error) {
|
|
var buf map[FileType]*bytes.Buffer
|
|
var err error
|
|
if buf, err = getBytes(config, initMinify()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return finalize(config, buf)
|
|
}
|