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.SetIndent("", " ")
if err := enc.Encode(docshelper.DocProviders); err != nil {
if err := enc.Encode(docshelper.GetDocProvider()); err != nil {
return err
}

View File

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

View File

@ -193,6 +193,9 @@ markup
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 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.
## Configure Minify
{{< new-in "0.68.0" >}}
Default configuration:
{{< code-toggle config="minify" />}}
## Configure File Caches
Since Hugo 0.52 you can configure more than just the `cacheDir`. This is the default configuration:

View File

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

View File

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

View File

@ -12,8 +12,7 @@ import (
// This is is just some helpers used to create some JSON used in the Hugo docs.
func init() {
docsProvider := func() map[string]interface{} {
docs := make(map[string]interface{})
docsProvider := func() docshelper.DocProvider {
var chromaLexers []interface{}
@ -48,11 +47,11 @@ func init() {
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)
}
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() {
docsProvider := func() map[string]interface{} {
docs := make(map[string]interface{})
docs["markup"] = parser.LowerCaseCamelJSONMarshaller{Value: Default}
return docs
docsProvider := func() docshelper.DocProvider {
return docshelper.DocProvider{"config": map[string]interface{}{"markup": parser.LowerCaseCamelJSONMarshaller{Value: Default}}}
}
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.
func init() {
docsProvider := func() map[string]interface{} {
docs := make(map[string]interface{})
docs["types"] = DefaultTypes
return docs
docsProvider := func() docshelper.DocProvider {
return docshelper.DocProvider{"media": map[string]interface{}{"types": DefaultTypes}}
}
docshelper.AddDocProvider("media", docsProvider)
docshelper.AddDocProviderFunc(docsProvider)
}

View File

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

View File

@ -14,7 +14,6 @@
package minifiers
import (
"fmt"
"testing"
"github.com/spf13/viper"
@ -26,8 +25,8 @@ func TestConfig(t *testing.T) {
c := qt.New(t)
v := viper.New()
v.Set("minifiers", map[string]interface{}{
"enablexml": false,
v.Set("minify", map[string]interface{}{
"disablexml": true,
"tdewolff": map[string]interface{}{
"html": map[string]interface{}{
"keepwhitespace": false,
@ -36,10 +35,11 @@ func TestConfig(t *testing.T) {
})
conf, err := decodeConfig(v)
fmt.Println(conf)
c.Assert(err, qt.IsNil)
c.Assert(conf.MinifyOutput, qt.Equals, false)
// explicitly set value
c.Assert(conf.Tdewolff.HTML.KeepWhitespace, qt.Equals, false)
// default value
@ -47,6 +47,19 @@ func TestConfig(t *testing.T) {
c.Assert(conf.Tdewolff.CSS.KeepCSS2, qt.Equals, true)
// `enable` flags
c.Assert(conf.EnableHTML, qt.Equals, true)
c.Assert(conf.EnableXML, qt.Equals, false)
c.Assert(conf.DisableHTML, 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.
type Client struct {
// Whether output minification is enabled (HTML in /public)
MinifyOutput bool
m *minify.M
}
@ -62,30 +65,30 @@ func New(mediaTypes media.Types, outputFormats output.Formats, cfg config.Provid
m := minify.New()
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.
if conf.EnableCSS {
if !conf.DisableCSS {
addMinifier(m, mediaTypes, "css", &conf.Tdewolff.CSS)
}
if conf.EnableJS {
if !conf.DisableJS {
addMinifier(m, mediaTypes, "js", &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)
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)
}
if conf.EnableXML {
if !conf.DisableXML {
addMinifier(m, mediaTypes, "xml", &conf.Tdewolff.XML)
}
// HTML
if conf.EnableHTML {
if !conf.DisableHTML {
addMinifier(m, mediaTypes, "html", &conf.Tdewolff.HTML)
for _, of := range outputFormats {
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) {

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)
v := viper.New()
v.Set("minifiers", map[string]interface{}{
"enablexml": false,
v.Set("minify", map[string]interface{}{
"disablexml": true,
"tdewolff": map[string]interface{}{
"html": map[string]interface{}{
"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.
func init() {
docsProvider := func() map[string]interface{} {
docs := make(map[string]interface{})
docs["formats"] = DefaultFormats
docs["layouts"] = createLayoutExamples()
return docs
docsProvider := func() docshelper.DocProvider {
return docshelper.DocProvider{
"output": map[string]interface{}{
"formats": DefaultFormats,
"layouts": createLayoutExamples(),
},
}
}
docshelper.AddDocProvider("output", docsProvider)
docshelper.AddDocProviderFunc(docsProvider)
}
func createLayoutExamples() interface{} {

View File

@ -14,6 +14,7 @@
package parser
import (
"bytes"
"encoding/json"
"regexp"
"unicode"
@ -35,6 +36,12 @@ func (c LowerCaseCamelJSONMarshaller) MarshalJSON() ([]byte, error) {
converted := keyMatchRegex.ReplaceAllFunc(
marshalled,
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 key.
if len(match) > 2 {

View File

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

View File

@ -41,23 +41,3 @@ func TestTransform(t *testing.T) {
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.
func init() {
docsProvider := func() map[string]interface{} {
docs := make(map[string]interface{})
docsProvider := func() docshelper.DocProvider {
d := &deps.Deps{
Cfg: viper.New(),
Log: loggers.NewErrorLogger(),
@ -41,11 +40,11 @@ func init() {
}
docs["funcs"] = namespaces
return docs
return docshelper.DocProvider{"tpl": map[string]interface{}{"funcs": namespaces}}
}
docshelper.AddDocProvider("tpl", docsProvider)
docshelper.AddDocProviderFunc(docsProvider)
}
func newTestConfig() *viper.Viper {