Liking cljdoc? Tell your friends :D

hyper.render

Rendering pipeline.

Handles rendering hiccup to HTML and formatting Datastar SSE events.

Rendering pipeline.

Handles rendering hiccup to HTML and formatting Datastar SSE events.
raw docstring

apply-render-middlewareclj

(apply-render-middleware render-fn middleware-chain)

Wrap a render-fn with a middleware chain.

Middleware are functions of the form (fn [handler] (fn [req] ...)), identical to Ring middleware. The chain is a sequence of middleware where earlier entries wrap outermost (execute first).

Returns the wrapped render-fn, or the original render-fn if the chain is empty.

Wrap a render-fn with a middleware chain.

Middleware are functions of the form `(fn [handler] (fn [req] ...))`,
identical to Ring middleware.  The chain is a sequence of middleware
where earlier entries wrap outermost (execute first).

Returns the wrapped render-fn, or the original render-fn if the chain
is empty.
sourceraw docstring

fingerprintclj

(fingerprint v)

Compute a short hex fingerprint for a value using Clojure's hash. Used to give each head element a stable, content-based identity so the SSE head-update JS can diff by fingerprint rather than blindly removing and re-appending every element on each render cycle.

Compute a short hex fingerprint for a value using Clojure's `hash`.
Used to give each head element a stable, content-based identity so the
SSE head-update JS can diff by fingerprint rather than blindly removing
and re-appending every element on each render cycle.
sourceraw docstring

format-connected-eventclj

(format-connected-event tab-id)

Format the initial SSE connected event for a tab.

Format the initial SSE connected event for a tab.
sourceraw docstring

format-datastar-fragmentclj

(format-datastar-fragment html)

Format HTML as a Datastar patch-elements SSE event.

Datastar expects Server-Sent Events in the format: event: datastar-patch-elements data: elements <html content>

For multi-line HTML content, emits multiple 'data: elements' lines that Datastar will concatenate. This prevents in HTML from prematurely terminating the SSE event.

Format HTML as a Datastar patch-elements SSE event.

  Datastar expects Server-Sent Events in the format:
  event: datastar-patch-elements
  data: elements <html content>

  For multi-line HTML content, emits multiple 'data: elements' lines
  that Datastar will concatenate. This prevents 
in HTML from
  prematurely terminating the SSE event.
sourceraw docstring

format-datastar-fragmentsclj

(format-datastar-fragments html-strings)

Format multiple HTML strings as separate Datastar patch-elements SSE events. Used for partial reactive renders where each fragment targets a different element.

Format multiple HTML strings as separate Datastar patch-elements SSE events.
Used for partial reactive renders where each fragment targets a different element.
sourceraw docstring

format-head-updateclj

(format-head-update title extra-head-html)

Build a self-removing <script> SSE event that imperatively updates the document title and diffs user-provided <head> elements.

Why not morph? Morphing <head> inner content via idiomorph can disconnect <style>/<link> elements from the browser's CSSOM — the nodes stay in the DOM but styles stop applying. By using JS to selectively remove/append we guarantee the browser re-evaluates only the elements that actually changed.

Each user-managed head element carries a data-hyper-head attribute whose value is a content-based fingerprint (short SHA-256 hex). On each SSE cycle the emitted JS:

  1. Collects existing fingerprints from [data-hyper-head] nodes in the DOM.
  2. Removes any DOM node whose fingerprint is NOT in the new set (stale element).
  3. Appends only elements whose fingerprint is NOT already in the DOM (new or changed element).

This avoids FOUC and prevents third-party scripts from re-executing when their content hasn't changed (issue #42).

The script tag uses Datastar's mode append + selector body pattern (the SDK's ExecuteScript convention) with data-effect="el.remove()" so it auto-cleans after execution.

Build a self-removing <script> SSE event that imperatively updates
the document title and diffs user-provided <head> elements.

Why not morph?  Morphing <head> inner content via idiomorph can
disconnect <style>/<link> elements from the browser's CSSOM — the
nodes stay in the DOM but styles stop applying.  By using JS to
selectively remove/append we guarantee the browser re-evaluates only
the elements that actually changed.

Each user-managed head element carries a `data-hyper-head` attribute
whose value is a content-based fingerprint (short SHA-256 hex).  On
each SSE cycle the emitted JS:
1. Collects existing fingerprints from `[data-hyper-head]` nodes in
   the DOM.
2. Removes any DOM node whose fingerprint is NOT in the new set
   (stale element).
3. Appends only elements whose fingerprint is NOT already in the DOM
   (new or changed element).

This avoids FOUC and prevents third-party scripts from re-executing
when their content hasn't changed (issue #42).

The script tag uses Datastar's `mode append` + `selector body` pattern
(the SDK's ExecuteScript convention) with `data-effect="el.remove()"`
so it auto-cleans after execution.
sourceraw docstring

format-heartbeatclj

(format-heartbeat)

Format an SSE keepalive as a comment line. Lines beginning with ':' are comments per the SSE spec and are ignored by every conformant parser (including Datastar), so this carries no payload — its only job is to keep the stream warm through idle-timeout proxies and to surface a dead/half-open channel the next time the renderer writes.

Format an SSE keepalive as a comment line.  Lines beginning with ':' are
comments per the SSE spec and are ignored by every conformant parser
(including Datastar), so this carries no payload — its only job is to keep
the stream warm through idle-timeout proxies and to surface a dead/half-open
channel the next time the renderer writes.
sourceraw docstring

get-render-fnclj

(get-render-fn app-state* tab-id)

Get the render function for a tab.

Get the render function for a tab.
sourceraw docstring

mark-head-elementsclj

(mark-head-elements head-hiccup)

Add {:data-hyper-head "<fingerprint>"} to each top-level hiccup element in a resolved :head value. The fingerprint is a content-based hash of the element's hiccup (excluding the data-hyper-head attr itself), giving each element a stable identity.

On SSE re-renders, the head-update JS uses these fingerprints to diff against what's already in the DOM: unchanged elements are left alone, stale ones are removed, and only genuinely new/changed elements are appended. This avoids FOUC and prevents third-party scripts from re-executing when their content hasn't changed.

Handles:

  • a single vector element [:style ...]
  • a seq/list of elements ([:link ...] [:style ...])
  • a vector of elements [[:link ...] [:style ...]]
Add `{:data-hyper-head "<fingerprint>"}` to each top-level hiccup element
in a resolved :head value.  The fingerprint is a content-based hash of the
element's hiccup (excluding the data-hyper-head attr itself), giving each
element a stable identity.

On SSE re-renders, the head-update JS uses these fingerprints to diff
against what's already in the DOM: unchanged elements are left alone,
stale ones are removed, and only genuinely new/changed elements are
appended.  This avoids FOUC and prevents third-party scripts from
re-executing when their content hasn't changed.

Handles:
- a single vector element  `[:style ...]`
- a seq/list of elements   `([:link ...] [:style ...])`
- a vector of elements     `[[:link ...] [:style ...]]`
sourceraw docstring

register-render-fn!clj

(register-render-fn! app-state* tab-id render-fn)

Register a render function for a tab.

Register a render function for a tab.
sourceraw docstring

render-tabclj

(render-tab app-state* session-id tab-id)
(render-tab app-state* session-id tab-id base-req)

Render the current view for a tab and return the rendered data.

Returns nil when no render-fn is registered for the tab, or one of:

  • A Ring response map (when the render-fn returns a map with :status), passed through as-is for redirects, error responses, etc.

  • A render result map with pre-serialized HTML strings: :title - resolved page title string, or nil :head-html - HTML string of marked <head> elements, or nil :body-html - HTML string of the rendered page body :url - current route URL string, or nil :declared-signals - vector of signal declaration maps for HTML injection

Binds context/*request* and context/*action-idx* for the duration of both rendering and HTML serialization, so lazy hiccup sequences (from for, map, etc.) that read *request* see the correct bindings when realized by Chassis.

An optional base-req (Ring request map) can be provided for initial page loads so the render function sees the full Ring request context (headers, cookies, query-params, etc.). On SSE re-renders, base-req is nil and a synthetic request is built with only Hyper context keys. Accessing HTTP-only keys (e.g. :cookies, :headers) on the synthetic request returns nil and logs a warning, alerting developers to use middleware + cursors for data that must survive re-renders.

On each render, re-resolves the render-fn from live routes so that:

  • Redefining the routes Var with new inline fns picks up the new function
  • Var-based :get handlers (e.g. #'my-page) automatically deref to the latest

Title is resolved from route :title metadata via hyper.routes/resolve-title, supporting static strings, functions of the request, and deref-able values (cursors/atoms) so that title updates reactively with state changes.

Render the current view for a tab and return the rendered data.

Returns nil when no render-fn is registered for the tab, or one of:

- A Ring response map (when the render-fn returns a map with :status),
  passed through as-is for redirects, error responses, etc.

- A render result map with pre-serialized HTML strings:
    :title             - resolved page title string, or nil
    :head-html         - HTML string of marked <head> elements, or nil
    :body-html         - HTML string of the rendered page body
    :url               - current route URL string, or nil
    :declared-signals  - vector of signal declaration maps for HTML injection

Binds `context/*request*` and `context/*action-idx*` for the duration
of both rendering and HTML serialization, so lazy hiccup sequences
(from `for`, `map`, etc.) that read `*request*` see the correct
bindings when realized by Chassis.

An optional base-req (Ring request map) can be provided for initial
page loads so the render function sees the full Ring request context
(headers, cookies, query-params, etc.).  On SSE re-renders, base-req
is nil and a synthetic request is built with only Hyper context keys.
Accessing HTTP-only keys (e.g. :cookies, :headers) on the synthetic
request returns nil and logs a warning, alerting developers to use
middleware + cursors for data that must survive re-renders.

On each render, re-resolves the render-fn from live routes so that:
- Redefining the routes Var with new inline fns picks up the new function
- Var-based :get handlers (e.g. #'my-page) automatically deref to the latest

Title is resolved from route :title metadata via hyper.routes/resolve-title,
supporting static strings, functions of the request, and deref-able values
(cursors/atoms) so that title updates reactively with state changes.
sourceraw docstring

safe-renderclj

(safe-render render-fn req render-error-fn)

Safely render a view with an error boundary.

On exception, logs via telemere and delegates UI rendering to render-error-fn, a function (fn [error req] -> hiccup). The fn is invoked as an IFn, so a Var pointing at a renderer (e.g. #'my.app/error-page) works transparently and picks up redefinitions without restarting the server.

Safely render a view with an error boundary.

On exception, logs via telemere and delegates UI rendering to
`render-error-fn`, a function `(fn [error req] -> hiccup)`.  The fn is
invoked as an `IFn`, so a Var pointing at a renderer (e.g.
`#'my.app/error-page`) works transparently and picks up redefinitions
without restarting the server.
sourceraw docstring

unwrap-bodyclj

(unwrap-body hiccup)

Strip a top-level [:body ...] wrapper from user hiccup.

Hyper owns the <body> tag — it carries data-init (for the Datastar SSE connection) and the SPA navigation scripts, with a <div id="hyper-app"> inside it as the morph target for SSE re-renders.

When a render function returns [:body ...], the nested <body> is tolerated by browsers on initial load, but on SSE re-renders idiomorph replaces #hyper-app content with HTML containing a <body>, which corrupts the DOM and stops further updates (see issue #40).

This function detects the pattern, logs a warning, and returns only the children so downstream code always receives hiccup without <body>. Any attributes on the [:body] tag are discarded (the warning tells the developer to remove it).

Strip a top-level `[:body ...]` wrapper from user hiccup.

Hyper owns the `<body>` tag — it carries `data-init` (for the Datastar
SSE connection) and the SPA navigation scripts, with a `<div id="hyper-app">`
inside it as the morph target for SSE re-renders.

When a render function returns `[:body ...]`, the nested `<body>` is
tolerated by browsers on initial load, but on SSE re-renders idiomorph
replaces `#hyper-app` content with HTML containing a `<body>`, which
corrupts the DOM and stops further updates (see issue #40).

This function detects the pattern, logs a warning, and returns only
the children so downstream code always receives hiccup without `<body>`.
Any attributes on the `[:body]` tag are discarded (the warning tells the
developer to remove it).
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