Code to Browser
This guide follows one small Resux component line by line: from the code you write, through compilation and server rendering, to the exact work the browser performs after a click.
The generated snippets below are conceptual and simplified. Internal module names, payload fields, and DOM marker attributes may change. Use them to understand the flow, not as public APIs.
The complete example
<script setup lang="ts">
const count = useState('counter', () => 0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<section>
<p>Count: {{ count }}</p>
<p>Double: {{ doubled }}</p>
<button @click="increment" :disabled="count >= 10">
Add
</button>
</section>
</template>Line-by-line map
| Authored line | Compiler meaning | Server work | Browser work |
|---|---|---|---|
useState('counter', () => 0) | Create named, component-scoped resumable state | Create the ref for this rendered scope and serialize its JSON-safe value | Restore the same scoped ref when the component resumes |
computed(() => count.value * 2) | Create derived reactive state | Evaluate it when the template reads it | Re-evaluate it only after a dependency changes |
function increment() | Register a browser-safe handler candidate | Keep handler metadata; do not run it | Import and run it after the matching click |
| Create a dynamic text binding | Render the initial text | Patch only this text when count changes |
| Create another dynamic text binding | Render the initial derived value | Patch it after count invalidates the computed value |
@click="increment" | Normalize the shortcut to Resux event metadata | Render a resumable handler marker | A shared delegated listener finds the marker |
:disabled="count >= 10" | Normalize the shortcut to a dynamic attribute binding | Render the initial disabled state | Add or remove the attribute after updates |
Step 1: parse the SFC
Resux uses Vue compiler packages to parse the .vue file, but the normal component does not become a hydrated Vue application.
The compiler separates:
- the setup script,
- the template tree,
- event handlers,
- dynamic expressions,
- styles,
- and page metadata when the file is a page.
The concise template syntax is normalized:
<button @click="increment" :disabled="count >= 10">means the same Resux operations as:
<button rx-on:click="increment" rx-bind:disabled="count >= 10">@event and :binding are authoring shortcuts. The compiler stores one normalized event and binding model after parsing.
Step 2: create the compiled component model
The real generated source is optimized and may change. Conceptually, Resux records information similar to this:
{
id: 'components/counter',
setup: serverSetupFunction,
template: [
{ type: 'text-binding', expression: 'count' },
{ type: 'text-binding', expression: 'doubled' },
{ type: 'event', name: 'click', handler: 'increment' },
{ type: 'attribute-binding', name: 'disabled', expression: 'count >= 10' }
],
browserHandlers: {
increment: 'generated handler module'
}
}Three design choices matter here:
- Static HTML remains static and needs no browser expression.
- Dynamic bindings receive stable internal identifiers so they can be found later.
- Handler code is separated so it can be imported on demand instead of joining one large page bundle.
Step 3: run setup on the server
For the initial request, Resux creates a component scope and executes setup.
const count = useState('counter', () => 0)The key belongs to this rendered component scope. The same key used by another component instance does not overwrite it.
const doubled = computed(() => count.value * 2)The computed value is lazy. It is evaluated because the server template reads it, and its value is cached until count changes.
The handler is defined, but no click has happened, so the server does not run it.
Step 4: render HTML
The server evaluates the template with count = 0 and doubled = 0.
A simplified result looks like this:
<section>
<p data-resux-binding="count">Count: 0</p>
<p data-resux-binding="doubled">Double: 0</p>
<button
data-resux-handler="increment"
data-resux-binding="disabled"
>
Add
</button>
</section>The data-resux-* names above are explanatory placeholders. The important idea is that the browser receives usable HTML plus small markers for the dynamic parts. It does not receive an empty shell that must wait for a full application bundle before showing content.
Step 5: serialize only resumable data
Because count uses useState, its current value crosses the server/browser boundary.
A simplified scope payload looks like this:
{
"scopes": {
"s0": {
"module": "components/counter",
"state": {
"counter": 0
}
}
}
}Only JSON-compatible values belong in resumable state. Do not place functions, DOM nodes, class instances, sockets, streams, database clients, Map, or Set in useState or useGlobalState.
Step 6: boot without full hydration
On page load, the browser runtime:
- reads the Resux payload,
- installs shared delegated event listeners,
- prepares routing and client plugins,
- and leaves handler modules unloaded until they are needed.
The browser does not walk the whole page to recreate a hydrated Vue component tree.
Step 7: resume after the click
When the user clicks Add:
- the shared click listener receives the event,
- the event path is checked for Resux handler metadata,
- the generated handler module is imported if it is not cached,
- scope
s0is reconstructed from the payload, increment()runs,count.valuechanges from0to1,doubledbecomes dirty,- dependent bindings are scheduled for update.
Conceptually, only this authored line performs the mutation:
count.value++The reactivity graph determines everything that depends on it.
Step 8: patch only affected DOM
Resux re-evaluates the marked expressions:
count // 1
count * 2 // 2
count >= 10 // falseThe resulting DOM becomes:
<p>Count: 1</p>
<p>Double: 2</p>
<button>Add</button>Static elements are not rebuilt. Unrelated components do not rerender. This is the performance goal of resumability plus fine-grained reactivity.
Choose the smallest state tool
The counter uses useState to make the serialization step visible. In application code, choose the smallest scope that satisfies the requirement.
| Requirement | Use |
|---|---|
| One ordinary local value | ref |
| Related local fields or an object | reactive |
| A value derived from reactive state | computed |
| Named component state that must be serialized and resumed | useState |
| One intentionally shared value across component scopes | useGlobalState |
| Server data with pending and error state | useAsyncData or useFetch |
Do not use useState as a default replacement for ref. Do not use useGlobalState for a button, modal, tab, or form field owned by one component.
Follow the flow while debugging
Use this order when a value is correct on the server but wrong after interaction:
- Confirm the authored expression and handler use supported syntax.
- Check compiler diagnostics and source locations.
- Confirm the value is JSON-compatible when it crosses the boundary.
- Inspect the rendered HTML for a dynamic binding or handler marker.
- Run
resux dev --trace-resumeto follow handler loading and scope restoration. - Check that the handler mutates a tracked ref or reactive property.
- Confirm cleanup is returned for timers, observers, listeners, and client enhancements.
Continue with Rendering Lifecycle, Resumability and Handlers, State and Reactivity, and the Compiler Reference.