<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="374315" data-post-id="374315">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="olivermt" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/olivermt/120/2354_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  olivermt
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I want the smallest possible surface of integration that gives the largest possible space of custimization basically. Only option 1 has that within feasible timeframe imo.</p>
<p>Familiarity with LV is a bonus, but I meant it more as a pattern that works just fine.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="374315" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-for-hologram-javascript-interop-what-would-you-like-to-see/72518/23">Post #22</a>
	                </div>
	            </div>
              <div id="likers-container-374315" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="374315"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #22"></div>
  </section>
</div>
    <div class="postbit" id="374318" data-post-id="374318">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Eiji" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Eiji/120/36743_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Eiji
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="frankdugan3" data-post="21" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/frankdugan3/48/39210_2.png" class="avatar"> frankdugan3:</div>
<blockquote>
<p>I don’t mean for the entire API, just at the boundary of the FFI interface to ensure an exception doesn’t crash the entire application.</p>
</blockquote>
</aside>
<p>Since entire <code>JavaScript</code> API would use such <code>FFI interface</code> under the hood then it’s for entire API - that’s exactly what I mean. You say about it like a single <code>try/catch</code> for <code>FFI interface</code>, but look that it would be called very, very often even for a single client and it’s definitely not like that the app itself would use just single <code>FFI interface</code> call - it would happen every time you execute a getter/setter/function in <code>JavaScript</code> or at best it’s one call for the small <code>JS</code> snippet.</p>
<aside class="quote no-group" data-username="frankdugan3" data-post="21" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/frankdugan3/48/39210_2.png" class="avatar"> frankdugan3:</div>
<blockquote>
<p>Of course, if Hologram is already doing some sort of supervision tree emulation, that isn’t necessary and we can fall back on the let it fail ideology. I just assumed that wasn’t the case since we’re talking about calling into arbitrary JS here.</p>
</blockquote>
</aside>
<p>If it’s not a case then it should become one as otherwise it’s a bad pattern that would be way too overused. For FFI we would either need <code>Rust NIF</code>-like solution that is much more safe (maybe <code>TypeScript</code>?) or let it crash the entire VM as it happens with every other <code>NIF</code>.</p>
<p>I have a huge respect to core developers and I don’t believe that I would ever deliver a better implementation i.e. <code>NIF</code>-like solution that is completely safe and fast at the same time while other <code>NIFs</code> crash <code>Erlang VM</code>. Maybe there would be some expert of experts that could do that, but I would say that’s definitely out of scope for a typical forum reader. Of course it would be amazing if it would happen, but it’s not something that every developer could simply plan to implement or just add to backlog. Maybe I trust core developers too much, but I bet that the <code>Erlang VM</code> crashes happen for very solid reasons and it’s very hard (maybe overcomplicated) to avoid it, it’s hard to make a cross-platform solution or it’s even against the <code>Erlang VM</code> nature at all.</p>
<p>I would say we should focus on much safer ways like the one I suggested. The more structs we implement the more getters/setters/apply calls we would be able to implement. This way other developers could implement <code>DOM</code> API as long as all the necessary structs used for it could be translated in said getters/setters/apply calls.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="374318" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-for-hologram-javascript-interop-what-would-you-like-to-see/72518/24">Post #23</a>
	                </div>
	            </div>
              <div id="likers-container-374318" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="374318"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #23"></div>
  </section>
</div>
    <div class="postbit" id="374319" data-post-id="374319">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="bartblast" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/120/17647_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  bartblast
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Hologram</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="jam" data-post="15" data-topic="72518" data-full="true">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jam/48/36266_2.png" class="avatar"> jam:</div>
<blockquote>
<p>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.</p>
</blockquote>
</aside>
<p>This can be implemented in many, maaany… different ways. Some examples:</p>
<h2><a name="p-374319-h-1-message-passing-1" class="anchor" href="#p-374319-h-1-message-passing-1" aria-label="Heading link" rel="nofollow"></a>1. Message passing</h2>
<p>In Elixir:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule MyComponent do
  use Hologram.Component

  port :just_draw_it

  def action(:my_action, _params, component) do
    send_port_message(:just_draw_it, ["my_canvas", 100, 200, [{34, 223}, {13, 93}]])
    component
  end
end
</code></pre>
<p>In JS:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">import * from Chart;

window.Hologram.ports.just_draw_it = (element_id, width, height, data_points) =&gt; {
   const element = document.getElementById(element_id)
   
   const chart = new Chart(element, width, height)
   chart.draw(data_points)
}
</code></pre>
<p>Message passing is async, so we don’t expect a result from <code>just_draw_it</code> function.<br>
Also, the port definitions and calls could as well look more like function calls.</p>
<h2><a name="p-374319-h-2-foreign-function-interface-ffi-2" class="anchor" href="#p-374319-h-2-foreign-function-interface-ffi-2" aria-label="Heading link" rel="nofollow"></a>2. Foreign Function Interface (FFI)</h2>
<p>In Elixir:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defjs greet(name), "./my_js_file.mjs", "greet"
</code></pre>
<p>In JS:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">export function greet(name) {
    return `Hello, ${name}!`;
}
</code></pre>
<p>or we can go even further, by separating interop functionality to specialized interop modules like here:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule MyJSInterop do
  use Hologram.JS

  js_dep :local, "./my_js_file.mjs", "greet"
  js_dep, :npm, "some_lib", "my_fun"

  defjs greet(name), “greet” 

  (…)
end
</code></pre>
<p>potentially even providing specialized functions to handle the Elixir/JS semantic mismatch:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">  def just_draw_it(element_id, width, height, data_points) do
    element =
     "document"
      |&gt; js_ref()
      |&gt; js_call("getElementById", [element_id])

    if js_unwrap(element) == nil do
      raise "Element not found"
    end

    chart =
     "Chart"
      |&gt; js_ref()
      |&gt; js_create([element, %{width: width, height: height}])

    chart
    |&gt; js_read("id")
    |&gt; IO.inspect(label: "Chart id")

    js_call(chart, "draw", data_points)

    js_call(chart, "addEventListener", [
      "pointClick",
      fn point -&gt; IO.inspect(point, label: "Point clicked") end
    ])

    chart
    |&gt; js_call("getWidth")
    |&gt; js_unwrap()
    |&gt; IO.inspect(label: "Chart width")

    chart
    |&gt; js_call("getHeight")
    |&gt; js_unwrap()
    |&gt; IO.inspect(label: "Chart height")
  end
</code></pre>
<p>However it could also potentially get out of hand, and requires JS knowledge anyway.</p>
<hr>
<p>JS → Hologram could be similar for both cases, we could simply dispatch a Hologram action or command like this:</p>
<p>In JS:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">Hologram.dispatchAction("MyComponent", "my_action", "my_target", params)
</code></pre>
<p>Calling Elixir modules wouldn’t make sense, because JS doesn’t know what Elixir modules have been transpiled, so such call should go through pages or components. Hologram sees modules used in pages/components and can include them in the bundle.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="374319" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-for-hologram-javascript-interop-what-would-you-like-to-see/72518/25">Post #24</a>
	                </div>
	            </div>
              <div id="likers-container-374319" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="374319"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #24"></div>
  </section>
</div>
    <div class="postbit" id="374320" data-post-id="374320">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Eiji" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Eiji/120/36743_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Eiji
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="bartblast" data-post="25" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>potentially even providing specialized functions to handle the Elixir/JS semantic mismatch:</p>
</blockquote>
</aside>
<p>A huge <img src="https://forum.elixirforum.com/images/emoji/apple/+1.png?v=15" title=":+1:" class="emoji" alt=":+1:" loading="lazy" width="20" height="20"> for solution with refs.</p>
<aside class="quote no-group" data-username="bartblast" data-post="25" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>However it could also potentially get out of hand, and requires JS knowledge anyway.</p>
</blockquote>
</aside>
<p>That’s not a problem at all. It’s much more better than <code>Hooks</code>. The only thing I would change is to avoid using <code>js_</code> prefix in function names if possible. I’m in a group of people who prefer alias over imports (especially when importing from many modules) and with such way we would double <code>js</code> naming. Think about <code>JS.js_unwrap("Chart")</code>.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="374320" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-for-hologram-javascript-interop-what-would-you-like-to-see/72518/26">Post #25</a>
	                </div>
	            </div>
              <div id="likers-container-374320" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="374320"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #25"></div>
  </section>
</div>
    <div class="postbit" id="374323" data-post-id="374323">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="jam" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jam/120/36266_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  jam
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Thanks for those examples. I like the idea of having the solution be as simple as just writing functions and calling them.</p>
<aside class="quote no-group" data-username="bartblast" data-post="25" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>Also, the port definitions and calls could as well look more like function calls.</p>
</blockquote>
</aside>
<p>Sounds interesting. If that’s the case, then maybe it would encompass one of the things I like most about <span class="hashtag-raw">#2</span>.</p>
<aside class="quote no-group" data-username="bartblast" data-post="25" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<ol start="2">
<li>Foreign Function Interface (FFI)</li>
</ol>
</blockquote>
</aside>
<p>This still feels like the simplest approach to me. It would allow me to just write js functions when I need them and then add a one liner to wire it up in elixir. I wonder if there is another way to cut down on boilerplate / binding maintenance with macro magic or other. Would something like this be possible?</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">js do
  """
   function greet(name) { 
    return `Hello, ${name}!`;
  }
  """
end
</code></pre>
<p>Invoked with:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">JS.greet("Bart")
</code></pre>
<aside class="quote no-group" data-username="bartblast" data-post="25" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>potentially even providing specialized functions to handle the Elixir/JS semantic mismatch</p>
</blockquote>
</aside>
<p>Hmm my first reaction to this is it feels a bit complicated but maybe I’m missing something. It’s not immediate clear to me where the DX win is but it would require learning more syntax that is unique to Hologram. Since JS interop is meant to be an escape hatch, I think the js code should just be js.</p>
<aside class="quote no-group" data-username="bartblast" data-post="25" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p><code>Hologram.dispatchAction("MyComponent", "my_action", "my_target", params)</code></p>
</blockquote>
</aside>
<p>Interesting. What do you think of using <code>Hologram.put_action</code> so there’s less surface area to learn? Also, fwiw as a minor thing, I think it’d be good to use snake_case even though it’s not the JS convention.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="374323" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-for-hologram-javascript-interop-what-would-you-like-to-see/72518/27">Post #26</a>
	                </div>
	            </div>
              <div id="likers-container-374323" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="374323"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #26"></div>
  </section>
</div>
    <div class="postbit" id="374663" data-post-id="374663">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="bartblast" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/120/17647_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  bartblast
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Hologram</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group quote-modified" data-username="Eiji" data-post="16" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/eiji/48/36743_2.png" class="avatar"> Eiji:</div>
<blockquote>
<aside class="quote no-group" data-username="bartblast" data-post="13" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>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 <a class="mention" href="/u/olivermt" rel="nofollow">@olivermt</a>.</p>
</blockquote>
</aside>
<p>Oh, that’s simple then. First we need a lot of structs (for window, document, element, event and all other data used in APIs). Then we need a helper functions (getters, setters for properties and apply) or sigil for a <code>JS</code> template, for example:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def get_element_by_id(component_or_page, id) do
  component_or_page
  |&gt; Hologram.JS.get_document()
  |&gt; Hologram.JS.apply(:getElementById, [id])
end

def set_data(element, key, value) do
  element
  |&gt; Hologram.JS.get_property(:dataset)
  |&gt; Hologram.JS.set_property(key, value)
end

# or with templates:
def get_element_by_id(component_or_page, id) do
  assigns = %{document: Hologram.JS.get_document(component_or_page), id: id}

  ~HJS"""
  return {@document}.getElementById({@id});
  """
end

def set_data(element, key, value) do
  assigns = %{element: element, key: key, value: value}

  ~HJS"""
  return {@element}.dataset.{@key} = {@value};
  """
end
</code></pre>
</blockquote>
</aside>
<p>That’s interesting directions to explore for higher-level APIs. But right now I’m really trying to nail down the most primitive building blocks - think more like “how do I call someJsFunction(arg1, arg2) from Elixir and get the result back” rather than “how do I elegantly manipulate the DOM through Elixir-style APIs”. For now, I’m not even sure we should allow DOM manipulation through Elixir at all - we already have the template system for that. Maybe DOM manipulation should just stay in JS directly, and the interop should focus on calling into JS libraries and getting data back. I don’t know.</p>
<p>The main goal here is to create an escape hatch that serves as a permanent foundation - something that unblocks users who need to integrate with existing JS libraries or APIs today, and that will remain as the underlying layer even as we build more elegant abstractions on top.</p>
<p>If someone needs to use Google Maps (like <a class="mention" href="/u/olivermt" rel="nofollow">@olivermt</a>’s example), integrate Chart.js, or call into their company’s existing JavaScript codebase, they should be able to do it with basic primitives rather than waiting for me to build high-level abstractions.</p>
<p>This isn’t a temporary solution - it’s the foundational layer. The more elegant solutions you’re describing would be built on top of these same primitives. Something like <code>Hologram.DOM.query_selector</code> examples could be implemented using the low-level interop if we decide we want to allow DOM manipulation outside Hologram template system. And users could even create their own Web API wrappers in pure Elixir using these same building blocks.</p>
<aside class="quote no-group quote-modified" data-username="Eiji" data-post="16" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/eiji/48/36743_2.png" class="avatar"> Eiji:</div>
<blockquote>
<aside class="quote no-group" data-username="bartblast" data-post="13" data-topic="72518">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>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.</p>
</blockquote>
</aside>
<p>I think that <code>JavaScript</code> equivalent for atoms are symbols, see:</p>
<blockquote>
<p><strong><code>Symbol</code></strong> is a built-in object whose constructor returns a <code>symbol</code> <a href="https://developer.mozilla.org/en-US/docs/Glossary/Primitive" rel="noopener nofollow ugc">primitive</a> — also called a <strong>Symbol value</strong> or just a <strong>Symbol</strong> — that’s guaranteed to be unique. Symbols are often used to add unique property keys to an object that won’t collide with keys any other code might add to the object, and which are hidden from any mechanisms other code will typically use to access the object.</p>
<p>Source: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol" rel="noopener nofollow ugc">Symbol - JavaScript | MDN</a></p>
</blockquote>
<p>I believe that you can create <code>tuple</code>-like arrays in <code>JavaScript</code> by using <code>EcmaScript 2015</code> feature called <code>Object.seal</code>, see:</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal" class="inline-onebox" rel="noopener nofollow ugc">Object.seal() - JavaScript | MDN</a> <a href="https://stackoverflow.com/questions/21988909/is-it-possible-to-create-a-fixed-length-array-in-javascript" rel="noopener nofollow ugc">https://stackoverflow.com/questions/21988909/is-it-possible-to-create-a-fixed-length-array-in-javascript</a></p>
<p>I guess there are lots of ways for pattern-matching (if needed at all), see:</p>
<p><a href="https://github.com/suchipi/safety-match" class="inline-onebox" rel="noopener nofollow ugc">GitHub - suchipi/safety-match: Rust-style pattern matching for TypeScript · GitHub</a></p>
<p>I don’t think that immutable data structures needs to be translated.</p>
</blockquote>
</aside>
<p>Regarding your point about mapping Elixir data types to JS equivalents - while it’s technically possible, I think that’s a dead end for interop. We’d inevitably end up with a leaky abstraction at some point. Plus, what happens when JavaScript or Elixir evolve?</p>
<p>The interop layer should embrace the fact that we’re crossing language boundaries rather than trying to hide it. Let JS be JS, let Elixir be Elixir.</p>
<p>I’d rather have libraries that work and feel like Elixir than trying to pretend we can write JS directly in Elixir.</p>
<p>For example, a Canvas Elixir wrapper could look like this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">"my_element"
|&gt; Canvas.new()
|&gt; Canvas.fill_style("green") 
|&gt; Canvas.fill_rect(10, 10, 150, 100)
</code></pre>
<p>Notice this feels like Elixir and hides the canvas context object and other metadata in the Canvas struct.</p>
<p>This code would be easily testable directly in Elixir, because we could have a server (test-only) version of this module that records all ops on the struct (instead of calling JS) and verifies the result.</p>
<p>Otherwise, we’re just pretending we’re using Elixir while trying to map to JS one-to-one, which doesn’t feel like Elixir and forces you to understand JS notions of mutability, objects and references.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="374663" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-for-hologram-javascript-interop-what-would-you-like-to-see/72518/28">Post #27</a>
	                </div>
	            </div>
              <div id="likers-container-374663" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="374663"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #27"></div>
  </section>
</div>
    <div class="postbit" id="374664" data-post-id="374664">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="bartblast" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/120/17647_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  bartblast
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Hologram</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="frankdugan3" data-post="18" data-topic="72518" data-full="true">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/frankdugan3/48/39210_2.png" class="avatar"> frankdugan3:</div>
<blockquote>
<p>A follow-up thought: If these calls were automatically wrapped with try/catch, that could eliminate the problem of runtime errors, right? I’m sure that is some overhead, but I don’t know just how much. Perhaps try/catch would be the default wrapper, with an option to disable it for performance-critical calls. <img src="https://forum.elixirforum.com/images/emoji/apple/thinking.png?v=15" title=":thinking:" class="emoji" alt=":thinking:" loading="lazy" width="20" height="20"></p>
</blockquote>
</aside>
<p>There is some overhead, but it’s minimal during normal execution. Modern JavaScript engines are pretty good at optimizing try/catch blocks when no exceptions are thrown. However, in some cases, functions containing try/catch might be less aggressively optimized by the JIT compiler.</p>
<p>The real overhead happens during actual exception handling - stack trace generation and capture, unwinding the call stack, object creation for the Error instance, and searching for appropriate catch handlers.</p>
<aside class="quote no-group" data-username="Eiji" data-post="19" data-topic="72518" data-full="true">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/eiji/48/36743_2.png" class="avatar"> Eiji:</div>
<blockquote>
<p>Using <code>try/catch</code> for entre <code>JavaScript</code> API is rather an anti-pattern. Generally <code>Elixir</code> ecosystem prefers <strong>let it fail</strong> as fail and restart process is much simpler and therefore faster than catching all possible errors and passing all error data. I guess it would be better to let people spawn tiny processes.</p>
</blockquote>
</aside>
<p>The thing is, Elixir/Erlang runtime characteristics are one thing, and Hologram’s client-side runtime performance characteristics are another. I’d say we need to be practical here.</p>
<p>For now, there’s no supervision at all on the client side, and I’m not sure we should try to force the UI part into an Elixir process model anyway - JavaScript is single-threaded by nature. We can’t map everything 1-to-1 from Elixir to JS. Even when we eventually have processes ported to JS (for example through Web Workers), it may require special APIs that don’t directly mirror Elixir’s process model. I don’t know yet exactly how it will look, but I want to keep in mind that we can’t assume a perfect 1-to-1 mapping of all Elixir concepts to JavaScript.</p>
<p>So while I deeply respect the “let it crash” philosophy for server-side Elixir, the client-side JavaScript interop boundary might warrant a more defensive approach, at least initially.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="374664" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-for-hologram-javascript-interop-what-would-you-like-to-see/72518/29">Post #28</a>
	                </div>
	            </div>
              <div id="likers-container-374664" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="374664"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #28"></div>
  </section>
</div>
    <div class="postbit" id="374674" data-post-id="374674">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="robak86" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/robak86/120/39186_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  robak86
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Interesting thread, lots of good ideas here. Let me throw in my two cents.</p>
<p>After going through the proposed examples, I’m personally a fan of introducing a small set of primitive functions for interacting with JavaScript — such as the ones already mentioned: <code>js_ref</code>, <code>js_call</code>, <code>js_create</code> or more explicitly <code>js_call_constructor</code>, <code>js_read</code> or alternatively a pair of <code>js_get</code> and <code>js_set</code> for property access. I’m not entirely sure whether the <code>js_</code> prefix is the best naming convention, but the concept itself feels solid.</p>
<p>Building on the earlier examples, I’d imagine it like this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Chart do
  use Hologram.JS.Interop

  def draw(element_id, width, height, data_points) do
    element =
      js_ref("document")
      |&gt; js_call("getElementById", [element_id])

    if is_nil(js_unwrap(element)) do
      # handle case when element is missing
    end

    js_ref("Chart")
    |&gt; js_create([element, %{width: width, height: height}])
    |&gt; js_call("draw", [data_points])
  end
end
</code></pre>
<p>I think this kind of low-level API enables several valuable characteristics:</p>
<ol>
<li>
<p><strong>Composability</strong>. It gives Elixir code a straightforward, structured way to express JS interop. If Hologram also transpiles macros and custom sigils, this unlocks a wide range of opportunities. For example, developers could implement their own JSX-style sigil that maps HTML to React elements, or a <code>defn</code> macro (like the one from the <code>nx</code> library) that transpiles to TensorFlow.js computational graphs.</p>
</li>
<li>
<p><strong>Encapsulation</strong>. Having granular primitives could encourage developers to isolate JS interop inside dedicated modules, rather than scattering inline snippets across the app. That in turn could lead to Hex packages that wrap JS libraries with clean Elixir APIs.</p>
</li>
<li>
<p><strong>Flexibility</strong>. Low-level primitives are inherently more flexible than the other approaches discussed. They let Elixir compose JS calls while still leaving room for higher-level abstractions (e.g. message-based APIs) to emerge later on. Those higher-level APIs could simply be implemented on top of the primitives once common usage patterns are clearer.</p>
</li>
</ol>
<p>Of course, a fundamental question arises: <em>to what extent can all types and idioms be mapped between Elixir and JS</em>? Probably not 100%. But even without full coverage (runtime errors for few worst-case scenarios), this approach seems more powerful and flexible than a system based solely on messages, ports, or sigils—since at the end of the day, one might still need to get results from the JavaScript side and unwrap them to Elixir types.</p>
<p>That said, certain constructs lend themselves to fairly direct mappings. For example, promises could be mapped reasonably well to Elixir’s Tasks, which offer similar semantics for retrieving asynchronous values. The underlying scheduling differs — JS has cooperative scheduling via its event loop, while Elixir uses preemptive scheduling — but in most cases, when delegating behaviour to JavaScript libraries, the difference shouldn’t be a blocker.</p>
<p>Finally, when it comes to error handling, the <code>js_</code> primitives could consistently return result tuples such as <code>{:ok,  %JS.Ref{}} | {:error, %JS.Error{kind, js_stack, cause}}</code>, keeping things aligned with Elixir conventions.</p>
<p>One more idea: a primitive for importing ESM modules asynchronously:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Utils do
  def enable_copy_button() do
    import_task = js_import("https://esm.sh/clipboard@2.0.11")
    clipboard_mod = Task.await(import_task) 
    js_call_constructor(clipboard_mod, [".copy-btn", %{text: fn -&gt; "some text to copy" end}])
  end
end

# or as macro that fetches and bundles js at compile-time

defmodule Utils do
  use Hologram.JS.Interop

  js_import "https://esm.sh/clipboard@2.0.11", as: :clipboard_mod

  def enable_copy_button() do
    js_call_constructor(clipboard_mod(), [".copy-btn", %{text: fn -&gt; "some text to copy" end}])
  end
end
</code></pre>
<p>Overall, I think a small set of clear building blocks could give both structure and flexibility. Structure, because every interop call would stay explicit, unified and easy to follow in Elixir. Flexibility, because higher-level helpers could still be built on top without losing the low-level power when needed. This also feels like the safest way forward—thin, explicit core now, with room to add higher-level APIs as they emerge.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="374674" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-for-hologram-javascript-interop-what-would-you-like-to-see/72518/30">Post #29</a>
	                </div>
	            </div>
              <div id="likers-container-374674" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="374674"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #29"></div>
  </section>
</div>
    <div class="postbit" id="374679" data-post-id="374679">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Eiji" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Eiji/120/36743_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Eiji
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Completely agree, as already mentioned I’m also not sure for <code>js_*</code> prefix, so let’s see how people would like to write a code if they would prefer <code>alias</code> over <code>import/use</code>:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Chart do
  alias Hologram.JS

  def draw(element_id, width, height, data_points) do
    element =
      "document"
      |&gt; JS.ref()
      |&gt; JS.call("getElementById", [element_id])

    if is_nil(JS.unwrap(element)) do
      # handle case when element is missing
    end

    "Chart"
    |&gt; JS.ref()
    |&gt; JS.create([element, %{width: width, height: height}])
    |&gt; JS.call("draw", [data_points])
  end
end
</code></pre>
<p>I’m not only sure about <code>JS.unwrap/1</code> … It would be absolutely amazing if we could use unwrapped element in <code>JS</code> API, see:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Chart do
  alias Hologram.JS

  def draw(element_id, width, height, data_points) do
    element =
      "document"
      |&gt; JS.ref()
      # already unwrapped after this call
      |&gt; JS.call("getElementById", [element_id])

    if is_nil(element) do
      # handle case when element is missing
    end

    "Chart"
    |&gt; JS.ref()
    |&gt; JS.create([element, %{width: width, height: height}])
    |&gt; JS.call("draw", [data_points])
  end
end
</code></pre>
<p>This would not only simplify the code, but would be a lot easier for creating a said high-level APIs. We could use a special map key. Let’s call it a <code>__hologram_unwrap__</code> (instead of <code>__struct__</code> special key) for now. What do you think about it?</p>
<p>Maybe instead of taking <code>ref</code> from <code>String.t()</code> we could use atoms in low-level API. Think that instead of <code>JS.ref("Chart")</code> we simply use <code>:Chart</code> and <code>JS.ref("document")</code> would be changed to <code>:document</code>. <code>Hologram</code> should not care what way is used and this solution could really improve the <code>DX</code>.</p>
<p>If both ideas would be accepted this is how the updated code would look like:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Chart do
  alias Hologram.JS

  def draw(element_id, width, height, data_points) do
    # already unwrapped
    element = JS.call(:document, "getElementById", [element_id])

    if is_nil(element) do
      # handle case when element is missing
    end

    # no extra calls just to create JS reference - it would be done by Hologram itself
    :Chart
    |&gt; JS.create([element, %{width: width, height: height}])
    |&gt; JS.call("draw", [data_points])
  end
end
</code></pre>
<p>Look that we don’t really need a high-level API to make code much cleaner. It perfectly follows <code>Elixir</code>’s 10x less code rule, so we only focus on the logic of our application. From here the high-level API would in fact be something like a syntax sugar, see:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule MyHologramLib.HighLevelJS do
  alias Hologram.JS

  def get_element_by_id(atom_or_unwrapped \\ :document, id) do
    JS.call(atom_or_unwrapped, "getElementById", [id])
  end
end
</code></pre>
<p>It’s so simple! Doesn’t it look just amazing? <img src="https://forum.elixirforum.com/images/emoji/apple/heart_eyes.png?v=15" title=":heart_eyes:" class="emoji" alt=":heart_eyes:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="374679" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-for-hologram-javascript-interop-what-would-you-like-to-see/72518/31">Post #30</a>
	                </div>
	            </div>
              <div id="likers-container-374679" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="374679"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #30"></div>
  </section>
</div>
    <div class="postbit" id="374688" data-post-id="374688">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="olivermt" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/olivermt/120/2354_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  olivermt
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>It looks very custom tbf. Usually when you need to reach into js you just want to do some js stuff and send some results back and forth.</p>
<p>If I want this level of control I would reimplement the chart in Hologram.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="374688" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-for-hologram-javascript-interop-what-would-you-like-to-see/72518/32">Post #31</a>
	                </div>
	            </div>
              <div id="likers-container-374688" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="374688"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #31"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <a class="load-more-button" data-turbo-stream="true" href="/topics/72518/load_more?page=4">Load more posts (18 remaining)</a>
</div></template></turbo-stream>