Join the discussion on Hacker News!
Last year, we migrated our web development to ClojureScript and haven’t looked back. Over the last few months, we’ve been building out dommy, a ClojureScript library for DOM templating and manipulation. The library is relatively far along and we think it’s time to share our reasons for why we’d write yet another DOM library, rather than use jQuery within ClojureScript. We built dommy because we felt that a ClojureScript DOM library could be a simpler, faster, and better version of jQuery that fits organically into expressive functional code. In this post, we’ll talk about how we made dommy so much faster than jQuery (for example, 15x faster for a simple ID selector) and how we can acheive jQuery-style patterns with less overhead using ClojureScript-native abstractions.
Macros make you much faster
Probably the most common uses of jQuery are DOM selectors. Most selectors in the wild are simple queries for either an element by ID (e.g.,
$(‘#some-id’)) or by class (e.g,
$(‘.some-class’)). Both jQuery and dommy eventually delegate to fast native DOM methods for such selectors. However in jQuery, the work of parsing the selector string and detecting that is a simple ID selector is done on the client runtime. Dommy, on the other hand, parses the expression at compile-time and rewrites the selector directly to the native DOM method where applicable. Here are some specific examples of how selectors are rewritten:
jQuery, by contrast, incurs significant overhead in parsing the selector at runtime:
We covered dommy macro templating in a past post, but it bears repeating since it’s yet another example of why macros are awesome for client-side performance. Suppose you want to generate DOM for a user profile where each profile displays several user-generated posts. In dommy you might create two small templates for this:
Simpler, Better, and Extensible
Away with DOM wrapper objects
jQuery outputs are typically DOM objects inside a wrapper object. A source of frustration in jQuery is not being sure if you’re dealing with an actual DOM element or jQuery’s wrapper around such an element. There is also a non-trivial performance hit. So what’s the upshot? The jQuery object effectively exists to support chaining of jQuery methods:
In dommy, each of our DOM manipulation functions return plain unwrapped DOM nodes. We use Clojure’s single-threaded arrow macro
-> to chain expressions:
In Clojure, the idea of threading an object through a series of functions which update the object and return the updated object can be expressed cleanly using the
One is the loneliest number
Another messy part of jQuery’s semantics are that many (but not all) of the jQuery methods will happily work on one or many things which can be coerced into jQuery DOM objects. The intent is to support processing one or many objects in a similar way. Here’s a concrete example in jQuery:
jQuery’s API has a suite of sequence processing function (
addClass function is forced to operate on both a singleton and a sequence.
add-class! only works on a single DOM object, but we still the sequence-processing expressiveness using built-in Clojure language features:
We can even use Clojure’s standard library to do things you can’t easily do with jQuery:
Aside from just yielding a smaller library, dommy’s avoidance of wrapper objects is also a significant performance boost since no runtime cycles are wasted trying to guess what your input objects are and constructing objects to wrap them.
Like jQuery, we want our DOM manipulation to accept multiple kinds of things which can be coerced to DOM-like objects. In jQuery, this is limited to a pre-defined set of things (strings representing html, native DOM objects, and jQuery wrapper objects themselves). In dommy, there is a Clojure protocol for what can be converted to a DOM, which any object can implement. We extend that protocol to native Clojure data structures (this is how templating works in dommy). This makes it really easy to extend the DOM manipulation and templating system in client code:
Altogether, dommy’s integration into the Clojure environment lets you treat DOM nodes as first-class objects and protocols:
Are you saying jQuery sucks?
No. jQuery was a massive improvement in web development, but was fundamentally limited by the language that it was implemented in. Dommy is mostly us re-considering how we’d write DOM manipulation and templating in a way that felt Clojure-y and functional native, drawing on a lot of the good choices jQuery did make.
In fairness, jQuery currently has more features and supports a much broader range of browsers. Over the next few months, we plan on increasing dommy’s coverage to cover at least 95% of browsers (somewhere on par with jQuery 2.0’s coverage). Stay tuned for future developments and updates to dommy!