Critical CSS Finder Javascript Snippet and Bookmarklet

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 the 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/pibbyhttps://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 to get your CSS.

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


Originally published at pibby.com.

Like what you read? Give Katie Harron a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.