v1.6.0
The big one: Weave renders to HTML at build time, and the browser resumes it instead of rebuilding it.
Everything here is opt-in and additive — a normal weave build is byte-for-byte what it was, and no existing
code changes.
✨ Features
-
weave build --ssg— static generation. Every route renders to real HTML at build time: painted on
arrival, crawlable, and served as plain files (no server at request time). Routes are derived automatically,
each page gets its own<title>, and each is its own chunk — a reader downloads the page they opened, not
your whole site. On our own documentation site that is 1555.7 KB → 169.7 KB of transferred payload for a
single page, measured in the browser. -
ssg: { resume: true }— resumability. With--ssgalone the browser client-renders over the
prerendered HTML; add this and it adopts it instead. The build snapshots the reactive graph — every
signal's value, per component instance — into an inline<script type="application/weave">; the client
rebuilds the signals and attaches the existing DOM to them.setup()is never called on the client.
The nodes the server wrote are the nodes you keep, down to the same text node. Re-running means paying for
the render twice — once to produce the HTML, once to discover what it already says. Resuming pays once. -
Data prerenders with the page. A
resource()that fetches during a build is awaited before the HTML is
written, and its result travels in the snapshot. No spinner on first paint, and no second request for data
the build already has. -
lazy()prerenders too. A lazily-imported component still renders its real HTML into the page, and still
stays out of everyone else's bundle. Lazy means "not in your bundle", not "not in your HTML" — so there is no
trade between a complete first paint and a small download. -
Resume covers what real components actually do: nested components,
<slot>, elementrefs (re-bound
from the adopted DOM rather than serialized),use:actions,@if/@for/@switch/@key/@render/
@snippet, routed views, component-levelon:handlers,props, module-scope bindings,effect()and
onMount()insetup(). -
It tells you what it cannot resume, at build time. A value that cannot cross the wire (a live socket, a
class instance) makes that component client-render — with a warning that names the binding, the file and the
cause, never a silent degradation.weave build --ssgalso warns for a handler that won't inline or a
computed that can't be rebuilt. -
<Timepicker>,<Select>,<Datepicker>,<DateRangePicker>use lucide icons instead of hand-drawn
glyphs, matching the rest of the library.
🐛 Fixes
-
--ssg+resumewas broken for every multi-root app — including ours. The client entry handed the
adopt walkfirstElementChild, which is right for a single-root component and wrong for a multi-root one,
whose roots are the mount target's children. It threw on its first step, silently, and the page stayed
inert server HTML. Nothing on our documentation site had ever resumed. The compiler now publishes the
contract (adopt.container) rather than the caller guessing it, and a root that cannot adopt client-renders
outright instead of arming handlers over DOM nobody adopted. -
A bare
effect()insetup()is re-created on resume. It binds no name, so nothing rebuilt it: a
per-routedocument.titleeffect stayed frozen at the server's value forever. -
An
onMount()insetup()resumes. An earlier build refused to adopt any component that had one; that
refusal was wrong (the hook is re-created, exactly as an effect is) and it was also the more expensive
answer, since client-rendering re-runssetup()and fires the hook anyway. -
onMountis inert during a build by construction — there is no browser at build time, and it no longer
depends on the render happening to be synchronous to stay that way. -
Regex literals, comments, type annotations, casts, destructuring, generics and shadowed declarations are
no longer misread as variable references by the compiler's setup analysis. Each of these quietly narrowed
what could resume.
🔬 Under the hood
- CI. This repository had none: the test suite, typecheck, lint and size budgets ran only when someone
remembered. Three jobs now run on every push, includingverify:resume— a real app, built by the real CLI,
resumed and clicked in a real browser. - Size budgets are enforced (
verify:size). The SPA core is 21.2 KB gzipped; resume, adopt and serialize
sit on their own budget lines and cost a SPA-only app zero bytes. - Zero third-party runtime dependencies, unchanged.