-
Notifications
You must be signed in to change notification settings - Fork 5
/
ssr.html
245 lines (217 loc) Β· 33.4 KB
/
ssr.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
<!DOCTYPE html>
<html lang="en"><head>
<link rel="icon" href="/logo.svg">
<title>VanJS - Fullstack Rendering (SSR, CSR and Hydration)</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/poppins/v20/pxiEyp8kv8JHgFVrJJfedw.ttf) format('truetype');
}
</style>
<link rel="stylesheet" href="/code/w3-v1.css">
<link rel="stylesheet" href="/code/prism-v1.css">
<link rel="stylesheet" href="/vanjs.css">
</head>
<body class="line-numbers" data-prismjs-copy="π">
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Q0NB75RY7E"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-Q0NB75RY7E');
</script>
<script type="text/javascript" src="/code/prism-v1.js" defer></script>
<!-- Sidebar/menu -->
<nav class="w3-sidebar w3-red w3-collapse w3-top w3-large w3-padding" style="z-index:3;width:280px;font-weight:bold;" id="mySidebar"><br>
<a href="javascript:void(0)" onclick="w3_close()" class="w3-button w3-hide-large w3-display-topleft" style="width:100%;font-size:22px">Close Menu</a>
<div class="w3-container">
<h1 class="w3-padding-16 w3-xxxlarge">
<img src="/logo.svg" alt="VanJS" width="192px" height="192px">
</h1>
</div>
<div id="nav" class="w3-bar-block"><a href="/" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Home</a><a href="/start" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Getting Started</a><a href="/tutorial" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Tutorial</a><a href="/demo" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">VanJS by Example</a><a href="/convert" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">HTML/MD to VanJS</a><a href="/vanui" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">VanUI</a><a href="/minivan" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Mini-Van</a><a href="/ssr" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white current">SSR & Hydration</a><a href="/x" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">X</a><a href="/advanced" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Advanced Topics</a><a href="/media" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Media Coverage</a><a href="/about" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">About</a></div>
</nav>
<!-- Top menu on small screens -->
<header class="w3-container w3-top w3-hide-large w3-red w3-xlarge w3-padding">
<a href="javascript:void(0)" class="w3-button w3-red w3-margin-right" onclick="w3_open()">β°</a>
<span id="title-bar">SSR & Hydration</span>
</header>
<!-- Overlay effect when opening sidebar on small screens -->
<div class="w3-overlay w3-hide-large" onclick="w3_close()" style="cursor:pointer" title="close side menu" id="myOverlay"></div>
<!-- !PAGE CONTENT! -->
<div class="w3-main" style="margin-left:300px;">
<div id="page">
<div id="content"><h1 class="w3-xxlarge"><b>VanJS</b>: Fullstack Rendering (SSR, CSR and Hydration)</h1><p><i>Requires <b>VanJS</b> <a href="https://github.com/vanjs-org/van/discussions/114" class="w3-hover-opacity">1.2.0</a> or later, and <b>Mini-Van</b> <a href="https://github.com/vanjs-org/mini-van/releases/tag/0.6.0" class="w3-hover-opacity">0.6.0</a> or later.</i></p><p><b>VanJS</b> offers a seamless and framework-agnostic solution for fullstack rendering. We will provide a walkthrough for a sample application with SSR (server-side rendering), CSR (client-side rendering) and hydration. As an outline, here are the major steps we're going to take to build the sample application:</p><ol><li>Define common UI components that can be shared on both server-side and client-side.</li><li>Implement server-side script with the help of <b>Mini-Van</b> for serving the HTML content to end users.</li><li>Implement client-side script with the help of <b>VanJS</b> for adding client-side components and enabling hydration.</li></ol><p>The sample application requires a bare minimum of dependencies. The server-side script can be run by Node.js. We can also build a fullstack application with other JavaScript runtime like Deno or Bun. Other front-end frameworks like Vite or Astro are not required, but it should be easy to integrate with them.</p><p>The source code of the sample application can be found <a href="https://github.com/vanjs-org/vanjs-org.github.io/tree/master/hydration-example" class="w3-hover-opacity">here</a> with the following directory structure:</p><ul class="dir-tree"><li class="folder"><code class="symbol">hydration-example</code>: Root of the sample application.<ul><li class="folder"><code class="symbol">src</code>: Source files of the application.<ul><li class="folder"><code class="symbol">components</code>: Common components that are shared on both server-side and client-side.<ul><li class="file"><code class="symbol"><a href="https://github.com/vanjs-org/vanjs-org.github.io/tree/master/hydration-example/src/components/hello.ts" class="w3-hover-opacity">hello.ts</a></code>: <code class="symbol">Hello</code> component.</li><li class="file"><code class="symbol"><a href="https://github.com/vanjs-org/vanjs-org.github.io/tree/master/hydration-example/src/components/counter.ts" class="w3-hover-opacity">counter.ts</a></code>: <code class="symbol">Counter</code> component.</li></ul></li><li class="file"><code class="symbol"><a href="https://github.com/vanjs-org/vanjs-org.github.io/tree/master/hydration-example/src/server.ts" class="w3-hover-opacity">server.ts</a></code>: server-side script to serve the HTML content.</li><li class="file"><code class="symbol"><a href="https://github.com/vanjs-org/vanjs-org.github.io/tree/master/hydration-example/src/client.ts" class="w3-hover-opacity">client.ts</a></code>: client-side script for client-side components and hydration.</li></ul></li><li class="folder"><code class="symbol">dist</code>: Bundled (and minified) client-side <code class="symbol">.js</code> files.</li><li class="file"><code class="symbol"><a href="https://github.com/vanjs-org/vanjs-org.github.io/tree/master/hydration-example/package.json" class="w3-hover-opacity">package.json</a></code>: Basic information of the application. Primarily, it defines the NPM dependencies.</li></ul></li></ul><p>You can preview the sample application via <a href="https://codesandbox.io/p/sandbox/github/vanjs-org/vanjs-org.github.io/tree/master/hydration-example?file=%2Fsrc%2Fserver.ts%3A1%2C1" class="w3-hover-opacity">CodeSandbox</a>.</p><p><i>A Bun-based variation of this example can be found </i><a href="https://github.com/vanjs-org/van/tree/main/bun-examples/hydration" class="w3-hover-opacity">here</a>.</p><h2 class="w3-xxlarge w3-text-red" id="package-lock-json-file"><a class="self-link" href="#package-lock-json-file"><code class="symbol">package-lock.json</code> File</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>Dependencies are declared in <code class="symbol"><a href="https://github.com/vanjs-org/vanjs-org.github.io/tree/master/hydration-example/package.json" class="w3-hover-opacity">package.json</a></code> file:</p><pre><code class="language-json"> "dependencies": {
"finalhandler": "^1.2.0",
"mini-van-plate": "^0.6.1",
"serve-static": "^1.15.0",
"vanjs-core": "^1.5.2"
}
</code></pre><ul><li><a href="https://www.npmjs.com/package/finalhandler" class="w3-hover-opacity">finalhandler</a> and <a href="https://www.npmjs.com/package/serve-static" class="w3-hover-opacity">serve-static</a>: Server-side packages for serving static files (primarily used for serving <code class="symbol">.js</code> files).</li><li><a href="https://www.npmjs.com/package/mini-van-plate" class="w3-hover-opacity">mini-van-plate</a>: The <b>Mini-Van</b> package used for SSR.</li><li><a href="https://www.npmjs.com/package/vanjs-core" class="w3-hover-opacity">vanjs-core</a>: The <b>VanJS</b> package used for CSR.</li></ul><h2 class="w3-xxlarge w3-text-red" id="shared-ui-components"><a class="self-link" href="#shared-ui-components">Shared UI Components</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>Now, let's build some shared UI components that can run on both server-side and client-side.</p><h3 class="w3-large w3-text-red" id="static-component"><a class="self-link" href="#static-component">Static Component</a></h3><p>First, let's take a look at a static (non-reactive) component - <code class="symbol">Hello</code>:</p><pre><code class="language-ts">import { env } from "mini-van-plate/shared"
export default () => {
const {a, div, li, p, ul} = env.van.tags
const fromServer = typeof window === "undefined"
return div(
p(() => `πHello (from ${fromServer ? "server" : "client"})`),
ul(
li("πΊοΈWorld"),
li(a({href: "https://vanjs.org/"}, "π¦VanJS")),
),
)
}
</code></pre><p>Compared to the <code class="symbol"><a href="/demo#hello-world" class="w3-hover-opacity">Hello</a></code> component in the "VanJS by Example" page, there are following notable differences:</p><ul><li>We import the <code class="symbol">env</code> object from <code class="symbol">mini-van-plate/shared</code>. The purpose of the <code class="symbol">env</code> object is to provide an abstract <code class="symbol">van</code> object for shared components so that shared components don't have to depend on a concrete <code class="symbol">van</code> object. The client-side and server-side scripts are expected to provide the actual <code class="symbol">van</code> object (from <b>VanJS</b> or <b>Mini-Van</b>, respectively) via function <code class="symbol">registerEnv</code>, as shown later.</li><li>We can determine if the component is being rendered on the server-side or client-side:<pre><code class="language-ts">const fromServer = typeof window === "undefined"</code></pre> and show different content based on it:<pre><code class="language-ts">p(() => `πHello (from ${fromServer ? "server" : "client"})</code></pre>This will help us differentiate whether the component is rendered from server or from client.</li></ul><p><b>Limitations: </b><i>For the abstract <code class="symbol">van</code> object, the typechecking for tag functions and <code class="symbol">van.add</code> is quite limited. This is because it's hard to unify the type system across the common types between server-side and client-side.</i></p><h3 class="w3-large w3-text-red" id="reactive-component"><a class="self-link" href="#reactive-component">Reactive Component</a></h3><p>Next, let's take a look at a reactive component - <code class="symbol">Counter</code>:</p><pre><code class="language-ts">import { env, State } from "mini-van-plate/shared"
interface Props {
readonly id?: string
readonly init?: number
readonly buttonStyle?: string | State<string>
}
export default ({id, init = 0, buttonStyle = "ππ"}: Props) => {
const {button, div} = env.van.tags
const stateProto = Object.getPrototypeOf(env.van.state())
const val = <T>(v: T | State<T>) =>
Object.getPrototypeOf(v ?? 0) === stateProto ? (<State<T>>v).val : <T>v
const [up, down] = [...val(buttonStyle)]
const counter = env.van.state(init)
return div({...(id ? {id} : {}), "data-counter": counter},
"β€οΈ ", counter, " ",
button({onclick: () => ++counter.val}, up),
button({onclick: () => --counter.val}, down),
)
}
</code></pre><p>Notable differences from the <code class="symbol"><a href="/demo#counter" class="w3-hover-opacity">Counter</a></code> component in the "VanJS by Example" page:</p><ul><li>Similar to the <code class="symbol">Hello</code> component, it uses <code class="symbol">env.van</code> imported from <code class="symbol">mini-van-plate/shared</code> to make the component environment-agnostic.</li><li>You can define states and bind states to DOM nodes as you normally do on the client-side. This is because in <b>Mini-Van</b> <code class="symbol">0.4.0</code> release, we adjusted its implementation to make it compatible to states and state-bindings related API, though with the absence of reactively (i.e.: changing a state won't lead to the update of the DOM tree), which is only possible on the client-side after hydration.</li><li>You can optionally specify the ID of the component with the <code class="symbol">id</code> property. This is helpful to locate the component while hydrating.</li><li>You can optionally specify the initial counter value (default: <code class="symbol">0</code>) with the <code class="symbol">init</code> property.</li><li>You can optionally specify the style of the increment/decrement buttons. As illustrated later, we will see how to make the button style of the <code class="symbol">Counter</code> component reactive to user selection.</li><li>We keep the <code class="symbol">data-counter</code> attribute of the component in sync with the current value of the counter. This will help us keep the counter value while hydrating.</li></ul><h2 class="w3-xxlarge w3-text-red" id="server-side-script-html-template"><a class="self-link" href="#server-side-script-html-template">Server-Side Script: HTML Template</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>Now, let's build the server-side script that enables SSR:</p><pre><code class="language-ts">import { createServer } from "node:http"
import { parse } from "node:url"
import serveStatic from "serve-static"
import finalhandler from "finalhandler"
import van from "mini-van-plate/van-plate"
import { registerEnv } from "mini-van-plate/shared"
import Hello from "./components/hello.js"
import Counter from "./components/counter.js"
const {body, div, h1, h2, head, link, meta, option, p, script, select, title} = van.tags
const [env, port = 8080] = process.argv.slice(2);
const serveFile = serveStatic(".")
registerEnv({van})
createServer((req, res) => {
if (req.url?.endsWith(".js")) return serveFile(req, res, finalhandler(req, res))
const counterInit = Number(parse(req.url!, true).query["counter-init"] ?? 0)
res.statusCode = 200
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(van.html(
head(
link({rel: "icon", href: "logo.svg"}),
title("SSR and Hydration Example"),
meta({name: "viewport", content: "width=device-width, initial-scale=1"}),
),
body(
script({type: "text/javascript", src: `dist/client.bundle${env === "dev" ? "" : ".min"}.js`, defer: true}),
h1("Hello Components"),
div({id: "hello-container"}, Hello()),
h1("Counter Components"),
div({id: "counter-container"},
h2("Basic Counter"),
Counter({id: "basic-counter", init: counterInit}),
h2("Styled Counter"),
p("Select the button style: ",
select({id: "button-style", value: "ππ"},
option("ππ"),
option("ππ"),
option("πΌπ½"),
option("β«β¬"),
option("ππ"),
),
),
Counter({id: "styled-counter", init: counterInit, buttonStyle: "ππ"}),
),
)
))
}).listen(Number(port), () => console.log(`Try visiting the server via http://localhost:${port}.
Also try http://localhost:${port}?counter-init=5 to set the initial value of the counters.`))
</code></pre><p>The script implements a basic HTTP server with the built-in <code class="symbol">node:http</code> module (no web framework needed). You will probably first notice this line:</p><pre><code class="language-ts">registerEnv({van})</code></pre><p>This is to tell all shared components to use the <code class="symbol">van</code> object from this file, which is imported from the <a href="/minivan#npm-van-plate" class="w3-hover-opacity"><code class="symbol">van-plate</code> mode</a> of <b>Mini-Van</b>.</p><p>Then for this line:</p><pre><code class="language-ts">if (req.url?.endsWith(".js")) return serveFile(req, res, finalhandler(req, res))</code></pre><p>It tells the HTTP server to serve the file statically if any <code class="symbol">.js</code> file is requested.</p><p>The bulk of the script is declaring the DOM structure of the page that is enclosed in <code class="language-ts">van.html(...)</code>. As you can see, the expressiveness of tag functions enable us to declare the entire HTML page, including everything in the <code class="language-html"><head></code> section and <code class="language-html"><body></code> section.</p><p>The code declares an HTML page with one <code class="symbol">Hello</code> component and two <code class="symbol">Counter</code> components - one with the default button style, and the other whose button style can be selected by the user. Here are a few interesting things to note:</p><ul><li>The line:<pre><code class="language-ts">script({type: "text/javascript", src: `dist/client.bundle${env === "dev" ? "" : ".min"}.js`, defer: true})</code></pre>indicates that we're choosing different JavaScript bundle files under different modes: <code class="symbol">client.bundle.js</code> in dev mode whereas <code class="symbol">client.bundle.min.js</code> in prod mode. It makes sense to use original client-side script during development and use the minified script in production.</li><li>We're allowing users to set the initial value of the counters via query parameters. Specifically, the line:<pre><code class="language-ts">const counterInit = Number(parse(req.url!, true).query["counter-init"] ?? 0)</code></pre>and line:<pre><code class="language-ts">Counter({van, id: "basic-counter", init: counterInit})</code></pre>enable that.</li><li>We're choosing <a href="/minivan#npm-van-plate" class="w3-hover-opacity"><code class="symbol">van-plate</code> mode</a> as SSR is done with pure text templating without any DOM manipulation. If you want some DOM manipulation for your SSR, you can choose <a href="/minivan#npm-mini-van" class="w3-hover-opacity"><code class="symbol">mini-van</code> mode</a> instead.</li></ul><h2 class="w3-xxlarge w3-text-red" id="client-side-script-csr-and-hydration"><a class="self-link" href="#client-side-script-csr-and-hydration">Client-Side Script: CSR and Hydration</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>The final step is to complete the client-side script file.</p><h3 class="w3-large w3-text-red" id="registering-the-van-object"><a class="self-link" href="#registering-the-van-object">Registering the <code class="symbol">van</code> Object</a></h3><p>First, let's register the <code class="symbol">van</code> object from <b>VanJS</b> so that it can be used by all shared components.</p><pre><code class="language-ts">registerEnv({van})</code></pre><h3 class="w3-large w3-text-red" id="client-side-component"><a class="self-link" href="#client-side-component">Client-Side Component</a></h3><p>Then, let's add a client-side component:</p><pre><code class="language-ts">van.add(document.getElementById("hello-container")!, Hello())</code></pre><p>This will append a CSR <code class="symbol">Hello</code> component right after the SSR <code class="symbol">Hello</code> component. You can tell whether the component is rendered on the server-side or on the client-side by checking whether the text is <code class="symbol">πHello (from server)</code> or <code class="symbol">πHello (from client)</code>.</p><h3 class="w3-large w3-text-red" id="hydration"><a class="self-link" href="#hydration">Hydration</a></h3><p>Next, let's hydrate the counter components rendered on the server side to add the reactivity. We can use <code class="symbol">van.hydrate</code> to achieve that:</p><pre><code class="language-ts">van.hydrate(document.getElementById("basic-counter")!, dom => Counter({
id: dom.id,
init: Number(dom.getAttribute("data-counter")),
}))
</code></pre><p><code class="symbol">van.hydrate</code> replaces the SSR component (located by <code class="language-ts">document.getElementById("basic-counter")!</code>) with the CSR <code class="symbol">Counter</code> component. Note that the 2nd argument of <code class="symbol">van.hydrate</code> is the hydration function that takes the existing DOM node as its parameter and returns the new hydrated component. This way we can get the current state of SSR component (via <code class="language-ts">Number(dom.getAttribute("data-counter"))</code>) and pass-in the information while constructing the hydrated component, which keeps the counter value the same after hydration.</p><p>In the hydration function, you can read the <code class="symbol">val</code> property of external states. In this way, the hydrated component will be a <a href="/tutorial#state-derived-child" class="w3-hover-opacity"><code class="symbol">State</code>-derived node</a>, i.e.: a DOM node that will be updated whenever its dependency states change. Now, with that, let's build a <code class="symbol">Counter</code> component whose button style can be adjusted by end users. First, let's define a state <code class="symbol">buttonStyle</code> whose <code class="symbol">val</code> is bound to the <code class="symbol">value</code> of the <code class="symbol">#button-style</code> <code class="language-html"><select></code> element:</p><pre><code class="language-ts">const styleSelectDom = <HTMLSelectElement>document.getElementById("button-style")
const buttonStyle = van.state(styleSelectDom.value)
styleSelectDom.oninput = e => buttonStyle.val = (<HTMLSelectElement>e.target).value
</code></pre><p>Next, let's make the hydrated <code class="symbol">Counter</code> component reactive to <code class="symbol">buttonStyle</code> state:</p><pre><code class="language-ts">van.hydrate(document.getElementById("styled-counter")!, dom => Counter({
van,
id: dom.id,
init: Number(dom.getAttribute("data-counter")),
buttonStyle,
}))
</code></pre><p>Since <code class="symbol">buttonStyle</code> is passed into the <code class="symbol">Counter</code> component where its <code class="symbol">val</code> property is referenced, the hydrated <code class="symbol">Counter</code> component will be reactive to the change of <code class="symbol">buttonStyle</code> state.</p><p>Note that, this is an illustrative example to show how to make the entire hydrated component reactive to external states. In practice, the implementation of <code class="symbol">Counter</code> component can be optimized to only make the <code class="language-html"><button></code>s' child text nodes of the <code class="symbol">Counter</code> component reactive to <code class="symbol">buttonStyle</code> state. This can be achieved by binding more localized DOM nodes (i.e.: the child text nodes of <code class="language-html"><button></code>s) to the <code class="symbol">buttonStyle</code> state. You can check out the implementation below for an optimized <code class="symbol">Counter</code> component:</p><pre><code class="language-ts">import { env, State } from "mini-van-plate/shared"
interface Props {
readonly id?: string
readonly init?: number
readonly buttonStyle?: string | State<string>
}
export default ({id, init = 0, buttonStyle = "ππ"}: Props) => {
const {button, div} = env.van.tags
const stateProto = Object.getPrototypeOf(env.van.state())
const val = <T>(v: T | State<T>) =>
Object.getPrototypeOf(v ?? 0) === stateProto ? (<State<T>>v).val : <T>v
const counter = env.van.state(init)
return div({...(id ? {id} : {}), "data-counter": counter},
"β€οΈ ", counter, " ",
button({onclick: () => ++counter.val}, () => [...val(buttonStyle)][0]),
button({onclick: () => --counter.val}, () => [...val(buttonStyle)][1]),
)
}
</code></pre><h3 class="w3-large w3-text-red" id="api-hydrate"><a class="self-link" href="#api-hydrate">API reference: <code class="symbol">van.hydrate</code></a></h3><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">van.hydrate(dom, f) => undefined</code></td></tr><tr><td><b>Description</b></td><td>Hydrates the SSR component <code class="symbol">dom</code> with the hydration function <code class="symbol">f</code>.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">dom</code></b> - The root DOM node of the SSR component we want to hydrate.</li><li><b><code class="symbol">f</code></b> - The hydration function, which takes a DOM node as its input parameter and returns the new version of the DOM node. The hydration function describes how we want to convert an existing DOM node into a new one with added reactivity. If the <code class="symbol">val</code> property of any states are referenced in the hydration function, the hydrated component will be bound to the dependency states (i.e.: reactive to the changes of the referenced states). In this case, the behavior of the hydrated component will be similar to a <a href="/tutorial#state-derived-child" class="w3-hover-opacity"><code class="symbol">State</code>-derived child node</a>.</li></ul></td></tr><tr><td><b>Returns</b></td><td><code class="symbol">undefined</code></td></tr></tbody></table><h2 class="w3-xxlarge w3-text-red" id="demo"><a class="self-link" href="#demo">Demo</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>Now, let's check out what we have built so far. You can preview the application via <a href="https://codesandbox.io/p/sandbox/github/vanjs-org/vanjs-org.github.io/tree/master/hydration-example?file=%2Fsrc%2Fserver.ts%3A1%2C1" class="w3-hover-opacity">CodeSandbox</a>. Alternatively, you can build and deploy application locally by following the steps below:</p><ol><li>Clone the GitHub repo:<pre><code class="language-shell">git clone https://github.com/vanjs-org/vanjs-org.github.io.git</code></pre></li><li>Go to the directory for the demo:<pre><code class="language-shell">cd vanjs-org.github.io/hydration-example</code></pre></li><li>Install NPM packages:<pre><code class="language-shell">npm install</code></pre></li><li>Launch the development server:<pre><code class="language-shell">npm run dev</code></pre>You will see something like this in the terminal:<pre><code class="language-">Try visiting the server via http://localhost:8080.
Also try http://localhost:8080?counter-init=5 to set the initial value of the counter.
</code></pre></li><li>By clicking the links printed in the terminal, you will go to the demo page.</li><li>You can build the bundle for production with:<pre><code class="language-shell">npm run build</code></pre></li></ol><p>Let's go to the demo page now. You will probably first notice the <code class="symbol">Hello</code> components of the demo:</p><img src="/code/hydration-hello-screenshot.png" width="300px"><p>You can see an SSR <code class="symbol">Hello</code> component followed by a CSR <code class="symbol">Hello</code> component.</p><p>The second part of the demo page is for hydrating the <code class="symbol">Counter</code> components. In real-world use cases, hydration typically happens immediately after the page load, or when the application is idle. But if we do that in our sample application, hydration will happen so fast that we won't even be able to notice how hydration happens. Thus, for illustration purpose, we introduce a <code class="language-html"><button></code> where hydration only happens upon user click:</p><pre><code class="language-ts">van.add(document.getElementById("counter-container")!, p(button({onclick: hydrate}, "Hydrate")))</code></pre><p>As a result, the second part of the demo will look like this:</p><img src="/code/hydration-counter-screenshot.png" width="300px"><p>You can verified that all the <code class="symbol">Counter</code> components are non-reactive before the <code class="symbol">Hydrate</code> button is clicked and can be turned reactive upon clicking the <code class="symbol">Hydrate</code> button.</p><h2 class="w3-xxlarge w3-text-red" id="fullstack-rendering-for-vanx"><a class="self-link" href="#fullstack-rendering-for-vanx">Fullstack Rendering for VanX</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>Fullstack rendering for <a href="/x" class="w3-hover-opacity"><b>VanX</b></a>-based UI components is also supported. To enable this, <code class="symbol">env</code> in <code class="symbol">mini-van-plate/shared</code> provides an abstract <code class="symbol">vanX</code> object as well, and you can register the concrete <code class="symbol">vanX</code> object via <code class="symbol">registerEnv</code> in the client-side and server-side scripts. In addition, <code class="symbol">mini-van-plate/shared</code> provides a <code class="symbol">dummyVanX</code> object which allows you to register <code class="symbol">vanX</code> on the server-side.</p><p>A sample application (a <b>VanX</b>-based TODO list) can be found <a href="https://github.com/vanjs-org/vanjs-org.github.io/tree/master/hydration-example2" class="w3-hover-opacity">here</a> (preview via <a href="https://codesandbox.io/p/sandbox/github/vanjs-org/vanjs-org.github.io/tree/master/hydration-example2?file=%2Fsrc%2Fserver.ts%3A1%2C1" class="w3-hover-opacity">CodeSandbox</a>).</p><h2 class="w3-xxlarge w3-text-red" id="the-end"><a class="self-link" href="#the-end">The End</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>π Congratulations! You have completed the walkthrough for fullstack rendering. With the knowledge you have learned, you will be able to build sophisticated applications that take advantage of SSR, CSR and hydration.</p></div>
<aside id="toc"><ul><li><a href="#package-lock-json-file" class="w3-hover-opacity">package-lock.json File</a></li><li><a href="#shared-ui-components" class="w3-hover-opacity">Shared UI Components</a><ul><li><a href="#static-component" class="w3-hover-opacity">Static Component</a></li><li><a href="#reactive-component" class="w3-hover-opacity">Reactive Component</a></li></ul></li><li><a href="#server-side-script-html-template" class="w3-hover-opacity">Server-Side Script: HTML Template</a></li><li><a href="#client-side-script-csr-and-hydration" class="w3-hover-opacity">Client-Side Script: CSR and Hydration</a><ul><li><a href="#registering-the-van-object" class="w3-hover-opacity">Registering the van Object</a></li><li><a href="#client-side-component" class="w3-hover-opacity">Client-Side Component</a></li><li><a href="#hydration" class="w3-hover-opacity">Hydration</a></li><li><a href="#api-hydrate" class="w3-hover-opacity">API reference: van.hydrate</a></li></ul></li><li><a href="#demo" class="w3-hover-opacity">Demo</a></li><li><a href="#fullstack-rendering-for-vanx" class="w3-hover-opacity">Fullstack Rendering for VanX</a></li><li><a href="#the-end" class="w3-hover-opacity">The End</a></li></ul></aside>
</div>
</div>
<script>
// Script to open and close sidebar
const w3_open = () => {
document.getElementById("mySidebar").style.display = "block"
document.getElementById("myOverlay").style.display = "block"
}
const w3_close = () => {
document.getElementById("mySidebar").style.display = "none"
document.getElementById("myOverlay").style.display = "none"
}
const tocDom = document.getElementById("toc")
// Tracks the current toc item
const trackToc = () => {
const allHeadings = [...document.querySelectorAll("h2,h3")]
const currentHeadingIndex = allHeadings.findIndex(h => h.getBoundingClientRect().top >= 0)
let currentHeading
if (currentHeadingIndex < 0) currentHeading = allHeadings[allHeadings.length - 1]; else {
currentHeading = allHeadings[currentHeadingIndex]
if (currentHeadingIndex > 0 && currentHeading.getBoundingClientRect().top > innerHeight)
currentHeading = allHeadings[currentHeadingIndex - 1]
}
for (const e of document.querySelectorAll("#toc li a"))
if (e.href.split("#")[1] === currentHeading?.id) {
e.classList.add("current-heading")
const {top: tocTop, bottom: tocBottom} = tocDom.getBoundingClientRect()
const {top: eTop, bottom: eBottom} = e.getBoundingClientRect()
if (eBottom > tocBottom) tocDom.scrollTop += eBottom - tocBottom
else if (eTop < tocTop) tocDom.scrollTop -= tocTop - eTop
} else
e.classList.remove("current-heading")
}
trackToc()
document.addEventListener("scroll", trackToc)
addEventListener("resize", trackToc)
const copy = e => {
const file = e.previousElementSibling.innerText
const importLine = file.includes("nomodule") ?
`<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/${file}"><\/script>` :
`import van from "https://cdn.jsdelivr.net/gh/vanjs-org/van/public/${file}"`
navigator.clipboard.writeText(importLine)
.then(() => e.querySelector(".tooltip").innerText = "Copied")
.catch(() => e.querySelector(".tooltip").innerText = "Copy failed")
}
const resetTooltip = e => e.querySelector(".tooltip").innerText = "Copy import line"
</script>
<!-- Place this tag in your head or just before your close body tag. -->
<script async defer src="https://buttons.github.io/buttons.js"></script>
<link rel="prefetch" href="/code/prism-v1.js" as="script"><link rel="prefetch" href="https://www.gstatic.com/charts/loader.js" as="script"><link rel="prefetch" href="/code/diff.min.js" as="script"><link rel="prefetch" href="/code/van-1.5.2.nomodule.min.js" as="script"><link rel="prefetch" href="/code/van-x-0.6.1.nomodule.min.js" as="script"></body></html>