Supporting Web Components in LiveView

Hello! After seeing a couple of posts from Chris and Jose about supporting web components in LiveView, I thought it would be great to initiate a conversation about what this means and how to do it. This is a subject that is near and dear to my heart, and one I’ve tried to approach with a library called LiveElements: GitHub - launchscout/live_elements: A library to make custom elements and LiveView so happy together. In a nutshell, LiveElements facilitates custom elements integrating with LiveView by declaratively creating LiveView functional component wrappers for custom elements. To a developer this makes custom elements “quack” like any other LV functional component. If any of this library is interesting a starting point, I’d be more than happy to work on whatever integration strategy makes sense. If starting over from scratch makes more sense, I’d be happy to help with that too.

First tho, it probably makes sense to talk about what supporting web components might mean. Here are some thoughts based on experience. To best support web components (custom elements specifically) it’s been helpful to:

  • Handle custom events dispatched from custom elements in handle_event callbacks in LiveView
  • Pass more complex data types into custom elements from LiveView
  • create functional component wrappers to handle the above

LiveElements handles all this, so might be useful as a starting point if these features are desired. But I’d love to hear more about if these are the right features, or if not, what is! Very excited the LiveView team is moving forward on this topic :slight_smile:

8 Likes

What posts of Chris and Jose are you referring to here?

When talking about webcomponents I think we have to first define what exactly we talk about:

  1. single elements that are made with LV in mind
  2. using a webcomponent lib like Adobe Spectrum, Adobe Carbon, Siemens IX, shoelace (will be webawesome)

(1) should be easy enough. (2) is outright impossible right now without significant hacks, the main prolems being that the components store their state in properties which LV kills. You have to know exactly what those are and catch them (doable without touching LV-js). Also libs that build their own form elements can only be used if one patches the special form handling inside the LV-js (afaik Siemens IX is the only one that augments standard form-elements and is thus the only one of the big libs that are right now usable with LV - with some hassle).

I think there has to be some LV-awareness in the webcomponent lib. For example it could come with some kind of manifest so that LV knows what not to touch, or even generate some API to manipulate client state, eg open a modal.

Web apps have client and server state (plus realtime and LiveView) - Dashbit Blog and Phoenix LiveView 1.0-rc is here! - Phoenix Blog

1 Like

Some mix of heex and declarative shadow dom templates could be the way to go here

Nice just saw this post (you mentioned on Better interop with Javascript libraries (React, Svelte, etc.))

Anyway I agree with all your points, though I don’t even think you’d need point 3) about functional wrappers if this was done correctly.
Bullet points 1) and 2) are the important ones though.

Here’s some thoughts I had which may or may not be feasible but I’d love to see something like this:

Currently in HEEX you can do

<my-element some-prop={@some_prop} />

but not much else I think, and @some_prop gets converted into an HTML safe string.

I think if HEEX introduced

  1. some delimiter syntax that basically means “don’t turn this into a string but rather assign as an object (string, number, boolean, array or object, i.e. JSON-stringifiable data)”, such as some-prop=(@some_prop) (note the round brackets, but could be anything that’s different from ={@some_prop}, e.g. :={@some_prop}, =[@some_prop], ~{@some_prop}, etc.)
  2. an easy way of doing the equivalent of element.addEventListener("some-event", ...)

Then that’s really all you’d need.
A few examples of what it might look like, with

  • an array prop [1,2,3]
  • listening to a CustomEvent "appendNumber", with event.detail as a new number, which calls back to handle_event("append", num, socket):

All of these examples effectively do the equivalent of

const element = document.createElement("my-element");
element.numbers = [1,2,3];
element.addEventListener("appendNumber", (event) => lv.pushEvent("append", event.detail);

Different ideas…

# Assumes you always send event.detail
<my-element numbers=([1,2,3]) @appendNumber="append" />

# Specifies exactly what from the event object to take
<my-element numbers=([1,2,3]) @appendNumber=({"append", "&1.detail"}) />
# side-note: that would also allow for more general events like clicks
<div @click=({"click", "&1.clientX"}) />

# Use phx-xxx attributes like other functionality, also just sends all of event.detail
<my-element numbers=([1,2,3]) phx-custom-event="appendNumber:append" />

# Use JS module
<my-element numbers=([1,2,3]) phx-on-appendNumber={JS.push_event("append")} />

# As above but specifying what to take from event
<my-element numbers=([1,2,3]) phx-on-appendNumber={JS.push_event("append", "&1.detail")} />

Anyway just throwing out a few ideas.

If that were possible, then:

  • Svelte, Vue, Angular, Solid can all easily compile to web components
  • Lit, Stencil etc. compile to web components
  • React components could be easily wrapped on a per-component basis (because mapping function callbacks → custom events would need to be done per-component)

and therefore would be even easier to integrate into a liveview app.

I don’t know how the HEEX compiler works (though am interested to look into it), so don’t know how feasible this would be, but I imagine it would mean the need for a Javascript script to be added after the whole page or something, and possibly the need for ids for that JS code to target the correct elements

Regarding having a reliable interface, it would seem that having an “ideal” collaborative web component library in mind would be helpful.

From some of the comments I’ve seen on repos for those libraries, they were not interested in considering SSR (which is fair, LV is unique) , and you had to know how it worked before you could effectively wrap it reliably.

I’ve read up and really like zag-js’ approach of modeling the web component as a state machine and providing consistent, declarative ways to interface with it, where integrating with a lot of other libraries had felt very imperative and broke as soon as you needed to rebuild the web component to some state that the author only expected a user could get it into.

3 Likes