jQuery for one selector
I bundled 30 KB just to toggle a class... twice. Here's what the native version costs now, and why I'm still not ripping it out of anything that works.
I got my first dev job in August at a place that has about 80 active clients which means I have spent the last couple months opening other people’s work for the first time. It is the fastest education I have had, and most of it is not flattering to anybody (very much including me!)
Last week I worked on a site where jQuery was loaded on every page and used in only one place. Its sole purpose was to add a class to an element when a button was clicked.
Thirty-odd KB, gzipped, parsed and executed on every page load, just to avoid typing classList.toggle.
I’m not writing this to dunk because I have done exactly this on my own projects. That’s the pattern: jQuery goes into the boilerplate on day one because it’s always in the boilerplate, and then the project turns out not to need much JavaScript, and nobody goes back and checks.
Earning its place in the boilerplate
It’s worth being clear-eyed about this because there’s a strain of jQuery bashing that’s just ahistorical.
In 2006 the DOM API was genuinely awful and, worse, it was differently awful in each browser. getElementById existed but selecting by class meant walking the tree yourself. Attaching an event was addEventListener in most browsers and attachEvent in IE, with a different this and a different event object. Ajax was XMLHttpRequest or an ActiveX object depending. Animations meant setInterval and doing some math.
jQuery made all of that one consistent thing that worked everywhere, and it did it so well that its selector engine got standardized into the platform. That’s about the highest compliment a library can receive: the browsers implemented it.
Which is also exactly why the case for it has weakened. It won.
The browsers implemented it, which is the highest compliment a library can get and also why you might not need it.
The direct translations
The stuff I actually use jQuery for on a day-to-day basis on a WordPress site is a very short list and all of it is now native.
$('.item') document.querySelectorAll('.item')
$('#thing') document.querySelector('#thing')
$el.addClass('is-open') el.classList.add('is-open')
$el.toggleClass('is-open') el.classList.toggle('is-open')
$el.hasClass('is-open') el.classList.contains('is-open')
$el.on('click', fn) el.addEventListener('click', fn)
$el.attr('href') el.getAttribute('href')
$el.text('hi') el.textContent = 'hi'
$el.closest('.card') el.closest('.card')
$(document).ready(fn)
classList and closest and querySelectorAll are all supported back to IE9 or so, with closest being the newest and needing a small polyfill for older IE. Which for most of my projects is fine now but maybe wasn’t two years ago.
The one that trips people up is that querySelectorAll returns a NodeList, not an array, so .forEach on it is not reliable everywhere yet. You end up doing Array.prototype.slice.call(...) or the spread operator if you’re transpiling, which is more annoying than $('.item').each(), and I’ll concede that one.
Still has its uses
Ajax, mostly, if I’m supporting older browsers. $.ajax handles a pile of inconsistencies and gives you a promise-ish thing. There’s a native fetch now which is much nicer to use, but it’s new enough this year that it needs a polyfill almost everywhere, so you’re loading something either way.
Animation, if it’s anything beyond a simple state change. Though honestly most of what I used .animate() for is now a CSS transition and a class toggle, which performs better anyway because it can run on the compositor.
And to be clear: any project where jQuery is already loaded and already used throughout, which brings me to the actual point.
I’m not ripping it out
This is where I’d push back on my own post a bit.
Removing jQuery from an existing project that uses it properly is a rewrite of every interactive thing on the site in exchange for maybe 30 KB. That is a terrible trade. You’d be introducing risk across a codebase to save less than one of the images on the homepage.
I did the sum on the site that started this.
jQuery: about 30 KB over the network The hero image: 190 KB after I’d already optimized it
There is no version of this where the library is the thing to attack first, and I’d been about to make the case for it because the library felt like my problem and the image felt like somebody else’s.
Trimming the boilerplate
What I’ve actually changed is what goes into a new project on day one, and that’s nothing.
I start with no JavaScript at all. Then, when I need something, I write it in vanilla JS first, and if the vanilla version is painful enough for the browsers in scope then I’ll pull in a library and at least then I’ll know why. On the last three projects that decision came out as “no jQuery” twice and “yes, for the Ajax” once, and in all of those cases it was a decision rather than an inheritance.
Which is the bit that generalizes, I think. The problem was never jQuery. It’s that the boilerplate accumulates things that solved problems that no longer exist, and nothing in the process ever prompts you to re-examine an assumption that isn’t currently hurting.
I don’t have a good fix for that beyond occasionally opening the network tab and being embarrassed. That is roughly my whole methodology at this point, and it has a worrying success rate.