跪拜 Guibai
← Back to the summary

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:

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:

  1. Smooth rendering under high-frequency state updates
  2. Replaceable rendering backends (DOM/Canvas/WebGL)
  3. 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:

  1. Preserve JSX without transpilation: Configure esbuild/oxc with jsx: 'preserve' so the build tool does not process JSX.
  2. Custom transformation pipeline: Implement the JSX → createView conversion logic in the transform hook.
  3. Compile-time macro support: Built-in handling of directives like v-if, v-else, v-model, v-show.
  4. Macro component support: Built-in compile-time optimization for components like Switch, IfBlock.
  5. 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

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:

  1. Deep understanding: An unprecedented understanding of the underlying principles of frontend frameworks.
  2. Technical control: No longer held hostage by framework API changes; able to autonomously control the technical roadmap.
  3. 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:

7.3 Advice for Other Developers

If you are also considering building a custom framework, my advice is:

  1. Define clear goals: Know exactly what problem you want to solve; don't reinvent the wheel for its own sake.
  2. Start simple: First implement a minimum viable version, then gradually improve.
  3. Stay open: Reference the designs of excellent frameworks, but have your own thoughts.
  4. 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.