Favourite bug in 2015: When ‘exports’ isn’t falsy

A story about my favourite frontend bug of 2015. Or, why you shouldn’t assume exports will be falsy.

A combination of an established module pattern, headings with dynamic IDs, content on a specific topic and the quirky way that browsers handle IDs on elements created an obscure bug.

The bug manifested in a way that broke other things, it started as a report about a contents link overlapping some content. The console showed the unhelpful error:

Uncaught TypeError: undefined is not a function

Exports and modules

CommonJS uses a global exports variable to export properties from a module. A pattern in frontend libraries is to check for the presence of exports to see if the library, in its current context, should behave as a module.

Here’s what you’ll usually see:

typeof exports !== 'undefined' ? exports : SomeLibrary

At GOV.UK we use Hogan for javascript templating. At the bottom of the Hogan library you’ll find a similar line:

(typeof exports !== 'undefined' ? exports : Hogan);

Element IDs

Browsers have a quirky and little known behaviour when it comes to element IDs. The window object must have a property key with the value element, if there is exactly one DOM element with the ID key.

Essentially, elements on a page with a unique ID create a global variable.

With this markup:

<h2 id="heading">Heading</h2>

You can access the element with the following JS:

// Each returns the element
window['heading']
window.heading
heading

Internet Explorer started it and now it’s a standard:

There’s a lot of bad things about this, Bob Ince on Stack Overflow gives a good summary of the mess you could find yourself in.

IDs on page headings

Well structured content gets broken down into parts, each with their own heading. Linking to part of a document is useful and the common approach is to link to an ID using a fragment identifier.

To make this easy publishing tools often generate IDs on headings, based on the heading text. GOV.UK does it, so too does this blog. The heading above, “IDs on page headings” has the generated ID: IDs_on_page_headings, this is a link to it.

When you’re government it’s likely you’ll create an article about imports and exports. Here’s one: A guide for international post users

Putting it all together

On pages with an exports section we see:

<h2 id="exports">Exports</h2>

Browsers take the ID and create window.exports. Now when we call the Hogan library code, the check for exports gives a false positive. Hogan gets passed an HTML element instead of an empty object, and fails to start properly. Subsequent calls to new Hogan.Template then fail.

var Hogan = {};

(function(Hogan) {
// The value of Hogan at this point is the HTML element
// <h2 id="exports">Exports</h2>

// Hogan code

})(typeof exports !== 'undefined' ? exports : Hogan);

The hot fix

We aren’t using the CommonJS module pattern. Removing the exports logic fixed the bug. Here’s the pull request: Remove typeof exports check in Hogan libraries.

-})(typeof exports !== 'undefined' ? exports : Hogan);
+})(Hogan);

This needs to be fixed upstream, and the use of this exports pattern re-considered. I’ve opened an issue on the Hogan repository.