Server-side rendering
An order list rendered on the server. The HTML that reaches the browser already contains the table, so the rows appear in the page source and are visible with JavaScript disabled. In the browser, the same screen takes the region over rather than redrawing it.
One view, two sides
The screen is one module that both the server and the browser import. There is one view, so there is no second implementation to keep in sync.
The module has no startup code. Top-level statements in an imported module run at import time, so App hydrate: in this module would also run on the server.
The server
The runtime runs under Node without a browser. App page: loads the designer's HTML, App mount:at: renders the screen, and App document returns the whole page.
Server class >> render: html data: json = (
App page: html.
screen := App mount: (Screen fromJson: ((Json parse: json) orElse: [Json null])) at: '#app'.
page := App document replaceAll: '</body>'
with: '<script id="lps-data" type="application/json">' , json , '</script></body>'.
screen ! (Done fields: {}).
page ).
The data used for rendering is embedded in the page as JSON so that the browser's actor can start from the same state. The actor is stopped after it answers, so nothing accumulates per request.
One render takes about a millisecond. The development server loads the built module once and calls Server render:data: per request; it does not start a process or compile anything.
The browser
App hydrate:at: takes over the existing region. It walks the VNode the view returned alongside the existing markup, reuses nodes whose tags match, and attaches event listeners. Nodes that do not match are rebuilt.
seed := ((Doc getElementById: 'lps-data') map: [:e | e textContent]) orElse: ['{}'].
screen := App hydrate: (Screen fromJson: ((Json parse: seed) orElse: [Json null])) at: '#app'.
Attributes are not written during hydration. The markup came from the same view, so it already matches, and writing value would erase text the user typed while the script was still loading.
App document leaves the designer's sample elements in a <template id="lps-original">, so Html clone: in the browser still finds them.
Requirements
The browser's actor must start from the same state as the server. If the states differ, the views differ and the region cannot be taken over.
Testing
Five tests run headless: the shape of App document, the markup after hydration, that typed text reaches the handler, and that clones come from the template. Six run in a real browser: that the same nodes are reused, that typed text and focus survive, and that listeners are attached.