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.
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.