Skip to content

πŸŒ€ 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:

typescript
// Use Vue's reactivity
import { useReactiveRuntime } from '@rasenjs/reactive-vue'
useReactiveRuntime()
typescript
// 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:

typescript
// 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:

typescript
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 host

Rasen's rendering flow:

Component receives host β†’ Component decides how to mount

Host Passes Down the Chain ​

The host isn't a black box inside the framework. It passes down the mount chain to every component:

typescript
// 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:

typescript
// 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:

typescript
// 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:

  1. Eliminates Boilerplate β€” No need for effectScope() and scope.run()
  2. Guaranteed Cleanup β€” Effects are automatically stopped on unmount, preventing memory leaks
  3. Correct Lifecycle β€” User cleanup runs before scope stops, ensuring proper resource release
  4. Clearer Intent β€” Using com signals "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)
  • com wrapper 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 ​

typescript
type Mountable<Node = unknown> = (
  node: Node,
  hooks: HostHooks<Node> | undefined
) => (() => void) | undefined

hooks 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 TargetHost TypeExamples
DOMHTMLElementdiv, span, button
Canvas 2DCanvasRenderingContext2Drect, circle, text
React NativeContainer (Fabric)view, text, image
HTML (SSR)string[]String concatenation
WebGLWebGLRenderingContextShaders, meshes
Three.jsScene3D objects
Terminal TUITerminalText interfaces
File GenerationFileWriterAny 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 ​

FeatureReactVueSolidRasen
Virtual DOMβœ…βœ…βŒβŒ
Built-in Reactivity❌ (Hooks)βœ…βœ…βŒ (Pluggable)
Cross Render TargetsLimitedβŒβŒβœ… Core Design
Closure PitfallsCommonRareRareStructurally Avoided
Core SizeLargeMediumSmallMinimal
Learning CurveSteepModerateModerateGentle

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:

  1. Don't Reinvent Wheels β€” Use the best existing reactive systems
  2. Don't Over-Engineer β€” Abandon VDOM, operate directly
  3. Set No Boundaries β€” Render to any mountable target
  4. Solve Closures with Closures β€” Three-phase functions, clear lifecycle
  5. 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.

Released under the MIT License.