Show HN: Auto-generate vanilla JavaScript alternatives for jQuery methods
github.com
github.com
For example, your `on` method [1]:
- Doesn't support event delegation.
- Doesn't support event namespaces.
- Doesn't support receiving an argument mapping events to callbacks like jQuery also can.
- It seems to have subtle bugs, like the way the events string is split makes so that double consecutive spaces in it (which can happen as a result of a typo) will result in listening to the empty string event. Basically: 'foo bar'.split ( ' ' ) => ['foo', '', 'bar'] (there are two spaces between foo and bar).
The `on` method we are using in Cash [2] is a lot more convoluted than that. On one hand it requires more bytes, but on the other the chances of it behaving exactly like jQuery's are much higher. In fact we can also run jQuery's test suite with Cash to spot issues.
Feel free to ping me if you are interested in joining forces.
[0]: https://github.com/fabiospampinato/cash
[1]: https://github.com/sachinchoolur/replace-jquery#on
[2]: https://github.com/fabiospampinato/cash/blob/master/src/even...
My intention was not to build another JavaScript utility library. I just wanted to make my JavaScript libraries jQuery independent.
I’m kind of assuming the underlying philosophy of the project is to make DOM manipulation as easy as in jQuery but smaller by removing seldom needed safeguards and relying on modern browser selectors. I’m okay with the additional footguns but think it should still preserve easy, concise syntax.
"Would you like to integrate that tool into my library?"
1) https://github.com/fabiospampinato/cash 2) https://github.com/madrobby/zepto
If you want to drop jQuery, you have to rethink the code.
I was expecting a tool that just tells me how to do something in Javascript - without any library - that I would be tempted to use just include jQuery for.
Instead I'm presented with a library.
I expected the comment sections to be talking about how AI/ML crowdsourced code aggregators keep messing up, instead I'm reading about a simple additional convenience library that a contractor wrote for themselves, and described wrong.
The problem is that the reasons are not often understood and then a tool like this comes along and now we can easily replace jQuery with a worse API adapter. Hooray!
One reason is that a lot of jquery’s bulk is compatibility concerns or BC in edges, if you decide that you don’t support old jQuery (mis)behaviour, older APIs, older browsers, … you can probably slim it down a fair bit.
A bunch of utilities you can also probably do without e.g. your jQuery.map, animations stuff, … jQuery is progressively deprecating more and more things, but it’s a harder sell to actually remove them.
You wouldn't want to remove jQuery from an app that is used by IE9.
People end up hearing the reasons and don't stop to think, hey does this actually apply to my code? What are the trade offs for that 30k of code?
Many just end up rewriting what jQuery did in a non reusable way that contributes to code bloat and bad readability.
How it will likely be used is a different matter -- if you're a company trying to hire young devs and don't like to admit you have any legacy tech in the stack, then this is HOT code right now. Same if you need some bullshit targets to hit before the end of the quarter. Get on it!
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...
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.
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!
AFAIK Google Closure Compiler [1] does similar things automagically.
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.
- 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?
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...
But now we live in a world where many common web pages have over 1000kb of resources on them. And nobody blinks an eyelid.
The problem you thought you solved was actually made exponentially worse when those dependencies dependencies also rewrote some jQuery using different native methods
I think what leads to pages with megabytes of JS is when people automatically assume that all projects must be a SPA built on one the popular everything-but-the-kitchen-sink frameworks.
I don't buy the second part of the argument either, you can load a 1000kb image on a blog post and that won't have nearly the same effect as loading 1000kb of JS. The JS needs to be parsed and executed and maybe the site doesn't even work without it, the image can probably be rendered progressively, can be decoded in another thread, nothing is really waiting on it to load, and if it doesn't load at all it's not the end of the world anyway.
With ~4kb you can have Preact, is jQuery Slim (~26kb) giving you ~6.5x times as much value as Preact really? Maybe it is, probably not.
For some context I maintain a ~6kb rewrite of a subset of jQuery (https://github.com/fabiospampinato/cash), which IMO is much better value proposition for many use cases.
The reality is my site (a discussion forum) has a substantially smaller footprint than just about any other similar site, let alone most Wordpress templates.
The size of jQuery is, for me, outstanding value for the efficiencies it gives me. I have not checked out Cash but the main reason I use jQuery is for the ajax scaffolding which most alternatives don't offer.
netflix.com (which doesn't even do anything): 1.6MB
CNN: 3.4MB
NYT: 4.1MB
Guardian: 5.7MB
IGN: 69.5MB
I still use jquery when I'm targeting ancient devices. Some of them still run Presto. Performance is fine, file size is fine - and drastically smaller than any of the sites above!
(I can see why one might not want jquery as a dependency of a dependency though.)
But it's auto-preloaded and playing even when not visible...
At some point I had to support an outside team working on external application running inside ours.
These guys used jQuery in ways it shouldn't be used when performance is an issue. Had to spend a few hours reducing their code to mostly CSS and just a tiny bit of honest to god js that even presto can understand and render.
Point is, it's really easy to overuse and that can have an effect on performance, even on presto
I think if someone queries the DOM a lot on these things, especially in response to keypresses, they're in trouble whether or not they're doing it with jquery.
I've played with some fairly decent machines as well. Quite a bit faster and cheaper but you can't just magically replacement millions of devices every time something new comes along
But maybe they should?
Like, I agree that if your web page loads a full MB of Javascript, eliminating JQuery should be the least of your concerns; it's a drop in the bucket, so optimize elsewhere. But web pages really shouldn't be loading 1 MB of Javascript in the first place.
It's so nice when I come across a website that's actually slim, I can really feel the difference...
There’s also of course the added cost of executing the library on the site but I’m guessing V8 and other JS engines have optimized the hell out of that too as to make it pretty negligible in terms of time difference.
[0] https://developers.google.com/web/updates/2020/10/http-cache...
[1] https://developer.mozilla.org/en-US/docs/Web/Privacy/State_P...
Ideally CDN’s are powerful and ubiquitous enough these days that at least when you have the end user going and asking for JQuery from Cloudflare or whoever the CDN probably has a very fast location right next to them distance wise and that data should get over the wire pretty dang quickly. It’s still another performance hit though since you know at least the first time on the site they have to go and get it and if you are hosting your own stuff it might make more sense to just webpack everything together and hand it off to the CDN instead of having something like JQuery be on its own. Less overhead for opening another network request and all that.
Maybe another advantage of this is that new implementations target runtime version that is new enough to avoid most of the feature-checking and shims? Would be great if README addressed those questions.
Even if somebody made a bundler plugin for doing the heavy work jQuery can only be partially compiled with whole modules excluded, you can't exclude individual methods (e.g. you can't just remove `toggleClass`, you have to remove also `addClass`, `removeClass` etc.).
FYI I maintain a jQuery alternative that supports being partially compiled with individual modules turned off, but it requires manually listing them: https://github.com/fabiospampinato/cash
jquery is/was usually loaded into the global scope. There is no standard way for tree shakers to deduct which parts of the library you are using.
Yes, browser APIs have matured significantly since jquery was new and such a library doesn't add much value anymore.
I agree it would be great if the README gave a bit more background. Whoever is looking to migrate away from jquery will be aware of the background though (and will likely appreciate this tool).
This is akin to all those StackOverflow answers suggesting to use `any` for every TypeScript problem they encounter.
Now that would be a milestone.