Stream: ideas

Topic: basic-dom


view this post on Zulip Richard Feldman (Jun 09 2025 at 17:21):

this is an idea I had sketched out and wanted to write down in Zulip!

view this post on Zulip Richard Feldman (Jun 09 2025 at 17:21):

so in the past when we've talked in the past about Roc in the browser, there's been a baseline assumption of doing it as some sort of Virtual DOM system.

view this post on Zulip Richard Feldman (Jun 09 2025 at 17:21):

it occurred to me recently that we never actually talked about what a simpler platform would look like - something like "I want to use all the browser APIs, but I want to write Roc instead of JavaScript" and what that platform would look like.

view this post on Zulip Richard Feldman (Jun 09 2025 at 17:21):

in other words, just like how basic-webserver is just trying to do the most basic thing, and then something like nea is trying to do something fancier, it seems like a good idea to have a basic-dom and then there can be fancier alternatives that are doing things like abstracting the browser APIs more

view this post on Zulip Richard Feldman (Jun 09 2025 at 17:25):

I think the fundamental goals would be:

view this post on Zulip Richard Feldman (Jun 09 2025 at 17:27):

I think it would also be fine to make some things more formalized, e.g. querySelector in JS takes a query string that uses CSS selectors which would need to be parsed, whereas query_selector! in Roc could take a Query value that you build up in a config-builder style.

view this post on Zulip Richard Feldman (Jun 09 2025 at 17:29):

DOM node property access would have to be done with effectful functions, e.g. in JS there's node.textContent but in Roc it would need to be:

Node.get_text_content! : Node => Str
Node.set_text_content! : Node, Str => Str

view this post on Zulip Richard Feldman (Jun 09 2025 at 17:30):

here's how defining a custom element in Roc could look:

CustomElem.define!(
   "my-custom-element",
    CustomElem
        .class("MyCustomElement")
        .constructor(|this| {
            this.super!()
        })
        .connected_callback(|this| {

        })
        .disconnected_callback(|this| {

        })
)

view this post on Zulip Richard Feldman (Jun 09 2025 at 17:34):

and here's how the wasm <-> JS boundary could look - basically converting object methods to plain functions that pass the object as the first argument: (these can now come across from JS to wasm as externref types, which modern browser support - basically they're an opaque pointer that wasm can't dereference, but rather can hold onto and pass back to JS - perfect for this use case)

const wasmDomApi = {
  documentQuerySelector: (selector) => document.querySelector(selector),
  nodeQuerySelector: (node, utf8selector) => node.querySelector(utf8Decoder.decode(utf8selector)),
  getTextContent: (node) => node.textContent,
  getFirstChild: (node) => node.firstChild,
  getNextSibling: (node) => node.nextSibling,
  // ...etc
};

WebAssembly.instantiateStreaming(fetch("roc_app.wasm"), {
  env: wasmDomApi
});

view this post on Zulip Richard Feldman (Jun 09 2025 at 17:34):

anyway, something to think about!

view this post on Zulip Brian Carroll (Jun 09 2025 at 21:34):

Interesting idea. When I made a brief attempt at building a virtual DOM lib in Roc a couple of years ago, this is roughly where I drew the boundary - everything in Roc except calls to DOM APIs.

The main difference is that if you are writing a virtual DOM lib you only need like 10 or 20 actual DOM functions. But if you are making bindings to the DOM API, that's truly enormous. I know you are only picking a subset, but the trick is in deciding which subset, especially if there is no particular design in mind.

One annoying detail is that strings can't be passed across the Wasm boundary. You have to pass them as pointer and length, and convert to and from JS strings with TextEncoder and TextDecoder.

view this post on Zulip Brendan Hansknecht (Jun 09 2025 at 22:08):

If you are using raw dom apis, I assume that means registering tons of callbacks. Will this work well with roc and a basic platform?

view this post on Zulip Richard Feldman (Jun 10 2025 at 00:25):

I'm not sure, maybe! :smile:

view this post on Zulip Brian Carroll (Jun 12 2025 at 06:38):

Oh yeah for callbacks, you can't pass them across the boundary from Wasm to JS. You have to build a system for that. For example you put each user-defined callback into some List or Dict and give it a numeric ID. Then you pass the ID over to JS instead. You create a JS closure that captures the ID, serializes the JS arguments to bytes, and passes it all back to Wasm. Then a Wasm a dispatch function finds the original user callback and calls it.

Problem: In the most common cases, the argument passed to the callback will be an EventTarget, which is unserializable! In particular it contains a DOM node, which forms a doubly-linked list, pointing at its parent _and_ at its children. Even if it was serializable it would be too big so you wouldn't want to serialize it.

One way to solve that is to make a system for specifying only certain fields to serialize and send back to Wasm. So you could say you want event.target.children[0].value (like jq syntax or some AST version of it). Then you arrange for your JS callback to dig out that value and return it to Wasm.

The other approach is to use the newer "reference types" feature in Wasm. Then Wasm can hold on to an opaque JS reference and call back out to JS again to manipulate it and access fields.

view this post on Zulip Richard Feldman (Jun 12 2025 at 13:15):

yeah I was thinking of externref (the new feature) - it's apparently widely supported now!

view this post on Zulip Kiryl Dziamura (Jun 16 2025 at 12:44):

that's actually all what I wanted from roc for the web client. client is not only about dom and documents, there are still workers. it would be nice to have smth similar to wasm-bindgen from the rust world: a Web IDL based codegen with simple custom api wrapper builders

view this post on Zulip Richard Feldman (Jul 23 2025 at 12:03):

related: "When Is WebAssembly Going to Get DOM Support?" https://queue.acm.org/detail.cfm?id=3746174

view this post on Zulip Tobias Steckenborn (Jul 24 2025 at 06:21):

Would be really interesting to see how this compares to javascript based frameworks and where potential drawbacks or benefits are.

Was there already some thinking about how "graphical" platforms could share the same "principles"? E.g. if one would build something like Ctrl-K / Command Menu, would we imagine an abstraction layer that could make the same work for desktop and web?

view this post on Zulip Ghislain (Feb 27 2026 at 14:31):

https://hacks.mozilla.org/2026/02/making-webassembly-a-first-class-language-on-the-web/

view this post on Zulip Ghislain (Feb 27 2026 at 14:34):

It would be nice to have a Roc implementation of WebAssembly Component Model!

view this post on Zulip Luke Boswell (Feb 27 2026 at 20:04):

I have a rough basic-dom platform I've was poking at, I was using it to test the interpreter and find bugs or validate our platform host boundary (particularly for wasm).

I haven't looked at it in a while, the reason I stopped pushing it along was that we really need much smaller object files i.e. dev or llvm backends for it to be usable in any way.

My plan was to revisit that at some point and share it in a minimal working state in case anyone wanted to explore optimizations like skipping the JS glie altogether etc.


Last updated: Jun 16 2026 at 16:19 UTC