Replacing jQuery with Vanilla JS / ES6

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

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' );

    Documentation: classList, add

  • Remove a CSS class

    // jQuery:
    elem.removeClass( 'className' );
    // ES6:
    elem.classList.remove( 'className' );

    Documentation: classList, remove

  • 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!

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

24 Responses to Replacing jQuery with Vanilla JS / ES6

  1. Daniel Mendelsohn says:

    check out https://zeptojs.com/ for convenience

  2. Andrew says:

    You can still use $ instead of querySelector. Checkout Paul Irish’s bling js on Github.

  3. Stephen Tang says:

    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… :-/

      • Stephen says:

        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… :-)

          • Stephen says:

            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.

  4. Vincent says:

    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.

  5. Nico says:

    See also this resource: https://plainjs.com/

  6. Andrew Luca says:

    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

  7. Paolo Andrenacci says:

    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.

  8. Yeswanth says:

    innerHTML and jquery HTML differ.. jQuery HTMLmethod apart from adding HTML, also executes the scripts inside them.

  9. Nico says:

    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…

  10. 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/

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.