diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2018-08-05 12:13:49 +0300 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2018-08-06 20:58:41 +0300 |
commit | 789ef8c639e4621abd36da530bcb5942ac9297da (patch) | |
tree | f225fc3663affc49805f1d309b77b096d40fc8f6 /resource | |
parent | 71931b30b1813b146aaa60f5cdab16c0f9ebebdb (diff) |
Add support for minification of final output
Hugo Pipes added minification support for resources fetched via ´resources.Get` and similar.
This also adds support for minification of the final output for supported output formats: HTML, XML, SVG, CSS, JavaScript, JSON.
To enable, run Hugo with the `--minify` flag:
```bash
hugo --minify
```
This commit is also a major spring cleaning of the `transform` package to allow the new minification step fit into that processing chain.
Fixes #1251
Diffstat (limited to 'resource')
-rw-r--r-- | resource/image.go | 6 | ||||
-rw-r--r-- | resource/minifier/minify.go | 58 | ||||
-rw-r--r-- | resource/minifiers/minify.go | 115 | ||||
-rw-r--r-- | resource/resource.go | 7 | ||||
-rw-r--r-- | resource/resource_cache.go | 23 | ||||
-rw-r--r-- | resource/testhelpers_test.go | 7 | ||||
-rw-r--r-- | resource/transform.go | 2 |
7 files changed, 73 insertions, 145 deletions
diff --git a/resource/image.go b/resource/image.go index 6aa382331..57da4f93d 100644 --- a/resource/image.go +++ b/resource/image.go @@ -464,7 +464,7 @@ func (i *Image) copyToDestination(src string) error { } defer in.Close() - out, err := openFileForWriting(i.spec.BaseFs.PublishFs, target) + out, err := helpers.OpenFileForWriting(i.spec.BaseFs.PublishFs, target) if err != nil { res = err @@ -487,7 +487,7 @@ func (i *Image) copyToDestination(src string) error { func (i *Image) encodeToDestinations(img image.Image, conf imageConfig, resourceCacheFilename, targetFilename string) error { - file1, err := openFileForWriting(i.spec.BaseFs.PublishFs, targetFilename) + file1, err := helpers.OpenFileForWriting(i.spec.BaseFs.PublishFs, targetFilename) if err != nil { return err } @@ -498,7 +498,7 @@ func (i *Image) encodeToDestinations(img image.Image, conf imageConfig, resource if resourceCacheFilename != "" { // Also save it to the image resource cache for later reuse. - file2, err := openFileForWriting(i.spec.BaseFs.Resources.Fs, resourceCacheFilename) + file2, err := helpers.OpenFileForWriting(i.spec.BaseFs.Resources.Fs, resourceCacheFilename) if err != nil { return err } diff --git a/resource/minifier/minify.go b/resource/minifier/minify.go new file mode 100644 index 000000000..cef22efc0 --- /dev/null +++ b/resource/minifier/minify.go @@ -0,0 +1,58 @@ +// Copyright 2018 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package minifier + +import ( + "github.com/gohugoio/hugo/minifiers" + "github.com/gohugoio/hugo/resource" +) + +// Client for minification of Resource objects. Supported minfiers are: +// css, html, js, json, svg and xml. +type Client struct { + rs *resource.Spec + m minifiers.Client +} + +// New creates a new Client given a specification. Note that it is the media types +// configured for the site that is used to match files to the correct minifier. +func New(rs *resource.Spec) *Client { + return &Client{rs: rs, m: minifiers.New(rs.MediaTypes, rs.OutputFormats)} +} + +type minifyTransformation struct { + rs *resource.Spec + m minifiers.Client +} + +func (t *minifyTransformation) Key() resource.ResourceTransformationKey { + return resource.NewResourceTransformationKey("minify") +} + +func (t *minifyTransformation) Transform(ctx *resource.ResourceTransformationCtx) error { + if err := t.m.Minify(ctx.InMediaType, ctx.To, ctx.From); err != nil { + return err + } + ctx.AddOutPathIdentifier(".min") + return nil +} + +func (c *Client) Minify(res resource.Resource) (resource.Resource, error) { + return c.rs.Transform( + res, + &minifyTransformation{ + rs: c.rs, + m: c.m}, + ) +} diff --git a/resource/minifiers/minify.go b/resource/minifiers/minify.go deleted file mode 100644 index 604ac6f8c..000000000 --- a/resource/minifiers/minify.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2018 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package minifiers - -import ( - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/media" - - "github.com/gohugoio/hugo/resource" - "github.com/tdewolff/minify" - "github.com/tdewolff/minify/css" - "github.com/tdewolff/minify/html" - "github.com/tdewolff/minify/js" - "github.com/tdewolff/minify/json" - "github.com/tdewolff/minify/svg" - "github.com/tdewolff/minify/xml" -) - -// Client for minification of Resource objects. Supported minfiers are: -// css, html, js, json, svg and xml. -type Client struct { - rs *resource.Spec - m *minify.M -} - -// New creates a new Client given a specification. Note that it is the media types -// configured for the site that is used to match files to the correct minifier. -func New(rs *resource.Spec) *Client { - m := minify.New() - mt := rs.MediaTypes - - // We use the Type definition of the media types defined in the site if found. - addMinifierFunc(m, mt, "text/css", "css", css.Minify) - addMinifierFunc(m, mt, "text/html", "html", html.Minify) - addMinifierFunc(m, mt, "application/javascript", "js", js.Minify) - addMinifierFunc(m, mt, "application/json", "json", json.Minify) - addMinifierFunc(m, mt, "image/svg+xml", "svg", svg.Minify) - addMinifierFunc(m, mt, "application/xml", "xml", xml.Minify) - addMinifierFunc(m, mt, "application/rss", "xml", xml.Minify) - - return &Client{rs: rs, m: m} -} - -func addMinifierFunc(m *minify.M, mt media.Types, typeString, suffix string, fn minify.MinifierFunc) { - resolvedTypeStr := resolveMediaTypeString(mt, typeString, suffix) - m.AddFunc(resolvedTypeStr, fn) - if resolvedTypeStr != typeString { - m.AddFunc(typeString, fn) - } -} - -type minifyTransformation struct { - rs *resource.Spec - m *minify.M -} - -func (t *minifyTransformation) Key() resource.ResourceTransformationKey { - return resource.NewResourceTransformationKey("minify") -} - -func (t *minifyTransformation) Transform(ctx *resource.ResourceTransformationCtx) error { - mtype := resolveMediaTypeString( - t.rs.MediaTypes, - ctx.InMediaType.Type(), - helpers.ExtNoDelimiter(ctx.InPath), - ) - if err := t.m.Minify(mtype, ctx.To, ctx.From); err != nil { - return err - } - ctx.AddOutPathIdentifier(".min") - return nil -} - -func (c *Client) Minify(res resource.Resource) (resource.Resource, error) { - return c.rs.Transform( - res, - &minifyTransformation{ - rs: c.rs, - m: c.m}, - ) -} - -func resolveMediaTypeString(types media.Types, typeStr, suffix string) string { - if m, found := resolveMediaType(types, typeStr, suffix); found { - return m.Type() - } - // Fall back to the default. - return typeStr -} - -// Make sure we match the matching pattern with what the user have actually defined -// in his or hers media types configuration. -func resolveMediaType(types media.Types, typeStr, suffix string) (media.Type, bool) { - if m, found := types.GetByType(typeStr); found { - return m, true - } - - if m, found := types.GetFirstBySuffix(suffix); found { - return m, true - } - - return media.Type{}, false - -} diff --git a/resource/resource.go b/resource/resource.go index aedc7c22b..a1e29c52f 100644 --- a/resource/resource.go +++ b/resource/resource.go @@ -24,6 +24,7 @@ import ( "strings" "sync" + "github.com/gohugoio/hugo/output" "github.com/gohugoio/hugo/tpl" "github.com/gohugoio/hugo/common/loggers" @@ -259,7 +260,8 @@ func (r1 Resources) MergeByLanguageInterface(in interface{}) (interface{}, error type Spec struct { *helpers.PathSpec - MediaTypes media.Types + MediaTypes media.Types + OutputFormats output.Formats Logger *jww.Notepad @@ -275,7 +277,7 @@ type Spec struct { GenAssetsPath string } -func NewSpec(s *helpers.PathSpec, logger *jww.Notepad, mimeTypes media.Types) (*Spec, error) { +func NewSpec(s *helpers.PathSpec, logger *jww.Notepad, outputFormats output.Formats, mimeTypes media.Types) (*Spec, error) { imaging, err := decodeImaging(s.Cfg.GetStringMap("imaging")) if err != nil { @@ -296,6 +298,7 @@ func NewSpec(s *helpers.PathSpec, logger *jww.Notepad, mimeTypes media.Types) (* GenAssetsPath: genAssetsPath, imaging: &imaging, MediaTypes: mimeTypes, + OutputFormats: outputFormats, imageCache: newImageCache( s, // We're going to write a cache pruning routine later, so make it extremely diff --git a/resource/resource_cache.go b/resource/resource_cache.go index 28c3c23a2..e0b86ec9e 100644 --- a/resource/resource_cache.go +++ b/resource/resource_cache.go @@ -16,12 +16,12 @@ package resource import ( "encoding/json" "io/ioutil" - "os" "path" "path/filepath" "strings" "sync" + "github.com/gohugoio/hugo/helpers" "github.com/spf13/afero" "github.com/BurntSushi/locker" @@ -176,26 +176,7 @@ func (c *ResourceCache) writeMeta(key string, meta transformedResourceMetadata) } func (c *ResourceCache) openResourceFileForWriting(filename string) (afero.File, error) { - return openFileForWriting(c.rs.Resources.Fs, filename) -} - -// openFileForWriting opens or creates the given file. If the target directory -// does not exist, it gets created. -func openFileForWriting(fs afero.Fs, filename string) (afero.File, error) { - filename = filepath.Clean(filename) - // Create will truncate if file already exists. - f, err := fs.Create(filename) - if err != nil { - if !os.IsNotExist(err) { - return nil, err - } - if err = fs.MkdirAll(filepath.Dir(filename), 0755); err != nil { - return nil, err - } - f, err = fs.Create(filename) - } - - return f, err + return helpers.OpenFileForWriting(c.rs.Resources.Fs, filename) } func (c *ResourceCache) set(key string, r Resource) { diff --git a/resource/testhelpers_test.go b/resource/testhelpers_test.go index e78a536a2..2a5d2b3cd 100644 --- a/resource/testhelpers_test.go +++ b/resource/testhelpers_test.go @@ -16,6 +16,7 @@ import ( "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/media" + "github.com/gohugoio/hugo/output" "github.com/spf13/afero" "github.com/spf13/viper" "github.com/stretchr/testify/require" @@ -51,7 +52,7 @@ func newTestResourceSpecForBaseURL(assert *require.Assertions, baseURL string) * assert.NoError(err) - spec, err := NewSpec(s, nil, media.DefaultTypes) + spec, err := NewSpec(s, nil, output.DefaultFormats, media.DefaultTypes) assert.NoError(err) return spec } @@ -85,7 +86,7 @@ func newTestResourceOsFs(assert *require.Assertions) *Spec { assert.NoError(err) - spec, err := NewSpec(s, nil, media.DefaultTypes) + spec, err := NewSpec(s, nil, output.DefaultFormats, media.DefaultTypes) assert.NoError(err) return spec @@ -110,7 +111,7 @@ func fetchResourceForSpec(spec *Spec, assert *require.Assertions, name string) C src, err := os.Open(filepath.FromSlash("testdata/" + name)) assert.NoError(err) - out, err := openFileForWriting(spec.BaseFs.Content.Fs, name) + out, err := helpers.OpenFileForWriting(spec.BaseFs.Content.Fs, name) assert.NoError(err) _, err = io.Copy(out, src) out.Close() diff --git a/resource/transform.go b/resource/transform.go index 5ba5ec821..c61a9771e 100644 --- a/resource/transform.go +++ b/resource/transform.go @@ -267,7 +267,7 @@ func (r *transformedResource) initContent() error { func (r *transformedResource) transform(setContent bool) (err error) { openPublishFileForWriting := func(relTargetPath string) (io.WriteCloser, error) { - return openFileForWriting(r.cache.rs.PublishFs, r.linker.relTargetPathFor(relTargetPath)) + return helpers.OpenFileForWriting(r.cache.rs.PublishFs, r.linker.relTargetPathFor(relTargetPath)) } // This can be the last resource in a chain. |