Skip to content

🌀 RasenOne Reactive Core, Multiple Render Targets

A reactive rendering framework agnostic to both reactive systems and rendering targets.

Quick Example ​

tsx
import { Signal } from 'signal-polyfill'
import { useReactiveRuntime } from '@rasenjs/reactive-signals'
import { mount } from '@rasenjs/dom'

// Install the reactivity adapter once, at startup.
useReactiveRuntime()

const count = new Signal.State(0)

mount(
  <div class="counter">
    {/* a bare ref interpolates directly — the compiler binds it reactively */}
    <p>Count: {count}</p>
    <button onClick={() => count.set(count.get() + 1)}>+</button>
  </div>,
  document.getElementById('app')!
)
tsx
import { Signal } from 'signal-polyfill'
import { useReactiveRuntime } from '@rasenjs/reactive-signals'
import { canvas, mount } from '@rasenjs/dom'
import { rect, text } from '@rasenjs/canvas-2d'

useReactiveRuntime()

const x = new Signal.State(50)

// The canvas bridge lives in @rasenjs/dom; the shapes come from the target.
mount(
  canvas({
    width: 400,
    height: 200,
    children: [
      rect({ x, y: 50, width: 100, height: 80, fill: '#4CAF50' }),
      text({ text: 'Rasen', x: 12, y: 24, fill: '#333' })
    ]
  }),
  document.getElementById('app')!
)

// Shape props are reactive: writing the signal marks the canvas dirty.
const start = performance.now()
const spin = (now: number) => {
  x.set(150 + Math.sin((now - start) / 500) * 100)
  requestAnimationFrame(spin)
}
requestAnimationFrame(spin)
tsx
import { renderToString, div, p, ul, li } from '@rasenjs/html'

// Same components, string host — no DOM required.
const html = renderToString(
  div(
    { class: 'container' },
    p({ class: 'title' }, 'Hello from SSR!'),
    ul({ class: 'list' }, li('Item 1'), li('Item 2'))
  )
)
tsx
import { View, Text, TouchableOpacity, registerApp } from '@rasenjs/react-native'
import { useReactiveRuntime } from '@rasenjs/reactive-vue'
import { ref } from '@vue/reactivity'

useReactiveRuntime()

const count = ref(0)

registerApp('MyApp', () => (
  <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
    <Text style={{ fontSize: 48 }}>{count}</Text>
    <TouchableOpacity onPress={() => count.value++}>
      <Text>+</Text>
    </TouchableOpacity>
  </View>
))

The Paradigm ​

Everything in Rasen comes down to one simple type:

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

A component receives a host, mounts itself into it, and returns a cleanup function. That's it — no virtual DOM, no component instances.

Released under the MIT License.