I love you, jQuery. But maybe it’s time we started spending some time apart.
Improvements in cross-browser feature set and compatibility are reducing the need for jQuery, but more importantly, it turns out that a component-based app needs very little jQuery to begin with. These insights led to one of the first decisions I hit in starting Quetzal: should it require jQuery? The answer, so far, is no.
Since its inception in 2007, QuickUI has relied on jQuery as a crucial browser abstraction layer. Without jQuery, QuickUI would have never have come to exist. However, the QuickUI runtime itself only uses a fairly small set of core jQuery functions, and modern browsers now deliver standards-based solutions for those situations. Moreover, I’ve noticed that, when building QuickUI component-based apps, component code tends to rarely require sophisticated jQuery.
Meanwhile, the web has moved forward. The modern browsers — the latest Chrome, Safari, Mozilla, and Internet Explorer 10+ — are all much, much more consistent to develop for than browsers were six years ago. Glancing at pages on QuirksMode, one generally sees a see of green compliance, with most of the red markers of non-compliance for older versions of IE. And according to StatCounter, market shares for both IE 8 and 9 have now dropped below 10% (each), so it’s plausible for mainstream organizations to justify ignoring them.
Let’s break down the things jQuery is good at, and consider how necessary are in a web component-based application (specifically) targeting the modern browsers:
- Handling an array of DOM objects. It’s convenient to be able to apply a jQuery function or plugin to a jQuery object containing a collection of DOM elements. However, modern web languages (CoffeeScript, ES6, etc.) provide syntax for iterating over a collection, substantially reducing the need for a special library for this purpose. And, while jQuery’s array model is easy to consume, it also makes writing components harder. QuickUI control classes inherit from jQuery, which means every QuickUI class method has to consider the possibility it’s being applied to multiple objects at a time, not just one. In practice, that’s been a consistent source of complexity and bugs.
- Selectors. The standard querySelector and querySelectorAll functions provide a reasonable way to find matching elements. (I believe those functions were at least partially inspired by jQuery.) I’ve heard that jQuery’s Sizzle engine provides more power than querySelector, but again, in a component-based app, you’re rarely doing sophisticated searching of the DOM. In practice, when I’m writing an app with QuickUI components, I do very, very little searching of the DOM. Every QuickUI component already has its own reference to exactly the sub-elements it cares about. Polymer does something similar with automatic node finding. With such facilities in place, there’s no need to grovel around in the DOM to find something you’re looking for. In fact, with Shadow DOM, the things you’re probably looking for aren’t even findable. This is a good thing; encapsulation is preventing you from writing brittle code. Every time you do a $(“#foo”), you’re running the risk of picking up the wrong #foo — maybe not today, but tomorrow, when someone else adds a #foo element somewhere on the page. In Quetzal or Polymer, you never search for something like that; a component already has a direct reference (this.$.foo) to the element you want to manipulate. Unlike a DOM search, dereferencing is instantaneous and 100% reliable.
- Traversing. Ditto. In practice, component subtrees — that is, the set of elements managed directly by a component — just aren’t that deep. A deep subtree is, in fact, often an indicator that component refactoring is in order. It’s exactly analogous to the way a deeply-nested set of code blocks (conditionals, loops) within a single function usually indicates the function should be refactored. Nearly all the time a QuickUI component needs to traverse the DOM, it wants to iterate over its own set of children — which, as noted above, can now be easily done in a modern language with decent syntax.
- Function chaining. jQuery chaining lets you concisely apply a set of selector, traversal, and manipulation operations. In practice, all three of those types of operations come up less often in a web component-based app. In particular, one often sees long chains of jQuery function calls when populating the DOM, but a <template> is a cleaner way to do that declaratively. Over the years, in QuickUI apps, I’ve noticed that I use jQuery chaining less and less often, to the point where I only rarely take advantage of it today.
- Events. Microsoft IE 9 finally added support for addEventListener, so that it’s possible to wire up event handlers in a consistent way. I expect there are still many discrepancies lurking in the details — when each browser decides to fire an event, for example — that might prove tricky to work around without an abstraction layer like jQuery that can normalize behavior.
- Effects. CSS transitions and transition events now provide an easy, cross-browser way to do many of the same effects jQuery was first noted for. For years, the jQuery home page used to have a simple demo which, when you clicked a button, made a new DOM element appear with a transition effect. Such effects are now easily achievable without jQuery.
- Ajax. I don’t write a ton of Ajax code myself, so for argument’s sake, let’s stipulate that jQuery’s Ajax wrappers are handier than directly working with XMLHttpRequest. In particular, jQuery’s use of promises as a data type simplify the task of writing async code. Perhaps for the time being, this particular aspect of jQuery is worth using on its own. A proposed browser standard for futures may reduce that benefit, however.
- Plugins. Many, many of the jQuery plugins that exist today essentially create component-like things. These plugins effectively constitute a DOM template with some packaged behavior. I’d argue that a web component is a clearer, more maintainable way to achieve the same result. Encapsulation, in particular, is a huge advance to providing robust components. Moreover, many other uses for plugins could now be achieved by extending DOM elements directly.
Generally speaking, in a component-based app, you want to give each component the responsibility for managing its own appearance and behavior. You don’t want code walking all over the DOM tree and mucking around with things that aren’t its direct responsibility. Instead, you talk to the component managing the part of the DOM you care about, and ask that component to manipulate the elements it directly owns. In classic object-oriented programming terms, this is an application of the Law of Demeter.
In practice, compartmentalizing things that way leaves each component doing very simple manipulations on a comparatively small set of elements: instantiating a new element; iterating over its own children; applying or removing a style; etc. If a component wants to do something more sophisticated to its internals, more likely than not the component should delegate that operation to one of its own sub-components. The component’s own need to search is limited, reducing the need for a powerful selector/traversal engine. And, given a reasonably good programming language, simple DOM manipulations can be performed effectively — and with better performance — by directly accessing the DOM API.
From one standpoint, you could say I’ve just traded one browser abstraction layer (jQuery) for another (Polymer’s platform.js). However, platform.js feels like a pretty different animal than jQuery:
- The beauty of Polymer’s approach of polyfilling forthcoming web standards in older browsers is that you can write code against the abstraction layer today that should invisibly start working against native implementations where and when those exist. It’s still early, and that promise remains to be proven — but that’s a very compelling promise.
- Those future standards mean you’re working with facilities that someday every other developer will have (whether they want them or not). You’re writing code on top of a library that could get smaller over time, not larger.
- The other difference with platform.js, as far as today’s web component developers are concerned, is that it’s really the only game in town. Unless you have the luxury of targeting the latest release of Chrome, you’ll need to use platform.js (or the complete polymer.js) to run on other browsers.
- If one’s goal is to write components that many people will use, it makes sense to reduce the number of additional dependencies. If jQuery’s not required, then the lack of that dependency to some degree facilitates sharing.
We’ll see how the Quetzal experiment evolves, but so far, writing directly to the DOM API is working out okay.
So, jQuery, maybe we should spend some time apart. Maybe we should see some other people. It’s not you — it’s me! Don’t worry; I might miss you terribly and come running back. Or maybe we’ll just be friends.
It’s okay. We’ll always have IE6.
Great article. Though not only to web component authors not need jQuery, but Angular and Knockout users don’t either, as those all have full DOM manipulation capabilities and utility methods as part of the library or framework.
“In particular, jQuery’s use of promises as a data type simplify the task of writing async code. Perhaps for the time being, this particular aspect of jQuery is worth using on its own.”
Except it’s promises are broken, which is why I pulled out Angular’s $http service and made q-xhr (https://github.com/nathanboktae/q-xhr). Check it out.
Nathan: Thanks. q-xhr looks interesting too — I was frustrated when I discovered that jQuery’s promises were lame, and happy when I came found Q. I’ve seen a number of libraries that could benefit from q-xhr, some loading all of jQuery just to make an xhr call because they liked the convenience of promises. Doing that with real promises is a win.
RE: $.data, see dataset for pretty much 100% of what $.data was doing. (https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement.dataset)
Kevin: Thanks for the pointer; I hadn’t known about dataset. In my experience, all the situations in which I had previously been using $.data are now addressed by defining custom element properties. I guess dataset would be interesting if a component wanted to track per-node data (e.g., data for each of its children). I haven’t come across where I’ve needed to do that yet, though.
First, I agree that jQuery is no longer necessary on desktop. However, mobile WebKit has a lot of quirks which are being fixed by jQuery. jQuery devs said that old WebKit versions are bigger problem than oldIE nowadays.
Second, the Polymer framework is huge in terms of file size, which is a pain on mobile.
Adam: I’m only doing relatively basic stuff on mobile, but can’t recall having hit a bug that’s unique to WebKit on Mobile Safari. So far, for my purposes, platform.js + Polymer works as expected on Mobile Safari.
Regarding file size, it looks like a minified, gzipped jQuery currently weighs in ~30K, and the corresponding size of platform.js is ~45K. While platform.js is 50% bigger, I wouldn’t call that huge. So if you were using platform.js without the Polymer sugar, I’d think the download times would be roughly comparable. That’s not to say a web component-based app using platform.js would perform as well as the same UI created in jQuery; the latter might easier be much faster. Still, my own bias is to prefer a framework that closely mirrors my intent, and I feel like web components is so much better than jQuery in that regard that I’m happy to focus my work on web components. And if all goes well, the perf of a web component app will just get faster and faster as more browsers implement the standards natively. So the jQuery answer might be faster this year, but in, say, 12 months, it could easily be much slower.