Clojure → Datastar expression transpiler.
Write Datastar (data-*) expressions as s-expressions with hyper's own vocabulary — signals are atoms here exactly as they are in actions:
(let [open?* (h/local-signal :open false)] [:button {:data-on:click (->expr (swap! open?* not))}] ;; "$_open = !($_open)" [:div {:data-show (->expr @open?*)}]) ;; "$_open"
The same (reset! sig v) form means a server round-trip inside
h/action and an instant client-side assignment inside ->expr —
the surrounding macro decides where the code runs, not the syntax.
Boundary inference — what is Clojure and what is client-side:
$signal references, plain values become JS
literals. No unquoting needed.(reset! sig v) / (swap! sig f & args) on a signal local compile
to assignments: $sig = ....@sig (deref of a local) becomes a $sig reference.(:id person) evaluate as Clojure at runtime and
splice as literals (keywords are not callable client-side).~form explicitly splices any other Clojure expression.(h/action ...) (any var tagged :hyper/datastar-expr) runs as Clojure
at render time — registering the action — and contributes its raw
@post(...) expression, so actions compose inside client-side control
flow: (->expr (when (= $key "Enter") (h/action ...))).$value, $checked, $key, $detail,
$form-data, and any registered via hyper.client-params) expand to
their client-side JS accessor, the same vocabulary action uses:
$key → evt.key, $value → evt.target.value. These names take
precedence, so a signal literally named value is reached via its
signal object (@value*), not the raw $value symbol.$signal symbols, evt/el,
Datastar actions ((@post "/x")), JS interop and operators.Compilation happens at macro-expansion time via Squint — runtime cost
is string interpolation of spliced values only. Output is
dependency-free JavaScript for Datastar's sandboxed evaluator:
and/or/not/str etc. emit bare JS operators, never library calls.
The transpiler core is ported, with gratitude, from Casey Link's datastar-expressions (https://github.com/outskirtslabs/datastar-expressions, MIT). Hyper's additions: macro-time compilation with runtime splicing, boundary inference via &env, and atom-vocabulary signal support.
Covered: the simple, obvious expressions a human would write. It is possible to write forms that produce broken JavaScript — raw strings remain the escape hatch everywhere expressions are accepted.
Clojure → Datastar expression transpiler.
Write Datastar (data-*) expressions as s-expressions with hyper's own
vocabulary — signals are atoms here exactly as they are in actions:
(let [open?* (h/local-signal :open false)]
[:button {:data-on:click (->expr (swap! open?* not))}] ;; "$_open = !($_open)"
[:div {:data-show (->expr @open?*)}]) ;; "$_open"
The same `(reset! sig v)` form means a server round-trip inside
`h/action` and an instant client-side assignment inside `->expr` —
the surrounding macro decides where the code runs, not the syntax.
**Boundary inference** — what is Clojure and what is client-side:
- Locals (anything bound in the surrounding scope) splice at runtime:
signal objects become `$signal` references, plain values become JS
literals. No unquoting needed.
- `(reset! sig v)` / `(swap! sig f & args)` on a signal local compile
to assignments: `$sig = ...`.
- `@sig` (deref of a local) becomes a `$sig` reference.
- Keyword-call forms `(:id person)` evaluate as Clojure at runtime and
splice as literals (keywords are not callable client-side).
- `~form` explicitly splices any other Clojure expression.
- `(h/action ...)` (any var tagged `:hyper/datastar-expr`) runs as Clojure
at render time — registering the action — and contributes its raw
`@post(...)` expression, so actions compose inside client-side control
flow: `(->expr (when (= $key "Enter") (h/action ...)))`.
- Client-param symbols (`$value`, `$checked`, `$key`, `$detail`,
`$form-data`, and any registered via `hyper.client-params`) expand to
their client-side JS accessor, the same vocabulary `action` uses:
`$key` → `evt.key`, `$value` → `evt.target.value`. These names take
precedence, so a signal literally named `value` is reached via its
signal object (`@value*`), not the raw `$value` symbol.
- Everything else is client-side: `$signal` symbols, `evt`/`el`,
Datastar actions (`(@post "/x")`), JS interop and operators.
Compilation happens at macro-expansion time via Squint — runtime cost
is string interpolation of spliced values only. Output is
dependency-free JavaScript for Datastar's sandboxed evaluator:
`and`/`or`/`not`/`str` etc. emit bare JS operators, never library calls.
The transpiler core is ported, with gratitude, from Casey Link's
datastar-expressions (https://github.com/outskirtslabs/datastar-expressions,
MIT). Hyper's additions: macro-time compilation with runtime splicing,
boundary inference via &env, and atom-vocabulary signal support.
Covered: the simple, obvious expressions a human would write. It is
possible to write forms that produce broken JavaScript — raw strings
remain the escape hatch everywhere expressions are accepted.(->expr & forms)Compile Clojure forms into a Datastar expression string.
Signals use atom vocabulary — reset!, swap!, deref — exactly as in
actions, but compile to instant client-side signal operations. Locals
splice automatically; evt, el, $signal symbols, Datastar actions
((@post "/x")) and JS interop pass through to the client. Multiple
forms join with ;.
Examples: (->expr (swap! open?* not)) ;; "$_open = !($_open)" (->expr (reset! query* evt.target.value)) (->expr (when (= evt.key "Enter") (@post "/search"))) (->expr (set! $count 0)) ;; raw Datastar style works too (->expr (reset! person-id* (:id person))) ;; keyword calls are Clojure
Compilation happens at macro-expansion; spliced values interpolate at runtime. See the hyper.expr namespace docs for boundary rules.
Compile Clojure forms into a Datastar expression string. Signals use atom vocabulary — reset!, swap!, deref — exactly as in actions, but compile to instant client-side signal operations. Locals splice automatically; `evt`, `el`, `$signal` symbols, Datastar actions ((@post "/x")) and JS interop pass through to the client. Multiple forms join with `;`. Examples: (->expr (swap! open?* not)) ;; "$_open = !($_open)" (->expr (reset! query* evt.target.value)) (->expr (when (= evt.key "Enter") (@post "/search"))) (->expr (set! $count 0)) ;; raw Datastar style works too (->expr (reset! person-id* (:id person))) ;; keyword calls are Clojure Compilation happens at macro-expansion; spliced values interpolate at runtime. See the hyper.expr namespace docs for boundary rules.
(compile-form form)Compile a single (boundary-inferred) form to a Datastar expression string. Public for testing.
Compile a single (boundary-inferred) form to a Datastar expression string. Public for testing.
(splice v mode)Encode a spliced runtime value. :signal mode requires a signal object (the target of reset!/swap!); :value mode renders anything implementing DatastarExpr (signals -> $ref, actions -> raw @post(...)) as raw JS and everything else as a JS literal.
Encode a spliced runtime value. :signal mode requires a signal object (the target of reset!/swap!); :value mode renders anything implementing DatastarExpr (signals -> $ref, actions -> raw @post(...)) as raw JS and everything else as a JS literal.
(substitute template pairs)Replace placeholders in a compiled template with spliced runtime values.
Replace placeholders in a compiled template with spliced runtime values.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |