From fe4a23b550b7450e6e24cd3c78dfd575122d361d Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Sun, 23 Jun 2024 17:22:09 +0200 Subject: pre-compress generated files in production --- go.mod | 1 + go.sum | 2 ++ internal/builder/builder.go | 24 ++++++++----- internal/builder/files.go | 83 +++++++++++++++++++++++++++++++++++++++++++++ nix/gomod2nix.toml | 3 ++ 5 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 internal/builder/files.go diff --git a/go.mod b/go.mod index 5660e67..8595437 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/a-h/htmlformat v0.0.0-20231108124658-5bd994fe268e github.com/a-h/templ v0.2.707 github.com/adrg/frontmatter v0.2.0 + github.com/andybalholm/brotli v1.1.0 github.com/antchfx/xmlquery v1.4.0 github.com/antchfx/xpath v1.3.0 github.com/ardanlabs/conf/v3 v3.1.7 diff --git a/go.sum b/go.sum index 398c65f..a8bf342 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/adrg/frontmatter v0.2.0 h1:/DgnNe82o03riBd1S+ZDjd43wAmC6W35q67NHeLkPd github.com/adrg/frontmatter v0.2.0/go.mod h1:93rQCj3z3ZlwyxxpQioRKC1wDLto4aXHrbqIsnH9wmE= github.com/alanpearce/htmlformat v0.0.0-20240425000139-1244374b2562 h1:7LpBXZnmFk8+RwdFnAYB7rKZhBQrQ4poPLEhpwwbmSc= github.com/alanpearce/htmlformat v0.0.0-20240425000139-1244374b2562/go.mod h1:FMIm5afKmEfarNbIXOaPHFY8X7fo+fRQB6I9MPG2nB0= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/antchfx/xmlquery v1.4.0 h1:xg2HkfcRK2TeTbdb0m1jxCYnvsPaGY/oeZWTGqX/0hA= diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 44ab402..4f305db 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -32,30 +32,34 @@ type Result struct { Hashes []string } +var compressFiles = false + func mkdirp(dirs ...string) error { err := os.MkdirAll(path.Join(dirs...), 0755) return errors.Wrap(err, "could not create directory") } -func outputToFile(output io.Reader, filename ...string) error { - // log.Debug("outputting file", "filename", path.Join(filename...)) - file, err := os.OpenFile(path.Join(filename...), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) +func outputToFile(output io.Reader, pathParts ...string) error { + filename := path.Join(pathParts...) + // log.Debug("outputting file", "filename", filename) + file, err := openFileAndVariants(filename) if err != nil { return errors.WithMessage(err, "could not open output file") } defer file.Close() - if _, err := file.ReadFrom(output); err != nil { + if _, err := io.Copy(file, output); err != nil { return errors.WithMessage(err, "could not write output file") } return nil } -func renderToFile(component templ.Component, filename ...string) error { - // log.Debug("outputting file", "filename", path.Join(filename...)) - file, err := os.OpenFile(path.Join(filename...), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) +func renderToFile(component templ.Component, pathParts ...string) error { + filename := path.Join(pathParts...) + // log.Debug("outputting file", "filename", filename) + file, err := openFileAndVariants(filename) if err != nil { return errors.WithMessage(err, "could not open output file") } @@ -68,9 +72,10 @@ func renderToFile(component templ.Component, filename ...string) error { return nil } -func writerToFile(writer io.WriterTo, filename ...string) error { +func writerToFile(writer io.WriterTo, pathParts ...string) error { + filename := path.Join(pathParts...) // log.Debug("outputting file", "filename", path.Join(filename...)) - file, err := os.OpenFile(path.Join(filename...), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + file, err := openFileAndVariants(filename) if err != nil { return errors.WithMessage(err, "could not open output file") } @@ -252,6 +257,7 @@ func BuildSite(ioConfig IOConfig) (*Result, error) { return nil, errors.WithMessage(err, "could not get config") } config.InjectLiveReload = ioConfig.Development + compressFiles = !ioConfig.Development if ioConfig.BaseURL.URL != nil { config.BaseURL.URL, err = url.Parse(ioConfig.BaseURL.String()) diff --git a/internal/builder/files.go b/internal/builder/files.go new file mode 100644 index 0000000..57d4d2e --- /dev/null +++ b/internal/builder/files.go @@ -0,0 +1,83 @@ +package builder + +import ( + "compress/gzip" + "io" + "os" + + "github.com/andybalholm/brotli" +) + +const ( + gzipLevel = 6 + brotliLevel = 9 +) + +type MultiWriteCloser struct { + writers []io.WriteCloser + multiWriter io.Writer +} + +func (mw *MultiWriteCloser) Write(p []byte) (n int, err error) { + return mw.multiWriter.Write(p) +} + +func (mw *MultiWriteCloser) Close() error { + var lastErr error + for _, w := range mw.writers { + err := w.Close() + if err != nil { + lastErr = err + } + } + return lastErr +} + +func openFileWrite(filename string) (*os.File, error) { + return os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) +} + +func openFileGz(filename string) (*gzip.Writer, error) { + filenameGz := filename + ".gz" + f, err := openFileWrite(filenameGz) + if err != nil { + return nil, err + } + return gzip.NewWriterLevel(f, gzipLevel) +} + +func openFileBrotli(filename string) (*brotli.Writer, error) { + filenameBrotli := filename + ".br" + f, err := openFileWrite(filenameBrotli) + if err != nil { + return nil, err + } + return brotli.NewWriterLevel(f, brotliLevel), nil +} + +func multiOpenFile(filename string) (*MultiWriteCloser, error) { + r, err := openFileWrite(filename) + if err != nil { + return nil, err + } + gz, err := openFileGz(filename) + if err != nil { + return nil, err + } + br, err := openFileBrotli(filename) + if err != nil { + return nil, err + } + return &MultiWriteCloser{ + writers: []io.WriteCloser{r, gz, br}, + multiWriter: io.MultiWriter(r, gz, br), + }, nil +} + +func openFileAndVariants(filename string) (io.WriteCloser, error) { + if compressFiles { + return multiOpenFile(filename) + } else { + return openFileWrite(filename) + } +} diff --git a/nix/gomod2nix.toml b/nix/gomod2nix.toml index 0bc3fc9..268262c 100644 --- a/nix/gomod2nix.toml +++ b/nix/gomod2nix.toml @@ -20,6 +20,9 @@ schema = 3 [mod."github.com/adrg/frontmatter"] version = "v0.2.0" hash = "sha256-WJsVcdCpkIkjqUz5fJOFStZYwQlrcFzQ6+mZatZiimo=" + [mod."github.com/andybalholm/brotli"] + version = "v1.1.0" + hash = "sha256-njLViV4v++ZdgOWGWzlvkefuFvA/nkugl3Ta/h1nu/0=" [mod."github.com/andybalholm/cascadia"] version = "v1.3.2" hash = "sha256-Nc9SkqJO/ecincVcUBFITy24TMmMGj5o0Q8EgdNhrEk=" -- cgit 1.4.1