bartblast
Time for Hologram <> JavaScript Interop - What Would You Like to See?
It’s time to implement JavaScript interop for Hologram!
Eventually, there will be Hologram wrappers for most APIs, but sometimes we’ll need escape hatches - especially when working with legacy apps or integrating 3rd party libraries. Things like:
- Integrating charting libraries (Chart.js, D3, etc.)
- Using JavaScript-only APIs (Web APIs, browser-specific features)
- Calling into existing JavaScript codebases
- Leveraging the vast npm ecosystem
How would you envision a Hologram <> JavaScript interop API/DSL?
Some questions to spark discussion:
- Should it be declarative or imperative?
- How should data be marshaled between Elixir and JavaScript?
- What would the ideal developer experience look like?
- Are there specific JavaScript libraries you’re eager to use?
- How should errors be handled across the boundary?
I have some ideas brewing, but I don’t want to bias the discussion by sharing them upfront. I’m genuinely curious about your use cases and what would make the most sense from a developer ergonomics perspective.
Looking forward to hearing your thoughts!
Most Liked
olivermt
If I want to use google maps I am not looking to avoid javascript, I want to use javascript in the most practical manner possible.
Hologram is about isomorphic solution to 80-90% of your app, then for the rest where I can use pre-built stuff that is supported by a profitable business I just want a very ergonomic way to do javascript.
I do not think Bart spending time re-implementing Javascript in elixir is a good use of his time for that ![]()
bartblast
I’d prefer to avoid basing architectural decisions on polls though
Not that I don’t like polls, but I’m trying to provoke a deeper discussion here and am more interested in understanding the deeper trade-offs for Hologram’s specific context.
I’m honestly on the fence here and looking for someone to convince me why one of these approaches is genuinely better than the others. I don’t really care if something is familiar - what I care about is the best DX in general. Sure, familiarity might be part of DX, but there are many other factors like ease of use and ease of maintenance.
olivermt
For me the usecase is very straight forward and I suggest you even use it both as a thing to prototype with and as a showcase of interop.
I want to do a <div id=”map”/> and then initialize google maps on it.
In regular liveview this would just mean setting phx-update=”ignore” on it and start mapping JS hook events.
In Hologram I have no idea what would be most idiomatic since I haven’t really dug in yet due to lack of said JS interop.
I propose three usecases:
- Get the corners of the map (essentially the lon/lat of top left + lower right)
- Add a marker (ideally with some props like marker color etc)
- Delete a marker (this means you need to thread through back to Hologram the ID of an added marker)
bartblast
Let me clarify my current thinking on Hologram’s JS interop direction.
@Eiji - your examples are really helpful and show how high-level Hologram modules for abstracting JS Web APIs could work (like Hologram.DOM.query_selector, Hologram.DOM.add_event_listener, etc.). That’s a compelling proposition and we may very well go that way once simpler primitives are implemented and test themselves in battle.
But for now, I’m focusing on the lower-level foundation - just enabling basic interfacing between the Elixir world and JS world. The goal is simple: allow calling JS code from Hologram and vice versa, something more primitive that those higher-level APIs could be built on top of - something that enables calling Web APIs or interfacing with existing JS code like in the example described by @olivermt.
I’ve been analyzing different approaches to JS interop across languages and frameworks and found three main categories that could apply to Hologram.
1. Messaga passing
The first approach is message passing, like Elm’s ports (as mentioned by @kingdomcoder) or Phoenix LiveView hooks.
In Elm, you define ports for sending data out and subscribing to data coming in:
port sendData : String -> Cmd msg
port receiveData : (String -> msg) -> Sub msg
Then in JavaScript, you connect to these ports:
// Listen to data sent from Elm
app.ports.sendData.subscribe(function(data) {
console.log("Received from Elm:", data);
});
// Send data to Elm
app.ports.receiveData.send("Hello from JS")
LiveView uses a similar pattern - it communicates with the JS world through hooks.
Pros: Clear boundaries between language worlds, explicit control over data flow
Cons: Communication overhead, forces async patterns even for simple operations
2. Foreign Function Interface (FFI)
The second is Foreign Function Interface (FFI), like Gleam uses. You declare external functions that map to JavaScript:
@external(javascript, "./my_js_file.mjs", "greet")
pub fn greet(name: String) -> String
With corresponding JavaScript:
export function greet(name) {
return `Hello, ${name}!`;
}
This pattern is actually very common - most bindings with C/C++ libraries use this approach, and even Rust NAPI works similarly where you declare foreign functions that bridge between languages. For Hologram, this could look like declaring JS functions directly in Elixir modules.
Pros: Direct function calls, minimal overhead
Cons: Manual binding maintenance, potential runtime errors
3. Native compilation/interop
The third approach is native compilation/interop, like Kotlin/JS. Since the code compiles to JavaScript, you can directly interface with JS:
external fun alert(message: String)
external val console: Console
fun main() {
alert("Hello from Kotlin!")
console.log("Direct JS access")
}
Pros: Natural integration, full ecosystem access
Cons: Compilation complexity, language semantic mismatches
For Hologram, interestingly, the third approach doesn’t translate that well. While Hologram does compile to JavaScript, it uses its own client-side Elixir runtime rather than generating idiomatic JavaScript. This means we’re running Elixir semantics on top of JavaScript, and the different syntax and data types between JavaScript and Elixir (atoms, tuples, pattern matching, immutable data structures) don’t have direct JavaScript equivalents.
What do you think? Which approach resonates most with your use cases?
jam
At the moment, I’d vote 2. Seems simplest to me and feels like the most general / flexible which I think would be important. Though I would like to see some examples of 1 in the Hologram context.







