Skip to content

Instantly share code, notes, and snippets.

@kfranqueiro
Last active October 25, 2019 21:56
Show Gist options
  • Save kfranqueiro/f3badbe2e8f9c10b3ba3 to your computer and use it in GitHub Desktop.
Save kfranqueiro/f3badbe2e8f9c10b3ba3 to your computer and use it in GitHub Desktop.
spot - a script/bookmarklet providing utility functions for finding unused CSS selectors/classes
javascript:window.spot=function(){function a(a,b){for(var d,c=document.styleSheets,e=0,f=c.length;f>e;e++)if((!b||c[e].href&&-1!==c[e].href.indexOf(b))&&(d=c[e].cssRules,d&&d.length))for(var g=d.length;g--;)d[g].selectorText&&a(d[g].selectorText)}function b(a,c){c||a(document.documentElement);for(var d=(c||document.documentElement).children,e=0,f=d.length;f>e;e++)a(d[e]),b(a,d[e])}function c(a){var b={},c=getComputedStyle(a);for(var d in c)isNaN(d)&&"function"!=typeof c[d]&&(b[d]=c[d]);return b}function d(a,b){var c=Object.keys(a);if(c.length!==Object.keys(b).length)return!1;for(var d=c.length;d--;)if(a[c[d]]!==b[c[d]])return!1;return!0}return{unusedClasses:function(){var a=Array.prototype.slice,e={},f=[];b(function(b){if(b.className){var g,h,f=a.call(b.classList);g=c(b);for(var i=f.length;i--;)h=f[i],e[h]||(b.classList.remove(h),e[h]=!d(g,c(b)),b.classList.add(h))}});for(var g in e)e[g]===!1&&f.push(g);return f},unusedSelectors:function(b){var c={};return a(function(a){document.querySelector(a)||(c[a]=!0)},b),Object.keys(c)}}}();
window.spot = (function () {
function forEachSelector(callback, match) {
// Runs a callback on each selector found in the document's stylesheets.
// If `match` is specified, only iterates through stylesheets whose href
// contains `match`.
var sheets = document.styleSheets;
var rules;
for (var i = 0, l = sheets.length; i < l; i++) {
if (match && (!sheets[i].href || sheets[i].href.indexOf(match) === -1)) {
continue;
}
rules = sheets[i].cssRules;
if (rules && rules.length) {
for (var j = rules.length; j--;) {
if (rules[j].selectorText) {
callback(rules[j].selectorText);
}
}
}
}
}
function forEachElement(callback, _element) {
// Recursively traverses document, firing callback on each element.
if (!_element) {
// Start with documentElement then process its children;
// document.children doesn't work in IE
callback(document.documentElement);
}
var children = (_element || document.documentElement).children;
for (var i = 0, l = children.length; i < l; i++) {
callback(children[i]);
forEachElement(callback, children[i]);
}
}
function filterComputedStyle(element) {
// Creates a copy of an element's computed style with only the non-numeric properties.
var style = {};
var computedStyle = getComputedStyle(element);
for (var k in computedStyle) {
if (isNaN(k) && typeof computedStyle[k] !== 'function') {
style[k] = computedStyle[k];
}
}
return style;
}
function areObjectsEqual(a, b) {
// Performs a shallow property comparison between two objects.
var aKeys = Object.keys(a);
if (aKeys.length !== Object.keys(b).length) {
return false;
}
for (var i = aKeys.length; i--;) {
if (a[aKeys[i]] !== b[aKeys[i]]) {
return false;
}
}
return true;
}
return {
unusedClasses: function () {
// Returns an array of unused classes found in elements in the document.
var slice = Array.prototype.slice;
var testedClasses = {};
var unusedClasses = [];
forEachElement(function (element) {
if (!element.className) {
return;
}
var classes = slice.call(element.classList);
var beforeStyle;
var testedClass;
// Start with fully-styled element then remove one class at a
// time, each time comparing against the previous computed style
beforeStyle = filterComputedStyle(element);
for (var i = classes.length; i--;) {
testedClass = classes[i];
if (testedClasses[testedClass]) {
continue;
}
element.classList.remove(testedClass);
testedClasses[testedClass] = !areObjectsEqual(beforeStyle, filterComputedStyle(element));
element.classList.add(testedClass);
}
});
for (var k in testedClasses) {
if (testedClasses[k] === false) {
unusedClasses.push(k);
}
}
return unusedClasses;
},
unusedSelectors: function (match) {
// Returns an array of unused selectors found in the document's stylesheets.
// Maintain an object hash, then return a deduplicated array
var unusedSelectors = {};
forEachSelector(function (selector) {
if (!document.querySelector(selector)) {
unusedSelectors[selector] = true;
}
}, match);
return Object.keys(unusedSelectors);
}
};
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment