Hacker News new | ask | show | jobs
by franciscop 1748 days ago
I made a tiny jQuery alternative a while back called Umbrella JS: https://umbrellajs.com/

Seeing methods like addClass in "replace-jquery", I'm not fully satisfied. I could make Umbrella JS tiny (1/2 of the alternative listed elsewhere in the thread, Cash, and 10% the size of jQuery) because of heavy method reusal. For instance, in Umbrella JS addClass is just:

    u.prototype.addClass = function () {
      return this.eacharg(arguments, function (el, name) {
        el.classList.add(name);
      });
    };
In "replace-jquery" you are already depending on `this.`, so why not making a couple of useful utils? Right now it is more verbose, and doesn't accept e.g. an array of classes or classes as arguments:

    addClass(classNames = '') {
      this.each((el) => {
        classNames.split(' ').forEach((className) => {
          el.classList.add(className);
        });
      });
      return this;
    }
Cash JS's addClass (which is hidden behind toggleClass(cls, true)) is nice, it's bigger BUT that's because it's 3 implementations at once (addClass, removeClass, toggleClass). It properly uses a method to getSplitValues, which is very helpful and flexible:

    fn.toggleClass = function ( this: Cash, cls: string, force?: boolean ) {
      const classes = getSplitValues ( cls ),
            isForce = !isUndefined ( force );
      return this.each ( ( i, ele ) => {
        if ( !isElement ( ele ) ) return;
        each ( classes, ( i, c ) => {
          if ( isForce ) {
            force ? ele.classList.add ( c ) : ele.classList.remove ( c );
          } else {
            ele.classList.toggle ( c );
          }
        });
      });
    };
And I will spare you all jQuery's implementation, which is huge, but it can be seen here:

https://github.com/jquery/jquery/blob/main/src/attributes/cl...

2 comments

Does `cash` support jQuery-style `.ajax`?

When you consider replacing `$.ajax` with fetch, you'll quickly found out that fetch is severely lacking with regards to:

* handling cookies,

* HTTP status codes (404, 403, etc),

* CORS,

* and even just simply readability when dealing with JSON.

jQuery's ajax (and its aliases like $.GET) handle all of these (edge cases? are these really edge cases?!) with aplomb, so you don't have to worry about it.

This is the issue with all of the jQuery alternatives, even cash (which does look pretty awesome); you start using them, and then development hits a halt because you suddenly realize that you actually need something that jQuery already does quite nicely, and has done so, quietly and politely, for more than a decade.

I'm genuinely curious to know what you find severely lacking in fetch compared to $.ajax.

- handling cookies

    fetch('https://example.com', { credentials: 'include' })
- HTTP status codes

    fetch('https://example.com').then(response => {
        if (response.ok) {
            // Response is 200-299
        }
        if (response.status === 404) {
            // Status code specific handling
        }
    });
- CORS

    fetch('https://example.com', { mode: 'no-cors' });
- JSON handling

    fetch('https://example.com').then(response => response.json()).then(json => {
        // Do something with a parsed JSON object
    });
I've used all of the above patterns regularly for several years now and never found any of them particularly cumbersome and certainly not lacking feature wise. If async/await syntax is available to you, it's even more succinct than the Promise style above.

With that said, I've not used $.ajax in anger for a good long while so I may be missing out, particularly as I note there have been API changes in newer versions of jQuery. Are there some specific use cases that you've found fetch to be particularly inept in dealing with?

These are great snippets! Although, your last snipped definitely shows the readability problem (even though you do a good job with it!)

Because each intermediate step is async as well, fetch can definitely turn into "callback hell" (really, promise hell) that the old callback style of JS used to be well-known for (and was resolved in large part by jQ-style chaining in the form of promises).

You might have a look at this link, especially the "Differences with jQuery":

https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

"The Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure or if anything prevented the request from completing.

"fetch() won’t send cross-origin cookies unless you set the credentials init option (to include)."

With regards to CORS, you have very limited options as well: no-cors, cors, same-origin. I've definitely had issues with CORS requests that didn't apply to XHR (and jQuery's AJAX by extension.)

"Note that mode: "no-cors" only allows a limited set of headers in the request:

"Accept Accept-Language Content-Language Content-Type with a value of application/x-www-form-urlencoded, multipart/form-data, or text/plain"

For example:

"Note: Access-Control-Allow-Origin is prohibited from using a wildcard for requests with credentials: 'include'. In such cases, the exact origin must be provided; even if you are using a CORS unblocker extension, the requests will still fail."

The latter quotes from https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/U...

Cash's maintainer here. I think needing $.ajax is a fairly niche thing (like ~nobody using vue/react/svelte is asking for a jQuery-like ajax function, the world moved to fetch), and it's not something that you'd suddenly stumble upon either.
Umbrella JS' creator here, I fully agree with that statement. You'd normally use something like Axios/Got/etc for a reusable API interface for API-heavy interfaces, and for simple cases it's not too complex to add e.g. `credentials: 'include'` for cookies.
Cash's maintainer here. I really don't think anybody can squeeze 50% out of the 6kb min+gzip code that make up Cash, quite a bit of thought went into squeezing as many bytes as possible out of it. (while still preserving a large degree of compatibility with jQuery, support for many methods and support for partial compilation)

Your comparison isn't quite fair, our toggleClass function does a lot more than a simple addClass, and writing it this way lowers in fact the total min+gzip size.

I'd be pretty impressed if you can shave just 1kb out of those 6kb to be honest.

Agreed! Not a fair comparison either way; if I compare Cash' addClass then it doesn't really "do anything" besides call the other method, so I'd say the toggleClass is representative of _the 3 methods together_, so 1/3rd? It's nice code :)

Shaving off 1kb out of 6kb of optimized GZIP code is probably impossible unless you missed something important.

I also went deep into optimizing Umbrella JS, it does a lot less than Cash so hence it's a lot smaller as well; but I went so far as comparing the GZIP output of calling `for ()` vs `forEach`, etc:

https://medium.com/@fpresencia/understanding-gzip-size-836c7...

That's why I am not concerned of repeating e.g. `function() {}` (instead of the, back then, experimental () => {}) many times, since with gzip in my experience that gets all totally abstracted out.

Edit: edited the original, explaining that Cash it's doing it nicely!

> but I went so far as comparing the GZIP output of calling `for ()` vs `forEach`, etc

AFAIK Google Closure Compiler [1] does similar things automagically.

[1] https://github.com/google/closure-compiler