Following a discussion with a colleague about how “necessary” jQuery is now-days, considering how far ES6 has come, I decided to conduct a small experiment: I would clone a live, functioning JS module and replace as much jQuery as possible (ideally all) with vanilla ES6.
TL;DR / TOC
- Select something
- Select something within a parent
- Add a CSS class
- Remove a CSS class
- Set an attribute
- Get an attribute
- Set a
data
attribute - Get a
data
attribute - Insert a text string
- Insert an HTML string
- Append an HTML element
- Append an HTML string
- Loop through an Array
- Loop through an Object
- Add an Event Listener
- Add a Delegate Event Listener
- Make an Ajax Request
Initially, I thought this would take the better part of an afternoon, including testing. Day two, still going…
While working my way through the cloned module, there have definitely been moments where I had to stop and hunt for the vanilla JS equivalent (jQuery sure can make one lazy!), as well as situations where I had to conduct small function tests to see if this would work to replace that.
Along the way, I am realizing that pretty much all jQuery functionality can fairly easily be replaced by vanilla ES6 (jQuery is, after all, just JS), but what I can see myself missing is the jQuery convenience…
No matter how comfy you get typing .querySelector
, it will never be as easy as typing $
… Not to mention having to know whether you need .querySelector
or .querySelectorAll
, and the fact that .querySelectorAll
returns a NodeList
instead of an array
… And don’t get me started on the loss of function-chaining!
So, while jQuery certainly can be replaced, what remains to be seen is whether it should be… Yes, it is 30kb (minified), but is removing that additional weight and latency enough to warrant the additional coding time and code weight? No matter how frugal your code is, vanilla JS does take more code to write, which takes longer to type, and in the end makes your code base larger…
As of now, I am still enjoying the experiment, but more than once have I found myself thinking “You know, I should create a micro library to alleviate some of these pain-points, like creating my own $
selector, etc.” Then I immediately start to wonder how long it would take for that micro library to start approaching 30kb… :-)
Anyhow, below are a few of the work-arounds that I have found, documented mostly so that I can remember how to switch things later, but maybe some of you will find them useful, too!
-
Select something
// jQuery: $( '.something' ); // ES6: // single item document.querySelector( '.something' ); // multiple items document.querySelectorAll( '.something' );
Documentation: querySelector, querySelectorAll
-
Select something within a parent
// jQuery: parent.find( '.something' ); // ES6: // single item parent.querySelector( '.something' ); // multiple items parent.querySelectorAll( '.something' );
Documentation: querySelector, querySelectorAll
-
Add a CSS class
// jQuery: elem.addClass( 'className' ); // ES6: elem.classList.add( 'className' );
-
Remove a CSS class
// jQuery: elem.removeClass( 'className' ); // ES6: elem.classList.remove( 'className' );
-
Set an attribute
// jQuery: elem.attr( 'attributeName', 'newValue' ); // ES6: elem.setAttribute( 'attributeName', 'newValue' );
Documentation: setAttribute
-
Get an attribute
// jQuery: elem.attr( 'attributeName' ); // ES6: elem.getAttribute( 'attributeName' );
Documentation: getAttribute
-
Set a
data
attribute// jQuery: elem.data( 'attributeName', 'newValue' ); // ES6: elem.dataset.attributeName = 'newValue'; elem.setAttribute( 'data-attributeName', 'newValue' );
Documentation: dataset, setAttribute
-
Get a
data
attribute// jQuery: elem.data( 'attributeName' ); // ES6: elem.dataset; // { attributeName: 'newValue' } elem.dataset.attributeName; // 'newValue' elem.getAttribute( 'data-attributeName' ); // 'newValue'
Documentation: dataset, getAttribute
-
Insert a text string
// jQuery: elem.text( 'Something' ); // ES6: elem.innerText = 'Something';
Documentation: innerText
-
Insert an HTML string
// jQuery: elem.html( '<h1>Something</h1>' ); // ES6: elem.innerHTML = '<h1>Something</h1>';
Documentation: innerHTML
-
Append an HTML element
// jQuery: parent.append( '<a class="btn btn-success" href="' +url+ '">Sign up</a>' ); // ES6: let elem = document.createElement( 'a' ); elem.setAttribute( 'className', 'btn btn-success' ); elem.setAttribute( 'href', url ); elem.innerText = 'Sign up'; parent.appendChild( elem );
Documentation: createElement, setAttribute, innerText, appendChild
-
Append an HTML string
// jQuery: parent.append( '<a class="btn btn-success" href="' +url+ '">Sign up</a>' ); // ES6: parent.insertAdjacentHTML( 'beforeend', `<a class="btn btn-success" href="${url}">Sign up</a>` );
Documentation: insertAdjacentHTML
-
Loop through an Array
// jQuery: arr.each( (item), function{...} ); // ES6: arr.forEach( item => { ... } );
Documentation: forEach
-
Loop through an Object
// jQuery: obj.each( (key, value), function{...} ); // ES6: for ( const key in obj ) { console.log(key, obj[key]); } // or: for ( const [key, value] of Object.entries(obj) ) { console.log(key, value); }
Documentation: for…in, Object.keys(), Object.entries()
-
Add an Event Listener
// jQuery: elem.on( 'click', function(e) {...} ); // ES6: elem.addEventListener( 'click', (e) => {...} );
Documentation: addEventListener
-
Add a Delegate Event Listener
// jQuery: elem.on( 'click', 'div.className', function(e){...} ); // ES6: elem.addEventListener( 'click', (e) => { if ( e.target.nodeName === 'DIV' && e.target.classList.contains('className') ) { ... } });
Documentation: addEventListener, Event Delegation
-
Make an Ajax Request
// jQuery: $.ajax({ url: 'https://example.com', method: 'post', dataType: 'json', contentType: 'application/json', data: JSON.stringify(data), success: function( response ){ ... }, error: function( error ){ ... } }); // ES6: fetch( 'https://example.com', { method: 'post', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) .then( response => { ... }) .catch( error => { ... });
Documentation: fetch
Additional Resources
I found a few other similar resources, of course, some of which contained stuff I didn’t include, so I will link to them here; feel free to share your favorites too!
- https://tobiasahlin.com/blog/move-from-jquery-to-vanilla-javascript/
- https://youmightnotneedjquery.com/
Have any jQuery code that you need converted but isn’t addressed above? Feel free to share and I will see what we can come up with!
Happy coding,
Atg
check out https://zeptojs.com/ for convenience
Yeah, I remember Zepto from when it first came out, I want to say like 9kb?
My major concern then was, as I remember, it also used `$`, and that could be confusing to other developers.
But could be a good middle-ground.
Too bad jQuery doesn’t offer a build where you could select what “modules” you need, like most frameworks do…
This seems like a clunky attempt:
https://www.telerik.com/blogs/jquery-using-only-what-you-need
jQuery can be built with different modules, but it looks like you have to download the repo and build your own version yourself. There isn’t a user-friendly build process.
https://github.com/jquery/jquery#modules
Also, Zepto isn’t updated anymore. A possible alternative is Cash. It purposely follows the jQuery $ API, while stripping down some internals.
https://github.com/fabiospampinato/cash
Great recommendations, thanks Stephen!
You can still use $ instead of querySelector. Checkout Paul Irish’s bling js on Github.
Thanks, Andrew, another nice “shim”…
You’re still in development after all these years, Aaron!
A lot of of ES6 purists want to remove jQuery entirely, but as you said once someone starts going beyond a few one-off replacements, they often want to re-organize your ES6 code into something more efficient, the discussion will certainly focus on writing a wrapper library or utility functions for the ES6 code. At that point, you are walking the same path that jQuery’s creator did.
jQuery has been battle-tested and had some of the best minds on it. I would have serious reservations that any one individual or small group could write a wrapper library for ES6 that would be even smaller and better than jQuery.
Ha! Yeah, still slogging away… :-)
Agree with all your points. I think the bigger point needs to be “do we always need jQuery?” But we’re often in such a hurry that we don’t have/make the time to think like that… :-/
My current employer doesn’t really use jQuery. Most of the code is ES6 code. However, the agency that helped write the site a long time ago wrote some custom utility functions, and they mimic jQuery functionality. So, I am thinking to myself, “might as well used jQuery”
I really don’t think jQuery’s gzipped, compressed size is really that large in the scheme of things. I have seen third-party libraries and frameworks (especially ad-related libraries) that are way bigger.
If you don’t need single-page functionality, then jQuery is pretty good.
Agree for the most part about jQuery’s relative size, but also think those relative sizes tend to add up…
I can remember when we thought an image above 20-30kb was inexcusable… :-)
For some fundamental all-in-one library like jQuery, I don’t think I’d lose sleep with it being present. At least, if I ran a company or a team, that would be how I feel. There are other areas that can be optimized.
Images tend to be the worst, since those are supplied by editorial, who have no concept of image compression.
Most of the examples would be identical in ES5.
The only ES6 features I see in this post are const/let, arrow functions, and template strings, all of which can be used with or without jQuery.
Object.entries came in ES8.
Thanks, Vincent.
True points, and maybe more literal than I was being, regarding ES5/6/etc.
The idea of the article is not to use these with, but instead of, jQuery…
See also this resource: https://plainjs.com/
Cool, thanks, Nico!
I have a suggestion for improvement for “Add Delegate Event Listener”
“`js
// jQuery:
elem.on(‘click’, ‘div.className’, function(e){ /* … */ });
// ES6:
elem.addEventListener(‘click’, (e) => {
if (e.target.matches(‘div.className’)) {
/* … */
}
});
“`
https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
Nice, thanks, Andrew!
There is an easier way to do in vanilla js what you do with jQuery:
1) Open a jQuery source
2) Ctrl C
3) Open your vanilla js source
4) Ctrl V.
jQuery is vanilla js, it always was even before ES6!
Thanks, Paolo, you might be missing the point… :-)
The idea of dropping jQuery is to remove unnecessary kbs that your users download. But that reduction can cost developers additional time when building…
Another point of this article was to find and remember ways of replacing commonly-used jQuery functionality.
innerHTML and jquery HTML differ.. jQuery HTMLmethod apart from adding HTML, also executes the scripts inside them.
A great point, Yeswanth!
For some reason, sending an email to Aaron (from Gmail to Gmail) was blocked. Content refused. Still, an addition to the earlier comment about plainjs.com. Copy of email which I tried to send:
Hi Aaron,
FYI: I’ve done the same experiment as you. After publishing the Open Source project https://github.com/hfndb/cookware-headless-ice I wanted other projects in vanilla JavaScript. After reading plainjs.com I’ve developed a client microlib to replace JQuery, which grew as you wrote. See attached file, 11 KB as attached, 5.2 KB transcompiled by Babel and minified by code I wrote, in cookware-headless-ice. I did need to improve or rewrite code @ plainjs.com.
As a rule of thumb I prefer to create all code instead of using libs from others. Takes more attention, effords, energy but usually I need much less code than others. For example a searchable and sortable data grid in a web page can be quite compact. If you know what you are doing, compared to libs I saw.
Just trying to avoid bloated code and making sure I’m not copying mistakes by others.
P.S. Attaching this file isn’t possible in a comment like this. Still…
Nico, no idea what would cause the bounceback, though I have seen something similar like this with Gmail before…
Thanks for the info!
Perhaps more toward removing jQuery plugins, but this also might be useful for replacing JS completely with just HTML/CSS:
https://www.htmhell.dev/adventcalendar/2023/2/