UNPKG

posthtml-minify-inline-css

Version:

PostHTML plugin to minify inline CSS styles

161 lines (133 loc) 4.33 kB
'use strict'; var parseStyle = require('postcss-safe-parser'), entities = require('entities'); var property = function property(p) { return function (o) { return o[p]; }; }; var isWhitespace = function isWhitespace(s) { return (/^\s*$/.test(s) ); }; // Rules that are safe to strip from nodes with no child text or only // whitespace var contentProps = new Set(['color', 'font-family', 'text-align', 'font-weight', 'vertical-align', 'word-wrap', '-webkit-hyphens', '-moz-hyphens', 'hyphens']); var noContentProps = new Set(['font-size', 'line-height']); // Rules that are safe to strip from nodes where these rules are // overriden before they would be applied to any non-whitespace text. var contentPropsSafe = new Set(['color', 'font-family', 'font-weight', 'vertical-align', 'word-wrap', '-webkit-hyphens', '-moz-hyphens', 'hyphens']); // Like contentPropsSafe except will only be removed if never gets // applied to any text, whitespace or not var contentPropsNoTextSafe = new Set(['text-decoration', 'font-size']); function getText(tree) { return (tree.content || []).map(function (child) { if (typeof child === 'string') { return child; } else { return getText(child); } }).join(''); } function isPropNeverAppliedToText_(targetProp, tree, allowWhitespace) { if (typeof tree === 'string') { return allowWhitespace ? isWhitespace(tree) : false; } if (tree.tag === 'img') { if (tree.attrs && tree.attrs.style) { var styles = parseStyle(tree.attrs.style).nodes; if (styles.some(function (s) { return s.prop === targetProp; })) { return true; } } return false; } if (!tree.content) { return true; } if (tree.attrs && tree.attrs.style) { var _styles = parseStyle(tree.attrs.style).nodes; if (_styles.some(function (s) { return s.prop === targetProp; })) { return true; } } return tree.content.every(function (child) { return isPropNeverAppliedToText(targetProp, child); }); } // Same as the _ helper version, except don't check the first level // for the targetProp style function isPropNeverAppliedToText(targetProp, tree, allowWhitespace) { if (typeof allowWhitespace === 'undefined') { allowWhitespace = true; } if (typeof tree === 'string') { return false; } if (tree.tag === 'img') { return false; } if (!tree.content) { return true; } return tree.content.every(function (child) { return isPropNeverAppliedToText_(targetProp, child, allowWhitespace); }); } function postHtmlMinifyInlineCss(tree) { tree.match({}, function (node) { // Ignore text nodes if (!node.tag) { return node; } var text = node.content ? entities.decodeHTML(getText(node)) : ''; if (isWhitespace(text) && node.attrs && node.attrs.style) { if (node.tag.toLowerCase() !== 'img') { var styles = parseStyle(node.attrs.style); styles.nodes = styles.nodes.filter(function (o) { return !contentProps.has(o.prop); }); if (!node.content) { styles.nodes = styles.nodes.filter(function (o) { return !noContentProps.has(o.prop); }); } node.attrs.style = styles.toString(); } } if (node.attrs && node.attrs.style) { var _styles2 = parseStyle(node.attrs.style), props = new Set(_styles2.nodes.map(property('prop'))); contentPropsSafe.forEach(function (cp) { if (props.has(cp) && isPropNeverAppliedToText(cp, node)) { _styles2.nodes = _styles2.nodes.filter(function (o) { return o.prop !== cp; }); } }); contentPropsNoTextSafe.forEach(function (cp) { if (node.tag === 'a' && cp === 'text-decoration') { return; } if (props.has(cp) && isPropNeverAppliedToText(cp, node, false)) { _styles2.nodes = _styles2.nodes.filter(function (o) { return o.prop !== cp; }); } }); node.attrs.style = _styles2.toString(); } if (node.attrs && node.attrs.style === '') { delete node.attrs.style; } return node; }); } module.exports = function (options) { // Options not used; but may be later. return postHtmlMinifyInlineCss; };