Liking cljdoc? Tell your friends :D

hyper.expr

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: $keyevt.key, $valueevt.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.

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.
raw docstring

->exprcljmacro

(->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.
sourceraw docstring

compile-formclj

(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.
sourceraw docstring

spliceclj

(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.
sourceraw docstring

substituteclj

(substitute template pairs)

Replace placeholders in a compiled template with spliced runtime values.

Replace placeholders in a compiled template with spliced runtime values.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close