Optimising the Typekit Code

The Typekit kit editor provides two versions of the embed code, the default version blocks rendering and the asynchronous version which can produce a flash of unstyled content (FOUC).

In the case of Typekit, I prefer the FOUC over slowing down rendering. The asynchronous code also offers the advantage of your site staying online should Typekit go down.

I decided to spend a little time optimising the asynchronous code Typekit provides. Expanded to use meaningful variable names, the Typekit code is:

(function() {
  var config = {
    kitId: 'typeKit',
    scriptTimeout: 3000
  };
  var html = document.getElementsByTagName("html")[0];
  html.className += " wf-loading";
  var timer = setTimeout(function() {
    html.className = html.className.replace(/(s|^)wf-loading(s|$)/g, " ");
    html.className += " wf-inactive"
  }, config.scriptTimeout);
  var typekitScript = document.createElement("script"),
    done = false;
  typekitScript.src = '//use.typekit.net/' + config.kitId + '.js';
  typekitScript.type = "text/javascript";
  typekitScript.async = "true";
  typekitScript.onload = typekitScript.onreadystatechange = function() {
    var activeState = this.readyState;
    if (done || activeState && activeState != "complete" && activeState != "loaded") return;
    done = true;
    clearTimeout(timer);
    try {
      Typekit.load(config)
    } catch (exception) {}
  };
  var firstScript = document.getElementsByTagName("script")[0];
  firstScript.parentNode.insertBefore(typekitScript, firstScript)
})();

Note: this code is now out of date as Typekit have implemented many of the optimisations below into their codebase.

Consolidate className changes

Should Typekit fail to load, the wf-loading class is replaced with wf-inactive. The first step is bring the two lines of code into one, and update the regular expression to use the b switch.

html.className = html.className.replace(/bwf-loadingb/g, "")+" wf-inactive";

This produces a substantial speed increase.

Getting the HTML tag

The Typekit code uses getElementsByTagName to get the HTML tag, we can save a few bytes and get a speed increase with:

var html = document.documentElement;

Declaring variables in one command

The Typekit code declares each variable separately, using the comma separated notation and a bit of shuffling, we can save a few more bytes. Our code now starts with:

(function() {
  var config = {
    kitId: 'xow3svv',
    scriptTimeout: 3000
  },
    html = document.documentElement,
    timer = setTimeout(function() {
    html.className = html.className.replace(/bwf-loadingb/g, "")+" wf-inactive"
  }, config.scriptTimeout),
    typekitScript = document.createElement("script"),
    done = false,
    firstScript = document.getElementsByTagName("script")[0],
    activeState;
  html.className += " wf-loading";
  //snip

The inserted script element

All browsers presume that a <script> element is JavaScript, so the line setting the type can be dropped and save 30 bytes in the final version of the code. While making changes to the inserted element, the async property is also assumed for inserted scripts in most browsers and can be dropped too.

typekitScript.src = '//use.typekit.net/' + config.kitId + '.js';

If you need to support Firefox 3.6, Opera 10+ the async property does make a difference but its value does not. In this case you can use

typekitScript.async = typekitScript.src = '//use.typekit.net/' + config.kitId + '.js';

Passing commonly used values

Both document and the string 'script' are used multiple times throughout the code. By passing these to the anonymous function we can aid compression later.

The className property of the HTML tag is referenced a number of times, by switching to the array notation, html['className'] we can save a few bytes by passing className as a string too. While we’re about it, passing the string ' wf-' will also reduce the code base.

Our anonymous function is now declared as:

(function( document, script, className, _wf ) {
//snip
})( document, 'script', 'className', ' wf-' );

Compressing the variables

Replacing the meaningful variable names, the new code becomes:

(function( P, W, C, c ) {
  var t = { kitId: 'xow3svv',scriptTimeout: 3000 },
      y = P.documentElement,
      p = P.createElement(W),
      e = false,
      k = P.getElementsByTagName(W)[0],
      i,
      T = setTimeout(function() {
        y[C] = y[C].replace(/bwf-loadingb/g, "")+ c + "inactive"
      }, t.scriptTimeout);
  y[C] += c + "loading";
  p.src = '//use.typekit.net/' + t.kitId + '.js';
  p.onload = p.onreadystatechange = function() {
    i = this.readyState;
    if (e || i && i != "complete" && i != "loaded") return;
    e = true;
    clearTimeout(T);
    try {
      Typekit.load(t)
    } catch (exception) {}
  };
  k.parentNode.insertBefore(p, k)
})( document, 'script', 'className', ' wf-' );

Keeping the config code on a single line for clarity, the compressed version of the code is

(function(P,W,C,c){
var t={kitId: 'xow3svv',scriptTimeout: 3000},
y=P.documentElement,p=P.createElement(W),e=false,k=P.getElementsByTagName(W)[0],i,T=setTimeout(function(){y[C]=y[C].replace(/bwf-loadingb/g,"")+c+"inactive"},t.scriptTimeout);y[C]+=c+"loading";p.src='//use.typekit.net/'+t.kitId+'.js';p.onload=p.onreadystatechange=function(){i=this.readyState;if(e||i&&i!="complete"&&i!="loaded")return;e=true;clearTimeout(T);try{Typekit.load(t)}catch(x){}};k.parentNode.insertBefore(p,k)})(document,'script','className',' wf-');

Our finalised version of the code is 180 bytes smaller than the original code and uses a couple of small optimisations to run quicker than the original.

I’ve made up a gist with the optimised code to show each step.

Obviously this post was inspired by Mathias Bynens’ similar write up for the Google Analytics code.

Updates

  1. Feb 9, 2014: Remove wf-inactive’s dependance on wp-loading based on Typekit feedback below.

Comments

One response to “Optimising the Typekit Code”

  1. Bram Stein Avatar

    Thanks for these suggestions. We’ve taken most of them and incorporated them into our async embed code. We didn’t use the str.replace optimisation because it actually changes the behaviour of the code. Your replacement assumes wf-loading is always present, but this is not always the case. We also didn’t use the script, className, etc. optimisation, because we think it decreases the legibility of the code.

    I think we used all the other suggestions or at least some variation of them. Great work! Again, thanks a lot, we love community feedback!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.