Forked from victor-homyakov/detect-unused-css-selectors.js
Created
April 20, 2020 11:24
-
-
Save haalogen/760b6b9c96ad5cc9c0e1b809838c98fc to your computer and use it in GitHub Desktop.
Detect unused CSS selectors. Show possible CSS duplicates. Monitor realtime CSS usage.
This file contains hidden or 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
| /* eslint-disable no-var,no-console */ | |
| // detect unused CSS selectors | |
| (function() { | |
| var parsedRules = parseCssRules(); | |
| console.log('Parsed CSS rules:', parsedRules); | |
| detectDuplicateSelectors(parsedRules); | |
| var selectorsToTrack = getSelectorsToTrack(parsedRules); | |
| window.selectorStats = { unused: [], added: [], removed: [] }; | |
| console.log('Tracking style usage (inspect window.selectorStats for details)...'); | |
| setInterval(function() { | |
| var newSelectors = getSelectorsToTrack(parseCssRules()); | |
| // Calculation order for removed/added/unused is significant | |
| var removed = Object.keys(selectorsToTrack) | |
| .filter(selector => newSelectors[selector] === undefined); | |
| var added = Object.keys(newSelectors) | |
| .filter(selector => { | |
| if (selectorsToTrack[selector] === undefined) { | |
| selectorsToTrack[selector] = 0; | |
| return true; | |
| } | |
| return false; | |
| }); | |
| var unused = Object.keys(selectorsToTrack) | |
| .filter(selector => { | |
| if (document.querySelector(selector)) { | |
| selectorsToTrack[selector]++; | |
| } | |
| return selectorsToTrack[selector] === 0; | |
| }); | |
| var message = []; | |
| if (unused.length !== window.selectorStats.unused.length) { | |
| message.push(unused.length + ' unused'); | |
| } | |
| window.selectorStats.unused = unused; | |
| if (added.length > 0) { | |
| message.push(added.length + ' added'); | |
| window.selectorStats.added = added; | |
| } | |
| if (removed.length > 0) { | |
| message.push(removed.length + ' removed', removed); | |
| window.selectorStats.removed = removed; | |
| } | |
| if (message.length > 0) { | |
| console.log('Selectors: ' + message.join(', ')); | |
| } | |
| }, 1000); | |
| function parseCssRules() { | |
| var styleSheets = document.styleSheets, | |
| parsedRules = { | |
| fontFaces: [], | |
| keyframes: [], | |
| media: [], | |
| style: [], | |
| support: [], | |
| unknown: [] | |
| }; | |
| for (var i = 0; i < styleSheets.length; i++) { | |
| var styleSheet = styleSheets[i]; | |
| var rules = styleSheet.cssRules; // styleSheet.rules | |
| for (var j = 0; j < rules.length; j++) { | |
| var rule = rules[j]; | |
| var ruleClass = Object.prototype.toString.call(rule).replace(/\[object (.+)]/, '$1'); | |
| switch (ruleClass) { | |
| case 'CSSFontFaceRule': | |
| parsedRules.fontFaces.push(rule.cssText); | |
| break; | |
| case 'CSSKeyframesRule': | |
| parsedRules.keyframes.push(rule.cssText); | |
| break; | |
| case 'CSSMediaRule': | |
| // if (rule.conditionText) | |
| parsedRules.media.push(rule.conditionText); | |
| break; | |
| case 'CSSStyleRule': | |
| // if (rule.selectorText) | |
| parsedRules.style.push(rule.selectorText); | |
| // rule.cssText | |
| break; | |
| case 'CSSSupportsRule': | |
| parsedRules.support.push(rule.conditionText); | |
| break; | |
| default: | |
| parsedRules.unknown.push(rule); | |
| } | |
| } | |
| } | |
| return parsedRules; | |
| } | |
| function detectDuplicateSelectors(parsedRules) { | |
| var seenSelectors = {}, | |
| duplicatedSelectors = [], | |
| duplicatedSequence = []; | |
| parsedRules.style.forEach(function(selector) { | |
| if (selector in seenSelectors) { | |
| duplicatedSelectors.push(selector); | |
| duplicatedSequence.push(selector); | |
| } else { | |
| seenSelectors[selector] = true; | |
| if (duplicatedSequence.length > 5) { | |
| console.warn('Duplicated sequence of selectors:', duplicatedSequence); | |
| } | |
| duplicatedSequence = []; | |
| } | |
| }); | |
| if (duplicatedSelectors.length > 0) { | |
| console.log('List of all duplicated selectors:', duplicatedSelectors); | |
| } | |
| } | |
| function getSelectorsToTrack(parsedRules) { | |
| return parsedRules.style | |
| .filter(function(selector) { | |
| return !( | |
| selector === 'html' || | |
| selector.includes(':hover') || | |
| selector.includes('::after') || | |
| selector.includes('::before') | |
| ); | |
| }) | |
| .reduce(function(selectors, selector) { | |
| selectors[selector] = 0; | |
| return selectors; | |
| }, {}); | |
| } | |
| }()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment