vanrs
v1.1.2
Published
Frontend frame for rescript inspired by vanjs
Readme
vanrs
ReScript bindings for VanJS — zero-overhead reactive UI via direct FFI into vanjs-core v1.6.1.
Overview
vanrs provides typed ReScript APIs for building reactive DOM UIs. State management (VanRs.state, VanRs.derive), element construction (tag functions and VanRs.element), event handling, hydration, and JSX support are all thin wrappers over vanjs-core primitives.
Requires: ReScript ^12.3.0, vanjs-core ^1.6.1 (peer dependency)
Features
- Direct FFI bindings to vanjs-core — no custom reactivity engine
- Full state API:
state,get,set,raw,old,derive,watch - Tag DSL: HTML elements, SVG elements, namespaced tag creation
- JSX support via
VanRsJsxmodule (ReScript JSX v4) - Hydration support for SSR scenarios
- Type-safe event handlers and reactive attributes
- Opt-in
VanRs.safeUrlhelper for untrusted URL-bearing attributes
📦 Installation
1. Create a ReScript Application
npm create rescript-app@latestSee the official ReScript documentation for project setup.
2. Install Dependencies
npm i vanjs-core vanrsNote: vanjs-core is a peer dependency — you must install it explicitly. vanrs binds to vanjs-core v1.6.1 and expects it to be available at runtime.
3. Configure rescript.json
Add vanrs to your dependencies in rescript.json:
{
"dependencies": ["vanrs"]
}For JSX support, the project already includes the correct configuration:
{
"jsx": {
"version": 4,
"module": "VanRsJsx"
}
}⚡ Quick Start
Basic Reactive UI
// Mount a reactive counter into the DOM
let count = VanRs.state(0)
let app = VanRs.div([
VanRs.text("Clicks: "),
VanRs.stateInt(count),
VanRs.button(
~props=[VanRs.on("click", _ => VanRs.set(count, VanRs.get(count) + 1))],
[VanRs.text("Click me")]
)
])
// Add to document body
VanRs.add(VanRs.body(), [app])Using VanRs.nodeById
// Get an existing DOM element by id
switch VanRs.nodeById("root") {
| Some(root) => VanRs.add(root, [app])->ignore
| None => ()
}Derive Reactive Values
let name = VanRs.state("World")
let greeting = VanRs.derive(() => "Hello, " ++ VanRs.get(name) ++ "!")
// greeting updates automatically when name changes
VanRs.set(name, "ReScript")🧪 Testing
Test suite: 163 tests (223 assertions, all passing)
pnpm res:testBuild:
pnpm res:buildSee docs/api-reference.md for the complete public API surface.
⚛️ JSX Support
VanRs supports JSX syntax with type-safe prop mapping via the VanRsJsx module. JSX elements are created with correct XML namespaces: SVG elements (like <rect>, <g>, <clipPath>, <linearGradient>) use the SVG namespace (http://www.w3.org/2000/svg), and MathML elements (like <math>, <mrow>, <mi>) use the MathML namespace (http://www.w3.org/1998/Math/MathML). Unknown/custom tags fall back to HTML-namespace creation.
let clicks = VanRs.state(0)
let view = <button
className="btn"
onClick={_ => VanRs.set(clicks, VanRs.get(clicks) + 1)}
>
{clicks->VanRs.stateInt}
</button>See docs/jsx-mapping.md for the full JSX-to-API mapping with namespace coverage, and docs/api-reference.md for complete API details.
📚 Documentation
| Document | Description | |----------|-------------| | API Reference | Complete public API documentation | | JSX Mapping | JSX syntax to VanRs API mapping | | Known Divergences | 5 known test failures from vanjs-core binding integration |
Explore the docs: API Reference · JSX Mapping · Known Divergences
🏗️ Architecture
The public entry point is VanRs. Low-level vanjs-core FFI bindings are available via VanRsVan for advanced use cases requiring direct access to vanjs primitives.
🔒 Security
VanRs is safe-by-design for normal DOM construction: string children become text nodes (no HTML parsing), attributes route through setAttribute only (not DOM property setters), and event handlers are typed as functions (no string handlers). The one remaining gap is URL-bearing attributes (href, src, action, formaction, xlink:href). When you bind an untrusted string to any of these, wrap it with VanRs.safeUrl:
let link = VanRs.a(
~props=[VanRs.attr("href", VanRs.safeUrl(userInput))],
[VanRs.text("open")],
)safeUrl neutralizes javascript:, vbscript:, file:, data:text/*, and unknown schemes (returning ""), and defeats control-character tricks like "java\tscript:". It is opt-in; VanRs never sanitizes implicitly. See the API Reference for the full contract.
License
MIT — @MetalbolicX
