/prolog-vdom

Virtual DOM implementation for SWI-Prolog

Primary LanguagePrologMIT LicenseMIT

Prolog Virtual DOM

This is a Virtual DOM implementation for Prolog. It is designed to be used with the WebAssembly build of SWI-Prolog.

The implementation keeps the VDOM tree on the Prolog side and serializes the initial tree and modifications through the foreign language interface.

Demo: http://demos.rlaanemets.com/prolog-vdom/examples/wasm/

Overview

Please see examples/wasm/index.html and examples/wasm/app.pl for actual code examples.

The Prolog code runs inside the WebAssembly module that contains running SWI-Prolog instance. The communication between the page and Prolog is done through the SWI-Prolog Foreign Language Interface (FLI) and the Emscripten API.

The application starts by rendering the initial state into the initial VDOM tree, passes it to the page side and creates the actual DOM tree based on it. The glue code in the browser sets up generic event handlers (currently for events click, input and submit). Event handlers are set on the body element and rely on target element id's to differentiate between targets.

On each event the application renders new VDOM tree, compares it to the previous one and sends diff to the glue code. The JavaScript glue code decodes the diff and applies it to the actual page DOM.

Prolog VDOM

Prolog API

See example:

VIn = div([], [hello]),
VPrev = div([], [world]),
vdom_diff(VIn, VPrev, Diff, VOut).
  • VIn - current VDOM tree.
  • VPrev - previous VDOM tree.
  • Diff - serialized updates (diff(replace(path(0), "hello"))).
  • VOut - current full VDOM tree.

In this example the first child of the root node is being replaced by the text node containing "hello".

VDOM nodes

VDOM nodes are either atomic terms or tags. Atomic terms are atoms, numbers and strings.

Tags have the general form of name(Attributes, Body) and they correspond to real DOM tags unless there is a component registered with the same name.

Components

VDOM components are both an abstraction and optimization tool. Components are registered using the vdom_component_register(Name, Pred) predicate. Name refers to the component's name and Pred refers to the component's rendering predicate.

Components with the same input data at the corresponding VDOM node are not re-rendered during the diff calculation. This allows to skip major parts of the VDOM tree in the algorithm.

Keyed node

Keyed node is a VDOM node where children have distinct key attributes. This is a common case where a list is rendered into a list of VDOM nodes. The VDOM diff algorithm is able to:

  • Reorder children effectively.
  • Add/remove new children in/from the middle.

for a keyed node.

JavaScript (in-page) API

The JavaScript part consist of the following code:

  • File js/prolog.js - Prolog FLI bound using Emscripten.
  • File js/patcher.js - Helper that applies patches onto the real DOM and forwards DOM events.

Construct a new Prolog FLI instance:

const prolog = new Prolog(module, args);

where module is a Emscripten module object and args is an array or arguments for PL_initialise function.

With the semi-official SWI-Prolog WebAssembly build, args is:

[
    'swipl',
    '-x', 'wasm-preload/swipl.prc',
    '--nosignals'
]

Construct a new Patcher instance:

const patcher = new Patcher(prolog, root);

where prolog is a Prolog instance and root is the application's root DOM element (it does not have to be document.body).

DOM serialization

Atomic terms are serialized as strings.

Attribute terms:

attrs(name1(Value1), name2(Value2))

where Value1 and Value2 are strings.

Tags:

name(Attrs, body(Child1, Child2)).

where Attrs is an attribute term and Child1 and Child2 are serialized DOM subtrees.

DOM patch serialization

Node rebuild:

replace(Path, Dom)

where Path is a path term and Dom is a serialized DOM tree as described in "DOM serialization".

Set/unset attributes:

set_attrs(Path, Attrs)

where Path is a path term and Attrs an attributes term.

Reorder-or-create:

roc(Path, actions(Action1, Action2))

where Path is a path term and Action1 and Action2 are terms in the form:

Reuse term (reuses DOM node from the old index):

reuse(Index)

Create term (creates a new DOM node):

create(Dom)

where Dom is a serialized DOM tree for the node as described in "DOM serialization".

Path term:

path(0, 2)

Path term represents path from the root element down to the element that is being modified. For example, path(0, 2) refers to 1st child of the root, and 3rd subchild of the child.

TODO

  • Style updates.
  • Special each node.

License

The MIT license. See the LICENSE file.