Skip to content

Instantly share code, notes, and snippets.

@schell
Last active July 26, 2020 00:06
Show Gist options
  • Save schell/6791d0edeef326971580f3456beceb64 to your computer and use it in GitHub Desktop.
Save schell/6791d0edeef326971580f3456beceb64 to your computer and use it in GitHub Desktop.
mogwai server side rendering notes

mogwai uses web-sys and wasm_bindgen to accomplish DOM creation and dynamic updates. Mogwai's main type Gizmo<T> has a type variable T meant to be filled by web-sys types that represent DOM nodes. Since web-sys's operations rely on javascript->wasm interop, any server side (ie rust only) pre-rendering should not happen through the normal browser-bound mogwai codepath. Of course using web-sys in this way would compile fine - but attempting to build the DOM and run a mogwai Gizmo when compiled to native code would cause a panic. Not all is lost though, as this actually points us in the direction of a sane implementation so long as we don't get carried away.

mogwai is meant to be a lightning fast frontent library, not a feature-rich web development framework. There are already plenty of heavy frameworks (I'm looking at Yew, kid). To this end I don't want to push mogwai into becoming an "isomorphic" framework (ie one that can run the same code on both server and browser). Instead I would like mogwai to have a compelling SSR story that ties in well with its other goals:

  • be fast
  • be small
  • be explicit

Additionally I'd like to support SSR without requiring more effort from library users.

A solution

At the very least we need to add a function like

fn gizmo_to_string(gizmo:Gizmo) -> String;

which could be used to serialize a gizmo to a string. But of course this multiplies the amount of effort needed to define a mogwai component that can be serialized server-side. The library user now needs to define the view function and the gizmo_to_string function. Another alternative would be to define components using a builder (which was the case for mogwai v0.1:

fn view(...) -> GizmoBuilder<T>;

and then use this GizmoBuilder to build the DOM in the browser and to build the String on the server. However, this approach negatively affects DOM building performance, which is a major tenet of mogwai in the first place. The idea of a builder is a step in the right direction in my opinion, as it clarifies what we're really looking for here - one definition of the view that can be used on the server and the browser. The problem isn't trivial but if we enumerate the requirements we'll see that one of rust's tools is a good fit for the job:

  • define a component once with no added library user effort
  • build and run that component in the browser with no added performance cost
  • build and serialize that component on a server
  • only pay for what you use, ie - if you don't want SSR you should not have to change your code and there should be no compile-time or performance cost

tl;dr Define a component once and use that definition in multiple contexts without an intermediate abstraction.

This sounds like a job for procedural macros! If we had a macro DSL for defining components we could transform this definition:

ssr_component!(
  <div class="my_class">...</div>
)

into

fn view(...) -> Gizmo<...> {
    div()
      .class("my_class")
      ...
}

fn to_string(...) -> Gizmo<...> {
    r#"<div class="my_class">...</div>"#
}

In general I've been apprehensive about writing a JSX macro but it looks like it serves a nice use case here, and using a procedural macro opens up some interesting possibilities for separating the view into html(ish) templates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment