Announcing Deku - A PureScript Web Micro-Framework

Deku, like Svelte, is reactive and does not have a virtual DOM. But where it goes pound-for-pound in Svelte is via pursx.

In Svelte, they use an HTML-like templating language that gets compiled to HTML where possible so that, under the hood, it can do div.innerHTML = myHTML. For something with thousands of static nodes, this will be faster than JS.

Deku does the same thing via pursx. When you have:

type MyDiv = """
<div>Hello world!</div>
"""
myDeku = (Proxy :: _ MyDiv) ~~ {}

Deku takes the contents of MyDiv and uses this as the innerHTML content of its enclosing tag. Like in Svelte, the HTML is validated for correctness at compile-time (in Deku’s case, by the PureScript compiler).

Both Svelte and Deku have hooks into the templating language to allow for dynamic attributes and reactive components. The innerHTML method is still used, but little bits of the resulting DOM are then hooked up to the reactive system, which is still faster than creating it all in JS for sufficiently large chunks of DOM.

Another framework to compare Deku to is Solid.js. Solid is more for app-building and has a similar reactive design philosophy to Deku. Where Deku shines (in my immodest unhumble opinion) is its use of the FRP event-based architecture all the way down, which opens up the expressivity of FP without losing the speed of Solid/Svelte.

3 Likes

What does this mean? What do Solid and Svelte do differently?

1 Like

The Svelte compiler uses the $ character to order reactive blocks. This is a pretty fragile system that requires manually ordering things and is not compositional: you can’t stash to $ sections together and compose them later on. Even in their docs, they mention how the compiler performs static analysis and instructs people on how to write these blocks in order to achieve certain outcomes.

In Deku, on the other hand, all values are reactive events, which allows you to compose them in arbitrary ways. As an example:

D.div_ (map (\i -> text (show <$> filter (_.z == i) myEvent)) (0 .. 10))

And then if you push to whatever is feeding myEvent, ie:

pusher { z: 3, foo: "bar" }
pusher { z: 5, foo: "baz" }

etc., the 3 slot will update for 3, the 5 slot will update for 5, etc. Just from a single map and filter. In Svelte, this type of functional expressivity is just not possible with the compiler and its static analysis.

As for Solid, it doesn’t have this problem because it’s more JS-y, so there’s no special compiler (it’s a standard JSX compiler) and you can do anything in JS that you can do in PS of course. But the issue in Solid is one of keys: for example, the For tag requires you to key everything a la React, which has two issues:

  • You need to come up with some sort of key management system.
  • The presence and absence of elements is controlled by inserting into a sorted collection and then removing from that collection, which creates a mini VDOM, which can get slow for large collections.

Events solve both problems:

  • If an element is created by an event instead of associated to a key, then it can listen for arbitrary “kill me” events and unsubscribe itself when you don’t need it anymore. That reduces the complexity of managing dynamic collections - you can check out ie the todo mvc in the Deku docs, which has 0 key management because it uses (what I believe to be) a better abstraction.
  • Events unsubscribe themselves, so you don’t need to sort them or traverse a sorted list to unsubscribe them. This makes collections super snappy compared to Solid’s method of checking for keys’ referential equality.
7 Likes