The shortest round trip
One component, compiled twice from the same source: for the browser (default) and for the server (generate: "ssr", which @shadow-js/vite selects for SSR builds and…
One component, compiled twice from the same source: for the browser (default) and for the server
(generate: "ssr", which @shadow-js/vite selects for SSR builds and ssrLoadModule).
src/app.tsxtsx
import { useStore } from "@shadow-js/core";
import { For } from "@shadow-js/web";
export function App(props: { names: string[] }) {
const [count, setCount] = useStore(0);
return (
<main>
<h1>Hello {props.names[0]}</h1>
<ul>
<For each={props.names}>{(name) => <li>{name}</li>}</For>
</ul>
<button type="button" onClick={() => setCount((n) => n + 1)}>
clicked {count()}
</button>
</main>
);
}
export function Page() {
return <App names={["Ada", "Grace"]} />;
}
On the server:
server.tsxtsx
import { renderToString } from "@shadow-js/web/server";
import { Page } from "./src/app";
console.log(renderToString(() => <Page />));
<main data-hk=Reading the output:
data-hk="0-0-0-0"is a hydration key on each template root: the outermost element of a JSX expression, which is the root of a component's markup and also the root of each piece of markup a callback returns (aForrow, aShowbranch). Elements nested in the same expression (h1,ul,button) carry no key; the client reaches them through their root.<!--[-->...<!--]-->wrap every dynamic slot ({props.names[0]},{count()}, a list). They tell the client where a slot's content starts and ends.- The
onClickhandler is not in the markup. The server never serializes functions. - Text is escaped:
& < > " '.
In the browser, hydrate takes the same component call and the container that holds this markup:
The rules this proves, which hold for every component:
hydrate(code, container, options?)claims elements by key instead of creating them. Compiled DOM code is the same as for client rendering; onlytemplate()behaves differently while hydrating: it returns the server's element for the next key.- Comment markers exist only in the server's HTML.
hydrateremoves them as it claims regions, and sweeps any it did not claim at the end. After hydration (and after any client render) the live DOM contains zero comment nodes. hydratereturns a dispose function that stops the owner and removes the nodes. Calling it twice is safe.- A second
hydratewhile one is running throws; a pass is synchronous, so this only matters for re-entrant calls.