Critical CSS Finder Javascript Snippet and Bookmarklet

— Tagged: css javascript web-development web-performance

I've spent the last 2 weeks at work improving the performance of our homepage and Developer Portal with great results. One of the key optimizations was inlining our critical CSS in the HEAD of the page and asynchronously loading the full CSS.

While we do use Gulp to automate a lot of our tasks, I didn’t want to use it to sniff out the critical CSS. We use Haml to write all our HTML, and Sass for all of the CSS, so we don’t exactly have HTML pages to offer up as a source since we don’t compile them. The critical CSS Javascript snippet that most point others to is that one by Paul Kinlan, but our styles are all mobile-first, so just grabbing what is declared for Desktop using Paul’s snippet doesn’t include the earlier breakpoints that get built upon to create the Desktop layout. I needed something that would support media query detection for that reason and because we have a lot of mobile traffic, so I didn’t want to exclude those visitors. In the comments of Paul’s gist, a Github user named james-Ballyhoo posted a link to his forked version of the snippet that included media query support.

Curious how James made this work, I set about rewriting it and ensuring that it did exactly what I wanted. I wanted to be able to set my own target viewport height, include media queries, and output the resulting critical CSS in the console rather than in a box added to the page.

This is what I came up with:

/* 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)
*/

(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);
})()

Also available as a gist on Github

The main way that I use it is to copy the JS above and add it as a snippet in Chrome’s Dev Tools. I then visit the page that I need the critical CSS for and run the snippet. The CSS gets put in the console window, and it’s just a matter of copying those rules and doing a little cleanup.

You can easily create a bookmarklet, too:

  1. Copy the JS snippet above.
  2. Head over to Bookmarkleter.
  3. Paste in the code and select only the checkbox next to “Minify using UglifyJS.”
  4. Drag the generated bookmarklet to your favorties bar for quick and easy access.
  5. Visit your page, click the link you just created, and head to the console in Chrome.

I hope it helps with generating your critical CSS. Happy coding!

Want to have your say? Send me a tweet »
Share