tessguefen
1/7/2017 - 9:18 PM

Critical CSS Finder w/media query support for those of us who write mobile-first CSS; will output to console; configurable viewport height o

Critical CSS Finder w/media query support for those of us who write mobile-first CSS; will output to console; configurable viewport height on line 12.

/* Critical CSS Finder w/media query support and output to console
by Katie Harron - https://github.com/pibby - https://pibby.com
forked from james-Ballyhoo (https://gist.github.com/james-Ballyhoo/04761ed2a5778c505527) who forked from PaulKinlan (https://gist.github.com/PaulKinlan/6284142)
I don't know why this isn't keeping my 2 space indents :(
*/

(function() {
	function findCriticalCSS(w, d) {
		// Pseudo classes formatting
		var formatPseudo = /([^\s,\:\(])\:\:?(?!not)[a-zA-Z\-]{1,}(?:\(.*?\))?/g;
		// Height in px we want critical styles for
		var targetHeight = 900;
		var criticalNodes = [];

		// Step through the document tree and identify nodes that are within our targetHeight
		var walker = d.createTreeWalker(d, NodeFilter.SHOW_ELEMENT, function(node) { return NodeFilter.FILTER_ACCEPT; }, true);

		while(walker.nextNode()) {
			var node = walker.currentNode;
			var rect = node.getBoundingClientRect();
			if (rect.top < targetHeight) {
				criticalNodes.push(node);
			}
		}
		console.log("Found " + criticalNodes.length + " critical nodes.");

		// Find stylesheets that have been loaded
		var stylesheets = document.styleSheets;
		console.log("Found " + stylesheets.length + " stylesheet(s).");

		var outputCss = Array.prototype.map.call(stylesheets,function(sheet) {
			var rules = sheet.rules || sheet.cssRules;
			// If style rules are present
			if (rules) {
				return {
					sheet: sheet,
					// Convert rules into an array
					rules: Array.prototype.map.call(rules, function(rule) {
						try {
							// If the rule contains a media query
							if (rule instanceof CSSMediaRule) {
								var nestedRules = rule.rules || rule.cssRules;
								var css = Array.prototype.filter.call(nestedRules, function(rule) {
									return criticalNodes.filter(function(e){ return e.matches(rule.selectorText.replace(formatPseudo, "$1"))}).length > 0;
								}).map(function(rule) { return rule.cssText }).reduce(function(ruleCss, init) {return init + "\n" + ruleCss;}, "");
								return css ? ("@media " + rule.media.mediaText + " { " + css + "}") : null;

							} else if (rule instanceof CSSStyleRule) { // If rule does not contain a media query
								return criticalNodes.filter(function(e) { return e.matches(rule.selectorText.replace(formatPseudo, "$1")) }).length > 0 ? rule.cssText : null;
							} else {  // If identified via CSSRule like @font and @keyframes
								return rule.cssText;
							}
						} catch(e) {
							/* This results in an error if you have print styles with @page embedded. As I do, I'm commenting it out. */

							/*console.error("Improper CSS rule ", rule.selectorText);
								throw e;*/
						}
					}).filter(function(e) { return e; })
				}
			} else {
				return null;
			}
		}).filter(function(cssEntry) { return cssEntry && cssEntry.rules.length > 0 })
		.map(function(cssEntry) { return cssEntry.rules.join(""); })
		.reduce(function(css, out) {return out + css}, "")

		// Remove linebreaks
		console.log(outputCss.replace(/\n/g,""))
	}

	findCriticalCSS(window, document);
})()