by voat on 4/19/25, 2:10 AM with 132 comments
by athrowaway3z on 4/19/25, 6:01 AM
I use webcomponents and instead of adding state variables for 'flat' variable types I use the DOM element value/textContent/checked/etc as the only source of truth, adding setters and getters as required.
So instead of:
/* State variables */
let name;
/* DOM update functions */
function setNameNode(value) {
nameNode.textContent = value;
}
/* State update functions */
function setName(value) {
if(name !== value) {
name = value;
setNameNode(value);
}
}
it would just be akin to: set name(name) { this.nameNode.textContent = name }
get name() { return this.nameNode.textContent}
/* or if the variable is used less than 3 times don't even add the set/get */
setState({name}){
this.querySelector('#name').textContent = name;
}
Its hard to describe in a short comment, but a lot of things go right naturally with very few lines of code.I've seen the history of this creating spaghetti, but now with WebComponents there is separation of objects + the adjacent HTML template, creating a granularity that its fusilli or macaroni.
by zffr on 4/19/25, 4:04 AM
The design pattern is based on convention only. This means that a developer is free to stray from the convention whenever they want. In a complex app that many developers work on concurrently, it is very likely that at least one of them will stray from the convention at some point.
In comparison, a class based UI framework like UIKit on iOS forces all developers to stick to using a standard set of APIs to customize views. IMO this makes code way more predictable and this also makes it much more maintainable.
by dsego on 4/19/25, 6:53 AM
There is also a github repo that has examples of MVC patterns adapted to the web platform. https://github.com/madhadron/mvc_for_the_web
by epolanski on 4/19/25, 11:53 AM
I can't conclude it scales, whatever it means, but I can conclude that there are huge benefits performance-wise, it's fun, teaches you a lot, debugging is simple, understanding the architecture is trivial, you don't need a PhD into "insert this rendering/memoization/etc" technique.
Templating is the thing I miss most, I'm writing a small vite plugin to handle it.
by lylejantzi3rd on 4/19/25, 4:31 AM
A significant issue I have with writing code this way is that the functions nest and it becomes very difficult to make them compose in a sane way.
function printPosts(posts) {
let content = ""
posts.forEach((post, i) => {
content += printPost(post)
})
window.posts.innerHTML = content
}
function printPost(post) {
return `
<div class="post" data-guid="${post.guid}">
<div>
<img class="avatar" src="https://imghost.com${post.avatar.thumb}"/>
</div>
<div class="content">
<div class="text-content">${post.parsed_text}</div>
${post?.image_urls?.length > 0 ? printImage(`https://imghost.com${post.image_urls[0].original}`) : ''}
${post?.url_preview ? `<hr/><div class="preview">${printPreview(post.url_preview)}</div>` : ''}
${post?.quote_data ? `<hr/><div class="quote">${printQuote(post.quote_data)}</div>` : ''}
${post?.filtered ? `<div>filtered by: <b>${post.filtered}</b></div>` : ''}
</div>
</div>
`
}
by edflsafoiewq on 4/19/25, 4:40 AM
by atoav on 4/19/25, 8:33 AM
I just never understood why the overhead of those frameworks was worth it. Maybe that is because I am so strong with backends that I think most security-relevant interactions have to go through the server anyways, so I see JS more as something that adds clientside features to what should be a solid HTML- and CSS-base..
This kind of guide is probably what I should look at to get it from first principles.
by efortis on 4/19/25, 3:00 PM
const state = { count: 0 }
const init = () => document.body.replaceChildren(App())
init()
function App() {
return (
h('div', null,
h('output', null, `Counter: ${state.count}`),
h(IncrementButton, { incrementBy: 2 })))
}
function IncrementButton({ incrementBy }) {
return (
h('button', {
className: 'IncrementButton',
onClick() {
state.count += incrementBy
init()
}
}, 'Increment'))
}
function h(elem, props = null, ...children) {
if (typeof elem === 'function')
return elem(props)
const node = document.createElement(elem)
if (props)
for (const [key, value] of Object.entries(props))
if (key === 'ref')
value.current = node
else if (key.startsWith('on'))
node.addEventListener(key.replace(/^on/, '').toLowerCase(), value)
else if (key === 'style')
Object.assign(node.style, value)
else if (key in node)
node[key] = value
else
node.setAttribute(key, value)
node.append(...children.flat().filter(Boolean))
return node
}
Working example of a dashboard for a mock server:
https://github.com/ericfortis/mockaton/blob/main/src/Dashboa...by yumaikas on 4/19/25, 7:18 AM
I don't quite have proper reactive/two-way data binds worked out, but grab/patch seem pretty nice as these things go. Also, the way this uses templates makes it very easy to move parts of the template around.
It's also largely injection safe because it's using innerText or value unless told otherwise.
by atum47 on 4/19/25, 4:11 AM
1 - https://github.com/victorqribeiro/TinyJS
2 - https://github.com/victorqribeiro/Chip8js/blob/master/js/Col...
by seumars on 4/19/25, 8:47 AM
by triyambakam on 4/19/25, 6:03 AM
by floydnoel on 4/19/25, 7:54 PM
it was fun and very fast to ship. no frameworks or libraries needed.
by jbverschoor on 4/19/25, 12:47 PM
by admiralrohan on 4/19/25, 8:01 AM
by dullcrisp on 4/19/25, 5:23 AM
by popcorncowboy on 4/19/25, 7:48 AM
by wruza on 4/19/25, 9:07 AM
Rather than building a querySelector-able tree of elements to and monkey-patching mutiplexing nodes for syncing element counts, you invent the most bizarre ways to chain yourselves to the wall. For long time I couldn't understand what exactly drives this almost traumatic habit, and it's still a mystery.
For the interested, this is the outline I count as non-bizarre:
- make an html that draws your "form" with no values, but has ids/classes at the correct places
- singular updates are trivial with querySelector; write a few generic setters for strings, numbers, dates, visibility, disability, e.g. setDate(sel, date)
- sync array counts through cloning a child-template, which is d-hidden and locatable inside a querySelector-able container; make syncArray(array, parentSel, childSel) function
- fill new and update existing children through "<parent> :nth-child(n) <name>"
- update when your data changes
Data can change arbitrarily, doesn't require passing back and forth in any form. All you have to do is to update parts of your element tree based on your projections about affected areas.
And no, your forms are not so complex that you cannot track your changes or at least create functions that do the mass-ish work and update ui, so you don't have to. For all the forms you've done, the amount of work needed to ensure that updates are performed is amortized-comparable with all the learning cliffs you had to climb to turn updates into "automatic". Which itself is a lie basically, cause you still have to jump through hoops and know the pitfalls. The only difference is that rather than calling you inattentive, they now can call you stupid, cause you can't tell which useCrap section your code should go to.
by smarkov on 4/19/25, 8:55 AM