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