« back

enterprise: a new ui framework

Posted on 2020-02-11 - 4 min read

This past weekend, while on my trip to Minneapolis, I completed a very early prototype of "enterprise", a new UI framework I've been kind of envisioning over the past couple of weeks. While the UI framework is mainly targeted at web apps, the hope is that with a bit more effort, native UIs can be produced with almost no changes to existing applications. Before I begin to describe how it works, I'd like to acknowledge Nathan Ringo for his massively helpful feedback in both the brainstorming and the implementation process.

Goals of the project

This project was born out of many frustrations with existing web frameworks. This is kind of the combination of several projects I wanted to tackle; since it's such a long-term thing I'm going to document a bit of what I want to achieve so I can stay on track. The high-level goals of the project are:

With that, let's dive into the code!

Demo: Initial Prototype

The prototype for experimenting is a simple "Hello, world" application. If you've looked at any web framework before, this is probably one of the simplest examples of bindings: type something into a box and watch as the text magically populates with whatever you wrote in the box. If you're using a WASM-compatible browser with JavaScript enabled, you should be able to try out the demo right here:

OK, you say, but I could implement this in 3 lines of JavaScript.

inputEl.addEventListener("change", () => {
    spanEl.innerText = inputEl.value;
});

Sure, this works, but it doesn't scale. If you try to write a page full of these kind of bindings directly using JavaScript, you're either going to start running into bugs or building up a pile of unmaintainable spaghetti code. How does enterprise represent this? Well, the enterprise DSL has no concrete syntax yet, but if it did, it would look something like this:

model {
    name: String,
}

view {
    <TextBox bind:value="name" />
    Hello, {name}!
}

This looks a lot closer to {React, Vue, Svelte, component structure}-ish code. The idea now is that the "compiler", as I've come to call it, reads the entire specification of the code and creates a sort of dependency graph of actions. For clarity, let's assign some IDs first:

Now we can model this as:

dependency graph view_value view_value model_name model_name view_value->model_name view_name view_name model_name->view_name

The arrows in this graph indicate a dependency where changes to view_value propagates down the graph until everything else is changed. For the initial prototype, the data structure passed through this graph is simply a string, but with encoding magic and additional specifications, we can add rich text later. What this means the compiler then generates code that looks something like (but not exactly):

fn create_view_value(model: &mut Model) -> INode {
    let el = (...);
    el.add_event_listener(|evt: InputListener| {
        let new_value = el.get_attribute("value");
        model.name = new_value;
        view_name.set_text(new_value);
    });
    el
}

There's some complications involving the exact model representation in memory as well as how web attributes are accessed that makes the real code a bit different, but from this example you should be able to see that our "compiler" generated real code that matches our specification above.

The full code for this can be found here.

Future

Obviously not everyone's application is as simple as a linear dependency graph of simple string values. In fact, I even cheated a bit to get this prototype to function; here's some of the shortcuts I took:

I'll be working on this some more in the coming weeks, so I'll try to keep updates posted here a bit more frequently. Next time, I'll be looking into implementing the TodoMVC example. Until then, thanks for reading!


End. This post is tagged with: computers , web-dev

tags · all pages

written by michael zhang. source