@raven-js/reflex
Version:
Universal reactive signals for modern JavaScript - zero dependencies, SSR/hydration, DOM updates
219 lines (155 loc) • 7.7 kB
Markdown
[](https://ravenjs.dev)
[](https://docs.ravenjs.dev/reflex)
[](https://github.com/Anonyfox/ravenjs)
[](https://nodejs.org/api/esm.html)
[](https://nodejs.org/)
<div align="center">
<img src="./media/logo.webp" alt="Reflex Logo" width="200" height="200" />
</div>
Universal reactive signals with automatic SSR, seamless hydration, and zero-dependency DOM updates.
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.
```bash
npm install @raven-js/reflex
```
```javascript
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"
```
Inline component-scoped SSR with per-component data injection. Each `ssr()` call gets unique cache isolation and works in any HTML structure.
```javascript
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 behavior
```
**Architectural 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
```javascript
import { mount } from "@raven-js/reflex/dom";
// Mount reactive components (browser-only)
mount(() => `<h1>Hello ${signal("World")()}</h1>`, "#app");
```
Computed-first microtask execution prevents glitched intermediate states. Computeds propagate until stable, then effects execute with consistent data view.
Render slots preserve component instances across renders, preventing object churn. Deferred effects execute after template completion rather than during rendering, enabling proper initialization order.
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](https://github.com/Anonyfox/ravenjs/tree/main/packages/wings) for fullstack reactivity:
```javascript
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 `LocalFetch` transparently 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
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:
[](https://github.com/sponsors/Anonyfox)
Your sponsorship helps keep RavenJS **zero-dependency**, **modern**, and **developer-friendly**.
---
**Built with ❤️ by [Anonyfox](https://anonyfox.com)**