Skip to content

WIP: Docs rewrite #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions guides/advanced_features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Advanced Features

This guide covers advanced LiveVue features and customization options.

## Using ~V Sigil

The `~V` sigil provides an alternative to the standard LiveView DSL, allowing you to write Vue components directly in your LiveView:

```elixir
defmodule MyAppWeb.CounterLive do
use MyAppWeb, :live_view

def render(assigns) do
~V"""
<script setup lang="ts">
import {ref} from "vue"
const props = defineProps<{count: number}>()
const diff = ref<number>(1)
</script>

<template>
Current count: {{ props.count }}
<input v-model="diff" type="range" min="1" max="10">
<button
phx-click="inc"
:phx-value-diff="diff"
class="button">
Increment by {{ diff }}
</button>
</template>
"""
end

def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0)}
end

def handle_event("inc", %{"diff" => diff}, socket) do
{:noreply, update(socket, :count, &(&1 + String.to_integer(diff)))}
end
end
```

## Lazy Loading Components

Enable lazy loading by returning a function that returns a promise in your components configuration:

```javascript
// assets/vue/index.js
const components = {
Counter: () => import("./Counter.vue"),
Modal: () => import("./Modal.vue")
}

// Using Vite's glob import
const components = import.meta.glob(
'./components/*.vue',
{ eager: false, import: 'default' }
)
```

When SSR is enabled, related JS and CSS files will be automatically preloaded in HTML.

## Customizing Vue App Instance

You can customize the Vue app instance in `assets/vue/index.js`:

```javascript
import { createPinia } from "pinia"
const pinia = createPinia()

export default createLiveVue({
setup: ({ createApp, component, props, slots, plugin, el, ssr }) => {
const app = createApp({ render: () => h(component, props, slots) })
app.use(plugin)
app.use(pinia) // Add your plugins

if (ssr) {
// SSR-specific initialization
}

app.mount(el)
return app
}
})
```

Available setup options:

| Property | Description |
|------------|------------------------------------------------|
| createApp | Vue's createApp or createSSRApp function |
| component | The Vue component to render |
| props | Props passed to the component |
| slots | Slots passed to the component |
| plugin | LiveVue plugin for useLiveVue functionality |
| el | Mount target element |
| ssr | Boolean indicating SSR context |

## Server-Side Rendering (SSR)

LiveVue provides two SSR strategies:

### Development (ViteJS)
```elixir
# config/dev.exs
config :live_vue,
ssr_module: LiveVue.SSR.ViteJS
```
Uses Vite's ssrLoadModule for efficient development compilation.

### Production (NodeJS)
```elixir
# config/prod.exs
config :live_vue,
ssr_module: LiveVue.SSR.NodeJS
```
Uses elixir-nodejs for optimized production SSR with an in-memory server bundle.

### SSR Performance

Vue SSR is compiled into string concatenation for optimal performance. The SSR step:
- Only runs during "dead" renders
- Skips during live navigation
- Can be disabled per-component with `v-ssr={false}`

## Client-Side Hooks

Access Phoenix hooks from Vue components using `useLiveVue`:

```vue
<script setup>
import {useLiveVue} from "live_vue"

const hook = useLiveVue()

// Access all Phoenix hook methods
hook.pushEvent("hello", {value: "world"})
hook.handleEvent("response", (payload) => {
console.log(payload)
})
</script>
```

## TypeScript Support

LiveVue provides full TypeScript support:

1. Use the example tsconfig.json from the example project
2. Check `example_project/assets/ts_config_example` for TypeScript versions of:
- LiveVue entrypoint file
- Tailwind configuration
- Vite configuration

For app.js TypeScript support:
```javascript
// app.js
import {initApp} from './app.ts'
initApp()
```

## Next Steps

- Check out the [FAQ](faq.html) for implementation details and optimization tips
- Visit the [Deployment Guide](deployment.html) for production setup
- Join our [GitHub Discussions](https://github.com/Valian/live_vue/discussions) for questions and ideas
193 changes: 193 additions & 0 deletions guides/basic_usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Basic Usage

This guide covers the fundamental patterns for using Vue components within LiveView.

## Component Organization

By default, Vue components should be placed in either:
- `assets/vue` directory
- Colocated with your LiveView files in `lib/my_app_web`

You can configure these paths by:
1. Modifying `assets/vue/index.js`
2. Adjusting the LiveVue.Components configuration:
```elixir
use LiveVue.Components, vue_root: ["your/vue/dir"]
```

## Rendering Components

### Basic Syntax

To render a Vue component from HEEX, use the `<.vue>` function:

```elixir
<.vue
v-component="Counter"
v-socket={@socket}
count={@count}
/>
```

### Required Attributes

| Attribute | Example | Required | Description |
|--------------|------------------------|-----------------|------------------------------------------------|
| v-component | `v-component="Counter"`| Yes | Component name or path relative to vue_root |
| v-socket | `v-socket={@socket}` | Yes in LiveView| Required for SSR and reactivity |

### Optional Attributes

| Attribute | Example | Description |
|--------------|----------------------|------------------------------------------------|
| v-ssr | `v-ssr={true}` | Override default SSR setting |
| v-on:event | `v-on:inc={JS.push("inc")}` | Handle Vue component events |
| prop={@value}| `count={@count}` | Pass props to the component |

### Component Shortcut

Instead of writing `<.vue v-component="Counter">`, you can use the shortcut syntax:

```elixir
<.Counter
count={@count}
v-socket={@socket}
/>
```

Function names are generated based on `.vue` file names. For files with identical names, use the full path:
```elixir
<.vue v-component="helpers/nested/Modal" />
```

## Passing Props

Props can be passed in three equivalent ways:

```elixir
# Individual props
<.vue
count={@count}
name={@name}
v-component="Counter"
v-socket={@socket}
/>

# Map spread
<.vue
v-component="Counter"
v-socket={@socket}
{%{count: @count, name: @name}}
/>

# Using shortcut
<.Counter
count={@count}
name={@name}
v-socket={@socket}
/>
```

## Handling Events

### Phoenix Events

All standard Phoenix event handlers work inside Vue components:
- `phx-click`
- `phx-change`
- `phx-submit`
- etc.

### Vue Events

For Vue-specific events, use the `v-on:` syntax:

```elixir
<.vue
v-on:submit={JS.push("submit")}
v-on:close={JS.hide()}
v-component="Form"
v-socket={@socket}
/>
```

Special case: When using `JS.push()` without a value, it automatically uses the emit payload:
```elixir
# In Vue
emit('inc', {value: 5})

# In LiveView
<.vue v-on:inc={JS.push("inc")} />
# Equivalent to: JS.push("inc", value: 5)
```

## Slots Support

Vue components can receive slots from LiveView templates:

```elixir
<.Card title="Example Card" v-socket={@socket}>
<p>This is the default slot content!</p>
<p>Phoenix components work too: <.icon name="hero-info" /></p>

<:footer>
This is a named slot
</:footer>
</.Card>
```

```vue
<template>
<div>
<!-- Default slot -->
<slot></slot>

<!-- Named slot -->
<slot name="footer"></slot>
</div>
</template>
```

Important notes about slots:
- Each slot is wrapped in a div (technical limitation)
- Slots are passed as raw HTML
- Phoenix hooks in slots won't work
- Slots stay reactive and update when their content changes

## Dead Views vs Live Views

Components can be used in both contexts:
- Live Views: Full reactivity with WebSocket updates
- Dead Views: Static rendering, no reactivity
- `v-socket={@socket}` not required
- SSR still works for initial render

## Client-Side Hooks

Access Phoenix hooks from Vue components using `useLiveVue`:

```vue
<script setup>
import {useLiveVue} from "live_vue"

const hook = useLiveVue()
hook.pushEvent("hello", {value: "from Vue"})
</script>
```

The hook provides all methods from [Phoenix.LiveView JS Interop](https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook).

## Next Steps

Now that you understand the basics, you might want to explore:

- [Advanced Features](advanced_features.html) to learn about:
- Using the `~V` sigil for inline Vue components
- Lazy loading components
- Customizing the Vue app instance
- SSR configuration and optimization
- [FAQ](faq.html) for:
- Understanding how LiveVue works under the hood
- Performance optimizations
- TypeScript setup
- Comparison with LiveSvelte
Loading