VanJS – A no-JSX framework based on vanilla JavaScript
vanjs.org
vanjs.org
const {circle, path, svg} = van.tagsNS("http://www.w3.org/2000/svg")
const {math, mi, mn, mo, mrow, msup} = van.tagsNS("http://www.w3.org/1998/Math/MathML")
Could be expressed like this instead: const {circle, path, svg} = van.tagsNS(van.SVG)
const {math, mi, mn, mo, mrow, msup} = van.tagsNS(van.MathML)
... then I looked at the code at https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.2.7.m... and realized that adding those constants would increase the size from 1729 to 1806 bytes - a 4.45% increase for a feature most people would never use.This thing is tight!
Just for fun, I ran their minified code through ChatGPT to see if it could deobfuscate it. It did a pretty good job of guessing sensible variable names for things: https://chat.openai.com/share/9dac0b13-7b24-409a-9cc1-494667...
Svelte 5 builds upon these signals / derivations as well, and I guess a bunch of other libraries.
It would be time for them to get together and standardize on a signals library at this point that could be adopted to be a web standard and help interoperability between web components built with different frameworks.
Solid's attempt feels more promising since it's building on existing native functionality (proxies / getters), and has intuitive, low-footprint usage (just function calls and reference comparisons).
Edit: It seems I'm not up to speed in fact, I hadn't seen Svelte 5 and runes yet, which are not compile-time. I will need to give it another try. I agree in general that signals and signal-based APIs are the future and definitely better than hooks at least, and a single agnostic standard would be good for everyone.
I also have often have smaller deps lists in React's useEffect when wanting to call a callback on the props because of some local state changes. I wouldn't want the effect to fire because the callback changed however. I'm not sure solid even supports that in its createEffect since it is all automatic.
JSX manages to feel just about right: it's plain-old-HTML when you want it to be, and plain-old-Javascript when you need an out.
Still... I wouldn't mind writing in a truly standard language. You're "really" writing the DOM, and HTML and DOM are this godawful bastardization of a user-interface description language. I'd be slightly less grumpy to have a good DOM-writing library, and deal with its ugliness, rather than mix-and-match the domain-specific-language in with my actual coding language.
I'd written myself a good Java version of this type of thing, and I found it kinda pleasant -- especially since it let me do HTML structure checking via the Java type checking. Typescript is -- God help me -- actually a pretty good language, and I'd be OK with actually writing my user interface structure in it, with the help of a good library.
At least then I'm writing in only one language. That saves me time to go out and learn the godawful bodge of CSS to make it look pretty.
https://en.wikipedia.org/wiki/HTML5#XHTML_5_(XML-serialized_...
If that's true, that means <div /> is not valid XHTML5 because 1) trailing slashes don't close tags in html and 2) div elements require a closing tag.
And if that's true, then it's still different from JSX. But this XHTML5 thing might be surpassing my comprehension.
Almost. You would need to make sure to write polyglot markup [1].
> And if that's true, then it's still different from JSX
Absolutely.
If you're not a daily JavaScript developer, any form of transpiler / build mechanism seems almost guaranteed to break in the gaps between when you are working on a specific project.
The projects I have that are transpiler/build free are SO MUCH more pleasant for me to intermittently hack on!
1. How easy is it to support loops? 2. How easy is it to compose (defining small components that can be used in larger components)? 3. How easy is it to make the UI reactive to client-side state changes?
> because it's not better than the alternatives
What is the alternative you're refering?
I did write myself EXTENSIVE notes such that when I return to that project in the future I have a fighting change of figuring out how to build it again: https://til.simonwillison.net/github-actions/vite-github-pag...
And you could clean up the GitHub Actions file by just passing the base url to your build script, IE `BASE_URL="/${{ github.event.repository.name }}/" script/build`.
I mean no offense, these notes aren't that extensive, and most of the content is for setting up GitHub Actions / GitHub Pages. npm install / npm run dev / npm run build aren't very esoteric, and can all be documented inside the repo.
I'm actually a little shocked to find out you're a co-creator of Django.
Thanks for the BASE_URL environment variable trick - that was very non-obvious to me! I found the documentation for that just now: https://vitejs.dev/guide/env-and-mode
I'm not sure what you mean by "trim down" those notes - if anything I'm always looking for ways to bulk this kind of document up, in order to capture as many of the details of how things work as possible.
I tried the script/x pattern for a bit but I've found that Justfiles fit my brain better - I have a TIL about those here: https://til.simonwillison.net/django/just-with-django
I experimented with VanJS integrating into Astro. No plugin was needed & getting the SSR HTML is as simple as calling `.render()`.
I wrote a fork of VanJS called relementjs & took this concept further, using `.toString()` instead of render. Now SSR components can be rendered inside a template string.
` <div>${my_component()}</div> `
I personally find XML much easier to parse than a lot of nested callbacks and I especially like that content is always inside tags, rather than another argument as part of the library's functions. p("Hello, world") is readable enough, but as soon as you start adding html attributes, it becomes less legible to me:
li(a({href: "https://vanjs.org/"}, "VanJS"))
vs.
<li><a href="https://vanjs.org/">VanJS</a></li>
I'm certain some people probably prefer the former over the latter, but I much prefer how JSX/XML reads. I can immediately tell where the content starts and in formatted code, I think it's a lot easier to see visually where things start and end, because of closing tags, instead of just a parentheses.
li(a({href: "https://vanjs.org/"}, "VanJS")/*a*/)/*li*/
There are some oddities with react but wow jsx makes my code clean and easy
Preact claims 3kb on their site, and for a 3-4x savings you're going to need to be fairly compelling. I don't mind paying a modest cost for 10x or 100x improvements, but (call it Stockholm syndrome) I like react syntax
In solid there's also managing batching updates to reduce rerenders since it eagerly updates dom. It feels like one needs to be more meticulous
Doing a=jsx and interpolation does NOT touch the vdom in any way.
VDOM only comes into play for jsx returned from components before being rendered to screen.
XML gets all the hate for being overly verbose (and for good reason [1])
however with HTML syntax, which is almost the same, everybody seems to be just fine...
[1] XML is sooo nineties, the "modern" developer uses markdown. inb4 S-Expressions
</rant> (...yes I know)
Some people feel that XML is great to represent nested structures/trees, and that trying to do that with code is less readable.
It's about getting rid of a flawed syntax.
But it'll probably never happen because people are used to it and oblivious to change.
Unless of course if one of the FAANG publishes it as their New FrameWork [tm], then everybody will jump on it, and people that don't are seen as "not modern".
React and JSX were needed in 2013, but with the power and convenience packed into new ES standards and supported by modern browsers, React and JSX should be viewed as bloated polyfills at this point.
It’s really heartening to see things trending back toward single script tags that help you avoid build tools, churn, and dependency hell. To that end, I’m leaning toward htmx for my own project, but what you’ve come up with looks really enticing as well.
Dev: "All the legacy crap is too bloated obnoxious to use and has to much overhead, packaging, and training required to do even smiple things. I'm going to invent a simple lightweight framework from scratch that uses first principles to stay clean."
Time passes framework starts to get used
Random user: "I need to be able to dynamically modify table spacing in Elbonian based languages, whilst allowing for non-standard viewports to dyanmically load partial DOM changes while asynchronously displaying a cat falling over gif animation and the only way to do it in the framework is a bloated hack, any serious enterprise tool needs this, until you get it this is just a toy"
Dev: "Well I guess that makes sense"
Time passes: Hundreds of these random edge cases pop up, og dev is tired, the project gets moved to a committee, usually some drama pops up for several reason that have more to do about posturing than the framework itself
New dev: "This framework is bloated complicated and has too many dependencies and layers of abstraction. After all what purpose on earth is there for me to have to configure whether or not I am using Elbonian derived language encodings that nonsense. I'm going to write my own framework that is lightweight and simple and does what I need it to without all this extra crap"
IT'S THE CIRCLE OF FRONTEND!!
AND IT MOVES US ONWARD!!
Love CLJS though, I use that sometimes for personal work when I have an urge to lisp.
In case anyone is interested, here are my forks.
https://github.com/relementjs/relementjs
https://github.com/rmemo/rmemo, which exports https://github.com/ctx-core/ctx-core/tree/main/rmemo. Note that rmemo also supports server side reactivity.
The stargazer example leaves me with questions like- are components both async and non-async? What if the component re-renders due to other state changes, is my network request fired more than once? Do I have a "component coloring" problem where once one subcomponent is async, the entire parent hierarchy has to be?
Im sure there's answers to these questions if I read the docs, but as a curious passer-byer an example mirroring this common ui pattern would answer a lot of questions for me!
import { useEffect, useState } from "react";
const GithubUser = ({ login, avatar_url, html_url }) => (
<div>
<img src={avatar_url} style={{width:40, height:40}}/>
<a href={html_url}>{login}</a>
</div>
);
export function App() {
const [stargazerResp, setStargazerResp] = useState([]);
const [repo, setRepo] = useState("vanjs-org/van");
useEffect(() => {
fetch(`https://api.github.com/repos/${repo}/stargazers?per_page=5`)
.then((r) => r.json())
.then((r) => Array.isArray(r) ? setStargazerResp(r) : setStargazerResp([]));
}, [repo]);
return (
<div className="App">
<input value={repo} onChange={(e) => setRepo(e.target.value)} />
<ul>
{stargazerResp.map((s) => (
<li key={s.login}>
<GithubUser
login={s.login}
avatar_url={s.avatar_url}
html_url={s.html_url}
/>
</li>
))}
</ul>
</div>
);
} const Stargazers = () => {
const stargazers = van.state([]);
fetch(`https://api.github.com/repos/vanjs-org/van/stargazers?per_page=5`)
.then((r) => r.json())
.then((r) => stargazers.val = r);
return ul(
stargazers.val.map((s) => li(s.login))
);
};I found this statement to be confusing; https://vanjs.org/tutorial#state-val-is-immutable While you can update State objects by setting the val property, you should never mutate the underlying object of val itself.
Then beneath is an example of the following;
const text = van.state("VanJS")
...
input({type: "text", value: text, oninput: e => text.val = e.target.value})
Which looks like a mutation - after reading a bit more around it is clearer that .val has a setter; but at a glance it just isn't obvious what is happening which I feel isn't intuitive.HTMX, LiveView, WASM, Dart, ClojureScript, Kotlin, ScalaJS, Dioxus -- There are lots of options folks. We don't need to surrender to the tyranny of JavaScript.
after trying react for years I now switched to htmx with a simple backend, until there is a simpler to learn and use SPA(no SSR please) somewhere.
Your code only executes when needed, and only that specific chunk of functionality.
If anything, I wish we could force the marketing team to compress their images better.
Your local mom-n-pop laundry place's website probably doesn't need these types of efficiencies, but if you're running a large ecommerce site, time-to-interactive is an important metric which can cost a lot of money.
> (A rule that can be told by words, is not the rule that should universally apply)
I'm curious where the dev got that translation or if they made it themselves. I have translated it before like "No method that can be explained is universally applicable" but I haven't seen many other translations like that.
1. There might be some universal rules about the world. 2. There are limitations in human's understanding and human language. 3. For all the rules that can be described in human language (which is subject to the limitations in 2), they can't hold universally.
Putting into the context where the words are cited. Any claim (in human language) about programming like "you should program in this way" is most likely not universally sound.
Of course a philosophy book 2000+ years ago can be interpreted in a different way. Happy to see the elaboration of your understanding "No method that can be explained is universally applicable".
Have people forgotten about event delegation? Surely the number of event listeners can get excessive in some cases.
Also, shameless plug for my own micro JS UI library. No signals, just regular JS variables: https://github.com/kevinfiol/umai
Looking at my own use of solid, things this is missing are:
- jsx
- stores (having some way to hook into van's reactivity would address this, maybe digging into van.state internals there's a way)
- onMount/onCleanup
- For/Index/Show which helps handle efficient reuse, this could probably be built as a layer on top of vanjs
I don't agree, this syntax for html is much nicer than JSX when you get used to it. Converter from HTML at https://www.solenya.org/convert
I don't mean to throw any shade on Tao. He is very talented & his approach is beneficially novel. I just don't think he as much experience what life is like as a full stack (or front end) developer as many here.
I forked his work to create relementjs & rmemo. Tao didn't want to support server side reactivity & I heavily use it. Also, Andrey (maintainer of Nanostores) didn't want to add my PR for autosubscriptions due to the complexity cost that would be added to Nanostores...So I wrote my own set of libraries. I hope library simplicity trends more because it is very nice to work with simple libraries.
Tao's approach is sound. He just relies on the DOM to manage the reactive state, so SSR reactivity would require a different approach. I ended up using WeakRef, which is significantly smaller than the approach used by traditional reactive state management libraries.
<DoStuff> args </DoStuff>
vs
DoStuff(args)
<DoStuff args={...} />
The {...} syntax is a non-standard extension to XML/HTML I think MS came first up with back in the day for XAML (WPF).
It's a hack to "fix" a shortcoming of the base language.
It does not compose, i.e. :
<DoStuff args={<DoMoreStuff args={...} />} />
does not work.
<DoStuff args={...}><DoMoreStuff args={...} /></DoStuff>
Still more readable than brackets hell like:
DoStuff(DoMoreStuff(DoEvenMoreStuff()))
<DoStuff args={...}><DoMoreStuff args={...}> <DoEvenMoreStuff args={...}/> </DoMoreStuff> </DoStuff>
vs DoStuff(DoMoreStuff(DoEvenMoreStuff()))
Both of them you would split into indented lines when they become too long. And 1 becomes too long much faster.Also with 2 you can do
const todo = DoMoreStuff(DoEvenMoreStuff());
DoStuff(todo);
where as with 1 you cannot. const todo = <DoMoreStuff args={...}><DoEvenMoreStuff args={...}/></DoMoreStuff>;
<DoStuff args={...}>{todo}</DoStuff>which is another one of its flaws.
Same as with regular code and functions — overly deep nesting screams bad code.
Honestly, JSX is just a verbose way of calling functions / constructing objects, which is probably OK if it was a language of its own (XML/HTML) — and even then, JSON/YAML won over XML — but makes little sense being embedded in a language which had functions & object literals from the get-go (JS)!
The only reason JSX exists is because it was a cool marketing feature to convert existing PHP/HTML developers to React. And React succeeded mostly because of that. Not because JSX is cool technology-wise (it isn't). But because it hit the right spot in the right time.
Either you should statically/server generate your site, or you have rich content that needs a client side app and you should pick a framework with rich features.
Evidence?
It took me a minute to get it.
0 bytes uncompressed, 25 bytes gzipped
25 bytes is the size of a gzipped empty file.
<script>
const Counter = () => {
let counter = 0;
const updCounter = (delta) => {
counter += delta;
document.getElementById('count').innerText = `♥ ${counter}`;
};
document.getElementById('up').addEventListener('click', () => updCounter(1));
document.getElementById('down').addEventListener('click', () => updCounter(-1));
updCounter(0);
};
Counter();
</script>
<span id="count"></span>
<button id="up">*thumbs-up*</button>
<button id="down">*thumbs-down*</button>
15 LoC vs 7 LoC, zero dependencies vs. 0.9kB gzipped or 2.7kB minified.
Additional learning curve: 0h vs 1h (allegedly).Honestly, I am biased and think Vanilla JS wins :D
This would lead to unexpected results. So you have to be careful with state management, regardless of whether it's automated or not. There are similar pitfalls, including performance issues, with any state management system, hence dedicated state management solutions like Redux exist to address this.
The core of the argument is that complexity sometimes cannot be avoided. You can quickly wind up with moving this complexity elsewhere, e.g. by pulling in an additional library dependency. This results in having to learn, master and manage additional dependencies. Whether that's fundamentally better than the explicit and straight forward way depends on the scope of the project and its specific requirements. There's no silver bullet in any case and pros and cons with every approach.
Complexity can't be avoided for extremely complex use cases. But that doesn't mean a simple solution that can work for most of the use cases has no value.
count.innerText =
up.onclick =
down.onclick =
etcAnyone can build a "reactive" type framework by following tutorials online now, they're super in vogue.
Is my cynicism here warranted, or am I just jaded from 20 years of watching pendulums swing left and right and watching the wheel be reinvented over and over?
Consider Hackernews: hn.js comes with 21,4 kB. The Y logo on the upper left is a 46,32 kB SVG!
What should i care about 1kB minimal framework or 85kB all-included framework? Skillset, maintenance burden, compatibility matter.
hn.js is just north of 5.2kB (2.3kB compressed), and the logo is 315 bytes.
But at one point our big contenders were also shiny and new, and I am glad someone less cynical than me was willing to put them to the test.
But the better reason why these projects exist is for experimentation and iteration. It's not like everyone who uses React will switch to this over the holidays, but maybe in five years some of the ideas in here will become more mainstream, and a future version of React (or a different framework) may adopt them.
But something like Decentraleyes prevents site A from seeing what resources you requested on site A.
(Or rather, whichever CDN provider site A is using.)
You could have both at the same time, but they are orthogonal. As for why it's not in browsers, assuming good intent, I'd think it's because it requires you to bundle a whole lot of libraries with the browser for it to be useful as a local CDN. If browser vendors decided which JS frameworks are bundled with the download and which ones are not, I'm not sure if that would help decentralisation!
I don’t think that’s the intention of the author or the person who posted this or the people who voted it to the top of HN
So long as JavaScript is interpreted it matters a whole lot on mobile devices.
The amount of code delivered and executed on the device has a huge bearing on performance on lower end devices. Unless you're on some $1000+ phone and arguing from a place of privilege.
Yet another example of language evolving through variations in common use that take hold.
--
[0] literally literally, not figuratively literally