posthtml-minify-inline-css
Version:
PostHTML plugin to minify inline CSS styles
161 lines (133 loc) • 4.33 kB
JavaScript
;
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;
};