Liking cljdoc? Tell your friends :D

hyper.component

Client-side web components compiled via embedded Squint.

This namespace provides the foundation for defining custom elements in a ClojureScript dialect (Squint) that are compiled to JavaScript at the JVM (no Node build step) and served as a single ES module bundle at /hyper/components.js.

The model follows Datastar's recommended escape hatch for rich client-side islands — web components with "props down, events up":

  • Attributes are the boundary. The server renders component data into HTML attributes (deterministic JSON for collections). Datastar's morph updates attributes in place, and the client runtime re-renders only when an attribute string actually changed.
  • Events are the channel out. Components dispatch bubbling, composed CustomEvents that the server catches with ordinary data-on:* + h/action (see the $detail client param).

The primary API is the defc macro, which compiles the component's client behaviour to JS at macro-expansion time and emits a server-side render fn so call sites look like ordinary hiccup functions. register-component! is the lower-level escape hatch for cases where raw Squint strings are needed.

Related namespaces: hyper.component.hiccup (macro-time hiccup -> ($h ...) descriptor compilation) and hyper.component.bundle (ES module assembly and the head script tag).

Client-side web components compiled via embedded Squint.

This namespace provides the foundation for defining custom elements in a
ClojureScript dialect (Squint) that are compiled to JavaScript at the JVM
(no Node build step) and served as a single ES module bundle at
/hyper/components.js.

The model follows Datastar's recommended escape hatch for rich client-side
islands — web components with "props down, events up":

- **Attributes are the boundary.** The server renders component data into
  HTML attributes (deterministic JSON for collections).  Datastar's morph
  updates attributes in place, and the client runtime re-renders only when
  an attribute string actually changed.
- **Events are the channel out.** Components dispatch bubbling, composed
  CustomEvents that the server catches with ordinary `data-on:*` +
  `h/action` (see the `$detail` client param).

The primary API is the `defc` macro, which compiles the component's client
behaviour to JS at macro-expansion time and emits a server-side render fn
so call sites look like ordinary hiccup functions.  `register-component!`
is the lower-level escape hatch for cases where raw Squint strings are
needed.

Related namespaces: hyper.component.hiccup (macro-time hiccup -> ($h ...)
descriptor compilation) and hyper.component.bundle (ES module assembly
and the head script tag).
raw docstring

attr-valueclj

(attr-value v)

Serialize a single component attribute value for HTML. Strings pass through raw (human-readable attributes); collections get deterministic JSON; keywords serialize as their name.

Serialize a single component attribute value for HTML.
Strings pass through raw (human-readable attributes); collections get
deterministic JSON; keywords serialize as their name.
sourceraw docstring

attrsclj

(attrs m)

Serialize a map of component attribute values for use in hiccup.

Component data attrs are serialized via attr-value. Keys that are part of the host-element contract — data-* (e.g. data-on:* action bindings), :id, :class, :style — pass through untouched.

Passing a signal object (un-deref'd) as an attribute value creates a live two-way client-side link: Datastar keeps the attribute synced to the signal (component re-renders/updates on signal change with no server round-trip), and the component writes the signal back by emitting an event named after the attribute:

;; server render fn (let [hover* (h/signal :hovered-symbol nil)] (stock-chart {:points @points* :hover hover*}))

;; inside the component: read hover like any attr; write it with (emit "hover" "AAPL")

Example: [:temp-gauge (hc/attrs {:value @temp* :label "CPU" :data-on:gauge-selected (h/action ...)})]

Serialize a map of component attribute values for use in hiccup.

Component data attrs are serialized via `attr-value`.  Keys that are
part of the host-element contract — `data-*` (e.g. `data-on:*` action
bindings), :id, :class, :style — pass through untouched.

Passing a **signal object** (un-deref'd) as an attribute value creates a
live two-way client-side link: Datastar keeps the attribute synced to
the signal (component re-renders/updates on signal change with no server
round-trip), and the component writes the signal back by emitting an
event named after the attribute:

  ;; server render fn
  (let [hover* (h/signal :hovered-symbol nil)]
    (stock-chart {:points @points* :hover hover*}))

  ;; inside the component: read `hover` like any attr; write it with
  (emit "hover" "AAPL")

Example:
  [:temp-gauge (hc/attrs {:value @temp*
                          :label "CPU"
                          :data-on:gauge-selected (h/action ...)})]
sourceraw docstring

check-alias-conflicts!clj

(check-alias-conflicts! tag-name requires)

Throw when any of requires uses an alias already registered by another component with a different URL — the bundle is a single module scope, so aliases must map 1:1 to URLs across all components.

Public because defc expansions call it from consumer namespaces.

Throw when any of `requires` uses an alias already registered by another
component with a different URL — the bundle is a single module scope, so
aliases must map 1:1 to URLs across all components.

Public because defc expansions call it from consumer namespaces.
sourceraw docstring

compile-squintclj

(compile-squint source)

Compile a string of Squint (CLJS-dialect) source to JavaScript.

Imports are elided and squint.core references are emitted against the $sc alias, which the assembled bundle binds via a single import of squint's core.js. Returns the JS string.

Throws ex-info with the source attached when compilation fails.

Compile a string of Squint (CLJS-dialect) source to JavaScript.

Imports are elided and squint.core references are emitted against the
`$sc` alias, which the assembled bundle binds via a single import of
squint's core.js.  Returns the JS string.

Throws ex-info with the source attached when compilation fails.
sourceraw docstring

defccljmacro

(defc cname & body)

Define a client-side web component.

Compiles the component's client behaviour to JavaScript via Squint at macro-expansion time and registers it in the global component registry. Also emits a server-side render function (with the same name) that serializes attributes and returns the correct hiccup host element, so call sites look like ordinary Clojure functions.

Syntax: (defc component-name "Optional docstring" {:require [["url" :as alias] ...]} ; optional ES module dependencies [{:keys [attr1 attr2 ...]}] ; observed attributes — must be :keys form (event ::handler-name [e] ; zero or more named event handlers ...) (render ; hiccup for the shadow root hiccup-returning-body) (mount [root] ...) ; optional: runs once after first render (update [root old-attrs] ...) ; optional: runs on attr changes instead ; of re-rendering (seamless mode) (unmount [root] ...)) ; optional: cleanup on real DOM removal

Two update models:

  • Declarative (no update segment): render re-runs whenever an attribute actually changes.

  • Seamless (update present): render is a once-only scaffold (or omit it for a bare root); mount initializes a JS library against the root element; update receives each data change so the library can transition in place — the DOM is never re-rendered, so chart instances and animations survive arbitrary server re-renders. Morph-driven DOM moves are debounced and do NOT unmount; only real removal does.

In every segment, ctx is in scope: a stable per-instance JS object carrying emit, and usable as the instance state slot:

(mount [root] (set! (.-chart ctx) (init-chart! root data))) (update [root] (redraw! (.-chart ctx) data)) (unmount [root] (destroy! (.-chart ctx)))

:require declares ES module dependencies, available in all segments via the alias (e.g. (d3/line)). Each URL is imported once in the bundle, deduplicated across components; an alias may map to only one URL across the whole app:

(defc stock-chart {:require [["https://esm.sh/d3@7" :as d3]]} [{:keys [points]}] (render [:svg (d3-path points)]))

Inside render, reference event handlers in :on maps using namespace-qualified keywords matching the event names: {:on {:click ::handler-name}}

Inside event bodies, emit is available as a function: (emit "event-name" detail-map)

The emitted server-side function accepts a map argument followed by any number of child elements. Pass your attribute values (including data-on:* action bindings) in the map; children are appended to the host element's light DOM (projected through any <slot> the component renders):

(my-component {:value @cursor* :label "CPU" :data-on:selected (h/action (handle! $detail))})

(my-component {:label "CPU"} [:span "slotted child"] [:span "another"])

Example: (defc temp-gauge [{:keys [value max label]}] (event ::selected [_e] (emit "gauge-selected" {:value value :label label})) (render (let [pct (js/Math.round (* 100 (/ value max)))] [:div {:on {:click ::selected}} [:span label ": " pct "%"]])))

Define a client-side web component.

Compiles the component's client behaviour to JavaScript via Squint at
macro-expansion time and registers it in the global component registry.
Also emits a server-side render function (with the same name) that
serializes attributes and returns the correct hiccup host element,
so call sites look like ordinary Clojure functions.

Syntax:
  (defc component-name
    "Optional docstring"
    {:require [["url" :as alias] ...]}  ; optional ES module dependencies
    [{:keys [attr1 attr2 ...]}]   ; observed attributes — must be :keys form
    (event ::handler-name [e]     ; zero or more named event handlers
      ...)
    (render                       ; hiccup for the shadow root
      hiccup-returning-body)
    (mount [root] ...)            ; optional: runs once after first render
    (update [root old-attrs] ...) ; optional: runs on attr changes instead
                                  ; of re-rendering (seamless mode)
    (unmount [root] ...))         ; optional: cleanup on real DOM removal

Two update models:

- **Declarative** (no `update` segment): `render` re-runs whenever an
  attribute actually changes.

- **Seamless** (`update` present): `render` is a once-only scaffold (or
  omit it for a bare root); `mount` initializes a JS library against the
  root element; `update` receives each data change so the library can
  transition in place — the DOM is never re-rendered, so chart instances
  and animations survive arbitrary server re-renders.  Morph-driven DOM
  moves are debounced and do NOT unmount; only real removal does.

In every segment, `ctx` is in scope: a stable per-instance JS object
carrying `emit`, and usable as the instance state slot:

  (mount [root] (set! (.-chart ctx) (init-chart! root data)))
  (update [root] (redraw! (.-chart ctx) data))
  (unmount [root] (destroy! (.-chart ctx)))

`:require` declares ES module dependencies, available in all segments via
the alias (e.g. `(d3/line)`).  Each URL is imported once in the bundle,
deduplicated across components; an alias may map to only one URL across
the whole app:

  (defc stock-chart
    {:require [["https://esm.sh/d3@7" :as d3]]}
    [{:keys [points]}]
    (render [:svg (d3-path points)]))

Inside `render`, reference event handlers in `:on` maps using
namespace-qualified keywords matching the `event` names:
  {:on {:click ::handler-name}}

Inside `event` bodies, `emit` is available as a function:
  (emit "event-name" detail-map)

The emitted server-side function accepts a map argument followed by any
number of child elements.  Pass your attribute values (including
`data-on:*` action bindings) in the map; children are appended to the
host element's light DOM (projected through any `<slot>` the component
renders):

  (my-component {:value  @cursor*
                 :label  "CPU"
                 :data-on:selected (h/action (handle! $detail))})

  (my-component {:label "CPU"}
    [:span "slotted child"]
    [:span "another"])

Example:
  (defc temp-gauge
    [{:keys [value max label]}]
    (event ::selected [_e]
      (emit "gauge-selected" {:value value :label label}))
    (render
      (let [pct (js/Math.round (* 100 (/ value max)))]
        [:div {:on {:click ::selected}}
         [:span label ": " pct "%"]])))
sourceraw docstring

extract-attrsclj

(extract-attrs binding-vec)

Extract the vector of observed attribute name keywords from a defc parameter vector of the form [{:keys [attr1 attr2 ...]}].

The first element of the vector must be a map-destructuring form containing :keys. Attribute names must be statically visible at macro-expansion time so observedAttributes can be declared.

Returns a vector of keywords, e.g. [:value :max :label].

Extract the vector of observed attribute name keywords from a `defc`
parameter vector of the form `[{:keys [attr1 attr2 ...]}]`.

The first element of the vector must be a map-destructuring form
containing `:keys`.  Attribute names must be statically visible at
macro-expansion time so `observedAttributes` can be declared.

Returns a vector of keywords, e.g. [:value :max :label].
sourceraw docstring

parse-requiresclj

(parse-requires requires)

Normalize and validate a :require option value.

Accepts a vector of ["url" :as alias] entries and returns a vector of {:url "..." :alias sym} maps. Throws on malformed entries.

Normalize and validate a :require option value.

Accepts a vector of ["url" :as alias] entries and returns a vector of
{:url "..." :alias sym} maps.  Throws on malformed entries.
sourceraw docstring

register-component!clj

(register-component! name spec)

Compile and register a client-side component.

name — custom element tag name string (must contain a hyphen, per the Custom Elements spec, e.g. "temp-gauge"). spec — map with: :attrs — vector of observed attribute names (strings or keywords) :render — Squint source string for a (fn [props ctx] hiccup) form. props is a JS object keyed by attribute name with values parsed from the attributes (JSON for collections, raw for plain strings). ctx provides: (.-emit ctx) — (fn [event-name detail]) dispatch a bubbling, composed CustomEvent across the boundary. :require — optional vector of ES module deps: [["url" :as alias] ...]. Alias-qualified symbols (alias/fn) resolve in the source and the bundle imports each URL once.

Compilation happens immediately (at the JVM); errors surface at definition time, not in the browser. Re-registering a name replaces the spec — connected tabs watching the registry receive the new bundle over SSE and live instances re-render.

Returns the component name.

Compile and register a client-side component.

name — custom element tag name string (must contain a hyphen, per the
       Custom Elements spec, e.g. "temp-gauge").
spec — map with:
  :attrs   — vector of observed attribute names (strings or keywords)
  :render  — Squint source string for a `(fn [props ctx] hiccup)` form.
             `props` is a JS object keyed by attribute name with values
             parsed from the attributes (JSON for collections, raw for
             plain strings).  `ctx` provides:
               (.-emit ctx) — (fn [event-name detail]) dispatch a bubbling,
                              composed CustomEvent across the boundary.
  :require — optional vector of ES module deps: [["url" :as alias] ...].
             Alias-qualified symbols (alias/fn) resolve in the source and
             the bundle imports each URL once.

Compilation happens immediately (at the JVM); errors surface at
definition time, not in the browser.  Re-registering a name replaces
the spec — connected tabs watching the registry receive the new bundle
over SSE and live instances re-render.

Returns the component name.
sourceraw docstring

registry*clj

Global component registry: name -> {:attrs [...] :js "..."}. Components are code (like defn), not per-app state, so the registry is global. Watched by connected tabs so REPL redefinition hot-swaps the bundle over SSE.

Global component registry: name -> {:attrs [...] :js "..."}.
Components are code (like defn), not per-app state, so the registry is
global.  Watched by connected tabs so REPL redefinition hot-swaps the
bundle over SSE.
sourceraw docstring

stable-jsonclj

Deterministic JSON encoding of a Clojure value, LRU-memoized so large unchanged data structures (e.g. chart datasets) serialize once across re-renders rather than per frame.

Deterministic JSON encoding of a Clojure value, LRU-memoized so
large unchanged data structures (e.g. chart datasets) serialize once
across re-renders rather than per frame.
sourceraw docstring

sym->tagclj

(sym->tag sym)

Convert a Clojure symbol to a custom-element tag name string. CamelCase becomes kebab-case; underscores become hyphens. The name must contain a hyphen (Custom Elements spec requirement).

Examples: temp-gauge -> "temp-gauge" TempGauge -> "temp-gauge" temp_gauge -> "temp-gauge"

Convert a Clojure symbol to a custom-element tag name string.
CamelCase becomes kebab-case; underscores become hyphens.
The name must contain a hyphen (Custom Elements spec requirement).

Examples:
  temp-gauge   -> "temp-gauge"
  TempGauge    -> "temp-gauge"
  temp_gauge   -> "temp-gauge"
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