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?
One thing I've been very glad for in all the years I've been using TS+Vue is that I'm not forced into JavaScript's craptastical OOP system that was a bolted-on kludge well before ES2015 class syntax was even a thing. Having to use patterns like "foo.bar.bind(foo)" is what I consider fighting the language. Having to know that `this` is something different in an event handler, and thus having to bind it to some other variable, unless of course I use an arrow function, where there is no `this` at all, is fighting the language.
When I use static closures, functions are just functions, closures are closures, things just work. I don't have anything against OOP whatsoever, and I'm even fine with prototype-based OO, but the the way it's done in JS is littered with everything from papercuts to outright landmines. So a framework like WebComponents or this one, that forces me to use JS's OO mechanics, is a non-starter. Of course so are React hooks, which are barkingly insane for completely different reasons.
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.
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.
In fact the precedent to Gea, my previous framework erste made use of `pug` tagged template literals! I started with that base, but then decided to ship with JSX instead. This would be a great addition to Gea and would love to see it as a community contribution. I'm hesitant about web components but if we could help web components to feel more "plain old JS" to write, it would be awesome!
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
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.
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.
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.
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.
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
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 :)
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.
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!
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!
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.
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.
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.
> 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?
Because JavaScript _is_ the language and people know it. I never understood the concept of a "React developer", for example, although I saw many junior devs who were very well-versed in React and didn't completely understand JavaScript.
In the end, it's a design choice. Of course frameworks don't inherently _need_ to be beholden to the standards of the underlying language, but I think this is just simpler, therefore a worthy goal to pursue.
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.
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?
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.
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.
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.
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.
I'm not following—the `new` keyword has been with us since JavaScript's inception. You might be confusing it with the `class` syntax, but before that we could always `new` functions.
And yes, Solid has signals that require you to know how to write and work with them. I answered another comment on the thread about Solid stores—they also introduce a couple of gotchas and weird syntax. You just can't do `this.users.push(...)` or `this.users[2].loggedIn = true` in Solid stores.
Therefore `createStore` is less native than `new Store()`, because `new Store()` just gives you a plain (proxied) object you can manipulate in various ways and reactivity will persist thanks to the compiler.
And Gea's design goal is also fine-grained reactivity, which it delivers without getter functions in the code that the developer writes, but rather, the handlers are generated via the compiler.
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.
This is a design choice, and explained in their docs:
Separating the read and write capabilities of a store provides a valuable
debugging advantage.
This separation facilitates the tracking and control of the components that
are accessing or changing the values.
> 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.
It's optional, and incidentally quite expressive and powerful. But they also support mutable drafts out of the box.
import { produce } from 'solid';
setStore(produce(state => {
state.users[2]?.loggedIn = false;
}))
I understand this bit. The bit that I don't understand is how you compare the two invented concepts like `setStore` and `produce` to just `state.users[2]?.loggedIn = false`. To me it's very clear Gea's syntax requires you to write less code, while also requiring you to know less concepts.
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?
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.
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.
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.
> 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.
I wish I could bring it to React! That would save so many developers and so much natural resources!
I've been working on the library for 6 months, and it's built upon my previous libraries tartJS (2011), erste (2017) and regie (2019). I just like to squash my commits before I make a public release, and that just happened 4 days 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.
This is an interesting idea, though, thanks for bringing it up! I will think about it and try to add it to the compiler. Especially native objects like `window` would be great to create handlers for in the compiler. It would make life really easy for the developer.
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.
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.
But all of those metrics differ by something like 1 millisecond, and you've only got benchmark data from an M4 Macbook Pro. On the strength of this, you promise us:
"The fastest compiled UI framework — ahead of Solid, Svelte, Vue, and React."
I know you've put quite a bit of work into the underlying libraries here, but this is the sort of claim people are sure to poke at. Is the Gea code used in the benchmark published anywhere?
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.
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.
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.
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?
One thing I've been very glad for in all the years I've been using TS+Vue is that I'm not forced into JavaScript's craptastical OOP system that was a bolted-on kludge well before ES2015 class syntax was even a thing. Having to use patterns like "foo.bar.bind(foo)" is what I consider fighting the language. Having to know that `this` is something different in an event handler, and thus having to bind it to some other variable, unless of course I use an arrow function, where there is no `this` at all, is fighting the language.
When I use static closures, functions are just functions, closures are closures, things just work. I don't have anything against OOP whatsoever, and I'm even fine with prototype-based OO, but the the way it's done in JS is littered with everything from papercuts to outright landmines. So a framework like WebComponents or this one, that forces me to use JS's OO mechanics, is a non-starter. Of course so are React hooks, which are barkingly insane for completely different reasons.
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.
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.
Would love to se tagged template Literals and w eb components as first citizen in this - lit.dev etc.
In fact the precedent to Gea, my previous framework erste made use of `pug` tagged template literals! I started with that base, but then decided to ship with JSX instead. This would be a great addition to Gea and would love to see it as a community contribution. I'm hesitant about web components but if we could help web components to feel more "plain old JS" to write, it would be awesome!
You can now use React Compiler and there would be no virtual DOM.
Already being used in production at large code bases.
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
Yeah afaik React Compiler doesn't really replace virtual DOM.
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.
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.
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.
React is just JavaScript. Also vanilla JavaScript is just JavaScript
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.
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
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 :)
There are many ways to write JavaScript, for example by destructuring variables, etc. How do Gea proxies work to retain reactivity then?
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.
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!
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!
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.
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.
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.
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.
> 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?
Because JavaScript _is_ the language and people know it. I never understood the concept of a "React developer", for example, although I saw many junior devs who were very well-versed in React and didn't completely understand JavaScript.
In the end, it's a design choice. Of course frameworks don't inherently _need_ to be beholden to the standards of the underlying language, but I think this is just simpler, therefore a worthy goal to pursue.
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.
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?
Two way props make for a nice demo, but are a nightmare to maintain.
How does this thing run so close to VanillaJS without carrying any extra baggage along for the ride?
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.
Thank you for your detailed answer!
> 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?
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.
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.
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.
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.
I'm not following—the `new` keyword has been with us since JavaScript's inception. You might be confusing it with the `class` syntax, but before that we could always `new` functions.
And yes, Solid has signals that require you to know how to write and work with them. I answered another comment on the thread about Solid stores—they also introduce a couple of gotchas and weird syntax. You just can't do `this.users.push(...)` or `this.users[2].loggedIn = true` in Solid stores.
Therefore `createStore` is less native than `new Store()`, because `new Store()` just gives you a plain (proxied) object you can manipulate in various ways and reactivity will persist thanks to the compiler.
And Gea's design goal is also fine-grained reactivity, which it delivers without getter functions in the code that the developer writes, but rather, the handlers are generated via the compiler.
What is the difference between mobx or solid stores or any of the reactive frameworks that do reactivity on proxy objects?
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.
> You always have to use setStore
This is a design choice, and explained in their docs:
> 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.It's optional, and incidentally quite expressive and powerful. But they also support mutable drafts out of the box.
I understand this bit. The bit that I don't understand is how you compare the two invented concepts like `setStore` and `produce` to just `state.users[2]?.loggedIn = false`. To me it's very clear Gea's syntax requires you to write less code, while also requiring you to know less concepts.
You wrote and shipped this in three days, eh?
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?
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.
That is a remarkably pedantic nitpick. If you're going that far, you should really be complaining that it's not "KiB".
I don't think it's pedantry, it's looking for clues as to the age of this.
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.
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
> 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.
I wish I could bring it to React! That would save so many developers and so much natural resources!
I've been working on the library for 6 months, and it's built upon my previous libraries tartJS (2011), erste (2017) and regie (2019). I just like to squash my commits before I make a public release, and that just happened 4 days 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:
But it's not, it's just on objects that subclass a Store.This is an interesting idea, though, thanks for bringing it up! I will think about it and try to add it to the compiler. Especially native objects like `window` would be great to create handlers for in the compiler. It would make life really easy for the developer.
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.
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.
It's impressive for sure!
But all of those metrics differ by something like 1 millisecond, and you've only got benchmark data from an M4 Macbook Pro. On the strength of this, you promise us:
"The fastest compiled UI framework — ahead of Solid, Svelte, Vue, and React."
I know you've put quite a bit of work into the underlying libraries here, but this is the sort of claim people are sure to poke at. Is the Gea code used in the benchmark published anywhere?
Thank you! And you are right. I hadn't submitted it to the original js-framework-benchmark repo yet, but I just added the benchmark code on gea's repo. You can find it on https://github.com/dashersw/gea/blob/main/benchmark/src/benc..., and the store on https://github.com/dashersw/gea/blob/main/benchmark/src/stor...
Not a JS dev but the site need to move away from tinted retro look if you want to stress the modernness.
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.
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.
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!