Cleanup: Remove old modes, add Svelte 5 reference library, update orchestrator
- Delete Mode1Test, Mode2Test, Mode3Test, Mode5Test (keep only Mode6Test) - Delete old documentation, Docker files, and k8s configs - Add comprehensive Svelte 5 reference library (svelte-reference/) - Update ModeSwitch orchestrator: - Fix port to 3005 (never use 3000/3001) - Add API endpoints for mode switching - Add bun-types and proper tsconfig - Clean up mode folder mappings
This commit is contained in:
@@ -0,0 +1,439 @@
|
||||
# Svelte 5 Runes Complete Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Runes are Svelte 5's new reactivity system. They replace the old `$:` reactive statements and `export let` declarations with explicit, predictable primitives.
|
||||
|
||||
---
|
||||
|
||||
## 📦 `$state` - Reactive State
|
||||
|
||||
### Basic Usage
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let count = $state(0);
|
||||
let user = $state({ name: 'John', age: 30 });
|
||||
let items = $state<string[]>([]);
|
||||
</script>
|
||||
|
||||
<button onclick={() => count++}>
|
||||
Clicked {count} times
|
||||
</button>
|
||||
```
|
||||
|
||||
### Deep Reactivity
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
// Objects and arrays are deeply reactive
|
||||
let todos = $state([
|
||||
{ id: 1, text: 'Learn Svelte', done: false }
|
||||
]);
|
||||
|
||||
function toggle(id: number) {
|
||||
const todo = todos.find(t => t.id === id);
|
||||
if (todo) todo.done = !todo.done; // This triggers updates!
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### `$state.raw` - Shallow Reactivity
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
// Use for large objects where you replace, not mutate
|
||||
let data = $state.raw<BigObject | null>(null);
|
||||
|
||||
async function load() {
|
||||
data = await fetchData(); // Must reassign, not mutate
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### `$state.snapshot` - Get Plain Object
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let user = $state({ name: 'John' });
|
||||
|
||||
function save() {
|
||||
// Get a plain, non-reactive copy
|
||||
const plain = $state.snapshot(user);
|
||||
localStorage.setItem('user', JSON.stringify(plain));
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 `$derived` - Computed Values
|
||||
|
||||
### Basic Derived
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let count = $state(0);
|
||||
let doubled = $derived(count * 2);
|
||||
let quadrupled = $derived(doubled * 2);
|
||||
</script>
|
||||
|
||||
<p>{count} × 2 = {doubled}</p>
|
||||
<p>{doubled} × 2 = {quadrupled}</p>
|
||||
```
|
||||
|
||||
### Complex Derived with `$derived.by`
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let items = $state([1, 2, 3, 4, 5]);
|
||||
let filter = $state('all');
|
||||
|
||||
// Use $derived.by for complex computations
|
||||
let filtered = $derived.by(() => {
|
||||
console.log('Recomputing filtered list...');
|
||||
|
||||
if (filter === 'even') {
|
||||
return items.filter(n => n % 2 === 0);
|
||||
}
|
||||
if (filter === 'odd') {
|
||||
return items.filter(n => n % 2 !== 0);
|
||||
}
|
||||
return items;
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Derived from Multiple Sources
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let firstName = $state('John');
|
||||
let lastName = $state('Doe');
|
||||
|
||||
let fullName = $derived(`${firstName} ${lastName}`);
|
||||
let initials = $derived(`${firstName[0]}${lastName[0]}`);
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ `$effect` - Side Effects
|
||||
|
||||
### Basic Effect
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let count = $state(0);
|
||||
|
||||
$effect(() => {
|
||||
// Runs when count changes
|
||||
console.log(`Count is now ${count}`);
|
||||
document.title = `Count: ${count}`;
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Effect with Cleanup
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let running = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
if (!running) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
console.log('tick');
|
||||
}, 1000);
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### `$effect.pre` - Before DOM Update
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let messages = $state<string[]>([]);
|
||||
let container: HTMLDivElement;
|
||||
|
||||
$effect.pre(() => {
|
||||
// Runs BEFORE DOM updates
|
||||
// Useful for preserving scroll position
|
||||
messages; // Track dependency
|
||||
if (container) {
|
||||
// Save scroll position before new messages render
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### `$effect.tracking` - Check if Reactive
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
$effect(() => {
|
||||
console.log($effect.tracking()); // true
|
||||
});
|
||||
|
||||
function regularFunction() {
|
||||
console.log($effect.tracking()); // false
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### `$effect.root` - Untracked Effect Scope
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
// Create effects outside component lifecycle
|
||||
const cleanup = $effect.root(() => {
|
||||
$effect(() => {
|
||||
// This effect lives until cleanup() is called
|
||||
});
|
||||
|
||||
return () => {
|
||||
// Custom cleanup logic
|
||||
};
|
||||
});
|
||||
|
||||
// Later: cleanup();
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 `$props` - Component Props
|
||||
|
||||
### Basic Props
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let { name, age }: { name: string; age: number } = $props();
|
||||
</script>
|
||||
|
||||
<p>{name} is {age} years old</p>
|
||||
```
|
||||
|
||||
### Optional Props with Defaults
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
name: string;
|
||||
age?: number;
|
||||
greeting?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
name,
|
||||
age = 18,
|
||||
greeting = 'Hello'
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<p>{greeting}, {name}! You are {age}.</p>
|
||||
```
|
||||
|
||||
### Rest Props
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||
|
||||
interface Props extends HTMLButtonAttributes {
|
||||
variant?: 'primary' | 'secondary';
|
||||
}
|
||||
|
||||
let { variant = 'primary', ...rest }: Props = $props();
|
||||
</script>
|
||||
|
||||
<button class={variant} {...rest}>
|
||||
<slot />
|
||||
</button>
|
||||
```
|
||||
|
||||
### Children as Props (Snippets)
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
header?: Snippet;
|
||||
children: Snippet;
|
||||
footer?: Snippet;
|
||||
}
|
||||
|
||||
let { header, children, footer }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="card">
|
||||
{#if header}
|
||||
<header>{@render header()}</header>
|
||||
{/if}
|
||||
|
||||
<main>{@render children()}</main>
|
||||
|
||||
{#if footer}
|
||||
<footer>{@render footer()}</footer>
|
||||
{/if}
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 `$bindable` - Two-Way Binding Props
|
||||
|
||||
### Basic Bindable
|
||||
```svelte
|
||||
<!-- TextInput.svelte -->
|
||||
<script lang="ts">
|
||||
let { value = $bindable('') }: { value?: string } = $props();
|
||||
</script>
|
||||
|
||||
<input bind:value />
|
||||
|
||||
<!-- Parent.svelte -->
|
||||
<script lang="ts">
|
||||
import TextInput from './TextInput.svelte';
|
||||
let name = $state('');
|
||||
</script>
|
||||
|
||||
<TextInput bind:value={name} />
|
||||
<p>Name: {name}</p>
|
||||
```
|
||||
|
||||
### Bindable with Fallback
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
// Provide a fallback for when parent doesn't bind
|
||||
let { checked = $bindable(false) }: { checked?: boolean } = $props();
|
||||
</script>
|
||||
|
||||
<input type="checkbox" bind:checked />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 `$inspect` - Development Debugging
|
||||
|
||||
### Basic Inspect
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let count = $state(0);
|
||||
let user = $state({ name: 'John' });
|
||||
|
||||
// Logs whenever count or user changes
|
||||
$inspect(count, user);
|
||||
</script>
|
||||
```
|
||||
|
||||
### Custom Inspect Handler
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let data = $state({ x: 0, y: 0 });
|
||||
|
||||
$inspect(data).with((type, ...values) => {
|
||||
if (type === 'update') {
|
||||
console.log('Data updated:', values);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Breakpoint on Change
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let buggyValue = $state(0);
|
||||
|
||||
// Pauses debugger when value changes
|
||||
$inspect(buggyValue).with(() => {
|
||||
debugger;
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏪 Class-Based State with `$state`
|
||||
|
||||
```svelte
|
||||
<script lang="ts" module>
|
||||
// Can be in a separate .svelte.ts file
|
||||
export class Counter {
|
||||
count = $state(0);
|
||||
doubled = $derived(this.count * 2);
|
||||
|
||||
increment() {
|
||||
this.count++;
|
||||
}
|
||||
|
||||
decrement() {
|
||||
this.count--;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.count = 0;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const counter = new Counter();
|
||||
</script>
|
||||
|
||||
<button onclick={() => counter.decrement()}>-</button>
|
||||
<span>{counter.count} (doubled: {counter.doubled})</span>
|
||||
<button onclick={() => counter.increment()}>+</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Common Mistakes
|
||||
|
||||
### ❌ Wrong: Destructuring Loses Reactivity
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let user = $state({ name: 'John', age: 30 });
|
||||
|
||||
// ❌ This breaks reactivity!
|
||||
let { name, age } = user;
|
||||
|
||||
// ✅ Access properties directly
|
||||
// user.name, user.age
|
||||
</script>
|
||||
```
|
||||
|
||||
### ❌ Wrong: Effect Without Dependencies
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let count = $state(0);
|
||||
|
||||
// ❌ This runs only once (no reactive reads inside)
|
||||
$effect(() => {
|
||||
console.log('This only runs once');
|
||||
});
|
||||
|
||||
// ✅ Read reactive values to create dependencies
|
||||
$effect(() => {
|
||||
console.log(`Count is ${count}`);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### ❌ Wrong: Mutating Derived Values
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let items = $state([1, 2, 3]);
|
||||
let doubled = $derived(items.map(n => n * 2));
|
||||
|
||||
// ❌ Cannot mutate derived values!
|
||||
// doubled.push(8);
|
||||
|
||||
// ✅ Mutate the source
|
||||
items.push(4);
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Runes Comparison Table
|
||||
|
||||
| Svelte 4 | Svelte 5 | Purpose |
|
||||
|----------|----------|---------|
|
||||
| `export let x` | `let { x } = $props()` | Props |
|
||||
| `let x = 0` | `let x = $state(0)` | Reactive state |
|
||||
| `$: y = x * 2` | `let y = $derived(x * 2)` | Computed |
|
||||
| `$: { ... }` | `$effect(() => { ... })` | Side effects |
|
||||
| `$$props` | `$props()` with rest | All props |
|
||||
| `bind:value` | `$bindable()` | Two-way binding |
|
||||
Reference in New Issue
Block a user