Tee.js
Building My Own Frontend Framework: A Deep Dive into How React Actually Works
Last month, I decided to embark on what some might call a “crazy” project: building my own frontend framework from scratch. Not because I thought I could create the next React or Vue, but because I wanted to truly understand how these tools that I use every day actually work under the hood.
The result is Tee.js - a lightweight, component-based framework that implements the core concepts found in modern UI libraries, including a virtual DOM, reactive state management, and a diffing algorithm for efficient DOM updates.
Why Build Yet Another Framework?
The honest answer? Pure curiosity and learning.
Working with React daily, I found myself constantly wondering: “How does this actually work?” Sure, I understood the concepts - components, state, virtual DOM - but I wanted to understand the implementation. How does React know when to re-render? How does the diffing algorithm actually work? What makes the virtual DOM more efficient?
Reading documentation and articles only got me so far. I needed to build it myself.
The Core Challenges I Tackled
1. Virtual DOM Implementation
The first major challenge was creating a virtual representation of the DOM. In Tee.js, I implemented this using a simple tree structure:
class TNode<T extends Tag> {
tag: T;
id: string;
path: number[];
children: (TNode<Tag> | Component | BaseTypes)[];
props: IntrinsicAttributes<T>;
}
Each virtual node knows its position in the tree (via path
), making it easier to map changes back to the real DOM.
2. JSX-like Element Creation
Without JSX and Babel, I needed a way to create elements declaratively. I solved this by creating functions for every HTML tag:
// Instead of <div className="container">Hello</div>
div({ class: "container" }, "Hello")
// Nested elements work naturally
div(
h1("My App"),
p("Welcome to Tee.js"),
button({ onclick: () => console.log("Clicked!") }, "Click me")
)
This gives you the same declarative feel as JSX but works with plain TypeScript.
3. Reactive State Management
The trickiest part was implementing reactive state. I used JavaScript Proxies to intercept property changes and trigger re-renders:
public state<T>(value: T) {
const proxy = new Proxy(
(typeof value === "object" ? value : { value }) as ProxyRef<T>,
{
get: (target, prop) => Reflect.get(target, prop),
set: (target, prop, newValue) => {
const result = Reflect.set(target, prop, newValue);
this.rerender(); // Trigger re-render on change
return result;
}
}
);
return proxy;
}
This allows for natural state updates that automatically trigger UI updates:
class Counter extends Component {
#count = this.state(0);
build() {
return div(
button({ onclick: () => this.#count.value++ }, "+"),
` Count: ${this.#count.value} `,
button({ onclick: () => this.#count.value-- }, "-")
);
}
}
4. The Diffing Algorithm
This was the most complex part. When state changes, I need to:
- Generate a new virtual DOM tree
- Compare it with the old tree
- Create a minimal set of “patches” (DOM operations)
- Apply only the necessary changes to the real DOM
type Patch = {
action: "remove" | "add" | "replace" | "update";
path: number[];
node?: HTMLElement;
attributes?: Record<string, string>;
};
function diffTrees(tree1: HTMLElement, tree2: HTMLElement): Patch[] {
// Compare trees and generate minimal set of changes
}
This ensures that only the parts of the DOM that actually changed get updated, which is much more efficient than re-rendering everything.
5. Component Reconciliation
Components need to maintain their identity and state across re-renders. I implemented this using a key-based system similar to React:
// Components with the same key maintain their state
new TodoItem(todo.id.toString()) // Key ensures state persistence
What I Learned
Building Tee.js taught me more about frontend frameworks than years of just using them. Here are the key insights:
The Virtual DOM Isn’t Magic
It’s just a JavaScript representation of your UI that’s cheaper to create and compare than manipulating the real DOM directly. The “magic” is in the diffing algorithm that figures out the minimal changes needed.
Reactivity Is About Interception
Modern frameworks work by intercepting data changes (through Proxies, getters/setters, or compilation) and then deciding what needs to update. The challenge is doing this efficiently.
Component Identity Matters
Frameworks need to know which components are “the same” across renders to preserve state and optimize updates. This is why React has keys for list items.
Performance Is About Batching
Real frameworks batch updates to avoid excessive re-renders. I implemented a simple version with a pulse()
function that batches multiple state changes.
The Development Experience
Working on this project gave me a deep appreciation for the React team and other framework maintainers. Some challenges I faced:
- Debugging: When your diffing algorithm has bugs, the UI can get into weird states
- Memory management: Avoiding memory leaks with component cleanup
- Edge cases: Handling all the ways developers might use your API
- Performance: Making sure the framework doesn’t become the bottleneck
Current State and Future
Tee.js currently supports:
- Component-based architecture
- Reactive state management
- Basic routing
- Async data handling
- Global state management
It’s definitely not production-ready (and never intended to be), but it’s a fully functional framework that demonstrates the core concepts.
Some features I might add for learning:
- Server-side rendering
- More sophisticated scheduling (like React’s Fiber)
- Developer tools
- Performance optimizations
Try It Yourself
If you’re curious about how frameworks work, I highly recommend building your own. You don’t need to create something production-ready - the learning comes from tackling the core problems.
You can check out Tee.js on GitHub or try the live demo.
The codebase is intentionally kept simple and well-commented to serve as a learning resource. Whether you’re a junior developer trying to understand React better or a senior developer curious about framework internals, building your own framework is an incredibly rewarding experience.
Final Thoughts
This project reminded me why I love programming. There’s something deeply satisfying about understanding how the tools you use every day actually work. It’s made me a better React developer and given me a much deeper appreciation for the engineering that goes into modern frameworks.
Plus, now when someone asks “How does React work?”, I can answer with actual implementation details rather than hand-waving about “virtual DOM magic.”
Sometimes the best way to learn is to build it yourself.
If you found this interesting, you might also enjoy my other posts about [web development topics]. Feel free to reach out if you have questions about the implementation or want to discuss framework architecture!