liveview-native / liveview-native/gen_dom

DOM architecture improvements

Open
#1 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Elixir
Stars
22
Forks
0
PR merge metrics
No merged PRs in 30d

Description

```mermaid
graph TD
A["Document
(GenServer)
PID: #PID<0.120.0>"] --> B["vml
(Element GenServer)
PID: #PID<0.121.0>"]

B --> C["head
(Element GenServer)
PID: #PID<0.122.0>"]

B --> D["body
(Element GenServer)
PID: #PID<0.123.0>"]

D --> F["Text
(Element GenServer)
PID: #PID<0.125.0>"]

F --> H["'Hello World'
(Text GenServer)
PID: #PID<0.127.0>"]

classDef document fill:#e1f5fe,stroke:#01579b,stroke-width:2px
classDef element fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
classDef text fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px

class A document
class B,C,D,F element
class H text
```

The core concept that the DOM tree is compirsed of `Document` that contains `Nodes`. `Nodes` can be `Document`, `Element`, or `Text` (currently).

## The current implementation

As of this writing I implemented this quickly with the following:

When a new DOM is created each `child` will first be created. `Document`, `Element`, and `Text` are all valid nodes as they `use GenDOM.Node`. When a new `Node` is created a `name` is autogenerated for the `GenServer`. (I am considering dropping this in favor or just pure PIDs soon)

Then a `Node` should "join" a parent through one of the following:

* `append_child`
* `insert_before`
* `replace_child`

When these functions are called the child is added in the correct position in `child_nodes`. The `node` is then passed to the parent's `track` callback which will `Process.monitor` the child and add its name to the `all_child_names` `MapSet` field of the state. Then `GenServer.cast(parent.parent, {:track, child})` and the child is passed up the tree where each parent is going to track all of it's descendants. We do this so that we can implement faster querying for nodes. I suspect this will also be beneficial for the DOM Event system and event bubbling.

## Process groups?

Internally the LVN team has discussed the idea of using Process groups instead of the above implementation. Where each parent is a group leader, and all children are in the group of each. However, the Elixir implementation of this doesn't permit multiple groups. And the `:pg` Erlang implementation doesn't guarantee ordering, let alone allow for node insertion at certain indices or replacing children. Looking at the Erlang implementation of `:pg` and I can easily replicate and extend this in Elixir for a custom Process group implementation that supports our needs: https://github.com/erlang/otp/blob/OTP-28.0/lib/kernel/src/pg.erl#L703-L718

However, would this be any better? I don't yet have benchmarks on the current implementation and so far it meets our needs. But it may not be the most idiomatic way to go about this.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading the current Document, Node, Element, and Text GenServer implementation, focusing on child ordering, descendant tracking, and monitoring. Compare that behavior with the proposed custom process-group approach and establish benchmarks for the current implementation. Done requires an agreed architecture and evidence that it preserves insertion and replacement ordering while meeting query and event-system needs.

Written by the indexing model from the issue text.

Assessment

Tech stack
elixir
Domain
backend
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.