One Developer Ditched Vue and React to Build Their Own Framework — Here's What They Learned
I. Background Analysis of Framework Selection
1.1 Current State of the Frontend Ecosystem
The current frontend development landscape is dominated by a "framework-first" paradigm. Vue and React, as the two mainstream frameworks, occupy the vast majority of the market share, with a massive ecosystem built around them: state management, routing, UI component libraries, build tools, testing frameworks... There are dozens of choices at every layer.
Behind this prosperity lies the cognitive burden on developers to continuously learn and maintain. According to npm statistics, over 10,000 new packages are published weekly in the frontend domain, and the update frequency of frameworks and their surrounding ecosystems is dizzying.
1.2 Framework Dependency Dilemma in Commercial Projects
In enterprise-level development, framework selection often means long-term technical lock-in:
- High upgrade costs: Migrating from Vue 2 to Vue 3, or from React 17 to React 18+, requires significant refactoring.
- Ecosystem lock-in: Once a framework is chosen, the accompanying state management, routing, UI libraries, etc., tend to favor solutions within that ecosystem.
- Limited customization: When business requirements exceed the framework's capabilities, extensive hacks or waiting for official support are often necessary.
1.3 The Trigger Point for the Decision
The direct reason that prompted my decision to build a custom framework was a technical challenge in a real project: we needed to implement a complex visual editor that demanded extremely high rendering performance and flexible custom rendering capabilities. After trying Vue, React, and Solid, none of the frameworks could simultaneously meet the following requirements:
- Smooth rendering under high-frequency state updates
- Replaceable rendering backends (DOM/Canvas/WebGL)
- Fine-grained control over the rendering process
II. Pain Points of Mainstream Frameworks
2.1 Limitations of Vue
Vue 3 is known for its elegant reactive system and progressive design, but it still has pain points in actual use:
Black-box built-in components: Components like Teleport and Transition are powerful, but their implementation details are completely opaque to developers. When customizing animation logic or extending portal functionality, it can only be achieved through hacks.
2.2 Challenges with React
React is famous for its flexible JSX syntax and massive ecosystem, but it also has issues:
Complexity of Hooks rules: Managing dependency arrays for useEffect, useMemo, and useCallback is a perpetual pain point, easily leading to hard-to-debug bugs.
// Pitfalls of React Hooks dependency management
useEffect(() => {
fetchData(id).then(setData)
}, [id]) // Missing dependencies can cause closure issues
Performance overhead of Virtual DOM: Although React 18 introduced concurrent rendering and the Fiber architecture, the diff overhead of the Virtual DOM remains non-negligible in high-frequency update scenarios.
2.3 Shortcomings of Signal-Based Frameworks
Signal-driven frameworks like Solid and Svelte excel in performance, but their compile-time optimization strategies introduce new problems:
Opaque compiled output: The compiled code differs significantly from the source code, making debugging difficult. Limited runtime capabilities: Since much logic is completed at compile time, runtime dynamism and flexibility are constrained.
III. Technical Architecture Design of the Custom Framework
3.1 Core Design Principles
Based on the analysis of mainstream frameworks, I established three core design principles for the custom framework Vitarx:
| Design Principle | Specific Meaning | Advantage |
|---|---|---|
| Signal-level precise updates | Only update the associated DOM node when data changes | No diff overhead, excellent performance in high-frequency update scenarios |
| Runtime view tree | Retain a complete runtime structure, supporting dynamic operations | Flexible rendering orchestration capabilities |
| Everything is a component | All built-in components are built with public APIs | No black boxes, customizable, extensible |
3.2 Layered Architecture
Vitarx adopts a layered architecture design, with clear responsibilities and unidirectional dependencies for each layer:
┌─────────────────────────────────────────────┐
│ vitarx │ ← Aggregate package, unified export
├────────────┬─────────────┬──────────────────┤
│runtime-dom │ runtime-ssr │ │ ← Platform adaptation layer
├────────────┴─────────────┼──────────────────┤
│ runtime-core │ utils │ ← Runtime core & utilities
├──────────────────────────┼──────────────────┤
│ responsive │ │ ← Reactive system
└──────────────────────────┴──────────────────┘
3.3 Reactive System Design
The reactive system is the core of Vitarx. Unlike Vue's implementation, Vitarx uses doubly linked list dependency management, achieving more efficient dependency tracking and triggering:
/**
* DepLink — A doubly linked list node between signals and effects
* Each node exists in two linked lists simultaneously:
* - Signal list: which effects depend on this signal
* - Effect list: which signals this effect depends on
*/
class DepLink {
sigPrev?: DepLink // Predecessor in the Signal list
sigNext?: DepLink // Successor in the Signal list
ePrev?: DepLink // Predecessor in the Effect list
eNext?: DepLink // Successor in the Effect list
constructor(
public signal: Signal,
public effect: EffectRunner
) {}
}
Track (Dependency Collection): When an effect function executes and accesses reactive data, a linked list node is created or reused, simultaneously attached to both lists.
Trigger (Update Triggering): When data changes, traverse the Signal list to notify all dependents — no diff, no re-rendering.
3.4 View System Design
Vitarx's view system is based on the View abstraction layer, converting JSX into lightweight view objects, not Virtual DOM:
interface View {
init(ctx: ViewContext): void // Initialization
mount(container: HostContainer): void // Mount to container
dispose(): void // Destruction
}
This design allows the renderer to be completely replaceable — just implement the ViewRenderer interface:
interface ViewRenderer {
createElement(tag: string, parent: HostContainer): HostElement
createText(text: string): HostText
createComment(text: string): HostComment
append(child: HostNode, parent: HostContainer): void
remove(node: HostNode): void
setAttribute(el: HostElement, key: string, next: unknown, prev: unknown): void
// ... other methods
}
IV. Key Challenges and Solutions During Development
4.1 Challenge 1: Memory Management in the Reactive System
Problem: If dependency relationships between signals and effects are not cleaned up in time, it can lead to memory leaks.
Solution: Adopt a version number incremental marking mechanism. Each time an effect re-executes, first mark all old dependencies as stale, then re-collect dependencies and update the marks, finally clean up the expired dependency nodes.
export function trackSignal(signal: Signal): void {
const activeEffect = currentActiveEffect
if (!activeEffect) return
let link = activeEffect[DEP_INDEX_MAP]?.get(signal)
if (!link) {
link = createDepLink(activeEffect, signal)
activeEffect[DEP_INDEX_MAP]?.set(signal, link)
}
link[DEP_VERSION] = activeEffect[DEP_VERSION] // Mark as "still accessed this time"
}
4.2 Challenge 2: Efficient View Tree Updates
Problem: Maintaining a complete view tree structure at runtime — how to ensure the efficiency of update operations?
Solution: Introduce a View Switch Transaction (ViewSwitchTransaction), encapsulating view switching operations as interruptible, rollbackable transactions. This allows components like Transition to insert animation logic during view switching.
onViewSwitch((tx) => {
const { prev, next } = tx
tx.stopPropagation()
// First execute the leave animation
runTransition(prev.node, 'leave', props, () => {
tx.commit() // Commit the switch
// Then execute the enter animation
runTransition(next.node, 'enter', props)
})
})
4.3 Challenge 3: Customizing the JSX Runtime
Problem: How to make Vitarx use its own JSX compilation pipeline without relying on React's jsx-runtime?
Solution: Implement custom JSX compilation via the @vitarx/plugin-vite plugin in conjunction with Vite. The core workflow of this plugin is:
- Preserve JSX without transpilation: Configure esbuild/oxc with
jsx: 'preserve'so the build tool does not process JSX. - Custom transformation pipeline: Implement the JSX →
createViewconversion logic in thetransformhook. - Compile-time macro support: Built-in handling of directives like
v-if,v-else,v-model,v-show. - Macro component support: Built-in compile-time optimization for components like
Switch,IfBlock. - HMR injection: Automatically inject hot module replacement related code in development mode.
Usage only requires importing the plugin in vite.config.ts:
// vite.config.ts
import vitarx from '@vitarx/plugin-vite'
export default defineConfig({
plugins: [vitarx()]
})
Essential difference from React jsx-runtime: React's jsx-runtime executes createElement calls at runtime; Vitarx compiles JSX into createView calls at build time via the Vite plugin, with no additional JSX transformation overhead at runtime.
4.4 Challenge 4: Implementing Server-Side Rendering
Problem: How to implement server-side rendering and client-side hydration?
Solution: Implement two core APIs, renderToString and renderToStream, while also implementing incremental hydration on the client — only updating the changed parts.
import { renderToString, renderToStream } from '@vitarx/runtime-ssr'
// String rendering
const html = await renderToString(App)
// Stream rendering
const stream = await renderToStream(App)
V. Performance Benchmark Test Data
5.1 Test Environment
- Test Tool: JS Framework Benchmark
- Test Environment: Chrome 120, macOS 14.0
- Test Item: 1000-row data table, including operations like create, update, select, swap, delete
5.2 Test Results
Execution Time Comparison (ms)
| Test Scenario | Vue 3.6 | React 19 | Vitarx 4.0 |
|---|---|---|---|
| Create 1000 rows | 81.9 | 82.4 | 112.2 |
| Update 1000 rows | 88.2 | 84.8 | 122.4 |
| Partial update (every 10 rows) | 19.1 | 11.2 | 9.2 |
| Select row highlight | 9.8 | 5.8 | 4.5 |
| Swap two rows | 10.7 | 67.6 | 10.4 |
| Delete one row | 20.9 | 18.1 | 17.2 |
Memory Allocation Comparison (MB)
| Test Scenario | Vue 3.6 | React 19 | Vitarx 4.0 |
|---|---|---|---|
| Initial memory | 0.86 | 1.18 | 0.83 |
| Running memory | 3.77 | 4.42 | 5.46 |
| After create/cleanup | 1.23 | 1.97 | 1.16 |
Transfer Size Comparison
| Item | Vue 3.6 | React 19 | Vitarx 4.0 |
|---|---|---|---|
| Uncompressed | 63.7 KB | 180.3 KB | 57.8 KB |
| Compressed | 22.9 KB | 51.4 KB | 17.0 KB |
VI. Future Framework Iteration Plan
6.1 Short-Term Goals (2026 - 2027)
| Goal | Specific Content | Priority |
|---|---|---|
| Ecosystem improvement | Launch official UI component library & design system | High |
| VitaStack | Full-stack development framework | High |
| Toolchain | Develop browser debugging plugin | High |
| Documentation upgrade | Improve official docs and tutorials | Medium |
| State management | Develop official state management library | Medium |
6.2 Medium-Term Goals
| Goal | Specific Content | Priority |
|---|---|---|
| Cross-platform support | Launch mobile and desktop support | Medium |
| Performance optimization | Further optimize initial load and rendering performance | High |
6.3 Long-Term Goals
| Goal | Specific Content | Priority |
|---|---|---|
| AI integration | Explore AI-assisted development capabilities | Low |
| WebAssembly | WASM optimization for critical paths | Low |
| Community building | Establish an active developer community | Medium |
VII. Summary and Reflection
7.1 Value of a Custom Framework
Building a custom framework has given me more than just a usable tool; importantly:
- Deep understanding: An unprecedented understanding of the underlying principles of frontend frameworks.
- Technical control: No longer held hostage by framework API changes; able to autonomously control the technical roadmap.
- Innovation capability: Accumulated experience in building complex systems from 0 to 1.
7.2 Scenarios Suitable for Custom Frameworks
Custom frameworks are not suitable for all projects. The following scenarios are more appropriate:
- Projects with extreme performance requirements
- Projects requiring deep customization of framework behavior
- Teams pursuing architectural transparency
- Teams with sufficient technical capability and time investment
7.3 Advice for Other Developers
If you are also considering building a custom framework, my advice is:
- Define clear goals: Know exactly what problem you want to solve; don't reinvent the wheel for its own sake.
- Start simple: First implement a minimum viable version, then gradually improve.
- Stay open: Reference the designs of excellent frameworks, but have your own thoughts.
- Iterate continuously: A framework is alive; it needs constant optimization and improvement.
Final words: Vitarx is the crystallization of two years of technical exploration. It may not be the most perfect framework, but it is a framework that is "visible and tangible." If you are also interested in the underlying principles of frontend frameworks, feel free to join our discussion group to explore together.
- 🌟 GitHub — A Star is the greatest support
- 📖 Official Documentation — Complete usage guide and API docs
- 💬 QQ Group — Let's chat together