diff --git a/docs/docs/050-modules.md b/docs/docs/050-modules.md index 39f6fe4..d48d8cd 100644 --- a/docs/docs/050-modules.md +++ b/docs/docs/050-modules.md @@ -763,3 +763,24 @@ Due to [the limitation of PostHTML](https://github.com/posthtml/htmlnano/issues/ - `body` - `colgroup` - `tbody` + +### normalizeAttributeValues + +Normalize casing of attribute values. + +The module won't impact the plain-text size of the output. However it will improve the compression ratio of gzip/brotli used in HTTP compression. + +#### Example + +Source: + +```html +
+``` + +Minified: + +```html + +``` + diff --git a/lib/modules/normalizeAttributeValues.es6 b/lib/modules/normalizeAttributeValues.es6 new file mode 100644 index 0000000..2ebf80e --- /dev/null +++ b/lib/modules/normalizeAttributeValues.es6 @@ -0,0 +1,61 @@ +const caseInsensitiveAttributes = { + autocomplete: ['form'], + charset: ['meta', 'script'], + contenteditable: null, + crossorigin: ['audio', 'img', 'link', 'script', 'video'], + dir: null, + draggable: null, + dropzone: null, + formmethod: ['button', 'input'], + inputmode: ['input', 'textarea'], + kind: ['track'], + method: ['form'], + preload: ['audio', 'video'], + referrerpolicy: ['a', 'area', 'iframe', 'img', 'link'], + sandbox: ['iframe'], + spellcheck: null, + scope: ['th'], + shape: ['area'], + sizes: ['link'], + step: ['input'], + translate: null, + type: [ + 'a', + 'link', + 'button', + 'embed', + 'object', + 'script', + 'source', + 'style', + 'input', + 'menu', + 'menuitem' + ], + wrap: ['textarea'] +}; + +export default function normalizeAttributeValues(tree) { + tree.walk(node => { + if (!node.attrs) { + return node; + } + + Object.entries(node.attrs).forEach(([attrName, attrValue]) => { + const attrNameLower = attrName.toLowerCase(); + if ( + Object.hasOwnProperty.call(caseInsensitiveAttributes, attrNameLower) + && ( + caseInsensitiveAttributes[attrNameLower] === null + || caseInsensitiveAttributes[attrNameLower].includes(node.tag) + ) + ) { + node.attrs[attrName] = attrValue.toLowerCase(); + } + }); + + return node; + }); + + return tree; +} diff --git a/lib/presets/safe.es6 b/lib/presets/safe.es6 index 44983d0..b7e2b6a 100644 --- a/lib/presets/safe.es6 +++ b/lib/presets/safe.es6 @@ -33,6 +33,7 @@ export default { }, minifyConditionalComments: false, removeRedundantAttributes: false, + normalizeAttributeValues: true, removeEmptyAttributes: true, removeComments: 'safe', removeAttributeQuotes: false, diff --git a/test/modules/normalizeAttributeValues.js b/test/modules/normalizeAttributeValues.js new file mode 100644 index 0000000..d99d2e9 --- /dev/null +++ b/test/modules/normalizeAttributeValues.js @@ -0,0 +1,14 @@ +import { init } from '../htmlnano'; +import safePreset from '../../lib/presets/safe'; + +describe('normalizeAttributeValues', () => { + const options = safePreset; + + it('default behavior', () => { + return init( + '', + '', + options + ); + }); +});