Hologram Documentation
repository·dev·Indexed 23 days ago
https://github.com/bartblast/hologramA declarative component system for building interactive UIs entirely in Elixir. Hologram compiles Elixir code to JavaScript, enabling modern frontend capabilities without a separate JavaScript framework. It includes a component system with state and action management via Hologram.Component, an Erlang-compatible time system implementation in JavaScript, and a Last Writer Wins (LWW) CRDT Map for distributed state synchronization.
What's inside Hologram
- Hologram is a declarative component system that allows you to build rich, interactive user interfaces entirely in Elixir. It eliminates the need for manual JavaScript framework management by intelligently compiling your client-side Elixir code into JavaScript, providing modern frontend capabilities while keeping your logic within the Elixir ecosystem.
Use $click_outside for dismissible UI
devThe
$click_outsideevent fires when a click occurs anywhere outside the bound element and its descendants. This is ideal for implementing dropdowns, popovers, modals, and menus.Usage Pattern: Typically, the element containing the
$click_outsidebinding is rendered conditionally (e.g.,if @is_open). This ensures the listener is only active while the UI component is visible.Implement Middleware for authentication and logic
devMiddleware is reusable server-side logic that runs before a page renders (
init/3) or before commands execute. It is not PhoenixPlug.Middleware Types
- Inline: Define a public function on the page/component and attach it with
middleware :name. - Leaf: A module using
use Hologram.Middlewarethat implementsdef call(server, opts). - Composite: A module using
use Hologram.Middlewarethat declares a sub-chain usingmiddleware ...lines (omitscall/2).
Key Behaviors
- Attachment: Use
middleware SomeModuleormiddleware :some_functionon a page or component. - Flow Control: To stop the chain (e.g., for unauthorized access), set a status using
put_status(server, :forbidden),put_status(server, 403), orput_redirect(server, TargetPage). There is nohalt; the status is checked after the middleware returns. - Data Passing: Use
put_stash(server, :key, value)to pass data downstream toinit/3or commands. - Authentication: Use
put_user_id(server, user_id)to log in anddelete_user_id(server)to log out.
- Inline: Define a public function on the page/component and attach it with
Understand the Erlang Time System implementation in Hologram
devHologram implements an Erlang-compatible time system in JavaScript. The core relationship is modeled as:
Erlang System Time = Erlang Monotonic Time + Time OffsetIn this implementation, because JavaScript does not have Erlang's specific time correction mechanisms,
:erlang.system_time/0and:os.system_time/0both map directly toDate.now(). To maintain mathematical consistency, thetime_offsetis derived asos.system_time - monotonic_time.Use Realtime for server-to-client pushes
devRealtime allows the server to push actions to clients. A broadcast triggers the client's
action/3handler.In-Handler API (Recommended)
Use these inside
init/3orcommand/3. These are transactional and roll back if the handler fails.put_subscription(server, channel): Subscribes the current component to a channel.delete_subscription(server, channel): Removes a subscription.put_broadcast(server, channel, action, params): Queues a broadcast.put_broadcast_except(server, channel, action, params, {:instance, id}): Broadcasts to everyone except a specific instance.
Outside Handlers (Escape Hatch)
Use
Hologram.Realtimefunctions for background jobs or GenServers. These fire immediately and do not roll back.broadcast_action,subscribe,unsubscribe.subscribe/unsubscriberequire an explicitcid(e.g.,"page").
Channel Structure
Channels are structured values, not strings. Examples:
- Bare atom:
:notifications - Tuple:
{:room, 42}or{:doc, "abc-123", "v2"} - Identity channels:
{:instance, id},{:session, id},{:user, id}.
Share data using Context
devContext allows you to share data down the component tree without prop drilling. It is not a global store; data is only available to descendant components.
- Setting Context: Use
put_context(component, :key, value)in actions orinit/3functions. To avoid conflicts, use namespaced keys:put_context(component, {MyModule, :key}, value). - Accessing Context: Access values via props using the
from_contextoption:prop :user, :map, from_context: :current_user. - Best Practice: Use props for direct children; use context for deeply nested data.
prop :user, :map, from_context: :current_user- Setting Context: Use
Understand the Hologram Architecture
devHologram is a full-stack isomorphic Elixir web framework that compiles Elixir to JavaScript for the browser. It is fundamentally different from Phoenix LiveView.
Core Building Blocks
- Pages: Route entry points. They are always stateful and initialized on the server.
- Components: Reusable UI elements. They can be stateless or stateful.
Execution Model
- Actions: Run on the client (browser). Use these for state updates, navigation, and triggering commands.
- Commands: Run on the server. Use these for database access, API calls, and session/cookie management.
- State: Lives in the browser, enabling instant UI updates without network round-trips.
Communication
Client-server communication happens automatically over HTTP/2 persistent connections. Hologram automatically determines which code runs on the client vs server and compiles the client portions to JavaScript.
Configure Layouts
devLayouts in Hologram are regular components that use
use Hologram.Component. There is no special layout macro.Mandatory Requirements
- Runtime Component: A layout template must include
<Hologram.UI.Runtime />inside the<head>tag. - Content Slot: A layout template must include
<slot />where the page content will be inserted.
Targeting and Props
- The layout's component ID (cid) is always
"layout". Usetarget: "layout"to target actions at it. - Pass props to layouts via
layout MyApp.MainLayout, prop: valuein the Page definition, or viaput_state/2in the page'sinit/3.
- Runtime Component: A layout template must include
Use the ~HOLO Template Syntax
devHologram uses the
~HOLOsigil for templates. Do not use HEEx (~H) syntax.Syntax Rules
- Interpolation: Use curly braces
{expression}. Do not use<%= expression %>. - Variables: Access props and state with
@varsyntax:{@name},{@count}. - Component Nodes: Use module names:
<MyComponent prop="value" />. Do not use<.my_component>. - Conditionals: Use
{%if condition}...{/if}or{%if condition}...{%else}...{/if}. - Iteration: Use
{%for item <- @items}...{/for}. - Raw Output: Use
{%raw}...{/raw}to skip processing. - Escaping: Use
\{literal\}to escape curly braces. - Spreads: Use
...{@expr}to spread maps or keyword lists as attributes or props (e.g.,<div ...{@html_attrs}>).
Attribute Behavior
- If an attribute expression evaluates to
nilorfalse, the attribute is not rendered. - On HTML elements, spread keys are dasherized (
user_idbecomesuser-id). Nested maps/keyword lists use dash-joined names (data: [user_id: 1]becomesdata-user-id="1"). - On components, keys match declared prop names verbatim.
- Event bindings (prefixed with
$) cannot be spread; they must be written as literal attributes.
- Interpolation: Use curly braces
Navigate between pages
devUse the
Hologram.UI.Linkcomponent for all navigation. Do not use<.link navigate={...}>orlive_redirect.- Standard link:
<Link to={MyPage}>text</Link> - Link with parameters:
<Link to={MyPage, id: 123}>text</Link> - Programmatic navigation: From within an action, use
put_page(component, MyPage)orput_page(component, MyPage, id: 123).
- Standard link:
Bind global events with <window> and <document>
devTo listen for global events (like keyboard shortcuts or window resizing) that are not tied to a specific element, use the
<window>or<document>tags. These tags render nothing but attach listeners to the globalwindowordocumentobjects.<window>: Use for window-level events like$resizeor$scroll.<document>: Use for document-level events like tab visibility.- Keyboard shortcuts: Since keyboard events bubble, either tag works for global shortcuts.
Constraints:
- These tags accept only event bindings; any other attributes will cause a build failure.
- The listener only exists while the tag is rendered (e.g., inside a conditional).
<window $key_down.ctrl+k="open_palette" />Run the Hologram benchmark suite
devTo execute the performance benchmarks for Hologram within an Elixir environment, use themix compile.hologramMix task.mix compile.hologram