π Rasen Design Philosophy β
γγγ (Rasen) β Japanese for "Spiral". Inspired by the anime Tengen Toppa Gurren Lagann, where spiral energy represents the power to break through all limits.
Why Rasen? β
Modern frontend frameworks are mature, but we've observed several fundamental issues:
1. Reinventing the Reactive Wheel β
Vue, Solid, Signals... Reactive systems have been nearly perfected. As framework developers, we should let users choose "the best" rather than forcing them to accept yet another homegrown reactive solution.
2. The Overhead of Virtual DOM β
VDOM adds unnecessary complexity in many scenarios. Direct manipulation is often simpler and more efficient.
3. The Cross-Platform Struggle β
When you need to switch from HTML to Canvas to WebGL within the same context, existing frameworks offer little help. Cross-platform rendering shouldn't be this hard.
4. Frameworks Are Too Narrow β
Why is React limited to DOM and React Native? Why is Vue confined to DOM? The realm of graphical rendering extends far beyond these boundaries.
Core Design: Dual Decoupling β
Rasen's architecture is built on two fundamental decouplings:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β User Code β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Rasen Core (Paradigm) β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ€
β Reactive Runtime β Render Target β
β Adapters β Adapters β
β ββββββββββββββββββ β βββββββββββββββββββββββββββββββ β
β β Vue Reactivity β β β DOM / Canvas / React Native β β
β β TC39 Signals β β β HTML(SSR) / WebGL / ... β β
β β Any reactive β β β Any mountable target... β β
β ββββββββββββββββββ β βββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββReactive Decoupling β
We don't create a new reactive system. Through the ReactiveRuntime interface, users choose freely:
// Use Vue's reactivity
import { useReactiveRuntime } from '@rasenjs/reactive-vue'
useReactiveRuntime()// Or TC39 Signals
import { useReactiveRuntime } from '@rasenjs/reactive-signals'
useReactiveRuntime()One adapter per application: the runtime is global, and each adapter decides what counts as a ref.
Render Target Decoupling β
Through the unified Mountable<Node> contract, the same component paradigm renders to any target:
// DOM
const Button = () => div({}, 'Click me')
// Canvas 2D
const Circle = () => circle({ x: 100, y: 100, radius: 50 })
// React Native
const Card = () => view({ style: { β¦ }, children: [text({ children: 'Hi' })] })
// HTML (SSR/SSG)
const Page = () => div({}, h1({}, 'Hello'))JSX is the preferred spelling for all of them (<div><span>{count}</span></div>); the factories above are what JSX compiles to when the compiler cannot hoist a subtree.
Three-Phase Functions: Lifecycle as Closures β
A Rasen component is a three-phase function, each phase corresponding to a lifecycle stage:
const Component = (props) => { // π¦ Phase 1: Setup
// Initialize reactive state
const count = ref(0)
return (host) => { // π Phase 2: Mount
// Mount to host, establish subscriptions
const stop = getReactiveRuntime().subscribe(count, (val) => {
// Update the host's representation
})
return () => { // π§Ή Phase 3: Unmount
// Cleanup resources
stop()
}
}
}Why This Design? β
1. Closures Naturally Isolate β Avoiding React's Stale Closure Problem
React Hooks' stale closure issue stems from function components re-executing on every render. Rasen's three-layer closure structure inherently avoids this β each lifecycle phase has its own isolated closure scope.
2. Lifecycle Is Visible
No need to memorize useEffect dependency array rules. No need to understand when onMounted vs onUnmounted fires. The nested function structure directly expresses the lifecycle hierarchy.
3. Composition Over Inheritance
Components are functions. Functions compose freely. No classes, no extends, no mixins.
Self-Controlled Rendering: Components as Mounters β
Traditional framework rendering flow:
Component declares tags β Framework collects β Framework schedules β Framework operates hostRasen's rendering flow:
Component receives host β Component decides how to mountHost Passes Down the Chain β
The host isn't a black box inside the framework. It passes down the mount chain to every component:
// Primitive component: directly operates the host
const div = (props) => (host: HTMLElement) => {
const el = document.createElement('div')
host.appendChild(el)
// ...set attributes, events, children
return () => el.remove()
}
// Business component: appears as a single-layer function
const Counter = () => div(
{},
span({}, count), // a ref child β reactive text node
button({ onClick: increment }, '+')
)The com Wrapper: Automatic EffectScope Management β
Due to the reactive scope implementation, components with watchers or other effects require an extra nesting layer to automatically manage the effect scope lifecycle. Rasen provides the com wrapper to automate this pattern.
The Problem:
// Without com: manual effectScope management required
const Counter = (props) => {
const runtime = getReactiveRuntime()
const count = runtime.ref(0)
const scope = runtime.effectScope() // β Extra layer needed
return (host: HTMLElement) => {
return scope.run(() => {
// All subscriptions must be registered in this scope
runtime.subscribe(() => count.value, (val) => {
// Update rendering based on watched value
})
return () => scope.stop() // β Must manually stop
})
}
}The Solution:
// With com: automatic scope management
const Counter = com(() => {
const runtime = getReactiveRuntime()
const count = runtime.ref(0)
// Return the mountable directly β the scope is managed for you
return div(
{},
span({}, count),
button({ onClick: () => count(count() + 1) }, 'Increment')
)
// Scope is automatically stopped when component unmounts
})Why com Matters:
- Eliminates Boilerplate β No need for
effectScope()andscope.run() - Guaranteed Cleanup β Effects are automatically stopped on unmount, preventing memory leaks
- Correct Lifecycle β User cleanup runs before scope stops, ensuring proper resource release
- Clearer Intent β Using
comsignals "this component has reactive side effects"
For any component using watch, effect, or similar lifecycle-dependent reactive APIs, com is not optionalβit's the idiomatic pattern. While technically you could manage scopes manually, doing so is error-prone and defeats the clarity of Rasen's design.
Unified Components β
In Rasen, business components and primitive components have no fundamental difference. div, view, circle β these "tags" are also component functions; they just encapsulate host operations.
Benefits:
- No distinction between "native components" and "custom components"
- Primitive component implementation is transparent, freely extensible
- Business components appear as single-layer functions (primitives encapsulate mount logic)
comwrapper enables idiomatic business component definition
Minimal Core, Infinite Possibilities β
Rasen's core code is extremely small β nearly zero. Because what it delivers isn't code, but a paradigm.
The Essence of the Paradigm β
type Mountable<Node = unknown> = (
node: Node,
hooks: HostHooks<Node> | undefined
) => (() => void) | undefinedhooks is the host's capability set (markers, insertion, batching, text creation) β passed explicitly down the tree, so there is no global "current host" and components stay testable in isolation.
Anything with a concept of "mount" and "unmount" can use Rasen's pattern:
| Render Target | Host Type | Examples |
|---|---|---|
| DOM | HTMLElement | div, span, button |
| Canvas 2D | CanvasRenderingContext2D | rect, circle, text |
| React Native | Container (Fabric) | view, text, image |
| HTML (SSR) | string[] | String concatenation |
| WebGL | WebGLRenderingContext | Shaders, meshes |
| Three.js | Scene | 3D objects |
| Terminal TUI | Terminal | Text interfaces |
| File Generation | FileWriter | Any file format |
Rasen has no boundaries β because it's just a paradigm.
Positioning: Universal Graphical Rendering Paradigm for JS β
Rasen isn't just another frontend framework.
Our vision spans the entire graphical rendering domain within JavaScript execution environments:
- DOM/HTML is just one of many render targets
- On par with Canvas, WebGL, React Native, Terminal
- Long-term goal: become the universal rendering layer for the JS ecosystem
βββββββββββββββββββββ
β Rasen Paradigm β
ββββββββββ¬βββββββββββ
βββββββββββββββββββΌββββββββββββββββββ
βΌ βΌ βΌ
ββββββββββ ββββββββββ ββββββββββ
β DOM β β Canvas β β RN β ...
ββββββββββ ββββββββββ ββββββββββComparison with Other Frameworks β
| Feature | React | Vue | Solid | Rasen |
|---|---|---|---|---|
| Virtual DOM | β | β | β | β |
| Built-in Reactivity | β (Hooks) | β | β | β (Pluggable) |
| Cross Render Targets | Limited | β | β | β Core Design |
| Closure Pitfalls | Common | Rare | Rare | Structurally Avoided |
| Core Size | Large | Medium | Small | Minimal |
| Learning Curve | Steep | Moderate | Moderate | Gentle |
Rasen doesn't aim to replace these frameworks β it provides a lower-level, more flexible paradigm, letting developers choose freely based on their scenarios.
Summary β
One Reactive Core, Multiple Render Targets
Rasen's core philosophy:
- Don't Reinvent Wheels β Use the best existing reactive systems
- Don't Over-Engineer β Abandon VDOM, operate directly
- Set No Boundaries β Render to any mountable target
- Solve Closures with Closures β Three-phase functions, clear lifecycle
- Paradigm Over Implementation β Minimal core, infinite possibilities
Like the spiral energy in Tengen Toppa Gurren Lagann, Rasen pursues constant breakthrough β breaking through framework boundaries, rendering limitations, and the very definition of frontend.