first commit
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
// A page written in the .wrn language. Route: /hello
|
||||
//
|
||||
// SSR:
|
||||
// `view` compiles to server-rendered HTML and is returned on the first request.
|
||||
//
|
||||
// CSR:
|
||||
// `state`, `{expr}`, and `@click` compile to hydrated behavior.
|
||||
// The browser hydrates those directives with /__wrnexus/reactive.js.
|
||||
page Hello {
|
||||
layout = "public"
|
||||
|
||||
// Client state seed. Without this block the page is pure SSR.
|
||||
state count = 0
|
||||
|
||||
seo {
|
||||
title = "Hello from .wrn"
|
||||
description = "A WrNexus .wrn page showing SSR data, CSR hydration, cookies, sessions, and localStorage."
|
||||
canonical = "/hello"
|
||||
}
|
||||
|
||||
// SSR data bindings run on the server before the HTML is sent.
|
||||
// The API itself lives in app/api/users/ssr.ts; this block only calls it
|
||||
// and renders the response into HTML.
|
||||
ssr {
|
||||
functions {
|
||||
function userNames(users) {
|
||||
return users.map((user) => user.name).join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
api ssrUsers GET /api/users/ssr {
|
||||
const visits = Number(cookies.get("hello_visits") ?? "0") + 1
|
||||
cookies.set("hello_visits", String(visits), { sameSite: "Lax" })
|
||||
session.set("lastHelloVisit", visits)
|
||||
return `${userNames(users)} - visit ${visits}`
|
||||
}
|
||||
}
|
||||
|
||||
// Client data bindings hydrate after the first paint. The browser only sees
|
||||
// an opaque data-wrnexus-csr id; WrNexus calls app/api/users/csr.ts on the server.
|
||||
client {
|
||||
functions {
|
||||
function userNames(users) {
|
||||
return users.map((user) => user.name).join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
api csrUsers GET /api/users/csr {
|
||||
const label = localStorage.get("wrnexus.label") ?? "browser"
|
||||
session.set("lastClientLabel", label)
|
||||
return `${userNames(users)} - ${label}`
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<!-- These elements are rendered on the server first. -->
|
||||
<h1>Hello from .wrn</h1>
|
||||
|
||||
<!-- `{count}` and `{count * 2}` update in the browser after hydration. -->
|
||||
<p>Hello, WrNexus. Count is {count}, doubled is {count * 2}.</p>
|
||||
|
||||
<div class="data-grid">
|
||||
<div class="data-panel">
|
||||
<h2>SSR API data</h2>
|
||||
<p>This is fetched from app/api before HTML is sent.</p>
|
||||
<div class="user-list" api="ssrUsers">Loading SSR users...</div>
|
||||
</div>
|
||||
|
||||
<div class="data-panel">
|
||||
<h2>CSR API data</h2>
|
||||
<p>This is fetched from app/api after hydration.</p>
|
||||
<div class="user-list" api="csrUsers">Loading CSR users...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- `@click` becomes data-on-click and runs against the client scope. -->
|
||||
<div class="my-actions">
|
||||
<button @click="count++">Increment</button>
|
||||
<button @click="count = 0">Reset</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
// Page-local CSS is inlined with the server-rendered HTML.
|
||||
style {
|
||||
h1 {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.my-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.data-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.data-panel {
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.data-panel h2 {
|
||||
font-size: 1rem;
|
||||
margin: 0 0 0.5rem;
|
||||
}
|
||||
|
||||
.user-list {
|
||||
font-weight: 700;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.my-actions button {
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 0.45rem 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
realtime hello {
|
||||
on message(data) {
|
||||
console.log(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user