Reducing Complexity in Web Programming

Creating full-stack, interactive browser UI apps is a complex process, typically involving knowledge of multiple programming and layout/style languages, dependency managers, build systems, HTTP/Websockets, a frontend framework, a UI component library, and a backend framework. In addition, if you want a reactive UI that updates without reloading, you typically have to design a network protocol to shuttle messages back and forth and update the UI state in response to server messages.

Hyperdiv is an attempt to reduce the complexity of building basic browser UI apps and tools, while minimizing negative tradeoffs.

Hyperdiv arrives at:

A single language: Python

Hyperdiv is a pure Python framework and API. You pip install it, import it, and get going making an app.

No build steps or tools

Hyperdiv runs your Python app in Python. It does not compile your app to Javascript to ship it to the browser. You run your app with python my-app.py without extra-Pythonic steps or boilerplate.

Managed network

Hyperdiv internally manages the communication with the browser. It uses a custom internal protocol to keep the Python app and the browser in sync, without the Hyperdiv programmer having to worry about network communication details.

Built-in pre-styled component library

Hyperdiv comes pre-packaged with the Shoelace component library, whose components are pre-styled and which includes dark/light modes with color palettes that adapt to the mode. Hyperdiv has forms with a rich set of input components and input validation implemented in Python.

Hyperdiv also has built-in markdown support via Mistune and Pygments, data tables, and charts via Chart.js.

Implicitly reactive

Hyperdiv provides a concept of "state", from which all of its components are derived. Hyperdiv's reactive state architecture resembles that of Preact Signal Hooks, but with important differences.

When any piece of state is updated, Hyperdiv re-runs the top-level app function and the UI automatically updates.

Over-the-wire virtual DOM patching

When the app re-runs, Hyperdiv collects the new virtual DOM from the run and diffs it against the current virtual DOM, that was generated by the previous run. Then it sends minimal patches to the in-browser Hyperdiv Javascript frontend, which patches them into the real in-browser DOM.

Hyperdiv's frontend listens for UI events and sends state updates to Hyperdiv's Python server. The Python side applies the state updates, re-runs the app function, and sends virtual DOM patches to the frontend.

Declarative and ergonomic immediate-mode UI

Hyperdiv uniquely blends reactive state with immediate-mode UI. We believe the ergonomic features of immediate-mode UI lead to a smoother and more direct programming experience for people who want to get basic stuff done quickly. Namely:

1. Express the UI declaratively and implicitly.

Instead of having to build a UI data structure, like this:

def my_ui(show_text=False):
    ui = [button("Button 1")]
    if show_text:
        ui.append(text("Hello"))
    ui.append(button("Button 2"))
    return ui

Hyperdiv does this:

def my_ui(show_text=False):
    button("Button 1")
    if show_text:
        text("Hello")
    button("Button 2")

The UI is implicitly "collected" as the code runs, instead of you having to manually build a data structure. And then the return values of your functions can be whatever you want.

2. Events are in-line.

Instead of

def my_callback(event):
    # do something

def my_app():
    button("Click Me", on_click=my_callback)

Hyperdiv does this:

def my_app():
    b = button("Click Me")
    if b.clicked:
        # Do something

Or more succinctly

def my_app():
    if button("Click Me").clicked:
        # Do something

clicked is a prop like any other, with the important difference that it is (a) readonly and (b) automatically reset back to its default value False after one run of the app.

Note that for slow event handlers you can use Hyperdiv tasks to run handler code asynchronously.

3. Component props are proxies for mutable state.

We could have taken the common approach of immutable UI components + external mutable state. Hypothetical pseudocode example:

class State:
    checked = False

    @staticmethod
    def toggle():
        State.checked = not State.checked

def my_app():
    checkbox("Check Me", checked=State.checked, on_change=State.toggle)
    text("Checkbox is", State.checked)

In this popular approach, the checkbox component and its props are immutable, and the only way to endow them with mutability is to define external mutable state and connect the state to the checkbox's props.

Instead, Hyperdiv does this:

def my_app():
    ch = checkbox("Check Me")
    text("Checkbox is", ch.checked)

The checkbox's checked prop is automatically hooked up to mutable state internally, so you don't have to do that plumbing yourself. When a user toggles the checkbox in the UI, the checked state is mutated and the app re-runs with the new state.

Note that the checkbox does not "encapsulate" its state. And really, it cannot, since the my_app() function is executed over and over as state changes, and therefore the checkbox component is recreated over and over. The actual state is held in a global data structure that persists across app runs, and component props are read/write proxies into that global state.

Hyperdiv uses an indexing technique based on call stack introspection, previously explored in the Immediate-Mode UI space, to correctly look up the state of each component as the app re-runs.

Fixed JS

Hyperdiv's Javascript frontend is a piece of fixed, generic boilerplate that mainly applies incoming virtual dom and virtual dom patches to the in-browser dom. In this sense, Hyperdiv is similar to HTML over the wire systems like Hotwire and Htmx.

As a side effect, Hyperdiv does not expose your app's logic to the browser. At any one time, the browser only contains the currently rendered state of the app. An attacker trying to reverse engineer your UI's logic will be unable to do so, short of enumerating its internal states via UI interactions.

In addition, the Javascript frontend never receives unrendered data, as is typical in SPAs. It only receives already rendered virtual DOM patches. So there's no possibility of leaking out unrendered data fields over the network.

Try it out

Install Hyperdiv and open the documentation app locally:

> pip install hyperdiv
> hyperdiv docs

Hyperdiv requires Python 3.9+ and has been tested on macOS and Linux.

Star on GitHub