README.md

# PhoenixVapor

Vue templates as native Phoenix LiveView rendered structs.

Compiles Vue template syntax to `%Phoenix.LiveView.Rendered{}` via
[Vize](https://github.com/elixir-volt/vize_ex)'s Vapor IR — no JavaScript
runtime needed for template-only components.

## How it works

```
.vue template ──→ Vize.vapor_ir/1 ──→ Elixir IR maps ──→ %Rendered{} struct
                    (Rust NIF)         (Elixir maps)       (LiveView native)
```

1. **Vize** compiles the Vue template to Vapor intermediate representation (IR)
   as native Elixir maps — entirely in Rust, no JS execution
2. **PhoenixVapor** walks the IR and produces `%Rendered{}` structs with proper
   `static`/`dynamic`/`fingerprint` fields
3. The `%Rendered{}` struct participates in LiveView's diff engine — same
   WebSocket, same protocol, same client

No wrapper div. No `phx-update="ignore"`. No JSON props. Vue templates become
first-class LiveView rendered output with per-assign change tracking.

## Usage

### Sigil

```elixir
import PhoenixVapor.Sigil

def render(assigns) do
  ~VUE"""
  <div :class="status">
    <h1>{{ title }}</h1>
    <ul>
      <li v-for="item in items">{{ item.name }}</li>
    </ul>
    <p v-if="showFooter">{{ footerText }}</p>
  </div>
  """
end
```

The template compiles to Vapor IR at **compile time**. At runtime, only the
IR-to-Rendered transformation runs — a fast Elixir data walk.

### Programmatic

```elixir
# Compile once (at compile time or startup)
@ir Vize.vapor_ir!("<div :class=\"status\"><h1>{{ title }}</h1></div>")

# Render against assigns (at runtime)
def render(assigns) do
  PhoenixVapor.render(@ir, assigns)
end
```

### Runtime (for dynamic templates)

```elixir
PhoenixVapor.render("<div>{{ msg }}</div>", %{msg: "Hello"})
```

## Supported Vue features

- `{{ }}` text interpolation with HTML escaping
- `:attr` dynamic attribute binding
- `v-if` / `v-else-if` / `v-else` conditional rendering
- `v-for` list rendering
- Mixed static and dynamic attributes
- Nested elements and deeply nested text
- Multiple interpolations in a single text node
- Dot-access expression resolution (`user.name`, `item.id`)
- Per-assign change tracking (LiveView only sends diffs for changed values)

## Dependencies

- [Vize](https://hex.pm/packages/vize) — Vue compiler as Rust NIF
- [Phoenix LiveView](https://hex.pm/packages/phoenix_live_view) — `%Rendered{}` struct definitions

## Installation

```elixir
def deps do
  [
    {:phoenix_vapor, "~> 0.1.0"},
  ]
end
```