Hologram v0.8.0: Elixir Gets JavaScript Interop

v0.8.0 brings JavaScript interoperability - the most requested feature since the project's inception.

Why JS Interop Matters

Hologram's premise is that you shouldn't need to write JavaScript to build interactive web apps. But the web ecosystem is vast: npm has over 3 million packages, browsers expose hundreds of Web APIs, and the Web Components standard lets you use third-party UI elements. Cutting yourself off from all of that is a non-starter for real-world applications.

v0.8.0 bridges that gap. You can now call JavaScript functions, use npm packages, interact with Web APIs, instantiate classes, dispatch DOM events, and work with Web Components - all from Elixir code, instantly and without any latency on the client side.

JS Interop API

The interop API lives in the Hologram.JS module. To use it, add use Hologram.JS to your module (this also auto-imports the ~JS sigil and auto-aliases the JS module):

defmodule MyApp.DashboardPage do
  use Hologram.Page
  use Hologram.JS

  js_import from: "chart.js", as: :Chart

  def action(:render_chart, _params, component) do
    canvas = JS.call(:document, :getElementById, ["myChart"])

    chart =
      :Chart
      |> JS.new([canvas, %{type: "bar", data: component.state.data}])
      |> JS.call(:update, [])

    put_state(component, :chart, chart)
  end
end

The full API surface includes:

Values are automatically boxed/unboxed across the Elixir-JS boundary. JS types that don't have a direct Elixir equivalent (object references, functions, bigints, undefined, symbols) are treated as opaque - wrapped in a Hologram.JS.NativeValue struct on the Elixir side and transparently unwrapped when passed back to JS.

Interop also works in the other direction - you can trigger Elixir action handlers from JavaScript with Hologram.dispatchAction(), which is useful for integrating third-party JS libraries or handling events outside of Hologram's template system. Additionally, JS.dispatch_event/2-4 lets you dispatch events from Elixir, providing a familiar pattern for developers migrating from LiveView hooks.

For the full API reference and detailed examples, see the JavaScript Interop documentation.

Async Support and the Road to Elixir's Process Model

In Hologram's client-side Elixir runtime, JavaScript Promises become Elixir Tasks. Use Task.await/1 to get the result, just like you would with any other Task in Elixir. Remember, Hologram's client-side code is still pure Elixir. In this example, both fetch() and response.json() are async JS functions that return Promises:

def action(:fetch_data, _params, component) do
  data =
    :fetch
    |> JS.call(["/api/data"])
    |> Task.await()
    |> JS.call(:json)
    |> Task.await()

  put_state(component, :data, data)
end

Under the hood, this required significant work across the entire compilation and runtime stack to ensure async operations integrate seamlessly with Elixir's semantics.

This async foundation is the first piece of infrastructure needed to port Elixir's process model to the client side - something we plan to tackle in future releases.

Web Components Support

Thanks to Justin Wood (@ankhers), Hologram's template parser now supports custom HTML element tags (#691). This means you can use Web Components directly in your templates:

~HOLO"""
<my-datepicker value={@selected_date}></my-datepicker>
<el-dialog title="Settings">
  <p>Content here</p>
</el-dialog>
"""

Justin ran into this while integrating the @tailwindplus/elements package and contributed the fix himself.

Language Server Compatibility Fix

Michael Ward (@mward-sudo) fixed a bug where Hologram's compiler would repeatedly trigger recompilation in some language server build environments (#727). The fix broadened the detection of language server build directories beyond ElixirLS to also cover Expert, Lexical, and Next LS. This eliminates the "compiler already running, waiting..." repeated log messages and reduces CPU usage when editing Hologram projects.

Unicode Module Refactoring

Michael Ward also contributed refactoring of the ported Erlang :unicode module, extracting UTF-8 sequence length detection (#706) and UTF-8 code point decoding (#710) into the Bitstring utility class. This consolidates duplicated logic across normalization and validation paths.

Breaking Change

The ~JS sigil is no longer auto-imported in page and component modules. If you use the ~JS sigil or any JS interop function, you now need to explicitly add:

use Hologram.JS

This directive automatically:

If you previously had an explicit alias Hologram.JS in your module, it's now redundant and can be removed.

By the Numbers

This release includes 270 commits across 162 changed files. The JS interop PR alone (#732) added over 7,000 lines of code across 150 files - significantly more work than initially anticipated. It touched every layer of the stack: macros, IR analysis, call graph analysis, the encoder, the client-side interpreter and runtime, boxing/unboxing, and end-to-end feature tests.

Acknowledgments

Special thanks to Tomasz Robaczewski (@robak86) for extensive help with the JS interop API design. We had multiple multi-hour sessions and whiteboard discussions that were instrumental in shaping the final API. The design benefited enormously from having a second experienced perspective to challenge assumptions and explore edge cases.

Thanks to Justin Wood (@ankhers) for the web components support and to Michael Ward (@mward-sudo) for the language server fix and Unicode refactoring contributions.

Sponsors

I'd like to thank our sponsors whose support makes sustained development possible:

Thanks also to our GitHub sponsors:

And to all other GitHub sponsors - thank you. Every contribution, no matter the size, helps keep Hologram moving forward.

If you'd like to support Hologram's development, consider sponsoring the project.

Stay in the Loop

Subscribe to the Hologram newsletter for monthly updates on new releases, features, and community news. You can also join the community to connect with other Hologram developers.

What's Next

With JS interop in place, Hologram users can now integrate with the JavaScript ecosystem, use Web APIs, and leverage Web Components - all without leaving Elixir. The async infrastructure built for this release is also the first step toward porting Elixir's process model to the client, which will bring Elixir's concurrency patterns to the browser.

- Bart

Sponsored by