loganlinn.log

Advanced Compilation, Externs, and Window

Fixing issues that arise from advanced compilation mode with the Closure compiler can be tricky to debug. Referencing external libraries can be troublesome when the compiler doesn’t know about them. However, even when externs are sucessfully configured, things can go wrong… Here’s a quick lesson learned from fixing a function that my coworker identified as broken after advanced compilation was used.

This function uses the twitter-text-js library to count the length of a tweet after link shortening by calling twttr.txt.getTweetLength(). Our misbehaving ClojureScript function looked something like:

1
2
3
4
(defn tweet-length [text]
  (if ^boolean (.-twttr js/window)
    (.. js/window -twttr -txt (getTweetLength text))
    (count text)))

Pretty straight forward: it checks to see if the external script has been before calling a function that it provides or falls back counting the number of characters.

Let’s see what the advanced compiled code looks like:

1
2
3
function(a) {
  return window.de ? window.de.txt.getTweetLength(a) : M(a)
}

Spot the issue? The optimizer munged window.twttr to window.de even though the extern was configured.

As you may have guessed by now, externs are not recognized when referenced through window. It makes sense, but may not be immediately obvious, especially when debugging. After all, this function only broke after advanced compilation was used.

The fixed function:

1
2
3
4
(defn tweet-length [text]
  (if (exists? js/twttr)
    (.. js/twttr -txt (getTweetLength text))
    (count text)))

Note the use of cljs.core/exists?, to test whether a variable exists. Simply testing js/twttr as boolean when the script hasn’t been evaluated would throw a ReferenceError.

The advanced compiled result looks like:

1
2
3
function(a) {
  return "undefined" !== twttr ? twttr.txt.getTweetLength(a) : M(a)
}

Lessons learned: reference externs directly from js/ and, more generally, avoid js/window unless you’re accessing one of its actual properties.

Using the advanced compiler can bring tons of little nuances like this. They’re often things you may not consider when writing code and thinking about its correctness.

In the end, a rather simple lesson learned, but hopefully it’s helpful to others.