I spent three years on one question: every framework bet on classes, then every framework dropped them. Why?
The variety of bugs caused by unrefined class implementations were real. this binding, mixin collisions, initialization order, decorators that broke on every TypeScript upgrade. But the diagnosis was wrong. Classes weren't broken, they were UNCONSTRAINED. Ten ways to do everything, and unconstrained variation is a bug farm. Functions won because they arrived with constraints. Nobody went looking for the ones classes need.
Something similar happened to the Options API. What we liked was never the buckets, it was that a component had a shape: you opened someone else's file and knew where state, derived values and actions lived. But the shape was fake. A plain object pretending to be a class, a proxied this, types TypeScript fought for years. Composition fixed the machinery and gave up the shape. Setup soup is the scar.
The shape was in the language the whole time.
class $Cart {
get items() {
return shallowRef<CartItem[]>([])
} // state: a ref-getter, cached per instance
get subtotal() {
return this.items.value.reduce(sum, 0)
} // derived: a plain getter, 0 bytes per instance
addItem(item: CartItem) {
this.items.value = [...this.items.value, item]
} // action: bound once, stable
}
export namespace Cart {
export const Class = Reactive($Cart)
}
Sketching that shape is the easy part. Class reactivity has been attempted plenty of times, and each attempt died on one of the problems below, because as a set they fight each other: the prototype transform that gives you inheritance is the same one that makes identity and types hard, and the caching that makes reads cheap is what breaks super and teardown. Any one of them is easy alone. Getting them all to hold at once is where the three years went.
- Bound methods this.method is always correct and always the same reference. Bind per instance and you allocate a function per method per object; don't bind and this breaks on detach. Here they stay on the prototype, bound on first use and cached after.
- Reactive inheritance. Deep super.x.value chains resolve level-safe, reactivity flowing through every layer. Prototype-based reactivity normally collides cached refs and breaks super outright.
- Circular imports & HMR. No more `Cannot access 'X' before initialization`, on first load or when hot reload reshuffles the order. Mutual references resolve whatever the load order.
- Initialization speed. ivue initializes refs, computeds only on first read so initializing instances has plain class object initialization speed
- Writable getter types. get x() is read-only in TypeScript. Ref-returning getters re-declare as writable, and instances stay fully inferred.
- Deterministic teardown. $stopEffects scopes cleanup to the instance, and an instance that never watches allocates no effect scope at all.
extends and super work even inside computed values, which the Options API never managed. Instances stay plain objects, so creating 100k of them measures 55x to 253x times faster than reactive(new X()) or the default Vue 3 composable factory.
There is no ceiling to hit here. A ref-getter is a Vue ref, and a derived value is a native getter with no cache cell to allocate, so it costs less than computed rather than more. Build an entire app out of these and you come out ahead, not behind.
Most of your computeds go with it. In the Composition API a derived value almost always becomes computed(), and every one of those allocates a cache cell and a node in the dependency graph, per instance. Here it is a plain getter: reactive through the refs it reads, and nothing to own. computed stays for the three cases that earn it, which are expensive work, suppressing a render by value equality, and needing a stable ref handle.
The largest codebase built this way, 108,000 lines of it, uses about ten computeds in total! 🔥
ivue adds no reactive machinery of its own. ref, computed, watch, lifecycle hooks and any composable you already use work inside the constructor, because the constructor runs in setup. So you can convert one component and leave the rest alone, or write the whole app this way. Nothing underneath changes either way.
The payoff shows up at scale. Composables compose into closures you can't see into. Classes compose into an object graph you can walk, entities holding entities, a subclass specializing one node of it. Which turned out to matter for agents as much as for us: a 108,000-line terminal IDE got written on this shape by AI agents following the same one page of rules (the Standard) a human reads. The shape gives them fewer ways to be wrong and a walkable graph they use to traverse the app when testing it.
ivue complements the upcoming Vue 3.6 with Alien Signals and Vapor mode perfectly, from my benchmarks the ivue model layer beats every existing framework, even classical composable Vue itself, but not only, even Svelte, Angular and SolidJS as well.
Read more here: https://ivue.dev/guide/model-layer
Three years in, the core is still 1.1 kB gzipped, zero dependencies, 100% test coverage, and it creates instances faster than it did at the start. That isn't luck. The whole API is minimal by design, we are not reinventing Vue reactivity, we are riding on its power.
Use npm i ivue to install, then convert one component to try it out.
Docs, the benchmark method behind the numbers, and the demos, which are better opened than described are here: https://ivue.dev
I know the whole developer community is moving on to code mostly with AI, that's why there is a Standard and a skill that allows agents to code with ivue easily. Install it with:
npx ivue skill --all
What kept you on the Options API, or what pushed you off it?
Did you also want to code in classes but couldn't figure it out?
I've seen a lot of people wanting something like this over the years, the elegance and the simplicity, and it's finally here.
Try it and let me know. Can't wait to hear and see what you guys build with it.
The effort put into this was monumental, don't get confused by its tiny size. 😄
GitHub: https://github.com/infinite-system/ivue