liveview-native / liveview-native/gen_dom
DOM architecture improvements
- 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