[HN Gopher] Cash: A small jQuery alternative for modern browsers
___________________________________________________________________
Cash: A small jQuery alternative for modern browsers
Author : thunderbong
Score : 112 points
Date : 2024-11-02 12:42 UTC (10 hours ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| BiteCode_dev wrote:
| Note that it's really dom centric and doesn't include ajax.
| ceejayoz wrote:
| Isn't AJAX fairly well supported via fetch now?
| jitl wrote:
| Yeah at this point I've totally forgotten $.ajax API but
| fetch is pretty easy, just a single function call
| amelius wrote:
| Now we only need something that makes websockets more
| resilient against network errors and corporate firewalls.
| BiteCode_dev wrote:
| In the same way selectors and map replace jquery. It depends
| how much sugar you want.
| _hyn3 wrote:
| ... unless you want to send a body with your HTTP GET. There
| is tons of utility value in this! For example, let's say you
| want to GET some data but also provide some client request
| statistics along with the request -- happens all the time in
| the real world.
|
| Fetch will reject your GET if it contains a body (a
| deliberate maintainer decision), even though it's entirely
| permissible by HTTP and done by many real-world AJAX APIs.
| Real AJAX will do what it's supposed to. (The HTTP 1.1 2014
| Spec says that including a request body in a GET "might cause
| some implementations to reject the request." Guess which
| one!)
|
| Also, advanced features like progress are completely absent
| from Fetch as well.
|
| However, there are some fantastic libraries like Axios[1],
| SuperAgent (requires npm), and, yes, jQuery[2], that have
| really excellent API's (far superior to Fetch), or you could
| just write your own (or use an LLM) short wrapper around
| modern AJAX and call it a day. h/t to Claude:
| const xhr =
| ['GET','POST','PUT','PATCH','DELETE'].reduce((x,m) =>
| (x[m.toLowerCase()] = (u,d,opt={}) => new
| Promise((r,j) => { const q = new
| XMLHttpRequest(); q.open(m,u);
| q.responseType = opt.responseType || '';
| if(opt.headers) Object.entries(opt.headers).forEach(([k,v])
| => q.setRequestHeader(k,v)); if(opt.signal)
| opt.signal.addEventListener('abort', () => q.abort());
| q.withCredentials = opt.credentials === 'include';
| q.onload = () => r({ ok: q.status >= 200 &&
| q.status < 300, status: q.status,
| headers: new Headers(q.getAllResponseHeaders()),
| text: () => Promise.resolve(q.responseText),
| json: () => Promise.resolve(JSON.parse(q.responseText)),
| blob: () => Promise.resolve(new Blob([q.response])),
| response: q }); q.onerror = () =>
| j(new TypeError('Network request failed'));
| q.send(d instanceof FormData ? d : JSON.stringify(d));
| }), x), {});
|
| This gives you _xhr_ methods with a fetch-style API and you
| can still do all the things that fetch can 't (but this won't
| do real streaming or cache control like Fetch, but it'll do
| 95% of all common use cases in a tiny bit of code.)
|
| Each method listed above returns a Promise that resolves with
| the XMLHttpRequest object or rejects with the error. So you
| get both the Promise functionality and full access to the XHR
| object in the resolution.
|
| Usage: xhr.post('/api', { data: 123 }, {
| headers: { 'Content-Type': 'application/json' },
| credentials: 'include', signal:
| abortController.signal }) .then(res =>
| res.json()) .then(data => console.log(data));
|
| For more advanced AJAX stuff, check out the very powerful and
| flexible Axios library[1].
|
| And, if you don't need AJAX but do want some of the features
| from jQuery (like some of the more unusual _selectors_ ) that
| aren't in Cash (to save bytes!), AJAX (and special effects)
| is excluded from jQuery Slim which brings the code down to
| only 69KB[3].
|
| 1. Axios https://github.com/axios/axios (41kb)
|
| 2. jQuery AJAX https://api.jquery.com/jQuery.ajax/ (87kb but
| includes ALL of jquery!)
|
| 3. https://code.jquery.com/jquery-3.7.1.slim.min.js
| erik_seaberg wrote:
| Caching is the most important reason to consider GET for a
| non-hypertext API. Vary headers tell the server which
| header diffs should cause cache misses, but there's no way
| to do that for an encoded body.
| yieldcrv wrote:
| ah that's what people were looking for
|
| a jquery alternative
|
| actually the native typescript is interesting
| xg15 wrote:
| window.$ = document.querySelectorAll
| pwdisswordfishz wrote:
| Uncaught TypeError: 'querySelectorAll' called on an object that
| does not implement interface Document.
| xg15 wrote:
| Damn, you're right, sorry.
|
| window.$ = (x => document.querySelectorAll(x))
| Matheus28 wrote:
| I find this a little cleaner: window.$ =
| document.querySelectorAll.bind(document);
|
| Since it works properly for any function no matter the
| number of arguments it receives
| simonw wrote:
| I like wrapping it in an Array.from() so you can use
| .map/.filter/etc.
| dsego wrote:
| But it doesn't do chaining and you have to loop through
| elements to do anything with them.
| Spivak wrote:
| I'm always surprised that an API that is defined by matching
| 0-n dom elements doesn't return a container that by default
| maps over them list monad style.
| freeone3000 wrote:
| There's a fairly small polyfill that makes a DOMNodeList
| have the same functions as Array.
| jasonjayr wrote:
| Are the various browser JS implementations clever enough
| not to make a new Object for Array.from(DOMNodeList) ?
| roebk wrote:
| window.$ = document.querySelectorAll.bind(document)
| ComputerGuru wrote:
| The wrong syntax notwithstanding, this doesn't let you
| recursively use querySelector(All), e.g. to find children of a
| node like
| document.querySelector("#foo").querySelectorAll(".bar")
| xg15 wrote:
| I know, it was a bit of a joke.
|
| But I think the OP's jQuery replacement is also dropping
| features in the service of a small footprint. So this was my
| 80/20 contribution to the "smallest jQuery replacement"
| problem ;)
| wackget wrote:
| But why? With mainstream websites pumping out literal megabytes
| of JavaScript, why spend time rewriting an entire library (with
| less features) to save 50KB?
| simonw wrote:
| Some of us still try to ship websites that use less than 50KB
| of JavaScript total.
| happytoexplain wrote:
| Maybe if we embraced small dependencies rather than saying "why
| bother?", then dependencies would become smaller?
| szundi wrote:
| This
| w4 wrote:
| Not relevant to this package in particular, but this line of
| reasoning baffles me every time I see HN comments about JQuery.
| So many posters argue against the use of JQuery because of its
| package size and bandwidth constraints, while simultaneously
| advocating for SPA frameworks that use orders of magnitude more
| bandwidth. Absolutely ridiculous cargo cult reasoning.
| happytoexplain wrote:
| A. You're assuming they are largely the same people by
| extrapolating from your observations. It's impossible to
| actually know.
|
| B. Your two examples provide different things. This is like
| saying it's OK to include any old multi-megabyte dependency
| if a site loads a couple mb worth of images. There's no
| reason to stop considering the size of the small parts just
| because you decided you need some large parts. Things add up
| - that will never stop being a useful thing to remember, in
| _any_ context.
| nashashmi wrote:
| Two different types of people. One wants to create
| lightweight applications. The other wants lightweight
| development.
|
| Lightweight development for lightweight applications is a bit
| of an oxymoron at this time.
| hecanjog wrote:
| IMHO the way to achieve this is to pay the upfront cost of
| building out a small framework for your application, which
| has lightweight abstractions for common patterns. With some
| design, a small internal API can be as nice to work with as
| the kitchen sink abstractions. (Much nicer, too, when it
| comes to maintenance and debugging.)
| KronisLV wrote:
| > IMHO the way to achieve this is to pay the upfront cost
| of building out a small framework for your application
|
| And then 5 years down the line it has grown into a worse
| version of the popular alternatives, the original
| developers are gone and the ones who currently maintain
| the mess have to pay the price. In corporate or
| professional contexts, you probably just should pick
| whatever is popular.
|
| Though that anecdote about risk management should also
| have this link alongside it:
| https://www.robinsloan.com/notes/home-cooked-app/
|
| When you're working on something others won't have to
| maintain years down the line, thankfully your hands
| aren't tied then and you can have a bit more fun.
|
| For everything else? Svelte, HTMX, jQuery, Vue, React,
| Angular or whatever else makes sense.
|
| That said, sometimes I wonder what a world would look
| like, where the _browser_ would have the most popular
| options pre-packaged in a way where you wouldn't need to
| download hundreds of KB in each site you visit, but you'd
| get the packages with browser updates. It'd probably save
| petabytes of data.
|
| Except seems like we went in the opposite direction, with
| even CDNs being less efficient in some ways:
| https://httptoolkit.com/blog/public-cdn-risks/
| not_a_bot_4sho wrote:
| > Lightweight development for lightweight applications is a
| bit of an oxymoron at this time.
|
| Apt description
| leptons wrote:
| We're using jQuery on our sites which score 100% on all
| Google Lighthouse pagespeed tests. A smaller version of
| jQuery really wouldn't matter to us, our pages are already
| extremely fast to load and score amazingly well on any page
| speed/SEO test.
|
| About the only place I could see a benefit from this library
| is maybe in embedded, where space really is an issue. I've
| created a few IoT devices with web interfaces that are built-
| into the tiny ROM of the device. A 6KB library is nice, but
| I'm using Preact with everything gzipped in one single .html
| file and my very complex web app hosted in the IoT device is
| about 50KB total size gzipped - including code, content, SVG
| images and everything, so jQuery or a JQ substitute isn't
| going to be a better solution for me, but maybe it fits for
| someone that doesn't know how to set up the tooling for a
| react/preact app.
| oliwarner wrote:
| Mainstream websites are advertising-delivery trash. Don't use
| them as a benchmark for what we should be doing.
| karaterobot wrote:
| This argument confuses me. It seems equivalent to saying "with
| mainstream fast food restaurants selling meals with 1600
| calories, why are you making yourself a green salad for
| lunch?", or saying "with the national debt approaching $35
| trillion dollars, why are you shopping around for the best rate
| on a mortgage?". One answer for all three cases is: I'm not the
| thing that's big, I'm a different thing that's smaller. Another
| answer is: if being too large is the problem, then being
| smaller sounds like a solution.
|
| But I guess you're really asking why the _developer_ would
| spend time on rewriting a library. Is that really surprising?
| Most of programming is rewriting something that 's been made
| before, either because you have to for your job, or because you
| need it to do something _slightly_ different, or have different
| performance characteristics, or just want to learn how it 's
| done.
| EasyMark wrote:
| Embedded system? Or "I don't need all that stuff for my comic
| book collection manager" or "minimalism has it's own rewards"?
| knowitnone wrote:
| oh, ok. Let make things larger then.
| NotAnOtter wrote:
| Why rewrite an entire code base away from JQuery.. and not to
| native implementations?
|
| The era of jQuery and it's clones are over. People need to move
| on. If you're ever at the architecture level of your code base
| and think "What package should I use for DOM manipulation?",
| you're doing something wrong.
| moffkalast wrote:
| Finally a name that is perfectly fitting and describes the
| library surprisingly well.
| elaus wrote:
| Assuming you mean that ironically. Unfortunately, the README
| doesn't reveal where the name comes from, but it is truly
| absurdly misleading, as if it came from a random generator...
| luckylion wrote:
| I assumed it comes from jQuery defaulting to $ as an alias
| for the jQuery function.
| moffkalast wrote:
| Not sarcastic at all actually, I take you you've missed the
| absolute horde of dollar signs it uses in its syntax?
|
| Reminds me of this old joke: "Why do greedy developers all
| learn PHP? Because there's a lot of dollars in that."
| EasyMark wrote:
| For some reason I would have preferred they called it "Cash
| Money"
| mg wrote:
| Browsers have become so nice to work with, that these days, I get
| away with just the following two lines of code to simplify DOM
| manipulation: dqs =
| document.querySelector.bind(document); dqsA =
| document.querySelectorAll.bind(document);
|
| So instead of country =
| document.querySelector('#country'); cities =
| document.querySelectorAll('.city');
|
| I can write country = dqs('#country');
| cities = dqsA('.city');
|
| For everything else, I am fine with just using the native browser
| functions.
|
| I usually import the two functions from a module like this:
|
| import { dqs, dqsA } from '/lib/js/dqs.js';
|
| This is the module:
|
| https://github.com/no-gravity/dqs.js
| nashashmi wrote:
| I really wish it was a native script to use qs and qsa, rather
| than something I have to add.
|
| FYI: I know you meant to give an example, but element tags with
| ID are DOM variables as well.
| wmanley wrote:
| > Browsers have become so nice to work with, that these days, I
| get away with just the following two lines of code to simplify
| DOM manipulation: > > dqs =
| document.querySelector.bind(document); > dqsA =
| document.querySelectorAll.bind(document);
|
| Sounds useful and reasonable.
|
| > I usually import the two functions from a module like this: >
| > import { dqs, dqsA } from '/lib/js/dqs.js';
|
| Utterly absurd. Just copy and paste. It's only two simple
| lines, how could it be worth a dependency?
| brightball wrote:
| Included in multiple places?
| 0xCMP wrote:
| modern browsers support the import syntax natively, so it
| really shouldn't be a lot of overhead to import it.
| Cyphase wrote:
| It looks like it's intended to be copied and pasted into your
| codebase, not be an external dependency.
| 0x457 wrote:
| I guess it's meant to be processed by some bundler later.
| kfajdsl wrote:
| /lib/js/dq.js is part of their codebase.
| kccqzy wrote:
| I recently attempted to remove React as a dependency just to
| see what would happen. It turns out different browsers are
| still incredibly inconsistent when it comes to event handling.
| For example the select event on an <input> element somehow
| doesn't fire at all on Safari during my test, and doesn't fire
| when the caret is merely moved on some browsers. Using just the
| native browser functions isn't just fine, even if you don't
| need all the React features like components or state or props.
| It turns out React DOM is valuable as it papers over browser
| differences.
| divbzero wrote:
| I haven't tested myself, but according to MDN the select
| event on <input> elements should be supported by Safari?
|
| https://developer.mozilla.org/en-
| US/docs/Web/API/HTMLInputEl...
| kccqzy wrote:
| The MDN page includes a nice selection logger example. It
| just doesn't work on my Safari (iOS).
| openrisk wrote:
| Fine as an exercise but for a range of use cases what you really
| want is the smallest alternative to the bloated reactive js
| frameworks and alpine.js seems to be occupying that sweet spot.
| samdixon wrote:
| This seems pretty different from the functionality alpine
| provides, no?
| chmod775 wrote:
| Here's a stretch goal: use typescript template string magic to
| correctly infer the type of elements. For instance you can
| statically infer that $('div#name') will be a HTMLDivElement.
| hinkley wrote:
| Elixir and a few other languages have the pattern matching and
| type system that could pull that off but not a lot of languages
| do. Can you do that in typescript? I don't see how.
| dimava wrote:
| You can, using `function $<S>(sel: S | `${S}${ '
| '|'#'|'.'|'[' }${string}`): HTMLElementMap[T];` or
| export type inferSelectorElementName<sel extends selector> =
| | string extends sel ? HTMLElement : sel extends
| `${infer A},${infer B}` ? inferSelectorElementName<A | B>
| : sel extends `${infer A}${'.' | '[' | '#'}${infer _}` ?
| inferSelectorElementName<A> : sel
| export type inferElementFromSelector<sel extends selector> =
| | string extends sel ? HTMLElement :
| inferSelectorElementName<sel> extends infer S ? S
| extends '' ? HTMLElement : S extends keyof
| HTMLElementTagNameMap ? HTMLElementTagNameMap[S]
| : never : never
|
| TS types may go quite deep Check Arktype library
| [https://arktype.io/], it's type definitions are basically a
| Typescript written in JSON const user =
| type({ name: "string", platform:
| "'android' | 'ios'", "version?": "number |
| string" })
| totallykvothe wrote:
| You definitely can do that in TypeScript. The kinds of things
| you can do with generic inference and string literals are
| crazy
| dr_kretyn wrote:
| What does the "modern websites" mean? It honestly sounds like
| "this only works in the latest chrome, and only on the latest
| windows and macos".
| fabiospampinato wrote:
| "modern websites" means IE11+ for cash, it's a fairly old
| library.
| hinkley wrote:
| The way I see it once you've thinned the polyfills to next to
| nothing, the enduring feature of jQuery is the automatic list
| comprehensions. The ability to unselect all of the buttons in a
| form in a single call is still hard to match elsewhere. That and
| parent queries.
|
| The main problem I have with the implementation is that it
| chooses to fail silently when the list is empty. I've fixed too
| many bugs of this sort, often caused by someone refactoring a DOM
| tree to do some fancy layout trick after the fact. If I were
| implementing jquery again today, I'd make it error on empty set
| by default and add a call chain or flag to fail silently when you
| really don't care. I've spent a few hours poking around at jQuery
| seeing what it would take to pull out sizzle and do this, but
| never took things any farther than that.
|
| At the end of the day jquery is about the old debate of libraries
| versus frameworks. We've been doing SPAs with giant frameworks
| long enough now for the Trough of Disillusionment to be just
| around the corner again.
| luckylion wrote:
| Not having to worry whether some selector matches any elements
| is part of what makes jQuery attractive to many though. It's
| very "fire and forget", you send off your command to hide all
| .foo, and if there are any .foo they will be hidden, and if
| there are no .foo, nothing happens and you don't need to worry
| about it, much like CSS. If you write .foo { color: red; } and
| there isn't any .foo in the document it doesn't do anything but
| also has no negative side-effects (except that tiny overhead).
| bussyfumes wrote:
| Just had a script today that fires in 2 contexts and ran into
| an error where the element I attach a handler to doesn't
| exist in one of the contexts which breaks JS on the page.
| Since I already had jQuery as a dependency in the project, in
| the moment it felt easier to replace the querySelector call
| with jQuery, which I did, instead of checking the querySelect
| result so I second this, the 'fire and forget' part still
| holds up very well even though the tree traversal pain points
| have mostly been solved by browsers.
| spankalee wrote:
| You can modify every item in a query pretty nicely with a one-
| liner in modern browsers now:
| document.querySelectorAll('input[type=checkbox]').forEach((i)
| => i.checked = false);
|
| This takes advantage of iterable NodeList and iterator helpers.
|
| Many parent queries can be done with element.closest()
| robertoandred wrote:
| Not sure I'd call IE11 a modern browser. Aren't they leaving more
| size/speed improvements on the table by supporting it?
| fabiospampinato wrote:
| > Aren't they leaving more size/speed improvements on the table
| by supporting it?
|
| Only tiny ones, I don't remember the details now, IE11 ended up
| providing almost all the same APIs.
| aargh_aargh wrote:
| From the migration guide I learned a few things that jQuery can
| do (and Cash can't) that I didn't know and I'll probably use some
| time:
|
| https://github.com/fabiospampinato/cash/blob/master/docs/mig...
| aleclarsoniv wrote:
| I used this initially in a browser extension I'm building. Ended
| up migrating to a JSX library instead, because jQuery turns into
| hard-to-reason-about code pretty quickly once you're past "simple
| app" territory (and I say this as someone who wrote my own
| jQuery-inspired library[1]). Right tool for the job, as they say.
|
| [1]: https://github.com/aleclarson/dough
|
| P.S. If you can cope with jQuery in a medium/large app, good for
| you. But it's not my cup of tea.
| AltruisticGapHN wrote:
| I created a similar `$()` utility function for my projects albeit
| with 10 times less functionality.
|
| I used the same basic signature for the `$()` function. However I
| found that 95% of the time I don't need to use the chain method
| on a _collection_. There 's almost no scenario in which I want to
| do <collection>.addClass() etc. There's practically ZERO
| situations in which I would use something like attach an event to
| a collection of nodes, since event delegation is more elegant
| (attach a single event and check for event.type and
| event.target).
|
| So TLDR I made $() always select a single element with
| `querySelector()`, which means I could remove the collection/loop
| from every chained method like addClass() or css() or toggle().
|
| Point unless you write bad code to begin with, you can probably
| make this significantly smaller by removing the collection
| handling. The 1% of the time it is warranted to do an addClass()
| or something else on a bunch of nodes you can just go native and
| if the collection is small enough just call $() on each element.
|
| PS: I guess the subtext also to my post is sometimes something
| looks logically elegant, like the ability for any chained method
| to act on the collection selected by $(), but it may not make any
| sense in the real world.
___________________________________________________________________
(page generated 2024-11-02 23:01 UTC)