motion
Version:
motion - moving development forward
107 lines (99 loc) • 3.96 kB
JavaScript
;
var postcss = require('postcss');
var sort = require('alphanum-sort');
var unquote = require('./lib/unquote');
var parser = require('postcss-selector-parser');
var pseudoElements = ['::before', '::after', '::first-letter', '::first-line'];
function getParsed (selectors, callback) {
return parser(callback).process(selectors).result;
}
/**
* Can unquote attribute detection from mothereff.in
* Copyright Mathias Bynens <https://mathiasbynens.be/>
* https://github.com/mathiasbynens/mothereff.in
*/
var escapes = /\\([0-9A-Fa-f]{1,6})[ \t\n\f\r]?/g;
var range = /[\u0000-\u002c\u002e\u002f\u003A-\u0040\u005B-\u005E\u0060\u007B-\u009f]/;
function canUnquote (value) {
value = unquote(value);
if (value === '-') {
return false;
}
if (value) {
value = value.replace(escapes, 'a').replace(/\\./g, 'a');
return !(range.test(value) || /^(?:-?\d|--)/.test(value));
}
return false;
}
function optimise (rule) {
var selector = rule.raws.selector && rule.raws.selector.raw || rule.selector;
// If the selector ends with a ':' it is likely a part of a custom mixin,
// so just pass through.
if (selector[selector.length - 1] === ':') {
return;
}
rule.selector = getParsed(selector, function (selectors) {
selectors.nodes = sort(selectors.nodes, {insensitive: true});
var uniqueSelectors = [];
selectors.eachInside(function (selector) {
var next = selector.next();
var toString = String(selector);
// Trim whitespace around the value
selector.spaces.before = selector.spaces.after = '';
if (selector.type === 'attribute') {
if (selector.value) {
// Join selectors that are split over new lines
selector.value = selector.value.replace(/\\\n/g, '').trim();
if (canUnquote(selector.value)) {
selector.value = unquote(selector.value);
}
selector.operator = selector.operator.trim();
}
if (selector.raw) { selector.raw.insensitive = ''; }
selector.attribute = selector.attribute.trim();
}
if (selector.type === 'combinator') {
var value = selector.value.trim();
selector.value = value.length ? value : ' ';
}
if (selector.type === 'pseudo') {
var uniques = [];
selector.eachInside(function (child) {
if (child.type === 'selector') {
if (!~uniques.indexOf(String(child))) {
uniques.push(String(child));
} else {
child.removeSelf();
}
}
});
if (~pseudoElements.indexOf(selector.value)) {
selector.value = selector.value.slice(1);
}
}
if (selector.type === 'selector' && selector.parent.type !== 'pseudo') {
if (!~uniqueSelectors.indexOf(toString)) {
uniqueSelectors.push(toString);
} else {
selector.removeSelf();
}
}
if (selector.type === 'tag') {
if (selector.value === 'from') { selector.value = '0%'; }
if (selector.value === '100%') { selector.value = 'to'; }
}
if (selector.type === 'universal') {
if (next && next.type !== 'combinator') {
selector.removeSelf();
}
}
});
});
}
module.exports = postcss.plugin('postcss-minify-selectors', function () {
return function (css) {
css.walkRules(function (rule) {
return optimise(rule);
});
};
});