JavaScript Is Enough(geajs.com)
71 points by arbayi 19 hours ago | 19 comments
ludwigvan 48 minutes ago
React hooks always struck me as object-oriented programming reinvented through the back door of functions. We started with pure components, decided we needed state after all, and ended up with magic functions that stash and retrieve state from some hidden context — essentially re-deriving this with worse ergonomics and an implicit ordering contract. Some part of it was the functional language paradigms like immutability that were popular elsewhere at the time bolted on to JavaScript.

What I find refreshing about Gea is that it doesn't fight the language. Stores are classes. Computed values are getters. State mutation is just assignment. I've been waiting for a framework that embraces the actual paradigms of the language it's written in, rather than inventing a parallel universe of conventions. Excited to try this one.

That said, I'm genuinely curious where the edges are. Was React's complexity accidental due to its architecture or was it the price of solving genuinely hard problems (concurrent rendering, suspense boundaries, fine-grained error recovery) (which by the way most consumers of the library did not care that much about)?

Does Gea's simplicity hold up as apps get complex, or will we eventually hit patterns where the escape hatch is the complexity React already internalized?

bluegatty 44 minutes ago
Thoughtful.

I suspect everything about react seems like a backflip to get access to things we kind of needed in the first place.

Reacts complexity I believe are due to the fact that it's an overlay onto a system with high impedance mismatch - only then some degree of inherent complexity.

dashersw 32 minutes ago
Thank you for the thorough comments! I wholeheartedly agree. And while I believe React invented most of its problems (mostly because we've been engineering GUI solutions since mid-70's and, as I always say, Excel, the god of all UI apps, shipped in 1985) I also acknowledge modern problems like suspenses that occur as a result of elevated expectations.

Gea is frankly very new, and for example doesn't ship a solution for suspenses or fine-grained error recovery yet. And since noone, including me, built a very complex Gea app yet, we don't exactly know if the simplicity will hold up.

But Gea is the 3rd generation of my frontend frameworks, and I've been building vanilla JS-esque frontends since 2011 (when I released my first library, tartJS). The main feature of a good framework is to contain code complexity as the app grows and I believe as GUI engineers we have some good patterns to flatten the complexity. Gea is just trying to hide repetitive DOM updates behind a compiler, and while it has proven somewhat difficult to account for all the different ways people (or AI) write JavaScript, I'm constantly improving the compiler with new patterns.

That's why Gea ships with several GUI app implementations—my approach is kind of simple. If I can get AI to generate several different UI apps in different domains, I can capture a good enough set of complexities for which I can deliver solutions. I've already fixed tens of bugs that I could only see as a result of building a UI with the library.

Having said that, it's still very early for Gea. I guess only time will tell if we will have to resort to different, non-idiomatic solutions to handle more complex demands. At least the philosophy is very clear—Google built its original web 2.0 apps with Google Closure Library, an imperative UI framework, with lots of repetitive boilerplate. And that was enough to give us maps and google docs, etc., so I am hopeful for the future that we will be able to find idiomatic solutions.

bythreads 4 minutes ago
Would love to se tagged template Literals and w eb components as first citizen in this - lit.dev etc.
wg0 31 minutes ago
You can now use React Compiler and there would be no virtual DOM.

Already being used in production at large code bases.

puskuruk 20 minutes ago
React Compiler(originally react-forget) is a build-time tool that automatically optimizes your React app by automatically memoizing your code. It only replaces the manual ceremony of useMemo, useCallback, and React.memo with automatic caching at compile time. So, it's fixing the problem they introduced by React itself or you we can call it a "trade-off" in a sense
nine_k 18 hours ago
Disclaimer: I only read description, did not try to code.

What I like: the smart compiler that determines the actual dependencies, no need to declare them. Apparently the compiler is so smart as to compute the DOM diffs at compile time, which eliminates the need for virtual DOM.

What kills it for me: the two-way binding. The binding should be one-way to preserve your sanity as the project grows. Two-way bindings allow to build highly reactive Ruby Goldberg machines where anything can trigger anything else, and you won't know, because it's just a mutation of a property somewhere, indistinguishable from a non-reactive mutation. Two-way bindings are callback hell squared.

I want one-way data binding, immutability, and basically FRP. The biggest demonstration of FRP's immense real-life success is not React. It's the spreadsheet.

This may be good for small pieces of interactivity. But I likely would go for HTMX for that.

dashersw 10 hours ago
Hi, the author of Gea here. I have a long history of thinking about one- and two-way bindings, and I believe JavaScript as the language has a great solution to this. If you pass an object to a function, it's two-way bound, and if you pass a primitive, it's one-way bound. So I built Gea to replicate this. If users choose to pass an object in a one-way bound scenario, they could create a new object and pass that in, and it would work.

By the way, just as a syntactic sugar, Gea supports function components, too.

nine_k 2 hours ago
Thank you for your project, it's elegant! It's pretty obvious that one-way binding is a two-way binding with one of the roads not taken. I see that e.g. a careful naming scheme could make it obvious what is reactive, and what is not.

OTOH React arrived where it's now not by allowing a particular approach, but by enforcing it.

dangoodmanUT 34 minutes ago
This clearly AI generated website is such a turn-off.

I get that AI can be good at making websites, and someone might not care to spend a lot of time on it, but a website that looks like a slightly modified version of a generic "make an X landing page" from gpt-5.3-codex doesn't scream "I care about what I just made".

Just go super reductionist like planetscale did and have partially rendered markdown, don't put twinkling stars in the background

dashersw 27 minutes ago
As the author... I somewhat agree :) But I really like synthwave and ever since I came across the design trend I wanted to use it somewhere. And I put twinkling stars on purpose, there's even shooting stars if you wait enough. I understand it comes across as generic AI slop, but this is an early project and it will evolve. I will work on a planetscale-style webpage and maybe I can add it as an option you can toggle on :)
darepublic 1 hour ago
React is just JavaScript. Also vanilla JavaScript is just JavaScript
dashersw 55 minutes ago
React has its own idioms like hooks, and virtual DOM operations. A developer well-versed in JavaScript and who never saw React before wouldn't know how `useState`, or a `Context` would work. They wouldn't know that components would re-evaluate and re-render all the time. They also wouldn't know they shouldn't put useState inside a conditional statement. They wouldn't know they would need a useEffect, or that it depends on an array to declare its dependencies.

Vanilla JS, on the other hand, requires a good knowledge of DOM APIs.

Gea tries to be as close to plain old JavaScript as possible, the way we write it on the backend. The only necessary notion is that everything is reactive and DOM will update automatically as component/store members change.

skyturkish 1 hour ago
There are many ways to write JavaScript, for example by destructuring variables, etc. How do Gea proxies work to retain reactivity then?
dashersw 50 minutes ago
This is one reason Gea has lots of examples in the repository. It's an area of active development—I'm working on adding compiler support for more and more patterns. Basically, since we can analyze the code statically, and follow the dependencies even if they are destructured, we can create proxies or special handlers for each situation. It's a tiring job and unfortunately there's no one global, easy solution, especially as you pointed out, some of these patterns don't work in proxies. But I believe we cover an important base of idiomatic JavaScript right now, and I'm continuing with new releases to improve the compiler's handling on more exotic ways to write JavaScript.

Of course, one down-side of the compiler approach is, for example, if there's a statement you want to make reactive whose signature is only resolved in runtime (like a computed property name) it's practically impossible to wire. But Gea exposes enough of the underlying component structure so it's kind of straightforward for a developer to manually write an observer for these cases, and do the updates to static properties (that are rendered) in runtime.

Hope this clarifies the approach.

ch_sm 1 hour ago
hey! Great job on this, dashersw. I‘ve believed for a while now that the compile time dependency analyzer approach is the only good way for frameworks like this. Really neat choices on the API surface as well – so simple! Launching with a headless UI lib is smart. will try both in a side project soon! thanks and cheers!
dashersw 1 hour ago
Thank you! Honestly I tried building a replica of shadcn but that didn't prove to be viable. I thought it would be very difficult for developers to adopt without a first-party UI library so built one on top of Zag.js. And I expect a lot of AI-assisted coding sessions to happen with Gea so I also baked in enough AI skills to enable assistants, as well as migration guides for React. Hopefully these will help onboarding early adopters. Please let us know when you give Gea a try!
puskuruk 1 hour ago
How does this thing run so close to VanillaJS without carrying any extra baggage along for the ride?
dashersw 1 hour ago
I personally don't believe anything can beat hand-built and fine-tuned vanilla JS code, and in the benchmarks we see Gea beating vanilla JS implementation (for example in partial update or swap rows). One reason for this is Gea's compiler has special cases like row swap's to be as minimal calculations and DOM operations as possible. The compiler, also, is evolving to recognize more and more patterns, and compile them into a miniscule overhead.

One thing I borrowed from my earlier library erste is event delegation. Instead of creating event handlers bound to each DOM element (say, in a list render) which is memory-heavy and also consumes a lot of CPU cycles, Gea simply attaches one event listener per type on the body and uses a `.matches()` call to check whether that event applies to a given DOM element. This is one of the main reasons why Gea is so performant—there's no excess/unnecessary memory allocation or CPU cycles. This is also reflected in the benchmark results.

puskuruk 41 minutes ago
Thank you for your detailed answer!
cattown 18 hours ago
Two-way props! Yikes! That was a mess in the first version of Angular. I thought the consensus was that two-way props binding just opened the door to difficult to understand side-effect laden code.
dashersw 10 hours ago
The bindings in Gea work just like in JavaScript. Two-way if an object is passed, one-way if a primitive is passed. I think it's best to stick to the idioms of the underlying language.
anamexis 1 hour ago
I don’t know if I’d call it an idiom — rather I’d argue that in modern JavaScript, mutating passed objects is often an anti-pattern.
dashersw 1 hour ago
I personally agree. But the default expectation (and therefore the design) should follow the practices of the language. If JS allows mutations on the objects passed to a function to be reflected on the parent, I believe frameworks should follow this paradigm.

And in the end in Gea developers have full control over this, just in the same way they do in real life. `child({ ...obj })` easily solves this, for example, in both idiomatic JS and in Gea.

anamexis 34 minutes ago
> But the default expectation (and therefore the design) should follow the practices of the language. If JS allows mutations on the objects passed to a function to be reflected on the parent, I believe frameworks should follow this paradigm.

Why? Why should frameworks be beholden to the mutation semantics of the language, particularly with JS where there is no choice of language in the browser? Why should frameworks follow this paradigm?

mpalmer 17 minutes ago
You're using various terms to refer to concepts which are similar but distinct, and it's confusing the issue a bit.

> But the default expectation (and therefore the design) should follow the practices of the language

Languages do not have practices, developers do.

Regarding "idiom": core language features/semantics are not idioms. In programming, "idiom" usually refers to small commonly-used patterns that reside atop the language. "Mutating objects" is not an idiom, if only because I can think of any number of non-idiomatic uses of mutation.

> If JS allows mutations on the objects passed to a function to be reflected on the parent

JS "allows" mutations on objects to be "reflected" elsewhere, because that's how mutation works. If JS had to support scoped mutability at the language level, the language would be significantly more complex.

But this implies nothing about the value or advisability of using mutation and two-way binding in an application framework. That is a choice on which framework authors usually land on one side or the other.

It seems that by more or less equating "idiom", "practice" and "paradigm", you're opting out of the sorts of choices that not only distinguish web frameworks, but simplify the patterns involved in building with them.

wetpaws 1 hour ago
[dead]
kikimora 10 hours ago
Why? Instead of creating an event handler that changes a property you declare same thing as binding. Why this creates more issues than manual event handlers?
jinushaun 16 hours ago
Two way props make for a nice demo, but are a nightmare to maintain.
tkzed49 18 hours ago
> Solid has signals and createEffect... Gea takes a different path. It introduces no new concepts at all.

proceeds to introduce Stores and Components

what makes this magically easier than Solid, or any other Proxy-based reactive store frameworks?

dashersw 10 hours ago
Stores and Components are basic classes that don't introduce any new concepts (other than the fact that the JSX goes into the template method of the Component, and that they are reactive behind the scenes). There are no hooks like useState, and the design philosophy is that everything should feel as native and natural as JavaScript.
tkzed49 2 hours ago
you continue to contradict yourself by introducing concepts and saying they are not concepts.

I get what you're trying to say, that React hooks have special semantics, and that your abstraction feels more "native".

again, not sure how this is more "native" than Solid signals, just as an example.

dashersw 1 hour ago
You are bringing up an important topic. The way I see it is that Gea's Store is a plain old JS class. It's just a native class. There really is no special syntax you need to pay attention to. Whereas Solid signals require you to follow a specific syntax and approach, and has its own gotchas. Like, the language doesn't have a createSignal method by default, and you don't "execute" what look like values in JS as you need to do in Solid, and although I'm looking forward to the official Signal API, Solid isn't following that either.

That's basically how Gea is more native, because stores are plain classes. I hope this clarifies my point a little bit more.

mpalmer 5 minutes ago

    It's just a native class. There really is no special syntax you need to pay attention to.
Don't confuse syntax with code. Solid has no special syntax (other than JSX of course).

This isn't comparing apples to apples.

Solid has a Store primitive too, and it's a "plain old" proxied object.

How is `createStore` less native than `new Store()`? The `new` keyword didn't even exist in JS until 2015, and the underlying semantics (prototypical inheritance) never changed.

One of Solid's design goals is fine-grained reactivity for very high performance. That's why signals are getter functions, because calling them alerts the system to the precise "scope" that will need to be re-run when the value changes.

Since signals are functions, they can be composed easily, and passed around as values.

slopinthebag 36 minutes ago
What is the difference between mobx or solid stores or any of the reactive frameworks that do reactivity on proxy objects?
dashersw 2 minutes ago
Solid stores are a great improvement over raw signals, but they still come with their own gotchas. First off, it's an entirely new syntax. You need to learn its documentation. You always have to use setStore, and it has a weird syntax like `setStore("users", 2, "loggedIn", false)` and even pushing items to an array is weird. In Gea it's just regular JavaScript: `this.users[2].loggedIn = false` or `this.users.push(...)`. MobX also comes with its own syntax.

In the end Gea is basically as simple as a plain old JavaScript class, made reactive by its compiler.

slopinthebag 37 minutes ago
At first I thought this was using a compiler to figure out data dependencies on regular javascript objects, including built in's on the window and coming from third party dependencies, so you could do this:

   <div>window width: {window.innerWidth}</div>
But it's not, it's just on objects that subclass a Store.
mpalmer 47 minutes ago
The contrast of most of the text against the background is not accessible.

I'm not overly impressed with the claim "Faster than Solid" when only figure presented on the hero chart is the geometric average of the Duration scores for each framework.

Digging into the individual metrics, Solid is well within the margin of error on essentially every metric to tie or even beat Gea.

On top of that, Solid beats Gea handily on bundle size, 1:4 uncompressed and 1:2 compressed.

So at best, Gea is a tie on speed with Solid, the bundle is bigger, and even time to first paint is a little worse.

dashersw 12 minutes ago
Solid is honestly a beast, and I love it! It was a great challenge to even match its performance, let alone beat it. While on several metrics they are within margin of error, select row, swap rows, remove row, and clear rows performances are significantly better on Gea's side.

Having said that, pure performance wasn't the goal as much as the developer experience. I wanted to build something that felt _native_ to the language.

And thank you for the comment on accessibility—I just updated the website and the docs to make the text more legible.

aappleby 18 hours ago
You wrote and shipped this in three days, eh?
tredre3 18 hours ago
It was likely almost entirely AI-generated but there are two oddities:

- MIT — Copyright (c) 2017-present Armagan Amcalar: It would be an interesting bout of hubris to give yourself a copyright that predates the beginning of the project by 9 years.

- The README lists sizes as "kb" rather than "KB": I find it odd that it would get units wrong unless it was specifically instructed to do so?

dashersw 10 hours ago
Hi, the author here. Gea is built upon erste and regie that I released in 2017 and 2019. Hence the copyright. I thought a lot about this, also thought of releasing it as a new version of erste, but it would be too big of a change and re-imagination, and I didn't want to introduce the dichotomy and stress introduced by Angular 2.
marssaxman 15 hours ago
That is a remarkably pedantic nitpick. If you're going that far, you should really be complaining that it's not "KiB".
beardyw 11 hours ago
I don't think it's pedantry, it's looking for clues as to the age of this.
dashersw 10 hours ago
Hi, the author of Gea here. Gea is built upon the principles introduced by 11 years of history, based on all of the UI frameworks I have authored so far, the most recent of which are erste and regie from 2017-2019. And Gea itself is written over 6 months, the history of which is compressed in the initial commit as I don't believe in populating Github history with crappy commits that alter design decisions and introduce breaking changes. You can imagine Gea was closed source before the 1.0.0 launch, and made only open at the end.
arbayi 18 hours ago
not the author of the gea but from what I can see in the readme, the ideas go back to 2017, erste.js and regie were earlier versions of the same concept.

https://github.com/dashersw/erste https://github.com/dashersw/regie

afavour 18 hours ago
> The Vite plugin analyzes your JSX at build time, figures out which DOM nodes depend on which state, and wires up surgical patches — invisibly.

This part interests me… if it’s able to be brought to React somehow. Too many sites are shipping entirely reactive DOMs where only a tiny minority of content actually changes.

The fact that the entire project appears to have been written in three days, however, gives me some deep doubts.

18 hours ago
linhns 4 hours ago
Not a JS dev but the site need to move away from tinted retro look if you want to stress the modernness.
dashersw 1 hour ago
Thank you! It's the author here. I thought hard about this, and one angle Gea is bringing is that the old is new again, that's why I harkened to a retro style. I also just plain love synthwave, so... :) but you are right, and as Gea matures I believe we will iterate on the homepage.
s-y 54 minutes ago
Bro, is there anything you haven't thought hard about? I read throughout the thread and the answers sound really LLM-y. Although these days we might be calling wolf about gusts of wind.

Great framework tho. Awesome job.

dashersw 45 minutes ago
Heh, sorry it comes across as LLM-y, honest and human-written answers here only. I'm tired of AI slop as much as the next guy, but, yes, I _really_ took my time with Gea. I've been working on writing JS frameworks for 15 years, this is actually the 3rd generation of similar ideas, following tartJS (2011) and erste & regie (2017-2019). It took me several years to solidify what I expect from this generation, and I've been working on Gea for the past six months. That's why I thought about and evaluated many aspects raised here.

And thank you!

wetpaws 1 hour ago
[dead]
PufPufPuf 18 hours ago
[dead]
Brysonbw 19 hours ago
[flagged]
koakuma-chan 19 hours ago
No it's not. Stop upvoting AI slop.
arbayi 18 hours ago
not the author of the gea but from what I can see in the readme, the ideas go back to 2017, erste.js and regie were earlier versions of the same concept.

https://github.com/dashersw/erste https://github.com/dashersw/regie

leemcalilly 18 hours ago
[flagged]