Some minify configuration adjustments

This commit is contained in:
Bjørn Erik Pedersen 2020-03-20 16:34:53 +01:00
parent 574c2959b8
commit 7204b354a9
18 changed files with 173 additions and 143 deletions

View File

@ -64,7 +64,7 @@ func (g *genDocsHelper) generate() error {
enc := json.NewEncoder(f) enc := json.NewEncoder(f)
enc.SetIndent("", " ") enc.SetIndent("", " ")
if err := enc.Encode(docshelper.DocProviders); err != nil { if err := enc.Encode(docshelper.GetDocProvider()); err != nil {
return err return err
} }

View File

@ -231,11 +231,6 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
"duplicateTargetPaths", "duplicateTargetPaths",
} }
// Will set a value even if it is the default.
flagKeysForced := []string{
"minify",
}
for _, key := range persFlagKeys { for _, key := range persFlagKeys {
setValueFromFlag(cmd.PersistentFlags(), key, cfg, "", false) setValueFromFlag(cmd.PersistentFlags(), key, cfg, "", false)
} }
@ -243,9 +238,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
setValueFromFlag(cmd.Flags(), key, cfg, "", false) setValueFromFlag(cmd.Flags(), key, cfg, "", false)
} }
for _, key := range flagKeysForced { setValueFromFlag(cmd.Flags(), "minify", cfg, "minifyOutput", true)
setValueFromFlag(cmd.Flags(), key, cfg, "", true)
}
// Set some "config aliases" // Set some "config aliases"
setValueFromFlag(cmd.Flags(), "destination", cfg, "publishDir", false) setValueFromFlag(cmd.Flags(), "destination", cfg, "publishDir", false)

View File

@ -193,6 +193,9 @@ markup
menu menu
: See [Add Non-content Entries to a Menu](/content-management/menus/#add-non-content-entries-to-a-menu). : See [Add Non-content Entries to a Menu](/content-management/menus/#add-non-content-entries-to-a-menu).
minify
: See [Configure Minify](#configure-minify)
module module
: Module config see [Module Config](/hugo-modules/configuration/).{{< new-in "0.56.0" >}} : Module config see [Module Config](/hugo-modules/configuration/).{{< new-in "0.56.0" >}}
@ -481,6 +484,14 @@ The above will try first to extract the value for `.Date` from the filename, the
Hugo v0.20 introduced the ability to render your content to multiple output formats (e.g., to JSON, AMP html, or CSV). See [Output Formats][] for information on how to add these values to your Hugo project's configuration file. Hugo v0.20 introduced the ability to render your content to multiple output formats (e.g., to JSON, AMP html, or CSV). See [Output Formats][] for information on how to add these values to your Hugo project's configuration file.
## Configure Minify
{{< new-in "0.68.0" >}}
Default configuration:
{{< code-toggle config="minify" />}}
## Configure File Caches ## Configure File Caches
Since Hugo 0.52 you can configure more than just the `cacheDir`. This is the default configuration: Since Hugo 0.52 you can configure more than just the `cacheDir`. This is the default configuration:

View File

@ -1415,7 +1415,7 @@
"goldmark": { "goldmark": {
"renderer": { "renderer": {
"hardWraps": false, "hardWraps": false,
"xHTML": false, "xhtml": false,
"unsafe": false "unsafe": false
}, },
"parser": { "parser": {
@ -1452,14 +1452,15 @@
"footnoteReturnLinkContents": "" "footnoteReturnLinkContents": ""
} }
}, },
"minifiers": { "minify": {
"minifyOutput": false,
"disableHTML": false,
"disableCSS": false,
"disableJS": false,
"disableJSON": false,
"disableSVG": false,
"disableXML": false,
"tdewolff": { "tdewolff": {
"enableHtml": true,
"enableCss": true,
"enableJs": true,
"enableJson": true,
"enableSvg": true,
"enableXml": true,
"html": { "html": {
"keepConditionalComments": true, "keepConditionalComments": true,
"keepDefaultAttrVals": true, "keepDefaultAttrVals": true,

View File

@ -15,37 +15,37 @@
// is of limited interest for the general Hugo user. // is of limited interest for the general Hugo user.
package docshelper package docshelper
import ( type (
"encoding/json" DocProviderFunc = func() DocProvider
DocProvider map[string]map[string]interface{}
) )
// DocProviders contains all DocProviders added to the system. var docProviderFuncs []DocProviderFunc
var DocProviders = make(map[string]DocProvider)
// AddDocProvider adds or updates the DocProvider for a given name. func AddDocProviderFunc(fn DocProviderFunc) {
func AddDocProvider(name string, provider DocProvider) { docProviderFuncs = append(docProviderFuncs, fn)
if prev, ok := DocProviders[name]; !ok {
DocProviders[name] = provider
} else {
DocProviders[name] = merge(prev, provider)
}
} }
// DocProvider is used to save arbitrary JSON data func GetDocProvider() DocProvider {
// used for the generation of the documentation. provider := make(DocProvider)
type DocProvider func() map[string]interface{}
// MarshalJSON returns a JSON representation of the DocProvider. for _, fn := range docProviderFuncs {
func (d DocProvider) MarshalJSON() ([]byte, error) { p := fn()
return json.MarshalIndent(d(), "", " ") for k, v := range p {
if prev, found := provider[k]; !found {
provider[k] = v
} else {
merge(prev, v)
}
}
}
return provider
} }
func merge(a, b DocProvider) DocProvider { // Shallow merge
next := a() func merge(dst, src map[string]interface{}) {
for k, v := range b() { for k, v := range src {
next[k] = v dst[k] = v
}
return func() map[string]interface{} {
return next
} }
} }

View File

@ -12,8 +12,7 @@ import (
// This is is just some helpers used to create some JSON used in the Hugo docs. // This is is just some helpers used to create some JSON used in the Hugo docs.
func init() { func init() {
docsProvider := func() map[string]interface{} { docsProvider := func() docshelper.DocProvider {
docs := make(map[string]interface{})
var chromaLexers []interface{} var chromaLexers []interface{}
@ -48,11 +47,11 @@ func init() {
chromaLexers = append(chromaLexers, lexerEntry) chromaLexers = append(chromaLexers, lexerEntry)
docs["lexers"] = chromaLexers
} }
return docs
return docshelper.DocProvider{"chroma": map[string]interface{}{"lexers": chromaLexers}}
} }
docshelper.AddDocProvider("chroma", docsProvider) docshelper.AddDocProviderFunc(docsProvider)
} }

View File

@ -947,3 +947,33 @@ class-in-b {
build("never", true) build("never", true)
} }
func TestResourceMinifyDisabled(t *testing.T) {
t.Parallel()
b := newTestSitesBuilder(t).WithConfigFile("toml", `
baseURL = "https://example.org"
[minify]
disableXML=true
`)
b.WithContent("page.md", "")
b.WithSourceFile(
"assets/xml/data.xml", "<root> <foo> asdfasdf </foo> </root>",
)
b.WithTemplates("index.html", `
{{ $xml := resources.Get "xml/data.xml" | minify | fingerprint }}
XML: {{ $xml.Content | safeHTML }}|{{ $xml.RelPermalink }}
`)
b.Build(BuildCfg{})
b.AssertFileContent("public/index.html", `
XML: <root> <foo> asdfasdf </foo> </root>|/xml/data.min.3be4fddd19aaebb18c48dd6645215b822df74701957d6d36e59f203f9c30fd9f.xml
`)
}

View File

@ -94,11 +94,8 @@ var Default = Config{
} }
func init() { func init() {
docsProvider := func() map[string]interface{} { docsProvider := func() docshelper.DocProvider {
docs := make(map[string]interface{}) return docshelper.DocProvider{"config": map[string]interface{}{"markup": parser.LowerCaseCamelJSONMarshaller{Value: Default}}}
docs["markup"] = parser.LowerCaseCamelJSONMarshaller{Value: Default}
return docs
} }
docshelper.AddDocProvider("config", docsProvider) docshelper.AddDocProviderFunc(docsProvider)
} }

View File

@ -6,12 +6,8 @@ import (
// This is is just some helpers used to create some JSON used in the Hugo docs. // This is is just some helpers used to create some JSON used in the Hugo docs.
func init() { func init() {
docsProvider := func() map[string]interface{} { docsProvider := func() docshelper.DocProvider {
docs := make(map[string]interface{}) return docshelper.DocProvider{"media": map[string]interface{}{"types": DefaultTypes}}
docs["types"] = DefaultTypes
return docs
} }
docshelper.AddDocProviderFunc(docsProvider)
docshelper.AddDocProvider("media", docsProvider)
} }

View File

@ -14,6 +14,7 @@
package minifiers package minifiers
import ( import (
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/docshelper" "github.com/gohugoio/hugo/docshelper"
"github.com/gohugoio/hugo/parser" "github.com/gohugoio/hugo/parser"
@ -61,36 +62,43 @@ type tdewolffConfig struct {
XML xml.Minifier XML xml.Minifier
} }
type minifiersConfig struct { type minifyConfig struct {
EnableHTML bool // Whether to minify the published output (the HTML written to /public).
EnableCSS bool MinifyOutput bool
EnableJS bool
EnableJSON bool DisableHTML bool
EnableSVG bool DisableCSS bool
EnableXML bool DisableJS bool
DisableJSON bool
DisableSVG bool
DisableXML bool
Tdewolff tdewolffConfig Tdewolff tdewolffConfig
} }
var defaultConfig = minifiersConfig{ var defaultConfig = minifyConfig{
EnableHTML: true,
EnableCSS: true,
EnableJS: true,
EnableJSON: true,
EnableSVG: true,
EnableXML: true,
Tdewolff: defaultTdewolffConfig, Tdewolff: defaultTdewolffConfig,
} }
func decodeConfig(cfg config.Provider) (conf minifiersConfig, err error) { func decodeConfig(cfg config.Provider) (conf minifyConfig, err error) {
conf = defaultConfig conf = defaultConfig
m := cfg.GetStringMap("minifiers") // May be set by CLI.
if m == nil { conf.MinifyOutput = cfg.GetBool("minifyOutput")
v := cfg.Get("minify")
if v == nil {
return return
} }
// Legacy.
if b, ok := v.(bool); ok {
conf.MinifyOutput = b
return
}
m := maps.ToStringMap(v)
err = mapstructure.WeakDecode(m, &conf) err = mapstructure.WeakDecode(m, &conf)
if err != nil { if err != nil {
@ -101,11 +109,8 @@ func decodeConfig(cfg config.Provider) (conf minifiersConfig, err error) {
} }
func init() { func init() {
docsProvider := func() map[string]interface{} { docsProvider := func() docshelper.DocProvider {
docs := make(map[string]interface{}) return docshelper.DocProvider{"config": map[string]interface{}{"minify": parser.LowerCaseCamelJSONMarshaller{Value: defaultConfig}}}
docs["minifiers"] = parser.LowerCaseCamelJSONMarshaller{Value: defaultConfig}
return docs
} }
docshelper.AddDocProvider("config", docsProvider) docshelper.AddDocProviderFunc(docsProvider)
} }

View File

@ -14,7 +14,6 @@
package minifiers package minifiers
import ( import (
"fmt"
"testing" "testing"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -26,8 +25,8 @@ func TestConfig(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := viper.New() v := viper.New()
v.Set("minifiers", map[string]interface{}{ v.Set("minify", map[string]interface{}{
"enablexml": false, "disablexml": true,
"tdewolff": map[string]interface{}{ "tdewolff": map[string]interface{}{
"html": map[string]interface{}{ "html": map[string]interface{}{
"keepwhitespace": false, "keepwhitespace": false,
@ -36,10 +35,11 @@ func TestConfig(t *testing.T) {
}) })
conf, err := decodeConfig(v) conf, err := decodeConfig(v)
fmt.Println(conf)
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
c.Assert(conf.MinifyOutput, qt.Equals, false)
// explicitly set value // explicitly set value
c.Assert(conf.Tdewolff.HTML.KeepWhitespace, qt.Equals, false) c.Assert(conf.Tdewolff.HTML.KeepWhitespace, qt.Equals, false)
// default value // default value
@ -47,6 +47,19 @@ func TestConfig(t *testing.T) {
c.Assert(conf.Tdewolff.CSS.KeepCSS2, qt.Equals, true) c.Assert(conf.Tdewolff.CSS.KeepCSS2, qt.Equals, true)
// `enable` flags // `enable` flags
c.Assert(conf.EnableHTML, qt.Equals, true) c.Assert(conf.DisableHTML, qt.Equals, false)
c.Assert(conf.EnableXML, qt.Equals, false) c.Assert(conf.DisableXML, qt.Equals, true)
}
func TestConfigLegacy(t *testing.T) {
c := qt.New(t)
v := viper.New()
// This was a bool < Hugo v0.58.
v.Set("minify", true)
conf, err := decodeConfig(v)
c.Assert(err, qt.IsNil)
c.Assert(conf.MinifyOutput, qt.Equals, true)
} }

View File

@ -30,6 +30,9 @@ import (
// Client wraps a minifier. // Client wraps a minifier.
type Client struct { type Client struct {
// Whether output minification is enabled (HTML in /public)
MinifyOutput bool
m *minify.M m *minify.M
} }
@ -62,30 +65,30 @@ func New(mediaTypes media.Types, outputFormats output.Formats, cfg config.Provid
m := minify.New() m := minify.New()
if err != nil { if err != nil {
return Client{m: m}, err return Client{}, err
} }
// We use the Type definition of the media types defined in the site if found. // We use the Type definition of the media types defined in the site if found.
if conf.EnableCSS { if !conf.DisableCSS {
addMinifier(m, mediaTypes, "css", &conf.Tdewolff.CSS) addMinifier(m, mediaTypes, "css", &conf.Tdewolff.CSS)
} }
if conf.EnableJS { if !conf.DisableJS {
addMinifier(m, mediaTypes, "js", &conf.Tdewolff.JS) addMinifier(m, mediaTypes, "js", &conf.Tdewolff.JS)
m.AddRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), &conf.Tdewolff.JS) m.AddRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), &conf.Tdewolff.JS)
} }
if conf.EnableJSON { if !conf.DisableJSON {
addMinifier(m, mediaTypes, "json", &conf.Tdewolff.JSON) addMinifier(m, mediaTypes, "json", &conf.Tdewolff.JSON)
m.AddRegexp(regexp.MustCompile(`^(application|text)/(x-|ld\+)?json$`), &conf.Tdewolff.JSON) m.AddRegexp(regexp.MustCompile(`^(application|text)/(x-|ld\+)?json$`), &conf.Tdewolff.JSON)
} }
if conf.EnableSVG { if !conf.DisableSVG {
addMinifier(m, mediaTypes, "svg", &conf.Tdewolff.SVG) addMinifier(m, mediaTypes, "svg", &conf.Tdewolff.SVG)
} }
if conf.EnableXML { if !conf.DisableXML {
addMinifier(m, mediaTypes, "xml", &conf.Tdewolff.XML) addMinifier(m, mediaTypes, "xml", &conf.Tdewolff.XML)
} }
// HTML // HTML
if conf.EnableHTML { if !conf.DisableHTML {
addMinifier(m, mediaTypes, "html", &conf.Tdewolff.HTML) addMinifier(m, mediaTypes, "html", &conf.Tdewolff.HTML)
for _, of := range outputFormats { for _, of := range outputFormats {
if of.IsHTML { if of.IsHTML {
@ -94,7 +97,7 @@ func New(mediaTypes media.Types, outputFormats output.Formats, cfg config.Provid
} }
} }
return Client{m: m}, nil return Client{m: m, MinifyOutput: conf.MinifyOutput}, nil
} }
func addMinifier(m *minify.M, mt media.Types, suffix string, min minify.Minifier) { func addMinifier(m *minify.M, mt media.Types, suffix string, min minify.Minifier) {

View File

@ -75,11 +75,11 @@ func TestNew(t *testing.T) {
} }
func TestConfiguredMinify(t *testing.T) { func TestConfigureMinify(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := viper.New() v := viper.New()
v.Set("minifiers", map[string]interface{}{ v.Set("minify", map[string]interface{}{
"enablexml": false, "disablexml": true,
"tdewolff": map[string]interface{}{ "tdewolff": map[string]interface{}{
"html": map[string]interface{}{ "html": map[string]interface{}{
"keepwhitespace": true, "keepwhitespace": true,

View File

@ -10,15 +10,16 @@ import (
// This is is just some helpers used to create some JSON used in the Hugo docs. // This is is just some helpers used to create some JSON used in the Hugo docs.
func init() { func init() {
docsProvider := func() map[string]interface{} { docsProvider := func() docshelper.DocProvider {
docs := make(map[string]interface{}) return docshelper.DocProvider{
"output": map[string]interface{}{
docs["formats"] = DefaultFormats "formats": DefaultFormats,
docs["layouts"] = createLayoutExamples() "layouts": createLayoutExamples(),
return docs },
}
} }
docshelper.AddDocProvider("output", docsProvider) docshelper.AddDocProviderFunc(docsProvider)
} }
func createLayoutExamples() interface{} { func createLayoutExamples() interface{} {

View File

@ -14,6 +14,7 @@
package parser package parser
import ( import (
"bytes"
"encoding/json" "encoding/json"
"regexp" "regexp"
"unicode" "unicode"
@ -35,6 +36,12 @@ func (c LowerCaseCamelJSONMarshaller) MarshalJSON() ([]byte, error) {
converted := keyMatchRegex.ReplaceAllFunc( converted := keyMatchRegex.ReplaceAllFunc(
marshalled, marshalled,
func(match []byte) []byte { func(match []byte) []byte {
// Attributes on the form XML, JSON etc.
if bytes.Equal(match, bytes.ToUpper(match)) {
return bytes.ToLower(match)
}
// Empty keys are valid JSON, only lowercase if we do not have an // Empty keys are valid JSON, only lowercase if we do not have an
// empty key. // empty key.
if len(match) > 2 { if len(match) > 2 {

View File

@ -68,21 +68,16 @@ type Descriptor struct {
// DestinationPublisher is the default and currently only publisher in Hugo. This // DestinationPublisher is the default and currently only publisher in Hugo. This
// publisher prepares and publishes an item to the defined destination, e.g. /public. // publisher prepares and publishes an item to the defined destination, e.g. /public.
type DestinationPublisher struct { type DestinationPublisher struct {
fs afero.Fs fs afero.Fs
minify bool min minifiers.Client
min minifiers.Client
} }
// NewDestinationPublisher creates a new DestinationPublisher. // NewDestinationPublisher creates a new DestinationPublisher.
func NewDestinationPublisher(fs afero.Fs, outputFormats output.Formats, mediaTypes media.Types, cfg config.Provider) (pub DestinationPublisher, err error) { func NewDestinationPublisher(fs afero.Fs, outputFormats output.Formats, mediaTypes media.Types, cfg config.Provider) (pub DestinationPublisher, err error) {
pub = DestinationPublisher{fs: fs} pub = DestinationPublisher{fs: fs}
minify := cfg.GetBool("minify") pub.min, err = minifiers.New(mediaTypes, outputFormats, cfg)
if minify { if err != nil {
pub.min, err = minifiers.New(mediaTypes, outputFormats, cfg) return
if err != nil {
return
}
pub.minify = true
} }
return return
} }
@ -155,7 +150,7 @@ func (p DestinationPublisher) createTransformerChain(f Descriptor) transform.Cha
} }
if p.minify { if p.min.MinifyOutput {
minifyTransformer := p.min.Transformer(f.OutputFormat.MediaType) minifyTransformer := p.min.Transformer(f.OutputFormat.MediaType)
if minifyTransformer != nil { if minifyTransformer != nil {
transformers = append(transformers, minifyTransformer) transformers = append(transformers, minifyTransformer)

View File

@ -41,23 +41,3 @@ func TestTransform(t *testing.T) {
c.Assert(content, qt.Equals, "<h1>Hugo Rocks!</h1>") c.Assert(content, qt.Equals, "<h1>Hugo Rocks!</h1>")
} }
func TestNoMinifier(t *testing.T) {
c := qt.New(t)
spec, _ := htesting.NewTestResourceSpec()
spec.Cfg.Set("minifiers.enableXML", false)
client, _ := New(spec)
original := "<title> Hugo Rocks! </title>"
r, err := htesting.NewResourceTransformerForSpec(spec, "hugo.xml", original)
c.Assert(err, qt.IsNil)
transformed, err := client.Minify(r)
c.Assert(err, qt.IsNil)
content, err := transformed.(resource.ContentProvider).Content()
// error should be ignored because general users cannot control codes under `theme`s
c.Assert(err, qt.IsNil)
c.Assert(content, qt.Equals, original)
}

View File

@ -24,8 +24,7 @@ import (
// This file provides documentation support and is randomly put into this package. // This file provides documentation support and is randomly put into this package.
func init() { func init() {
docsProvider := func() map[string]interface{} { docsProvider := func() docshelper.DocProvider {
docs := make(map[string]interface{})
d := &deps.Deps{ d := &deps.Deps{
Cfg: viper.New(), Cfg: viper.New(),
Log: loggers.NewErrorLogger(), Log: loggers.NewErrorLogger(),
@ -41,11 +40,11 @@ func init() {
} }
docs["funcs"] = namespaces return docshelper.DocProvider{"tpl": map[string]interface{}{"funcs": namespaces}}
return docs
} }
docshelper.AddDocProvider("tpl", docsProvider) docshelper.AddDocProviderFunc(docsProvider)
} }
func newTestConfig() *viper.Viper { func newTestConfig() *viper.Viper {