Tools: I Built a UI Framework That Doesn’t Use a Virtual DOM

Tools: I Built a UI Framework That Doesn’t Use a Virtual DOM

Source: Dev.to

I Built a UI Framework That Doesn’t Use a Virtual DOM ## Introducing Zenith ## Why I Started Building Zenith ## What Zenith Is (and Isn’t) ## A Simple Counter Example ## Zenith ## What the Compiler Does ## Why {} Exists in Zenith ## Components in Zenith Are Structural ## Why No Virtual DOM? ## Current Status ## Why I’m Sharing This Now ## Final Thought Over the last few years, I’ve worked extensively with React, Vue, Next, Nuxt, Svelte, and compiler-adjacent tooling. Each of them solves real problems — but they also left me with a recurring question: What if the compiler already knew everything before runtime ever existed? That question is what led me to build Zenith. Zenith is a compiler-first UI framework that intentionally does not use a Virtual DOM, runtime diffing, or reactive boundaries. Not because those tools are bad — but because I wanted to explore what becomes possible when certainty replaces reconciliation. This post is an announcement, not a sales pitch. Zenith is different by design, and that difference is intentional. Most modern UI frameworks share a common assumption: UI updates are a runtime problem. State changes, components re-run, virtual trees are recreated, and the framework figures out what changed after the fact. Zenith flips that assumption: UI updates are a compile-time problem. Instead of asking “what changed?” at runtime, Zenith asks: “What could ever change?” …and answers that before runtime exists. Once that answer is known, there’s nothing left to diff. It’s a different axis entirely. Let’s start with something familiar. This works great — but under the hood: All decisions happen at runtime. Zenith enforces a strict separation: At compile time, Zenith: Emits exact instructions: There is no re-render path. There is no diffing step. There is nothing to guess. In Zenith, {} does not mean “evaluate an expression.” “Bind this node to a compiler-tracked value.” That’s why these are invalid: Those require runtime evaluation — and Zenith rejects them at compile time. The compiler must be certain, or it fails. One of Zenith’s core constraints is: Components do not introduce behavior or reactivity. They are purely structural transforms. This constraint is not accidental — it’s what makes the model possible. The Virtual DOM is an elegant solution to a hard problem. Zenith avoids the problem entirely. That’s not “better” — it’s different by construction. It’s not production-ready yet — and that’s okay. Right now, Zenith is about exploring: Zenith started as a question. It grew into a compiler. Now it’s a framework. I’m sharing it early because: If you’re curious, skeptical, or opinionated — that’s exactly the audience I want to hear from. Zenith isn’t trying to replace React or Vue. It’s asking a different question: What if the compiler already knew everything? Thanks for reading — more to come. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK: function Counter() { const [count, setCount] = useState(0) return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ) } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: function Counter() { const [count, setCount] = useState(0) return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ) } COMMAND_BLOCK: function Counter() { const [count, setCount] = useState(0) return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ) } CODE_BLOCK: <script> state count = 0 function increment() { count++ } </script> <button onclick="increment"> Count: {count} </button> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: <script> state count = 0 function increment() { count++ } </script> <button onclick="increment"> Count: {count} </button> CODE_BLOCK: <script> state count = 0 function increment() { count++ } </script> <button onclick="increment"> Count: {count} </button> CODE_BLOCK: {count + 1} {someFunction()} onclick={count++} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: {count + 1} {someFunction()} onclick={count++} CODE_BLOCK: {count + 1} {someFunction()} onclick={count++} - Compiler-driven - Structural by default - Predictable and explicit - Minimal at runtime - A Virtual DOM framework - A reactive runtime - A template engine - A drop-in replacement for React or Vue - The component function re-executes - A new Virtual DOM tree is created - React diffs old vs new - Then updates the DOM - Behavior lives in <script> - Markup only declares bindings - No inline expressions - No directives - No runtime interpretation - Resolves count as a state cell - Resolves increment as a function reference - Determines {count} affects a single text node - Emits exact instructions: One DOM event listener One direct DOM text update - One DOM event listener - One direct DOM text update - One DOM event listener - One direct DOM text update - The function runs - State mutates - The known DOM node updates directly - No lifecycle hooks - No hidden scopes - No reactive boundaries - Slot content to retain its original scope - State identity to remain intact - The compiler to reason globally - Recompute → then optimize - Recover from uncertainty - Prevents uncertainty - Emits intent, not guesses - Actively developed - Compiler-driven (Rust) - Early, opinionated, and evolving - What a compiler can guarantee - How much runtime can be eliminated - What UI looks like without reactive boundaries - I value architectural discussion - I want ideas challenged - And I believe UI tooling still has unexplored space