🌱 The Journey Begins
About five years ago, I shared a deeply personal approach to building web components — one rooted in Web Components and custom elements.
My idea was simple:
Define a new element, give it its own behavior, and interact directly with it — without re-rendering the entire DOM.
If you’ve ever opened YouTube → View Source, you’ll see how they structure their custom elements and attach isolated logic to each.
That’s what inspired me when I started.
So I built a tiny “engine” that allowed interaction with each element independently — minimizing DOM reflows, maximizing control.
The goal was clear: maximum control, minimal interference.
🤔 When Doubt Creeps In
At first, it all seemed to work perfectly.
But as projects grew, I faced an inevit…
🌱 The Journey Begins
About five years ago, I shared a deeply personal approach to building web components — one rooted in Web Components and custom elements.
My idea was simple:
Define a new element, give it its own behavior, and interact directly with it — without re-rendering the entire DOM.
If you’ve ever opened YouTube → View Source, you’ll see how they structure their custom elements and attach isolated logic to each.
That’s what inspired me when I started.
So I built a tiny “engine” that allowed interaction with each element independently — minimizing DOM reflows, maximizing control.
The goal was clear: maximum control, minimal interference.
🤔 When Doubt Creeps In
At first, it all seemed to work perfectly.
But as projects grew, I faced an inevitable truth:
Direct DOM manipulation — while powerful — isn’t always scalable or optimal.
When I needed to extend or reuse components, I found myself repeating a lot of manual steps.
That made me question:
“Is there a way to keep my control philosophy, but also benefit from what modern JS frameworks offer?”
🔁 Rewriting with a New Philosophy
I started experimenting again — combining my own understanding with the strengths of existing frameworks.
When I learned about Virtual DOM, I saw how it solved certain problems — but it wasn’t what I truly needed.
I wanted something simpler.
And then a new philosophy emerged:
Write less. Do more. My old process was:
- Mark the DOM.
- Use JS to find and bind elements.
- Write interactive logic directly on them. It gave me 100% behavioral control — but at a high cost in time and maintainability.
🧩 The Solutions I Studied
I explored several libraries with similar philosophies:
- Alpine.js – lightweight, easy to integrate, HTML-like syntax.
- Web Components – standards-based, framework-free, but verbose.
- HTMX – minimal JavaScript, great for server-driven UI.
- AngularJS 1.x – early reactive pioneer, but too heavy for small features.
- Petite-vue – a compact version of Vue, lovely but still limited. Each had its strengths, yet I still longed for something smaller, tighter, more controllable.
⚡ Discovering Proxy and My First Reactive Engine
A turning point came last year, while developing a data table component for a client.
To optimize data updates, I discovered JavaScript Proxy — a surprisingly elegant tool for creating reactive data without relying on a complex virtual DOM.
Proxy felt smooth.
But then, the same old pain returned: every new feature meant rewriting parts of the engine from scratch.
The dev loop became: build → fix → rewrite → repeat.
So I decided to go deeper — to build my first true reactive engine, designed around how I naturally structure components.
🚫 Why I Didn’t Choose Mainstream Frameworks
I didn’t go with React, Angular, or Vue (full) because:
- They were too heavy for my needs.
- Not optimized for SSR (Server-Side Rendering) in my workflow.
- They often required rewriting backend logic — I use Golang, and I didn’t want that friction. jQuery still survives today because it solves practical problems quickly. But I needed something even lighter, modern, and controllable.
🧠 The Development Philosophy: Micro Component
I imagine Micro Components like Microservices:
- Each component does exactly one thing.
- It can run independently.
- It can hydrate anywhere, anytime. This lets me embed mini JavaScript apps into mobile or web environments — without affecting other modules. Since SEO is already handled by the server, I can focus purely on enhancing user experience on the client.
⚙️ Example: How It Works
``<div data-kitmodule-component=”counter” data-counter-state=”count: 0” > <input data-counter-model=”count” /> <button data-counter-event=”click:count++”>+</button> <span data-counter-bind=”count”></span> </div>``
Explanation:
data-kitmodule-component— identifies the component.data-counter-state— declares the initial state.data-counter-model— two-way data binding with input.data-counter-event— attaches event handlers directly via attributes.data-counter-bind— updates the DOM when the state changes. The result: A lightweight, framework-free, yet fully reactive component.
🌸 What I Learned
This journey taught me that simplicity is often the ultimate sophistication.
Reactive doesn’t have to mean Virtual DOM or complex syntax.
Sometimes, all you need is:
- a small engine,
- readable, HTML-based syntax,
- and a sprinkle of smart JavaScript. In the next part, I’ll share the detailed architecture of this reactive engine — how it parses expressions, binds events, and syncs state with the DOM without using
evalorFunctionconstructors — ensuring CSP safety and XSS protection.
open: https://github.com/kitmodule/kitjs
📝 NOTES
- Originally written in 2025, reposted with updates.
- AI-powered translation from Vietnamese.
Read the original Vietnamese version here: [LINK_TO_ORIGINAL]
☕ More about me
Blog: huynhnhanquoc.com
GitHub: github.com/huynhnhanquoc
Open Source: github.com/kitmodule
Buy me a Coffee: buymeacoffee.com/huynhnhanquoc
Keep me Dreaming: ko-fi.com/huynhnhanquoc
Thanks for reading Huỳnh Nhân Quốc’s article! Subscribe for free to receive new posts and support my work.