You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@getforma/core

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@getforma/core - npm Package Compare versions

Comparing version
0.2.0
to
0.3.0
+3
-1
package.json
{
"name": "@getforma/core",
"author": "Forma <victor@getforma.dev>",
"version": "0.2.0",
"version": "0.3.0",
"description": "Real DOM reactive library — fine-grained signals, islands architecture, SSR hydration. No virtual DOM, no diffing. ~15KB gzipped.",

@@ -80,2 +80,3 @@ "type": "module",

"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"prepublishOnly": "npm run build"

@@ -117,2 +118,3 @@ },

"devDependencies": {
"@vitest/coverage-v8": "^4.0.18",
"fake-indexeddb": "^6.2.3",

@@ -119,0 +121,0 @@ "happy-dom": "^20.8.3",

+251
-13

@@ -7,3 +7,3 @@ # FormaJS

Reactive DOM library with fine-grained signals, islands architecture, and SSR hydration. ~15KB gzipped.
Reactive DOM library with fine-grained signals. No virtual DOM — `h()` creates real elements, signals update only what changed. ~15KB gzipped.

@@ -16,4 +16,68 @@ ## Install

## Quick Start
Or use the CDN (no build step required):
```html
<script src="https://unpkg.com/@getforma/core/dist/formajs-runtime.global.js"></script>
```
## Why FormaJS?
Most UI libraries make you choose: simple but limited (Alpine, htmx), or powerful but complex (React, Vue, Svelte). FormaJS gives you a single reactive core that scales from a CDN script tag to a full-stack Rust SSR pipeline.
**Design principles:**
- **Real DOM, not virtual DOM.** `h('div')` returns an actual `HTMLDivElement`. Signals mutate it directly. No diffing pass, no reconciliation overhead for simple updates. Inspired by [Solid](https://www.solidjs.com/).
- **Fine-grained reactivity.** Powered by [alien-signals](https://github.com/nicolo-ribaudo/alien-signals). When a signal changes, only the specific DOM text node or attribute that depends on it updates — not the whole component tree.
- **Three entry points, one engine.** HTML Runtime (like Alpine — zero build step), `h()` hyperscript (like Preact), or JSX. All share the same signal graph. Pick the right tool for the job, upgrade without rewriting.
- **CSP-safe by default.** The HTML Runtime includes a hand-written expression parser. `new Function()` is an opt-in fallback, not a requirement. Ship to strict CSP environments without worry.
- **Islands over SPAs.** `activateIslands()` hydrates independent regions of server-rendered HTML. Each island is self-contained. Ship less JavaScript, keep server-rendered content instant.
**What FormaJS is not:** It's not a framework with opinions about routing, data fetching, or state management patterns. It's a reactive DOM library. You bring the architecture.
## Three Ways to Use FormaJS
### 1. HTML Runtime (no build step)
Drop a script tag, write `data-*` attributes. Zero config, zero tooling.
```html
<script src="https://unpkg.com/@getforma/core/dist/formajs-runtime.global.js"></script>
<div data-forma-state='{"count": 0}'>
<p data-text="{count}"></p>
<button data-on:click="{count++}">+1</button>
<button data-on:click="{count = 0}">Reset</button>
</div>
```
#### Supported Directives
| Directive | Description | Example |
|-----------|-------------|---------|
| `data-forma-state` | Declare reactive state (JSON) | `data-forma-state='{"count": 0}'` |
| `data-text` | Bind text content | `data-text="{count}"` |
| `data-show` | Toggle visibility (display) | `data-show="{isOpen}"` |
| `data-if` | Conditional render (add/remove from DOM) | `data-if="{loggedIn}"` |
| `data-model` | Two-way binding (inputs) | `data-model="{email}"` |
| `data-on:event` | Event handler | `data-on:click="{count++}"` |
| `data-class:name` | Conditional CSS class | `data-class:active="{isActive}"` |
| `data-bind:attr` | Dynamic attribute | `data-bind:href="{url}"` |
| `data-list` | List rendering with keyed reconciliation | `data-list="{items}"` |
| `data-computed` | Computed value | `data-computed="doubled = count * 2"` |
| `data-persist` | Persist state to localStorage | `data-persist="{count}"` |
| `data-fetch` | Fetch data from URL | `data-fetch="GET /api/items → items"` |
| `data-transition:*` | Enter/leave CSS transitions | `data-transition:enter="fade-in"` |
CSP-safe expression parser — no `eval()` or `new Function()` by default. For strict CSP environments, use the hardened build:
```html
<script src="https://unpkg.com/@getforma/core/dist/formajs-runtime-hardened.global.js"></script>
```
### 2. Hyperscript — `h()`
```bash
npm install @getforma/core
```
```typescript

@@ -32,17 +96,191 @@ import { createSignal, h, mount } from '@getforma/core';

## Features
### 3. JSX
- **Real DOM** — `h()` creates actual DOM elements with reactive bindings. No virtual DOM, no diffing overhead.
- **Fine-grained reactivity** — signals, effects, computed values via alien-signals. Only what changed updates.
- **Islands architecture** — `activateIslands()` for independent hydration of server-rendered HTML regions.
- **SSR hydration** — `adoptNode()` walks server-rendered DOM and attaches reactive bindings without re-creating elements.
- **Conditional rendering** — `createShow()`, `createSwitch()` with branch caching for O(1) toggle.
- **List rendering** — `createList()` with LIS-based keyed reconciliation, handles 50K+ rows.
- **State management** — `createStore()` with deep reactivity, history, persistence.
Same `h()` function, JSX syntax. Configure your bundler:
```json
// tsconfig.json
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
}
}
```
```tsx
import { createSignal, h, Fragment, mount } from '@getforma/core';
const [count, setCount] = createSignal(0);
function Counter() {
return (
<>
<p>{() => `Count: ${count()}`}</p>
<button onClick={() => setCount(count() + 1)}>+1</button>
</>
);
}
mount(() => <Counter />, '#app');
```
If you use `@getforma/build`, JSX is preconfigured — just write `.tsx` files.
## Core API
### Signals
```typescript
import { createSignal, createEffect, createComputed, batch } from '@getforma/core';
const [count, setCount] = createSignal(0);
const doubled = createComputed(() => count() * 2);
createEffect(() => console.log('count:', count()));
batch(() => {
setCount(1);
setCount(2); // effect fires once with value 2
});
```
### Conditional Rendering
```typescript
import { createSignal, createShow, createSwitch, h } from '@getforma/core';
const [loggedIn, setLoggedIn] = createSignal(false);
// createShow — toggle between two branches
createShow(loggedIn,
() => h('p', null, 'Welcome back'),
() => h('p', null, 'Please sign in'),
);
// createSwitch — multi-branch with caching
const [view, setView] = createSignal('home');
createSwitch(view, [
{ match: 'home', render: () => h('div', null, 'Home') },
{ match: 'settings', render: () => h('div', null, 'Settings') },
], () => h('div', null, '404 Not Found'));
```
### List Rendering
```typescript
import { createSignal, createList, h } from '@getforma/core';
const [items, setItems] = createSignal([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]);
createList(
items,
(item) => item.id, // key function
(item) => h('li', null, item.name),
);
```
### Store (deep reactivity)
```typescript
import { createStore } from '@getforma/core';
const [state, setState] = createStore({
user: { name: 'Alice', prefs: { theme: 'dark' } },
items: [1, 2, 3],
});
// Read reactively
state.user.name; // 'Alice'
// Mutate — only affected subscribers update
setState('user', 'name', 'Bob');
setState('items', items => [...items, 4]);
```
### Components
```typescript
import { defineComponent, onMount, onUnmount, h } from '@getforma/core';
const Timer = defineComponent(() => {
const [seconds, setSeconds] = createSignal(0);
onMount(() => {
const id = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(id); // cleanup on unmount
});
return h('span', null, () => `${seconds()}s`);
});
document.body.appendChild(Timer());
```
### Context (Dependency Injection)
```typescript
import { createContext, provide, inject } from '@getforma/core';
const ThemeCtx = createContext('light');
provide(ThemeCtx, 'dark');
const theme = inject(ThemeCtx); // 'dark'
```
## Islands Architecture
For server-rendered HTML, activate independent interactive regions:
```typescript
import { activateIslands, createSignal, h } from '@getforma/core';
activateIslands({
Counter: (el, props) => {
const [count, setCount] = createSignal(props.initial ?? 0);
// Hydrate: attach reactivity to existing server-rendered DOM
},
});
```
```html
<!-- Server-rendered HTML -->
<div data-forma-island="Counter" data-forma-props='{"initial": 5}'>
<span>5</span>
<button>+1</button>
</div>
```
## Subpath Exports
| Import | Description |
|--------|-------------|
| `@getforma/core` | Signals, `h()`, `mount()`, lists, stores, components |
| `@getforma/core/runtime` | HTML Runtime — `initRuntime()`, `mount()`, `unmount()` |
| `@getforma/core/runtime-hardened` | Runtime with `new Function()` locked off (strict CSP) |
| `@getforma/core/ssr` | Server-side rendering — `renderToString()`, `renderToStream()` |
| `@getforma/core/tc39` | TC39-compatible `Signal.State` and `Signal.Computed` classes |
## Examples
See the [`examples/`](./examples) directory:
- **counter** — minimal `h()` counter
- **counter-jsx** — same counter with JSX syntax
- **todo** — todo list with `createList` and keyed reconciliation
- **data-table** — sortable table with `createList`
## Ecosystem
- [forma](https://github.com/getforma-dev/forma) — Rust server framework (forma-ir + forma-server)
- [forma-tools](https://github.com/getforma-dev/forma-tools) — Build tooling (@getforma/compiler + @getforma/build)
- [create-forma-app](https://github.com/getforma-dev/create-forma-app) — CLI scaffolder
| Package | Description |
|---------|-------------|
| [@getforma/core](https://www.npmjs.com/package/@getforma/core) | This library — `npm install @getforma/core` |
| [@getforma/compiler](https://www.npmjs.com/package/@getforma/compiler) | SSR compiler — `.tsx` to FMIR binary |
| [@getforma/build](https://www.npmjs.com/package/@getforma/build) | esbuild wrapper with JSX + SSR preconfigured |
| [create-forma-app](https://www.npmjs.com/package/@getforma/create-app) | `npx @getforma/create-app` project scaffolder |
| [forma](https://github.com/getforma-dev/forma) | Rust server framework (forma-ir + forma-server) |

@@ -49,0 +287,0 @@ ## License