Hacker News new | past | comments | ask | show | jobs | submit login
Replacing jQuery (110kb) With UmbrellaJS (8kb) (bennadel.com)
52 points by franciscop 3 months ago | hide | past | favorite | 52 comments

>Yes, "you may not need jQuery"; but, there's a lot of things in life that make life better even if you don't need them. It turns out, high-level, fluent APIs are one of those things.

I'm glad others are finally coming to this conclusion as well. I've been saying this for over a decade: jQuery success was not only cross browser compatibility. There were plenty of competing libraries at the time that offered it. jQuery won because of its powerful, composable and succinct API. Nothing has come close to it still for DOM manipulation.

And also: direct DOM manipulation is not evil, it just needs discipline, but so does programing in general.

I find it funny that I shared this article because I was proud that someone talked about my little library Umbrella JS, and so far 5 out of the 5 top-level comments are people recommending their/other libraries or ways of avoiding jQuery (and not 2 agree with the best way of avoiding jQuery!).

Take a look at most "show hn" posts, exactly the same issue. Looks like there is a group of people here that feel the need to self promote at any possible opportunity, from look at me I wrote this to look at me I also know about this. Quite sad.

While I agree that this happens often (I did it very early on!), just posting "try instead X" is fairly pointless and possibly against HN guidelines:

> Please don't post shallow dismissals, especially of other people's work. A good critical comment teaches us something.

I prefer `querySelector` and bare `<script type="module">` tags for quick and dirty jQuery replacement.

If you haven't keeping up with all the new script-tag parameters, I suggest looking at this: https://gist.github.com/jakub-g/385ee6b41085303a53ad92c7c8af...

Choosing type=module is usually better choice than classic script tags like in the article. Module tags fetches (if any modules is used) asynchronously, and executes after the parser, however it won't work with older browsers.

That's what I did! I saw a lot of people recommending doing basically:

    const $ = sel => [...document.querySelectorAll(sel)];
But then you get to it and are adding a lot of classes, so you might add a couple of helper methods:

    const addClass = (col, cls) => col.forEach(el => el.classList.add(cls));
    const removeClass = (col, cls) => col.forEach(el => el.classList.remove(cls));

    addClass($('ul li'), 'list-item');
Oh and you might need to inject content before/after, that's a bit trickier but thanks to the classic You Might Not Need jQuery[1] we know how to do it:

    const insertBefore = (col, html) => col.forEach(el => el.insertAdjacentElement('beforebegin', html));
    const insertAfter = (col, html) => col.forEach(el => el.insertAdjacentElement('afterend', html));

    insertAfter($('ul li:last-child'), 'New todo item');
Keep going a bit like that, until you realize you are basically reinventing jQuery but pretty badly, undocumented, and untested.

Add a couple of very nice-to-haves, like chaining (instead of nesting in these examples above) and prototypes and that's what Umbrella JS is, very thin methods to manipulate the DOM and handle events. In fact, compare our "addClass" implementation in this comment to Umbrella's addClass[2], it's almost the same size but hundred times more flexible:

    // Add class(es) to the matched nodes
    u.prototype.addClass = function () {
      return this.eacharg(arguments, function (el, name) {

[1] https://youmightnotneedjquery.com/

[2] https://github.com/franciscop/umbrella/blob/master/src/plugi...

Behold my jQuery replacement:

    export function each(qs, cb) {
      if (typeof qs === "string") {
        qs = document.querySelectorAll(qs);
      if (!qs) {
      if (qs.length === undefined) {
        qs = [qs];
      for (var i = 0; i < qs.length; i++) {
        cb(qs[i], i);

    export function on(ev, qs, cb) {
      let cancelFns = [];

      each(qs, (el) => {
        el.addEventListener(ev, cb);
        cancelFns.push(() => {
          el.removeEventListener(ev, cb);
      return () => {
        cancelFns.forEach((fn) => {

That's much worse than jQuery's implementation, there's no event delegation or namespacing



The author mentions that <script type="module"> enforces CORS restrictions which would break with their current server settings and CDN, otherwise I think they agree with you.

This aspect of JS makes no sense to me. Why add a CORS restriction to modules mode? It doesn't plausibly address any security hole, since nomodules mode is still around. It's just a big pain in the ass for no particular benefit, AFAICT.

Hmm, I think they should fix the CDN? I think if they just refer their script e.g. from https://www.jsdelivr.com/ it should just work? I have used bunch of modules from CDN.

The issue would lie with the site's CORS policy, no change to the CDN can work around that. And it's possible the author is not permitted to or does not wish to alter the CORS policy.

Here you'll want to use the "module/nomodule" trick. (Forgive the formatting / mangled HTML)

script type="module" src="module.js"

script nomodule src="nomodule.js"

Most people should not use nomodule. If you have code, but you never test it, it is almost certainly broken. If you actually do test your code in IE11, okay great, use nomodule, but what I mostly see are pages that don't actually work in IE11 because the polyfils don't quite add up, but the devs have no idea because it isn't QA tested. It's just a waste of time for everyone.

>Choosing type=module is usually better choice than classic script tags like in the article. Module tags fetches (if any modules is used) asynchronously, and executes after the parser, however it won't work with older browsers.

Also it provide some kind of JS hijack. All the code with in the module can be tainted by a random .js included elsewhere in the page. However code in module can still access the window object to have access to global and the dom.

So my project for this week has been removing jQuery from my app. I have to say it's been surprisingly easy. The only thing that's been annoying is that jQuery ignores the difference between querySelector and querySelectorAll, and just treats any operation done on a jQuery selection as if it were mapping a function to each item in a list. Which makes it super easy to write, but makes it much more difficult to know what's actually happening, and mildly annoying to then re-write.

That said, in addition to forcing me to restore a lot of fun animations that I had previously broken while porting the site from AngularJS to Angular, it also just ended up making the code a lot better. A lot of the times when we were doing something with jQuery, the same thing could have been done more cleanly using [ngClass] or whatever. So taking the three or four days to remove jQuery actually made the business logic of the site much easier to understand. Performance benefits aside, I'd definitely recommend it for this reason alone.

The way jQuery queries single or multiple elements was my favourite feature. Find xyz class and do something with it, I don’t care if there’s one instance or one thousand.

Yeah, the problem is just that later it makes it harder to go back and find code that's no longer working properly, dead code, and unused CSS. The fact that vanilla JS will throw an error when you try to perform an element operation on null is actually quite useful.

> The only thing that's been annoying is that jQuery ignores the difference between querySelector and querySelectorAll, and just treats any operation done on a jQuery selection as if it were mapping a function to each item in a list. Which makes it super easy to write, but makes it much more difficult to know what's actually happening

Does it though? Even years after moving on from jQuery it’s always very clear when reading old code that all selectors will be matched. There’s less cognitive load knowing you’re querying all matching items by default than mentally parsing the difference between querySelector and querySelectorAll, and also having to spread the result because NodeList doesn’t support array functions.

Cash is also a great jQuery replacement. It's slightly larger than Umbrella, but does more. It also supports partial builds to shrink it down further.


https://alpinejs.dev/ for me to replace jquery, it uses vue syntax and _much_ simpler to use, with a strong community.

I think zepto.js is a better choice. It's slightly bigger than cash but much closer to jquery (for example it has $.ajax)


Cash's maintainer here. I don't think this is true actually.

Zepto supports some methods that Cash doesn't, but you probably shouldn't use them to begin with, like $.ajax, $.isArray, $.fn.animate etc. In 2022 either better built-in solutions exist or better specialized tiny libraries exist.

Everything that is supported by both Zepto and Cash should either work identically or Cash's implementation should be closer to jQuery's. Just to mention one thing in this regard you can run jQuery's test suite with Cash, and Cash's test suite with jQuery, easily [0]. I've done so and looked at every single failed test manually a few times, I doubt nearly the same level of attention went into Zepto. Just to mention one difference: Cash supports jQuery-style event namespacing, Zepto just doesn't support this, it is implementing something that looks similar but that works differently in pretty important aspects, that's worse than not supporting obsolete methods IMO, but I guess it depends on your use case for a library like this.

[0]: https://github.com/fabiospampinato/cash/blob/272132a6dc1d885...

Umbrella JS maintainer here (the library this HN thread is supposed to be about). At 2.8kb gzipped UmbrellaJS doesn't support partial builds because that'd be a bit funny.

Also, and it seems to be something the author of the article likes a lot, Umbrella JS is closer to Javascript than the libraries you mention (& jQuery). Sure that means you need a bit of editing your code (which is made easy by being able to load both jQuery and Umbrella at the same time), but in the end you have a lot better result. Plugins are just methods extending the prototype, loop methods have similar arguments as Array's methods, which in turn makes it easier than ever to use arrow functions, etc.

But IMHO the killer feature of Umbrella JS is that it's quite well documented, every method has at least an example but usually few, where e.g. cash doesn't have any example interleaved.

Basically this would be my recommendation/conclusion:

- If you have thousands of lines of Javascript/jQuery, I'd recommend Cash/Zepto instead.

- If you just use jQuery for few methods/interaction, 100% go with Umbrella JS.

I didn't mean to hijack the thread, I was just replying to somebody saying something about Cash :)

I'm ambivalent regarding your documentation point, on one end jQuery is way more popular and one can largely just read jQuery's documentation for the methods that Cash implements too. On the other end that doesn't quite work when Cash's implementation works a bit differently than jQuery's, and just having a consistent, good, documentation is better in that case, and probably in general too.

I don't personally care much which libraries people use for their needs, personally I'm trying to incrementally move away to Cash and other jQuery-like direct DOM manipulation libraries entirely, but it takes time.

> the library this HN thread is supposed to be about

Articles can absolutely just be seeds for discussion, and I think the theme of replacing jQuery is quite relevant across the industry as motivators like "support IE" are increasingly falling away.

I generally replace jQuery with something like:

    $ = (s, p = document) => p.querySelector(s)
    $$ = (s, p = document) => p.querySelectorAll(s)
If I’m feeling arrayish I’ll wrap the latter in [...qSA] so I can use .map and the rest of them, otherwise for/of works just as well.

Anyone using $.animate() and $.ajax() this year should probably stop.

For some basic use cases that's probably good enough, but saying that that can replace jQuery is like saying that farts can replace rocket engines.

For example I can do the following with jQuery:

    $(document).on ( 'click.ns', '.button', callback );
How should one do the same with vanilla JS? You'd end up rewriting a (probably buggy) reimplementation of jQuery's $.fn.on method in the end.

With the code here https://news.ycombinator.com/item?id=29979680 it would be on("click", ".button", callback). It doesn't take much to replace most uses of jQuery.

Except that's broken beyond belief. Neither event delegation nor event namespacing are implemented in that snippet.

Weird definition of broken: not having features nobody asked for.

Hello, I explicitly asked for it:

> How should one do the same with vanilla JS?

And you said:

> With the code here

I used jQuery back in the day and I never used namespaces, so I had no idea what ns.click meant. I don't really see why it's a desirable feature now. It just seems like a weird artifact of the fact that jQuery predates CustomEvents. I mean, I guess if there were no CustomEvents, it would be useful, but they exist now, so just use them.

For the record the code you linked to doesn't implement event delegation either, which is something pretty useful that even more modern libraries like React use under the hood.

Here's [0] an article about the usefulness of namespaces, it has use cases beyond custom events, which have been implemented a real long time ago.

[0]: https://css-tricks.com/namespaced-events-jquery/

It doesn't support event namespacing.

I haven’t used namespacing in years. The point people are trying to make is that if you do need “advanced jQuery features” maybe you shouldn’t use jQuery. And the basics are also covered by native API, so the same conclusion applies.

I would not start a new project with jQuery nowadays just for that stuff.

The fact that $(s).remove() never throws for example wouldn’t be ok for me anymore, so I’m looking at more cons than pros.

Almost all of these articles always have several caveats in place about features that aren't supported or libraries that are added to include missing features.

So its not really 'replacing' JQuery is it?

(no knock meant to @franciscop who didn't write the article or the title)

So I understand the desire to write your own libraries and use them. But isn't the author just replacing one third-party library with another? How is that really any better?

Aside from the size which really seems to be a bizarre thing to be commenting on. I've seen png files used for buttons that were larger than a compressed JQuery library

The post states that going from "34kb compressed" to "about 3kb compressed" is a big change:

> That's a massive savings on size. Of course, there are some sacrifices taken in that reduction.

My experience is that, even with a slow connection and a slow smartphone, removing 30kB of JS would have very little impact. The exact gain is smaller: the size difference of the gzipped-6 stable releases is 27kB.

Although this shouldn't detract from the author's specific situation and choice, I think a big share of those using jQuery are those who are also using a development framework that is dependent upon it ==> e.g., Twitter Bootstrap (ver 5 and below). That could be an interesting project -- a drop in replacement for TBS' jQuery dependency.

I replaced jQuery with vanilla JavaScript a couple of years ago and never looked back. Between the abundance of features in ES6 now and the death of IE, I wouldn't recommend any junior developers utilize libraries like these.

Regardless if jQuery or Umbrella is used - you'd most likely get a better Lighthouse score if you simply inlined the JS into your HTML file vs having the JS as another downloadable asset (especially on mobile).

Is that a fair test? Most people have jquery in cache already, or at least that's the idea.

In Elixir world jQuery was completely displaced by so called PETAL stack:

Phoenix Elixir Tailwind Alpine.js LiveView

In fact, LiveView alone probably replaced like 90% of what jQuery was ever needed for. The remaining 10% is done with Alpine.

Yea, but which one to use and still have a nice auto-complete like the jQuery-UI offers? Lots of these jQuery replacement don't fully compatible, so that nice auto-complete gets lost

femto.js does all that in 0.9kb gzipped. https://github.com/vladocar/femtoJS

Nostalgic seeing the DHTML tag. I remember adding that to my resume back when it was a hot new thing.

I wonder if w3c or whatwg guys considered extending dom spec to support the query / navigation / css helpers.

They have to an extent. `querySelector` / `querySelectorAll` is the clearest example, but many API's presented here came after jQuery and were inspired by it: https://youmightnotneedjquery.com/

Yeah, most of the missing features that jQuery used to provide are no longer missing. But the vanilla JS alternatives are so verbose, they look like they were designed by a committee of German philosophers.

Luckily, we now have frameworks that help us avoid most of the DOM manipulation stuff.

What year is it?!

kb != kB.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact