1 - https://github.com/victorqribeiro/TinyJS
2 - https://github.com/victorqribeiro/Chip8js/blob/master/js/Col...
Otherwise looks like nice.
In tiny evrything looks like JS and you actually have to read it to know what is what
You don't, actually. If in HTML you write <select><option/><select/> in tiny you write select(option()) Also what if someone will define span variable?
I'm guilty of that myself. Tried to name a variable input when there's already a function with that name. It forces me to come up with better descriptive names. I could've wrapped those functions inside namespace like tiny.input() but I like the simplicity of it as is.Have you faced any scenarios where that's needed? I'm curious.
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>
`
}
It takes a little to wrap your head around, but essentially structures component rendering to follow the natural lifecycle of a generator function that takes as input the state of a previous yield, and can be automatically cleaned up by calling `finally` (you can observe to co-routine state update part in this notebook[1]).
This approach amounts to a really terse co-routine microframework [2].
[0]: https://lorenzofox.dev/posts/component-as-infinite-loop/#:~:...
It also will make it hard to scope anything you want to do to an individual DOM element. If you want granular updates, for example, you want to be able to do something like `document.querySelector(???)` and be certain it's going to refer to, say, a specific text input in your `printPost` template, without worrying about accessing the inputs created by other instances of the `printPost` template. You can do that with unique IDs, but it's fiddly and error-prone.
image = post.image_urls?[0] || "";
Then have the printImage function return an empty string if the argument is an empty string.
${printImage(image)}
Easier on the eyes.
Generating serialised HTML is a mug’s game when limited to JavaScript. Show me a mature code base where you have to remember to escape things, and I’ll show you a code base with multiple injection attacks.
You can do it from scratch, but you essentially need to track provenance of strings (either needs to be escaped and isn't html, e.g., user input, or html, which is either generated and with escaping already done or static code). It seems like you could build this reasonably simply by using tagged template literals and having e.g., two different Types of strings that are used to track provenance.
Server-side sanitization and xss injection should be left in the 2000s php era.
If you mean filtering out undesirable parts of a document (e.g. disallowing <script> element or onclick attribute), that should normally be done on the server, before storage.
If instead you mean serialising, writing a value into a serialised document: then this should be done at the point you’re creating the serialised document. (That is, where you’re emitting the HTML.)
But the golden standard is not to generate serialised HTML manually, but to generate a DOM tree, and serialise that (though sadly it’s still a tad fraught because HTML syntax is such a mess; it works better in XML syntax).
This final point may be easier to describe by comparison to JSON: do you emit a JSON response by writing `{`, then writing `"some_key":`, then writing `[`, then writing `"\"hello\""` after carefully escaping the quotation marks, and so on? You can, but in practice it’s very rarely done. Rather, you create a JSON document, and then serialise it, e.g. with JSON.stringify inside a browser. In like manner, if you construct a proper DOM tree, you don’t need to worry about things like escaping.
I think "normally" we should instead filter for XSS injections when we generate the DOM tree, or just before (such as passing backend data to the frontend, if that makes more sense).
The problem is that different contexts have different escaping rules. It’s not possible to give a one-size-fits-all answer from the server side. It has to be done in a context-aware way.
Field A is plain text. Someone enters the value “Alpha & Beta”. Now, what does your server do? If it sanitises by stripping HTML characters, you’ve just blocked valid input; not good. If it doesn’t sanitise but instead unconditionally escapes HTML, somewhere, sooner or later, you’re going to end up with an “Alpha & Beta” shown to the user, when the value gets used in a place that isn’t taking serialised HTML. It always happens sooner or later. (If it doesn’t sanitise or escape, and the client doesn’t escape but just drops it directly into the serialised HTML, that’s an injection vulnerability.)
Field B is HTML. Someone enters the value “<img src=/ onerror=alert('pwnd')>”. Now, what does your server do? If it sanitises by applying a tag/attribute whitelist so that you end up with perhaps “<img src="/">”, fine.
The old tried and true strategy of "never sanitize data, push to the database with prepared statements and escape in the templates" is basically bulletproof.
Keeping data in sync with the UI was a huge mental burden even with relatively simple UIs. I have no desire to go back to that.
[1] See the history section of https://en.m.wikipedia.org/wiki/Web_Components
If you check out his examples (e.g. clock), you will notice that he is using web components.
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.
Having a manual state that do not automatically sync to elements will only introduce an unnecessary complexity later on. Which is why libraries like react and vue works well, they automatically handle the sync of state to elements.
These have their places, but I don't see them as an either-or replacement for managed components with associated states.
Eg if you had child form fields that should be enabled/disabled based on this, and maybe they’re dynamically added so you can’t hardcode it in this parent form field. Can you pass that get function down the tree the same way you would pass react state as a prop?
A lot of people just wanted slight improvements like composable html files, and a handful of widgets that have a similar api. And for a long time it just wasn't worth the hassle to do anything other than react-create-app even if it pulled in 100x more than what people needed or even wanted.
But stuff has gotten a lot better, es6 has much better, web-components... are there, css doesn't require less/sass. It's pretty reasonable to just have a site with just vanilla tech. It's part of why htmx is as popular as it is.
If the user can display 2 contacts at once, etc...
It's unclear what you mean by "state variables". The alternative to state variables you're proposing with webcomponents are essentially component-specific state variables, but you're restricting their application to only cover component state instead of application state, and needlessly restricts implementations by making webcomponents mandatory.
> (...) but now with WebComponents there is separation of (...)
The separation was always there for those who wanted the separation. WebComponents in this regard change nothing. At most, WebComponents add first-class support for a basic technique that's supported my mainstream JavaScript frameworks.
setName(value) first checks the local state variable, and if different the value is both written to the state variable and the DOM.
The GP's pattern uses getters and setters to directly read and write to the DOM, skipping the need for a local variable entirely.
You need separation between components and data. For example you got a list of 1000 objects, each having 50 fields. You display 100 of them in a list at a time. Then you have a form to view the record and another to update it. You may also have some limited inline editing inside the list itself. Without model it will be hard to coordinate all pieces together and avoid code duplication.
The way to keep it simple is to have a single state object, which is the one place where state is organized and accessed.
The way to make it scale is architecture. Architecture is a fancy word that means a repeatable pattern of instances where each instance of a thing represents a predefined structure. Those predefined structures can then optionally scale independently of the parent structure with an internal architecture, but the utility of the structure’s definitions matter more.
Boom, that’s it. Simple. I have written an OS GUI like this for the browser, in TypeScript, that scaled easily until all system memory is consumed.
It seems like you're saying that it's easy to do UI with a centralized state, therefore agreeing with them whilst having the tone of disagreement.
I completely understand why JavaScript developers would fail to read this as such as most JavaScript developers are wholly incapable of programming, a forest for the trees problem.
The list of comments on a submission tells you how many comments exist, but the comment count is also made explicit at the top of the page directly underneath the submission title.
If one person comments multiple times, their user name will appear multiple times on the page, despite being the same every time.
All the timestamps are presented as relative timestamps, which means they're all dependent on the current time.
Now this is a very simple page, and it's not so important that everything be updated live. But if it were, you'd need to update every single timestamp on the page, keep all of the usernames in sync in case a user changed their name, insert new comments while also updating the comment count, etc. There is a lot of redundancy in most UIs.
In fact, I vaguely remember one of the early React blog posts using a very similar example (I think something to do with Messenger?) to explain the benefits of having a data-driven framework rather than using the DOM as the source of truth for data. For a messaging application, it's much more important that everything be live, and that elements don't end up out-of-sync.
If you just rerender everything every time, then it's no problem to keep the whole UI in sync. But you probably don't want to render everything all the time - that's unnecessary work, and will break any stateful elements in the UI (such as form inputs that will get reset with every render). That's where the idea of React comes from: write code as if the whole UI is being rerendered every time, but internally only rerender the parts of the UI that have changed.
Now that has its own disadvantages, and I think there are similar approaches out there, but the point is that keeping UIs in sync is a surprisingly hard problem.
If you have the edge case of lots of update (assignments to .name) then just wrap the `.name = ...` in a leading debounce.
How do you manage redundant state? For example a list with a "select all" button, then the state "all selected"/"some selected"/"none selected" would be duplicated between the "select all" button and the list of elements to select.
This is the fundamental (hard) problem that state management needs to solve, and your proposal (along with the one in the OP) just pretends the issue doesn't exist and everything is easy.
Maybe I don't understand the problem you are talking about.
Hell, even in react I try to follow a similar pattern as much as possible. I'll avoid hooks and local state as much as possible, using react like the early days where I pass in props, listen to events, and render DOM.
• Using DOM attribute or text nodes limits you to text only. This is, in practice, a very big limitation. The simple cases are Plain Old Data which can be converted losslessly at just an efficiency cost, like HTMLProgressElement.prototype.value, which converts to number. Somewhat more complex are things like classList and relList, each a live DOMTokenList mapping to a single attribute, which needs unique and persistent identity, so you have to cache an object. And it definitely gets more intractable from there as you add more of your own code.
• Some pieces of state that you may care about aren’t stored in DOM nodes. The most obvious example is HTMLInputElement.prototype.value, which does not reflect the value attribute. But there are many other things like scroll position, element focus and the indeterminate flag on checkboxes.
• Some browser extensions will mess with your DOM, and there’s nothing you can do about it. For example, what you thought was a text node may get an entire element injected into it, for ads or dictionary lookup or whatever. It’s hard to write robust code under such conditions, but if you’re relying on your DOM as your source of truth, you will be disappointed occasionally. In similar fashion, prevailing advice now is not to assume you own all the children of the <body> element, but to render everything into a div inside that body, because too many extensions have done terrible things that they should never have done in the first place.
It’s a nice theory, but I don’t tend to find it scaling very well, applied as purely as possible.
Now if you’re willing to relax it to adding your own properties to the DOM element (as distinct from attributes), and only reflecting to attributes or text when feasible, you can often get a lot further. But you may also find frustration when your stuff goes awry, e.g. when something moves a node in the wrong way and all your properties disappear because it cloned the node for some reason.
Interesting idea but breaks down immediately in any somewhat serious application of reasonable size. e.g. i18n
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
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.
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.
Reactive view libraries basically generate the updates for you (either from VDOM diffing, or observables/dependency tracking). This removes the entire problem of incorrect update functions and the code size for updates is now constant (just the size of the library).
It is the only framework that feels like I am using JSP, JSF, ASP.NET, Spring, Quarkus, PHP.
Don't plan to use anything else in JS space, unless by external decisions not under my control.
jQuery has become obsolete these days because the problems it solves have largely been solved by additions to JS, but the interactivity of websites has continued to increase and browsers have yet to catch up to that. Frameworks like React actively fight against the browser rather than work with it by maintaining its own DOM state and constantly creating copies of state for every re-render of a component, along with a bunch of other magic. That's a lot of unnecessary loopholes just to make up for JS's lack of features when it comes to writing reactive UI.
Your eyes deceive you.
https://finance.yahoo.com/news/exclusive-laravel-raises-57-m...
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.
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.
Just because a technology works well for a few cases shouldn’t mean it’s the default. What’s the 80% solution is much more interesting IMO.
The hardest part about scaling this approach is finding UX designers who understand the web. Just as frontend devs have trained themselves to "think in react" over the past decade, so have designers. The understanding of the underlying capabilities and philosophies of the web have been lost to the idea that the web and mobile can be effectively the same thing.
This approach can go far if the team using it knows and respect web technology.
I mean I totally agree on small personal projects. Thats just never the limiting factor though.
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...
zffr•13h ago
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.
netghost•13h ago
I think the maintainability comes from easy debugging. Stack traces are sensible and the code is straightforward. Look at a React stack trace and nothing in the trace will tell you much about _your_ code.
I'd also point out that this looks like it's about seven years old. We've shifted a lot of norms in that time.
_heimdall•5h ago
I think the OP here is basically proposing that the developer should be directly responsible for the conventions used. IMO that's not a bad thing, yes it means developers need to be responsible for a clean codebase but it also means they will better understand why the conventions exist and how the app actually works. Both of those are easily lost when you follow convention only because a tool or library said that's how its done.
nonethewiser•2h ago
_heimdall•5m ago
React is a particularly interesting one because it is still flexible enough that there is still a lot of reliance on developers actively sticking to the conventions recommended.