# Architecture
PhoenixVapor compiles Vue template syntax into native LiveView rendered trees. Four progressive modes share a common foundation: Vue/Vapor templates and LiveView rendered diffs have the same statics/dynamics shape.
## Core: Statics/Dynamics Split
A Vue template compiles into a **split** — static HTML fragments and dynamic insertion points:
```
Template: <div class="card"><h1>{{ title }}</h1><p>{{ body }}</p></div>
Statics: ["<div class=\"card\"><h1>", "</h1><p>", "</p></div>"]
Dynamics: [title, body]
```
This maps directly to `%Phoenix.LiveView.Rendered{}`:
- Statics sent once per fingerprint, cached by the client
- Only changed dynamics travel the wire on updates
- `v-if` → nested `%Rendered{}`
- `v-for` → `%Comprehension{}`
## Expression Evaluation
Template expressions are evaluated against LiveView assigns:
- Simple (`{{ count }}`, `item.name`) → Elixir map access via OXC AST
- Complex (`.filter()`, `.map()`, arrow functions) → QuickBEAM JS eval
- Change tracking: each slot knows which assigns it depends on (compile-time AST analysis)
## Mode 1: `~VUE` Sigil
Vue syntax as a template DSL. Zero client JS.
```
Template → Vize.vapor_split! → %Rendered{} → LiveView diff → morphdom
```
Expressions evaluate in Elixir. Events map to `phx-click`, `phx-submit`, etc. The browser runs the standard LiveView client — it doesn't know Vue exists.
## Mode 2: Reactive (`.vue` SFC)
Server-side Vue reactivity via QuickBEAM. Zero client JS.
```
SFC → ScriptSetup.parse → Runtime (GenServer + QuickBEAM)
→ Template → %Rendered{} with reactive state from JS runtime
```
A persistent QuickBEAM context per LiveView holds `ref()` values, `computed()` definitions, and function handlers. Vue's `@vue/reactivity` runs server-side on the BEAM. State survives across events but resets on process restart.
## Mode 3: Hybrid (`.vue` SFC)
Split reactivity — server owns data, client owns UI state.
```
SFC → Classifier (AST analysis)
→ Server: mount/render/handle_event (Elixir)
→ Client: Vue 3 component (JS, ~50KB)
→ Bridge: LiveView hook syncs props via data attributes
```
The compiler analyzes `<script setup>` and classifies each binding:
| Pattern | Classification |
|---------|---------------|
| `defineProps(["x"])` | Server prop |
| `ref(value)` | Client ref |
| `computed` using any prop | Mixed computed |
| Function with `"use server"` | Server action |
| Function writing to a prop | Server action (auto-detected) |
| Function writing only to refs | Client handler |
Server renders full HTML for first paint (SEO). Client hydrates with Vue 3 `createApp`, taking over reactive slots. Client interactions (search, sort, select) are instant — zero network. Server actions send events over the existing LiveView WebSocket.
### Wire Protocol
Initial render: server sends statics + dynamics + props JSON in `data-pv-props` attribute. Updates: only changed server slots + updated props JSON travel the wire. Client-only changes (typing in search) produce zero wire traffic.
### State Sync
- **Server → Client**: LiveView assign changes → re-render → diff with props JSON → hook's `updated()` → `__applyProps()` → Vue reactivity propagates
- **Client → Server**: `"use server"` function → `pushEvent` → `handle_event` → assign change → back to step 1
- **Client → Client**: `ref` mutation → computed recomputation → Vue re-render. No wire.
### Custom Elixir Code
A hybrid module is a standard LiveView. The `use PhoenixVapor` macro generates `render/1` and fallback `handle_event/3` stubs (via `@before_compile`) — everything else is yours to define. User-defined `handle_event` clauses take precedence over generated fallbacks.
The `"use server"` directive in the `.vue` file serves two purposes:
1. Tells the client codegen to generate a `pushEvent` call for that function name
2. Registers the event name so a fallback `handle_event` is generated if the developer doesn't write one
The developer writes the actual server logic in Elixir:
```elixir
def handle_event("deleteContact", %{"id" => id}, socket) do
Repo.delete!(Contact, id)
{:noreply, assign(socket, contacts: Repo.all(Contact))}
end
```
All standard LiveView callbacks work: `mount/3`, `handle_info/2`, `handle_params/3`, `terminate/2`. PubSub subscriptions, presence, streams — the full LiveView toolkit is available.
## Mode 4: Full Vue Runtime
Third-party Vue component libraries rendered server-side in QuickBEAM.
```
SFC + bundle → VueRuntime (GenServer + QuickBEAM + lexbor DOM)
→ HTML string → %Rendered{} → LiveView diff
```
Full Vue semantics: `provide`/`inject`, component composition, ARIA attributes. Used for libraries like Reka UI.
## Module Map
### Template / Render
- `PhoenixVapor` — main entry, `__using__` macro
- `PhoenixVapor.Sigil` — `~VUE` sigil
- `PhoenixVapor.Renderer` — Vapor IR → `%Rendered{}`
- `PhoenixVapor.Expr` — JS expression evaluation in Elixir
- `PhoenixVapor.Component` — component helpers
- `PhoenixVapor.Vue` — `.vue` file loading
### Reactive
- `PhoenixVapor.Reactive` — server-side reactivity implementation
- `PhoenixVapor.Runtime` — QuickBEAM GenServer for reactive state
- `PhoenixVapor.ScriptSetup` — `<script setup>` parsing
### Hybrid
- `PhoenixVapor.Hybrid` — split reactivity implementation
- `PhoenixVapor.Hybrid.Classifier` — binding classification via AST
- `PhoenixVapor.Hybrid.ServerCodegen` — Elixir code generation
- `PhoenixVapor.Hybrid.ClientCodegen` — Vue 3 JS generation
### Full Runtime
- `PhoenixVapor.LiveVue` — full Vue runtime (runtime: :full)
- `PhoenixVapor.VueRuntime` — QuickBEAM GenServer for full Vue
### Client JS
- `priv/js/hybrid-bridge.js` — LiveView hook for hybrid mode
- `priv/js/vue-reactivity.js` — `@vue/reactivity` for server-side reactive mode
- `priv/js/runtime-setup.js` — QuickBEAM reactive runtime bootstrap