Last active
October 25, 2019 21:56
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)}}}(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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