[0] https://blog.jquery.com/2024/07/17/second-beta-of-jquery-4-0...
Damn good README. Also, good library.
A readme should be more reference / show and less drivel.
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.
(() => {
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))
})() window['$'] = selector => document.querySelector(selector)
window['$$'] = selector => Array.from(document.querySelectorAll(selector))
is really what I want to have 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.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.
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).
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
jQuery proper is both maintained and has actively been working on a 4.0 release and still gets security and bug fixes
https://www.bennadel.com/blog/4184-replacing-jquery-110kb-wi...
"There can be only one"
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.
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!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.I'm hoping for this one:
const div = document.createElement('div');
const text = 'This is a dynamically generated paragraph';
div.insertAdjacentHTML("beforeend", `<div id="container" class="my-class">
<h1>Hello world</h1>
<p>${text}</p>
</div>`);
document.body.append(...div.children);
Edit, figured I'd add the docs: https://developer.mozilla.org/en-US/docs/Web/API/Element/ins...From the MDN page:
> When inserting HTML into a page by using insertAdjacentHTML(), be careful not to use user input that hasn't been escaped.
``` const myButton = button({ onclick: e => console.log(e), textContent: "Click me" }) ```
` window.$ = document.querySelector.bind(document) window.$$ = document.querySelectorAll.bind(document) `
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));
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!
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].---
JS keeps amazing me. It reminds me of Perl somehow. If Perl and Lua had a child, with some browsers APIs sprinkled on top.
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.
Edit: My tone may have indicated that parent's solution was brittle. It's not!
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.
But I do think the legacy browser behavior with the ID attribute as window properties is very brittle for the reasons you suggest
Reading it more slowly, I see it now, and, thank you!
<script>
document.addEventListener('DOMContentLoaded', () => {
window.thing.innerHTML = 'Hello, World!';
});
</script>
<div id="thing"></div>
<div id="parent"></div>
Whoops.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.
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 const api = new Proxy({route: baseRoute}, handler); // handler is the microlib's export
const result = await api.get.some.route.invoke(); // GET {baseRoute}/some/route
invoke() is just how it finally fires the call. I didn't feel like spending too much time making it automatic, the benefits just weren't large enough to justify compared to just calling invoke().`api.some.route.get()`
> Porffor is a unique JS engine/compiler/runtime, compiling JS code to WebAssembly or native ahead-of-time.
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)
Kind of cool if this is what you need.
window['$'] = selector => document.querySelector(selector)
window['$$'] = selector => Array.from(document.querySelectorAll(selector))
The other part is a small & naive (and I mean that in the best way) DOM abstraction.I think the discussion here seems to be about one or the other, not the thing as a whole.
Nowadays I tend to use Mithril instead, because immediate-mode UI is so much less hassle than retained-mode: https://mithril.js.org/
``` const $ = (param) => { if (typeof param === "string" || param instanceof String) { const elm = document.querySelectorAll(param); return elm.length > 1 ? elm : document.querySelector(param); } else { return [param].length === 1 ? [param][0] : [param]; } }; ```