[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)