[HN Gopher] TinyJS - Shorten JavaScript QuerySelect with $ and $$
       ___________________________________________________________________
        
       TinyJS - Shorten JavaScript QuerySelect with $ and $$
        
       Author : synergy20
       Score  : 54 points
       Date   : 2024-10-02 18:49 UTC (4 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | yu3zhou4 wrote:
       | Like jQuery but for 2024?
        
         | NetOpWibby wrote:
         | I dig it
        
         | wackget wrote:
         | That would just be jQuery.
        
           | tbeseda wrote:
           | Yep. jQuery 4.0 is in it's second beta[0] and seems to clock
           | in at 27kB. And the "slim" version is <20kB.
           | 
           | [0] https://blog.jquery.com/2024/07/17/second-beta-of-
           | jquery-4-0...
        
         | adhamsalama wrote:
         | jQuery? What are you, five? We use jjQuey.
        
       | NetOpWibby wrote:
       | > This README was generated by ChatGPT
       | 
       | Damn good README. Also, good library.
        
         | hombre_fatal wrote:
         | Too wordy yet doesn't even explain the tag part of the API. It
         | just shows up in an example.
         | 
         | A readme should be more reference / show and less drivel.
        
       | EMM_386 wrote:
       | I do believe I've seen something like this under another name.
       | 
       | Using "$" to shorten JavaScript? That seems a lot like jQuery.
       | 
       | > This README was generated by ChatGPT
       | 
       | You don't need AI to explain this one.
        
         | CSSer wrote:
         | That explains the pompous introductory tone. Ugh.
        
         | KTibow wrote:
         | I think the point is to be a more lightweight version, same
         | spirit as HTMX
        
       | synergy20 wrote:
       | This is the whole code:                   (() => {         const
       | assignDeep = (elm, props) => Object.entries(props).forEach(([key,
       | value]) =>         typeof value === 'object' ?
       | assignDeep(elm[key], value) : Object.assign(elm, {[key]: value}))
       | const tagNames = ['a', 'abbr', 'address', 'area', 'article',
       | 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote',
       | 'body', 'br', 'button', 'canvas', 'caption',     'cite', 'code',
       | 'col', 'colgroup', 'data', 'datalist', 'dd', 'del',
       | 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed',
       | 'fieldset',     'figcaption', 'figure', 'footer', 'form', 'h1',
       | 'h2', 'h3', 'h4', 'h5',     'h6', 'head', 'header', 'hr', 'html',
       | 'i', 'iframe', 'img', 'input', 'ins',    'kbd', 'label',
       | 'legend', 'li', 'link', 'main', 'map', 'mark', 'meta',
       | 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option',
       | 'output',     'p', 'param', 'picture', 'pre', 'progress', 'q',
       | 'rp', 'rt', 'ruby', 's',     'samp', 'script', 'section',
       | 'select', 'small', 'source', 'span', 'strong',     'style',
       | 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template',
       | 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr',
       | 'track', 'u',     'ul', 'var', 'video', 'wbr'  ].forEach(tag =>
       | window[tag] = function(...args) {          const props = typeof
       | args[0] == 'object' && !(args[0] instanceof HTMLElement) ?
       | args.shift() : null         const elm =
       | document.createElement(tag)         props && assignDeep(elm,
       | props)         elm.append(...args.map(a => typeof a == 'string' ?
       | document.createTextNode(a) : a))         return elm         })
       | window['$'] = selector => document.querySelector(selector)
       | window['$$'] = selector =>
       | Array.from(document.querySelectorAll(selector))
       | 
       | })()
        
         | synergy20 wrote:
         | window['$'] = selector => document.querySelector(selector)
         | window['$$'] = selector =>
         | Array.from(document.querySelectorAll(selector))
         | 
         | is really what I want to have
        
           | iudqnolq wrote:
           | I'm quite fond of a little helper like so:
           | createElement({             className: 'p-2 flex justify-
           | end',             contents: createElement({
           | tag: 'img',               src: '/os-logo-maps.svg'
           | }),           });
           | 
           | I got the idea from Lea Verou's blissfuljs. When you're
           | creating a bunch of nested elements it comes in handy.
        
             | meow_catrix wrote:
             | That's React without the JSX sugar.
        
               | iudqnolq wrote:
               | Yes, and?
        
           | smusamashah wrote:
           | What's the point of the rest of the code? Looks useless.
        
             | cies wrote:
             | Looks difficult. I have no clue what I'm reading there tbh.
        
         | jazzypants wrote:
         | Why do you pollute the global scope with all this crap? It's
         | 2024, dude. Just make an ESM module.
        
       | 123yawaworht456 wrote:
       | may I ask what's the point of `const tagNames =`? removing it
       | would make no difference, as far as I can tell - just make sure
       | the previous line ends with a semicolon (or add it at the same
       | line as the opening square bracket)
        
         | hoten wrote:
         | Well, it's polluting the global window scope with all those
         | helpers.
         | 
         | That's a reason to never use this imo.
         | 
         | There are plenty of other jquery-lite helper libraries out
         | there. 95 percent the utility with hardly any cost in terms of
         | script size.
         | 
         | Converting querySelectorAll to an array is honestly most of the
         | benefit of these libraries.
        
           | spankalee wrote:
           | > Converting querySelectorAll to an array is honestly most of
           | the benefit of these libraries.
           | 
           | Now that NodeList is iterable, I don't even know that
           | converting to an array is that useful anymore.
           | 
           | [...document.querySelector('div')] is pretty easy.
           | 
           | Iterator.from(document.querySelector('div')) too (doesn't
           | work yet in Safari).
        
           | xPaw wrote:
           | You don't need to convert querySelectorAll to an array, you
           | can directly iterate it, or use forEach.
        
             | meow_catrix wrote:
             | Not if you want to map or reduce it.
        
         | smusamashah wrote:
         | Yes, exactly. The first two statements have no use in the last
         | two lines that this library is for. Or may be I don't
         | understand Javascript much.
        
       | alexjplant wrote:
       | jQuery II: The Quickening!
       | 
       | I recently built a toy Golang SSR project using Zepto, a jQuery-
       | like library, and felt like I was 17 again (**EDIT: it's
       | unmaintained - don't use it). Also of note is that "Highlander
       | II" takes place in the year 2024 [1]. It's a sign! Everything old
       | is new again! `$` is immortal!
       | 
       | [1] https://en.wikipedia.org/wiki/Highlander_II:_The_Quickening
        
         | no_wizard wrote:
         | Zepto is unmaintained, which isn't good for security issues or
         | bugs, unfortunately.
         | 
         | jQuery proper is both maintained and has actively been working
         | on a 4.0 release and still gets security and bug fixes
        
           | alexjplant wrote:
           | Thanks for bringing this up. Edited my OP - there's no
           | impression as such on their front page and it seemed fairly
           | ergonomic. My bad on that one.
        
         | franciscop wrote:
         | I also created almost 10 years ago Umbrella JS, which is a tiny
         | jQuery replacement but with more array-like arguments in the
         | functions:
         | 
         | https://umbrellajs.com/
         | 
         | https://www.bennadel.com/blog/4184-replacing-jquery-110kb-wi...
        
         | luismedel wrote:
         | IIRC, I used Zepto with Phonegap around ~2012. What a blast
         | from the past.
         | 
         | "There can be only one"
        
       | CSSer wrote:
       | Calling those $ and $$ selector helper functions new is a bit
       | disingenuous. They've been present as Chrome dev tools shortcuts
       | for years. Let alone JQuery, I've also spent years seeing little
       | articles about this neat trick on everything from medium to
       | dev.to.
       | 
       | I recommend the author remove that or note inspiration from the
       | README to avoid detracting from the rest of the work. I realize
       | there's more here than the traditional
       | document.querySelector.bind(document) assignment.
        
       | wackget wrote:
       | I'll just leave this here:
       | https://www.youtube.com/watch?v=Uo3cL4nrGOk
        
       | henriquez wrote:
       | You didn't need a library to do this. Just alias
       | document.querySelector and document.querySelectorAll to something
       | shorter. Polluting the global namespace with functions to
       | document.createElement on every possible html tag is not a good
       | idea.
        
       | tomp wrote:
       | Why would this make any sense?                   const myDiv =
       | div(           {id: 'container', className: 'my-class'},
       | h1('Hello World'),            p('This is a dynamically generated
       | paragraph.')         );         document.body.appendChild(myDiv);
       | 
       | That's completely unnecessary these days with template strings.
       | It's gonna be much faster as well to use browser's native
       | parsing.                   const div =
       | document.createElement('div');         let text = 'This is a
       | dynamically generated paragraph';                  div.innerHTML
       | = `           <div id="container" class="my-class">
       | <h1>Hello world</h1>             <p>${text}</p>           </div>
       | `;         document.body.append(...div.children);
       | 
       | Keep it simple!
        
         | franciscop wrote:
         | One big difference and why I've been experimenting with JSX[1]
         | is that in that example, if the `text` comes from an untrusted
         | source it can lead to XSS, which TinyJS prevents (I checked)!
         | 
         | [1] https://x.com/FPresencia/status/1838176000267452704
        
           | wruza wrote:
           | Even if it comes from a trusted source, you usually want a
           | good distinction between html interpolation and just-text
           | chunks.
        
         | spullara wrote:
         | you should probably worry about untrusted data in text and use
         | a tagged template function that sanitizes its inputs for
         | insertion in html.
        
         | nikeee wrote:
         | You can use tagged template functions to escape `${text}`. The
         | result is pretty close to lit-html [1].
         | 
         | [1]: https://lit.dev/docs/v1/lit-html/introduction/
        
           | tomp wrote:
           | beautiful!
        
         | Nickersf wrote:
         | This is exactly what I was thinking. I'm always trying to have
         | fewer third-party dependencies in my codebase no matter how
         | tiny, especially if it's solving problems that already have
         | platform/system native solutions.
        
         | rikafurude21 wrote:
         | Makes sense to me, looks better. writing html strings like that
         | is annoying
        
         | TimTheTinker wrote:
         | Generally, I would recommend avoiding directly setting the
         | innerHTML property, since it's vulnerable to XSS and other
         | injection attacks. If you do, make _sure_ you HTML-escape each
         | variable you interpolate.
         | 
         | Here's a way to do that with a tagged template function (named
         | it `htmlFragment` to make it super clear what it's for):
         | // a couple of functions to help                  const
         | sanitizeHTML = (unsafeStr) => {             const div =
         | document.createElement('div');             div.textContent =
         | unsafeStr;             return div.innerHTML;         };
         | const htmlFragment = (fragments, ...variables) => {
         | const result = variables.map((variable, i) => fragments[i] +
         | sanitizeHTML(variable));
         | result.push(fragments[fragments.length-1]);             return
         | result.join('');         };                  // updated your
         | code here                  const div =
         | document.createElement('div');         let text = 'This is a
         | dynamically generated paragraph';
         | div.innerHTML = htmlFragment`           <div id="container"
         | class="my-class">             <h1>Hello world</h1>
         | <p>${text}</p>           </div>         `;
         | document.body.append(...div.children);
         | 
         | Unfortunately, to my knowledge there isn't yet a close-to-the-
         | metal solution for templating and data binding in HTML/JS,
         | although several proposals are currently being discussed.
        
           | meiraleal wrote:
           | > Unfortunately, to my knowledge there isn't yet a close-to-
           | the-metal solution for templating and data binding in HTML/JS
           | 
           | I'm hoping for this one:
           | 
           | https://github.com/WICG/webcomponents/issues/1069
        
       | dpweb wrote:
       | If ya don't want to do includes,
       | 
       | ` window.$ = document.querySelector.bind(document) window.$$ =
       | document.querySelectorAll.bind(document) `
        
         | qingcharles wrote:
         | Thanks, this is the more useful bit of the code to me.
         | 
         | edit: how about this so you can use array functions too when
         | you need?
         | 
         | window.$ = document.querySelector.bind(document);
         | 
         | window.$$ = document.querySelectorAll.bind(document);
         | 
         | window.$$$ = (selector) =>
         | Array.from(document.querySelectorAll(selector));
        
         | janalsncm wrote:
         | I've been doing this for years. Lightweight but still saves me
         | a ton of time.
        
       | mendyberger wrote:
       | Code is actually tiny. Impressive!
       | 
       | You could make it even smaller by removing all the element names,
       | and just have it be passed in as an argument. Would also reduce
       | the amount of functions you need to declare, though function
       | count is likely not a problem for modern JS engines.
       | 
       | Anyway, great job you did there!
        
       | emmanueloga_ wrote:
       | I use this:                   export const $ =
       | document.querySelector.bind(document);         export const $$ =
       | document.querySelectorAll.bind(document);
       | 
       | When using TypeScript the types for querySelectorAll are a bit
       | hairy to map but by defining the consts like above the types
       | "just work".                   for (const tag of
       | $$<HTMLParagraphElement>("p")) ...
       | 
       | I don't use Array.from in the result of $$ because sometimes
       | creating an Array is not necessary. The NodeList returned can be
       | iterated directly or converted to an array later if really
       | needed:                   [...$$("p")].map(p => ...)
       | 
       | Since I use TypeScript I lean on TSX for building HTML. I use
       | preact's render-to-string package to convert it to a string [1].
       | 
       | ---
       | 
       | 1: https://github.com/preactjs/preact-render-to-string
        
         | cies wrote:
         | You're a wizzard!
         | 
         | JS keeps amazing me. It reminds me of Perl somehow. If Perl and
         | Lua had a child, with some browsers APIs sprinkled on top.
        
       | jufitner wrote:
       | Someone will fork this with proper ES modules and no window
       | clobbering.
        
       | akira2501 wrote:
       | I've been using this for years:                   const $id = new
       | Proxy({}, {             // get element from cache, or from DOM
       | get: (tgt, k, r) => (tgt[k] || ((r = document.getElementById(k))
       | && (tgt[k] = r))),                          // prevent
       | programming errors             set: () => $throw(`Attempt to
       | overwrite id cache key!`)         });
       | 
       | It's nice to be able to refer to elements by property name, so:
       | <div id="thing"></div>
       | 
       | Is reachable with:                   $id.thing
       | 
       | And, since the underlying structure is just an object, you can
       | still enumerate it with Object.keys, which can sometimes be a
       | useful debugging aid and general catalog of accessed elements.
       | 
       | Anyways.. Proxy is a wildly underappreciated and used class in
       | JavaScript.
        
         | nattaylor wrote:
         | If you like brittle things, the id attribute is already made
         | into an attribute on the window for legacy reasons
         | 
         | Edit: My tone may have indicated that parent's solution was
         | brittle. It's not!
        
           | akira2501 wrote:
           | The id attribute can take on values that are already present
           | or reserved in window. "fetch", "opener", etc..
           | 
           | The reason to have a separate system that correctly calls
           | getElementById() is to avoid this issue.
           | 
           | So, it's actually a _less_ brittle mechanism that doesn't
           | rely on legacy mechanisms and lacks the surprises that come
           | with that.
        
             | nattaylor wrote:
             | Sorry, I didn't mean to suggest your solution was brittle
             | -- I actually quite like it and want to adopt it!
             | 
             | But I do think the legacy browser behavior with the ID
             | attribute as window properties is very brittle for the
             | reasons you suggest
        
               | akira2501 wrote:
               | My fault, I tend to "rapid fire" sometimes. Yours was
               | next to another reply on the identical subject and that
               | caused me to mistake the meaning of the comma in your
               | sentence.
               | 
               | Reading it more slowly, I see it now, and, thank you!
        
           | cies wrote:
           | Nah, i rather skip brittle :) But what's not brittle in JS?
           | Elm? (TS to some extend)
        
         | open-paren wrote:
         | Element IDs are automatically attached to the window object
         | <script>       document.addEventListener('DOMContentLoaded', ()
         | => {         window.thing.innerHTML = 'Hello, World!';
         | });       </script>       <div id="thing"></div>
        
           | akira2501 wrote:
           | <div id="parent"></div>
           | 
           | Whoops.
        
           | esprehn wrote:
           | That's (much) slower and has surprising edge cases:
           | https://news.ycombinator.com/item?id=32997636
        
         | jonathrg wrote:
         | Does caching help? If so, shouldn't getElementById do the same
         | thing under the hood?
        
           | akira2501 wrote:
           | It used to more in the past.
           | 
           | The native call does usually include a cache and will use it
           | optimistically; however, the strategies around invalidating
           | this cache are varied and some surprisingly basic DOM actions
           | can trigger it. Then browsers usually fallback to the full
           | DOM query case.
           | 
           | The cache eliminates this variability, which prior to
           | flexbox, could be very useful in highly nested site designs
           | particularly in mobile contexts.
        
         | kibibu wrote:
         | I also put together a Proxy microlib a few years ago. I agree
         | it's very powerful.
         | 
         | You can use it like:                   $('.my-
         | elements').className = 'Important';          $('.my-
         | elements').style.color = 'Red';              $('.my-
         | elements').classList.add('Another');                    const $
         | = (() => {             function listProxy(arr) {
         | return new Proxy(arr, {                     set: (t, p, v, r)
         | => {                         for(let x of t) {
         | x[p] = v;                         }                     },
         | get: (t, p, r) => {                         if(t.length > 0 &&
         | t[0][p] instanceof Function) {
         | return (...args) => { Array.prototype.map.call(t, (x) =>
         | x[p](args)) };                         } else {
         | return listProxy( Array.prototype.map.call(t, (x) => x[p]) );
         | }                     }                 })             }
         | return (sel, root) => {                 if(root === undefined)
         | root = document;                 return
         | listProxy(root.querySelectorAll(sel));             }
         | })();
         | 
         | demo: https://codepen.io/anon/pen/RxGgNR
        
         | mattlondon wrote:
         | Why cache? I expect that getElementById will be efficient
         | enough on its own.
        
         | MrLeap wrote:
         | This is awesome! Proxies can be so cool. I made this proxy many
         | years ago for tracking stats/buffs/debuffs/gear for game
         | things.
         | 
         | https://github.com/MrLeap/Statsi
        
       | gvx wrote:
       | With that name I'd expect a tiny implementation of JavaScript.
        
         | tbeseda wrote:
         | I was just reading about this https://porffor.dev
         | 
         | > Porffor is a unique JS engine/compiler/runtime, compiling JS
         | code to WebAssembly or native ahead-of-time.
        
       | dang wrote:
       | Related:
       | 
       |  _Show HN: Tiny JS - A Tiny JavaScript Library for Easy DOM
       | Creation_ - https://news.ycombinator.com/item?id=41462817 - Sept
       | 2024 (4 comments)
       | 
       |  _Show HN: A better way of writing HTML via JavaScript?_ -
       | https://news.ycombinator.com/item?id=41451559 - Sept 2024 (9
       | comments)
        
       | im_nullable wrote:
       | Based on the readme this is a subset of jQuery primarily targeted
       | at creating dom nodes.
       | 
       | Kind of cool if this is what you need.
        
       ___________________________________________________________________
       (page generated 2024-10-02 23:00 UTC)