@raven-js/reflex
v0.4.48
Published
Universal reactive signals for modern JavaScript - zero dependencies, SSR/hydration, DOM updates
Maintainers
Readme
Reflex: Universal Reactive Signals
Universal reactive signals with automatic SSR, seamless hydration, and zero-dependency DOM updates.
Purpose
Reactive programming suffers from complexity taxation - frameworks demand steep learning curves, bloated dependencies, and vendor lock-in. Most reactive systems exhibit glitches where intermediate states leak through, breaking consistency guarantees developers expect.
Reflex provides mathematical precision: computed-first execution prevents glitched states, component-scoped SSR eliminates global state headaches, and zero dependencies mean no supply chain vulnerabilities. Works identically across browser, Node.js, Deno, and Bun environments.
Installation
npm install @raven-js/reflexUsage
Core Signals
import { signal, computed, effect } from "@raven-js/reflex";
// Create reactive state
const count = signal(0);
const doubled = computed(() => count() * 2);
// React to changes
effect(() => {
console.log(`Count: ${count()}, Doubled: ${doubled()}`);
});
// Update state
count.set(5); // Logs: "Count: 5, Doubled: 10"Component-Scoped SSR
Inline component-scoped SSR with per-component data injection. Each ssr() call gets unique cache isolation and works in any HTML structure.
import { ssr } from "@raven-js/reflex/ssr";
const UserWidget = ssr(async () => {
const response = await fetch("/api/user"); // relative URLs work via RAVENJS_ORIGIN
const user = await response.json();
return `<span>Hello ${user.name}!</span>`;
});
const WeatherWidget = ssr(async () => {
const response = await fetch("/api/weather");
const weather = await response.json();
return `<div>${weather.temp}°C</div>`;
});
// Works anywhere in HTML structure - header, footer, modal, nested components
const layout = `
<header>${await UserWidget()}</header>
<main>Content here</main>
<footer>${await WeatherWidget()}</footer>
`;
// Server output:
// <span>Hello Alice!</span><script>window.__SSR_DATA__abc123 = {...}</script>
// <div>22°C</div><script>window.__SSR_DATA__def456 = {...}</script>
// Client hydrates using cached data, then restores normal fetch behaviorArchitectural Difference:
- Traditional SSR: Global state injection at HTML structure points (
</head>,</body>) - Reflex SSR: Component-scoped data injected inline with each component
- Result: Component isolation, layout independence, automatic cache management
DOM Utilities
import { mount } from "@raven-js/reflex/dom";
// Mount reactive components (browser-only)
mount(() => `<h1>Hello ${signal("World")()}</h1>`, "#app");Architecture
Signal Scheduler
Computed-first microtask execution prevents glitched intermediate states. Computeds propagate until stable, then effects execute with consistent data view.
Template Context System
Render slots preserve component instances across renders, preventing object churn. Deferred effects execute after template completion rather than during rendering, enabling proper initialization order.
Multi-pass SSR
Server runs multiple passes until output stabilizes (html === prevHtml && promises.size === 0). Uses exponential backoff between promise settlement attempts, individual timeouts per promise.
Fetch Integration in SSR
fetch() calls work within effect() callbacks using relative URLs via RAVENJS_ORIGIN environment variable. Server proxies globalThis.fetch, caches GET responses per component ID. Client finds cached entries across component-scoped SSR blobs, consumes them, then restores original fetch behavior.
Hydration Intelligence
mount() tracks write version baseline - skips initial DOM replacement when SSR content exists and no reactive writes occurred yet. Prevents downgrade from server-rendered to empty client state.
Performance Implementation
- WeakMap template caching
- Monomorphic signal reads optimize V8
- rAF + microtask scheduling for paint alignment
- WeakRef DOM tracking for cleanup
Wings Integration
Reflex integrates with @raven-js/wings for fullstack reactivity:
import { Router } from "@raven-js/wings";
import { html } from "@raven-js/beak";
import { ssr, signal, computed } from "@raven-js/reflex";
const router = new Router();
// Reactive route handlers - same code runs server & client
router.get(
"/todos",
ssr(async (ctx) => {
// Create reactive state
const todos = signal([]);
const filter = signal("all");
// Automatic fetch interception + SSR caching
const response = await fetch("/api/todos");
todos.set(await response.json());
// Computed values work perfectly
const filteredTodos = computed(() => {
switch (filter()) {
case "active":
return todos().filter((t) => !t.completed);
case "completed":
return todos().filter((t) => t.completed);
default:
return todos();
}
});
// Return beak template with reactive data
return ctx.html(html`
<html>
<head>
<title>Todo App</title>
<script src="/client.js"></script>
</head>
<body>
<h1>Todos (${filteredTodos().length})</h1>
<ul>
${filteredTodos().map(
(todo) => html`
<li class="${todo.completed ? "done" : ""}">${todo.title}</li>
`
)}
</ul>
</body>
</html>
`);
})
);
// Wings handles the server, Reflex handles the reactivity
router.listen(3000);Integration Benefits:
- Wings
LocalFetchtransparently resolves relative URLs and forwards headers - Component-scoped SSR data injection with cache isolation
- SSR components work in any HTML structure
- Client hydration uses server response cache, eliminates duplicate requests
- Signal changes trigger efficient DOM updates
- Beak template integration for HTML generation
SSR Fetch Behavior
In SSR, use relative URLs in fetch() and register Wings LocalFetch middleware. It resolves relative URLs against the current request, forwards headers (and self-signed HTTPS agent for internal hosts), and isolates per request.
Requirements
- Node.js: 22.5+
- Browsers: ES2020+ support
- Dependencies: Zero
The Raven's Reflex
Like a raven's lightning-fast reflexes responding to environmental changes, Reflex signals react instantly to state mutations, propagating updates through the system with surgical precision and unwavering consistency.
🦅 Support RavenJS Development
If you find RavenJS helpful, consider supporting its development:
Your sponsorship helps keep RavenJS zero-dependency, modern, and developer-friendly.
Built with ❤️ by Anonyfox
