I have been writing recently an application in plain "vanilla" TypeScript with vite, no rendering libraries, just old-style DOM manipulation and I have to say I more and more question front end "best" practices.
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.
This might be heresy to many JS devs, but I think 'state' variables are an anti-pattern.
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.
> use the DOM element value/textContent/checked/etc as the only source of truth
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.
This approach is simple but does not scale. People did this long time ago, perhaps starting with SmallTalk in 80’s and VB/Delphi in 90’s.
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.
This was my first thought as well. I like the convention the OP is proposing here, with this one tweak making the DOM the single source of truth rather than local state inside views or components.
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.
> (...) I think 'state' variables are an anti-pattern. I use webcomponents (...)
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.
I think this makes a lot of sense when you’re just wanting to update a single DOM node. And if you wanted to eg update its color as well, scoped CSS with a selector based on checked state is probably as nice as anything else. But how does this look if you want to pass that value down to child elements?
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?
+1 have had multiple bugs arise because the state in the variable was not the same as the UI / DOM. Haven't had any problems a pattern similar to yours.
If you have the edge case of lots of update (assignments to .name) then just wrap the `.name = ...` in a leading debounce.
I feel you, but isn't the state of truth for most websites supposed to be whatever is in the database? The example TODO List app, each TODO item has stuff in it. That's the source of truth and I believe what is trying to be solved for for most frameworks. In your example, where does name come from originally? Let's assume it's a contact app. If the user picks a different contact, what updates the name, etc...
If the user can display 2 contacts at once, etc...
I love the idea of a single source of truth. However, how does your approach handle browsers / plugins that modify the dom? For example, I can imagine Google Translate altering the textContent and some resulting downstream effects on business logic.
If the view needs to react to the updated DOM you could use a custom element and the attribute changed callback. If you don't need to react to if the updated text content would just be there the next time the view needs to read it.
this is what I did in jquery era and it works very well, since it seldom to have state management at that era. Sure there's data binding libs like backbonejs and knockoutjs for a more complex app, but this approach works well anyway.
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.
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.
A lot of UIs have redundant data, it's very common to have the same data represented in different ways. Consider the comments page on HN, for example, which has plenty of duplicate information:
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.
The read me says this approach is extremely maintainable, but I’m not sure I agree.
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.
Any code base lives or dies by how well it defines and then sticks to conventions. We can enforce it in different ways, or outsource the defining of convention to other tools and libraries, but we still have to use them consistently in the codebase.
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.
Convention works when the culture is there, but I think you're right a dash of typescript and a class or interface definition could go a long ways.
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.
Reactive view libraries exist to hide those details. I think the OP is proposing that the benefit of reactive views/state isn't worth the cost and complexity.
Do you mean subscribing to events/callbacks, manually managing object lifecycle, manually inserting list elements, keeping it in sync with the state, etc, etc. Because that was all friggen horrible. Maybe new approaches could make it less horrible, but there is no way I’d go back to what it was like before React. If anything, I want everything to be more reactive, more like immediate mode rendering.
They still nail "state" to element trees, which creates unbenchmarkable but real update costs. Svelte does better than react, but only within the same paradigm.
Can you describe what you mean by that a bit more? As I understand it, with the new signals-based system in Svelte, updating data directly updates the DOM.
It seems the "hard way" here is just avoiding frameworks. The real hard part of UI is in fact state management and the myriad of methods for handling state.
I came up with something similar recently, except it doesn't use template elements. It just uses functions and template literals. The function returns a string, which gets dumped into an existing element's innerHTML. Or, a new div element is created to dump it into. Re-rendering is pretty quick that way.
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.
This is begging for injection attacks. In this case, for example, if parsed_text and filtered can contain < or &, or if post.guid or post.avatar.thumb can contain ", you’re in trouble.
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.
Yeah, OPs code is asking for pain. I suspect there are now developers who've never had to generate html outside the confines of a framework and so are completely unaware of the kinds of attacks you need to protect yourself against.
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 means that your view code is inherently vulnerable to injection. You'll notice in modern systems you don't sanitize data in the database and you don't have to manually sanitize when rendering frontend code. It's like that for a reason.
Server-side sanitization and xss injection should be left in the 2000s php era.
How do you update the html when something changes? For me, that's the most interesting question for these sorts of micro-frameworks - templating HTML or DOM nodes is super easy, but managing state and updates is hard.
I find the coroutine/generator approach described in a series of posts by Lorenzo Fox/Laurent Renard to be a promising alternative[0].
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].
I call printPosts with the new post data. It rewrites the whole chunk in one go, which is pretty snappy. I haven't decided how I'm going to handle more granular updates yet, like comment count or likes.
Yeah, that's a pretty common approach. Unfortunately, browsers aren't very good at doing patch updates, so it'll completely reset any UI elements in the region being rerendered.
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.
I've been working on https://deja-vu.junglecoder.com which is an attempt to build a JS toolkit for HTML-based doodads that shares some ideas with this.
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.
On my first official job after college I was working on making a web version of a Delphi software. The team was already on their third rewrite of the front end cause they had to change frameworks. I made the cass that we should write our own framework, so I prototyped FOS (the components I use on my website) to prove my point. The team (a bunch of mostly Delphi programmers) did not like my suggestion. Anyways, soon after that another company made me a better offer so I left. Years went by an I finally take a shot at another framework: tiny.js [1]. I've been using it in all my personal projects so far. I'm particular proud of the ColorPicker [2] component I wrote that I've used in two projects so far. As you can see, one can argue that tiny.js it's not a framework at all, just some wrapper functions that helps you create Functional components.
I dont know... I kind of like diffrent look of HTML and JS.
At least you know what is what. In tiny evrything looks like JS and you actually have to read it to know what is what.
Also what if someone will define span variable? Does it override the span HTML component function?
Might be feasible with the advent of vibe coding. For frontend heavy applications can choose this route for performance reasons. Starred, will follow the project.
I like to prompt Claude to create artifacts in plain HTML, CSS and JS. I like the portability and hackability of these. React is too heavy for a lot of simple ideas even if reactivity is needed.
I program for roughly two decades now and I never got warm with frontend frameworks. Maybe I am just a backend guy, but that can't be it since I am better in vanilla JS, CSS and HTML than most frontend people I have ever met.
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.
Something is wrong with web developers culture, cause even in framework-free vanilla mode they cannot get rid of data localization and welding the data and "component" trees together irrepairably.
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.
People like to hate on PHP, but PHP provides you with all the tools you need to write a fully working backend, where as JS provides you with half-assed solutions for writing frontend, which is why we have 1000 frameworks and we still can't agree on how to write frontend code. Seriously, we don't even have a convention for writing a simple reusable component with vanilla JS, everyone makes up their own thing. Web components were supposed to be that, but they're a good example of what I meant by "half-assed", because they're ugly, verbose, clunky, don't really solve the right problems, and nobody likes writing them.
I don't think PHP is any better in solving the backend, than JS is in solving frontend. On the Frontend the situation is not ideal, but we made big leaps every let's say 5 years, going from jQuery to React and from React to later generation frameworks like svelte / solid etc. Yes, the landscape is fragmented and there are maybe too many options, but you make it sound like PHP is universally used as the backend solution, while I see it being used little these days except for legacy systems from 15-20 years ago.
> you make it sound like PHP is universally used as the backend solution, while I see it being used little these days except for legacy systems from 15-20 years ago.
I never said that PHP was universally used, just that it has answers to most problems.
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.
I have been writing recently an application in plain "vanilla" TypeScript with vite, no rendering libraries, just old-style DOM manipulation and I have to say I more and more question front end "best" practices.
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.
This might be heresy to many JS devs, but I think 'state' variables are an anti-pattern.
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:
it would just be akin to: 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.
> use the DOM element value/textContent/checked/etc as the only source of truth
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.
That is just select with multi. And one can also have class vs id.
This approach is simple but does not scale. People did this long time ago, perhaps starting with SmallTalk in 80’s and VB/Delphi in 90’s.
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.
That screams pub sub which is trivial with JavaScript proxy imho.
can you elaborate on the 'don't scale part'? because apps in 90's don't see 'smaller' than webapps now
So, state is simple, stupid simple.
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.
This was my first thought as well. I like the convention the OP is proposing here, with this one tweak making the DOM the single source of truth rather than local state inside views or components.
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.
> (...) I think 'state' variables are an anti-pattern. I use webcomponents (...)
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.
"State variables" is a section in the original article. It shows a variable in the view, "name", that holds the value separate from the DOM.
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.
I think this makes a lot of sense when you’re just wanting to update a single DOM node. And if you wanted to eg update its color as well, scoped CSS with a selector based on checked state is probably as nice as anything else. But how does this look if you want to pass that value down to child elements?
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?
+1 have had multiple bugs arise because the state in the variable was not the same as the UI / DOM. Haven't had any problems a pattern similar to yours.
If you have the edge case of lots of update (assignments to .name) then just wrap the `.name = ...` in a leading debounce.
I feel you, but isn't the state of truth for most websites supposed to be whatever is in the database? The example TODO List app, each TODO item has stuff in it. That's the source of truth and I believe what is trying to be solved for for most frameworks. In your example, where does name come from originally? Let's assume it's a contact app. If the user picks a different contact, what updates the name, etc...
If the user can display 2 contacts at once, etc...
I don't think that is heresy, essentially you are describing what MUI calls unmanaged components - if I understand you well.
These have their places, but I don't see them as an either-or replacement for managed components with associated states.
I love the idea of a single source of truth. However, how does your approach handle browsers / plugins that modify the dom? For example, I can imagine Google Translate altering the textContent and some resulting downstream effects on business logic.
If the view needs to react to the updated DOM you could use a custom element and the attribute changed callback. If you don't need to react to if the updated text content would just be there the next time the view needs to read it.
this is what I did in jquery era and it works very well, since it seldom to have state management at that era. Sure there's data binding libs like backbonejs and knockoutjs for a more complex app, but this approach works well anyway.
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.
I think this is a very popular opinion.
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.
The problem the name will have to be updated in 6 places in the UI.
1. Then add the 6 updates to the "setter" function 2. What UI has the same data presented 6 times? Seems unnecessary
A lot of UIs have redundant data, it's very common to have the same data represented in different ways. Consider the comments page on HN, for example, which has plenty of duplicate information:
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.
what are those 6 places? how were they updated before?
This is the exact same thing, but with the state pushed one level deeper, unless I misunderstand something here?
I really appreciate the concision and directness
The read me says this approach is extremely maintainable, but I’m not sure I agree.
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.
Any code base lives or dies by how well it defines and then sticks to conventions. We can enforce it in different ways, or outsource the defining of convention to other tools and libraries, but we still have to use them consistently in the codebase.
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.
Convention works when the culture is there, but I think you're right a dash of typescript and a class or interface definition could go a long ways.
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.
It appears to be exactly the kind of manual-update code that reactive view libraries exist to replace.
Reactive view libraries exist to hide those details. I think the OP is proposing that the benefit of reactive views/state isn't worth the cost and complexity.
It’s probably about time for that to become fashionable again
It's easy to forget how tedious things used to be before React became popular.
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.
Do you mean subscribing to events/callbacks, manually managing object lifecycle, manually inserting list elements, keeping it in sync with the state, etc, etc. Because that was all friggen horrible. Maybe new approaches could make it less horrible, but there is no way I’d go back to what it was like before React. If anything, I want everything to be more reactive, more like immediate mode rendering.
IIRC its what frameworks like Svelte do when they hit the compiler and optimize, which makes the best of both worlds.
They still nail "state" to element trees, which creates unbenchmarkable but real update costs. Svelte does better than react, but only within the same paradigm.
Can you describe what you mean by that a bit more? As I understand it, with the new signals-based system in Svelte, updating data directly updates the DOM.
This reminds me of the venerable backbone js library. https://backbonejs.org/#View
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
It seems the "hard way" here is just avoiding frameworks. The real hard part of UI is in fact state management and the myriad of methods for handling state.
I came up with something similar recently, except it doesn't use template elements. It just uses functions and template literals. The function returns a string, which gets dumped into an existing element's innerHTML. Or, a new div element is created to dump it into. Re-rendering is pretty quick that way.
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.
This is begging for injection attacks. In this case, for example, if parsed_text and filtered can contain < or &, or if post.guid or post.avatar.thumb can contain ", you’re in trouble.
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.
Yeah, OPs code is asking for pain. I suspect there are now developers who've never had to generate html outside the confines of a framework and so are completely unaware of the kinds of attacks you need to protect yourself against.
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.
Thus recreating Perl’s taint mode. Everything new is old.
Posts are sanitized on the server side. This is client side code.
Server-side sanitization means that your view code is inherently vulnerable to injection. You'll notice in modern systems you don't sanitize data in the database and you don't have to manually sanitize when rendering frontend code. It's like that for a reason.
Server-side sanitization and xss injection should be left in the 2000s php era.
How do you update the html when something changes? For me, that's the most interesting question for these sorts of micro-frameworks - templating HTML or DOM nodes is super easy, but managing state and updates is hard.
I find the coroutine/generator approach described in a series of posts by Lorenzo Fox/Laurent Renard to be a promising alternative[0].
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/#:~:...
[1]: https://observablehq.com/d/940d9b77de73e8d6
[2]: https://github.com/lorenzofox3/cofn
I call printPosts with the new post data. It rewrites the whole chunk in one go, which is pretty snappy. I haven't decided how I'm going to handle more granular updates yet, like comment count or likes.
Yeah, that's a pretty common approach. Unfortunately, browsers aren't very good at doing patch updates, so it'll completely reset any UI elements in the region being rerendered.
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.
I prefer something like this before building the template string.
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.
I like it. Not only does it move the UI into JavaScript, but it moves the scripting into the HTML!
Have a feeling this will lead to XSS vulnerabilities though.
I've been working on https://deja-vu.junglecoder.com which is an attempt to build a JS toolkit for HTML-based doodads that shares some ideas with this.
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.
On my first official job after college I was working on making a web version of a Delphi software. The team was already on their third rewrite of the front end cause they had to change frameworks. I made the cass that we should write our own framework, so I prototyped FOS (the components I use on my website) to prove my point. The team (a bunch of mostly Delphi programmers) did not like my suggestion. Anyways, soon after that another company made me a better offer so I left. Years went by an I finally take a shot at another framework: tiny.js [1]. I've been using it in all my personal projects so far. I'm particular proud of the ColorPicker [2] component I wrote that I've used in two projects so far. As you can see, one can argue that tiny.js it's not a framework at all, just some wrapper functions that helps you create Functional components.
1 - https://github.com/victorqribeiro/TinyJS
2 - https://github.com/victorqribeiro/Chip8js/blob/master/js/Col...
I dont know... I kind of like diffrent look of HTML and JS. At least you know what is what. In tiny evrything looks like JS and you actually have to read it to know what is what. Also what if someone will define span variable? Does it override the span HTML component function?
Otherwise looks like nice.
You might want to look at something like morph Dom to keep input focus when you have to re-render the form, for example.
Might be feasible with the advent of vibe coding. For frontend heavy applications can choose this route for performance reasons. Starred, will follow the project.
I like to prompt Claude to create artifacts in plain HTML, CSS and JS. I like the portability and hackability of these. React is too heavy for a lot of simple ideas even if reactivity is needed.
Why not use Web Components? Is it because they’re classes?
I think it’s because that repo is from 7 years ago, when browser support[1][2] for components wasn’t as widespread or comprehensive.
[1] See the history section of https://en.m.wikipedia.org/wiki/Web_Components
[2] https://caniuse.com/?search=web%20components
I program for roughly two decades now and I never got warm with frontend frameworks. Maybe I am just a backend guy, but that can't be it since I am better in vanilla JS, CSS and HTML than most frontend people I have ever met.
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.
...eschews abstractions...
Java script
Something is wrong with web developers culture, cause even in framework-free vanilla mode they cannot get rid of data localization and welding the data and "component" trees together irrepairably.
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.
People like to hate on PHP, but PHP provides you with all the tools you need to write a fully working backend, where as JS provides you with half-assed solutions for writing frontend, which is why we have 1000 frameworks and we still can't agree on how to write frontend code. Seriously, we don't even have a convention for writing a simple reusable component with vanilla JS, everyone makes up their own thing. Web components were supposed to be that, but they're a good example of what I meant by "half-assed", because they're ugly, verbose, clunky, don't really solve the right problems, and nobody likes writing them.
I don't think PHP is any better in solving the backend, than JS is in solving frontend. On the Frontend the situation is not ideal, but we made big leaps every let's say 5 years, going from jQuery to React and from React to later generation frameworks like svelte / solid etc. Yes, the landscape is fragmented and there are maybe too many options, but you make it sound like PHP is universally used as the backend solution, while I see it being used little these days except for legacy systems from 15-20 years ago.
> you make it sound like PHP is universally used as the backend solution, while I see it being used little these days except for legacy systems from 15-20 years ago.
Your eyes deceive you.
https://finance.yahoo.com/news/exclusive-laravel-raises-57-m...
I never said that PHP was universally used, just that it has answers to most problems.
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.
That is why I made my peace with Next.js.
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.
While I chafe at some of its decisions, you're still correct. It's the only thing really in that space that's fully featured enough.