Feuilles chauffantes¶
Svelte - Applications Web améliorées cybernétiques
Svelte est une nouvelle approche radicale pour construire des interfaces utilisateur. Alors que les cadres traditionnels comme React et Vue font la majeure partie de leur travail dans le navigateur, Svelte déplace ce travail dans une étape de compilation qui se produit lorsque vous construisez votre application.
Sommaire¶
- [Installation] (#installation)
- [Création de projets] (#project-creation)
- [Structure du projet] (#project-structure)
- [Component Syntaxe] (#component-syntax)
- [Réactivité] (#reactivity)
- [Props] (#props)
- [Logic et Templating] (#logic-and-templating)
- [Événements] (#events)
- [Bindings] (#bindings)
- [Fonctions du cycle de vie] (#lifecycle-functions)
- [Stores] (#stores)
- [Transitions et animations] (#transitions-and-animations)
- [SvelteKit] (#sveltekit)
- [Déploiement] (#deployment)
- [Essais] (#testing)
- [Meilleures pratiques] (#best-practices)
Installation¶
Préalables¶
# Node.js version requirements
node --version # Should be 16 or later
# Check npm version
npm --version
# Update npm if needed
npm install -g npm@latest
Créer un projet Svelte¶
# Create new SvelteKit project (recommended)
npm create svelte@latest my-app
# Create a basic Svelte project (without SvelteKit)
npm create vite@latest my-app -- --template svelte
# Navigate to project
cd my-app
# Install dependencies
npm install
# Start development server
npm run dev
```_
### Installation manuelle (Vite)
```bash
# Create project directory
mkdir my-svelte-project
cd my-svelte-project
# Initialize package.json
npm init -y
# Install Vite and Svelte plugin
npm install --save-dev vite @sveltejs/vite-plugin-svelte
# Install Svelte
npm install svelte
# Create vite.config.js
// vite.config.js
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [svelte()]
});
```_
### Paquet.json Scripts
```json
{
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
}
}
Création de projets¶
Configuration du projet SvelteKit¶
# Interactive setup
npm create svelte@latest my-app
# Select options:
# - Skeleton project (basic setup)
# - Demo app (with examples)
# - Library project
# Add TypeScript
# Add ESLint
# Add Prettier
# Add Playwright for browser testing
# Add Vitest for unit testing
Commandes de développement¶
# Start development server
npm run dev
# Start with specific port
npm run dev -- --port 3000
# Start with specific host
npm run dev -- --host 0.0.0.0
# Start with open browser
npm run dev -- --open
Construire des commandes¶
# Build for production
npm run build
# Preview production build
npm run preview
# Check for issues
npm run check
Structure du projet¶
Structure du projet SvelteKit¶
my-sveltekit-app/
├── src/
│ ├── lib/ # Library code (components, utils)
│ │ ├── components/ # Reusable components
│ │ └── utils/ # Utility functions
│ ├── routes/ # File-based routing
│ │ ├── +page.svelte # Homepage
│ │ ├── about/ # /about route
│ │ │ └── +page.svelte
│ │ └── blog/
│ │ ├── +page.svelte # /blog index
│ │ └── [slug]/
│ │ └── +page.svelte
│ ├── app.html # HTML template
│ └── app.d.ts # TypeScript declarations
├── static/ # Static assets
├── tests/ # Test files
├── package.json
├── svelte.config.js # SvelteKit configuration
└── tsconfig.json
Structure du projet¶
my-vite-svelte-app/
├── public/ # Static assets
├── src/
│ ├── assets/ # Assets
│ ├── lib/ # Svelte components
│ └── main.js # App entry point
├── index.html # HTML template
├── package.json
└── vite.config.js
Composante Syntaxe¶
Composante de base¶
<!-- src/lib/components/Greeting.svelte -->
<script>
// Component logic
let name = 'World';
</script>
<main>
<h1>Hello, {name}!</h1>
<p>This is a Svelte component.</p>
</main>
<style>
/* Scoped styles */
main {
text-align: center;
padding: 2rem;
}
h1 {
color: #ff3e00;
font-size: 2.5rem;
}
p {
color: #333;
}
</style>
Composant avec Props¶
<!-- src/lib/components/Welcome.svelte -->
<script>
export let name;
export let message = 'Welcome to our app!';
</script>
<div>
<h2>Hello, {name}!</h2>
<p>{message}</p>
</div>
<!-- Usage -->
<!-- <Welcome name="John" /> -->
<!-- <Welcome name="Jane" message="Glad to have you here!" /> -->
Composante avec logique¶
<!-- src/lib/components/Counter.svelte -->
<script>
let count = 0;
function increment() {
count += 1;
}
function decrement() {
count -= 1;
}
function reset() {
count = 0;
}
</script>
<div class="counter">
<h3>Counter</h3>
<p>Count: {count}</p>
<div class="buttons">
<button on:click={decrement}>-</button>
<button on:click={increment}>+</button>
<button on:click={reset}>Reset</button>
</div>
</div>
<style>
.counter {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
text-align: center;
}
.buttons {
display: flex;
gap: 0.5rem;
justify-content: center;
margin-top: 1rem;
}
</style>
Réactivité¶
Attributions réactives¶
<script>
let count = 0;
function handleClick() {
// Svelte automatically detects this assignment is reactive
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
Déclarations réactives¶
<script>
let count = 0;
// This will be re-evaluated whenever `count` changes
$: doubled = count * 2;
$: quadrupled = doubled * 2;
function handleClick() {
count += 1;
}
</script>
<p>{count} * 2 = {doubled}</p>
<p>{doubled} * 2 = {quadrupled}</p>
<button on:click={handleClick}>Increment</button>
Déclarations réactives¶
<script>
let count = 0;
// This block of code runs whenever `count` changes
$: {
console.log(`The count is ${count}`);
if (count >= 10) {
alert('Count is 10 or more!');
}
}
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>Count: {count}</button>
Mise à jour des tableaux et des objets¶
<script>
let numbers = [1, 2, 3];
let user = { name: 'John', age: 30 };
function addNumber() {
// To trigger reactivity, you must assign to the variable
numbers = [...numbers, numbers.length + 1];
}
function changeName() {
user.name = 'Jane';
// This won't trigger reactivity on its own
// To make it reactive, reassign the object
user = user;
}
</script>
<p>Numbers: {numbers.join(', ')}</p>
<button on:click={addNumber}>Add Number</button>
<p>User: {user.name}, {user.age}</p>
<button on:click={changeName}>Change Name</button>
Props¶
Déclarer les produits¶
<script>
// Declare props with `export let`
export let name;
export let age = 25; // Default value
export let tags = [];
export let user = { id: 1, name: 'Guest' };
// Renaming props
export let title as heading;
</script>
<h2>{heading}</h2>
<p>Welcome, {name} ({age})</p>
{#if tags.length > 0}
<p>Tags: {tags.join(', ')}</p>
{/if}
Passage des accessoires¶
<!-- ParentComponent.svelte -->
<script>
import UserProfile from './UserProfile.svelte';
let user = {
name: 'John Doe',
age: 30,
tags: ['developer', 'svelte']
};
</script>
<UserProfile
name={user.name}
age={user.age}
tags={user.tags}
title="User Profile"
/>
<!-- Shorthand for passing props -->
<UserProfile {...user} title="User Profile" />
Type de vérification avec TypeScript¶
<script lang="ts">
export let name: string;
export let age: number;
export let active: boolean = false;
export let onSave: (data: any) => void;
</script>
<p>{name} is {age} years old.</p>
<button on:click={() => onSave({ name, age })}>
Save
</button>
Logique et Templatation¶
Rendu conditionnel¶
<script>
let loggedIn = false;
let user = { name: 'John' };
</script>
{#if loggedIn}
<p>Welcome back, {user.name}!</p>
{:else}
<p>Please log in.</p>
{/if}
{#if user.role === 'admin'}
<p>Admin controls</p>
{:else if user.role === 'editor'}
<p>Editor controls</p>
{:else}
<p>User controls</p>
{/if}
En boucle¶
<script>
let cats = [
{ id: '1', name: 'Whiskers' },
{ id: '2', name: 'Mittens' },
{ id: '3', name: 'Shadow' }
];
</script>
<ul>
{#each cats as cat (cat.id)}
<li>{cat.name}</li>
{/each}
</ul>
<!-- With index -->
<ul>
{#each cats as cat, i}
<li>{i + 1}: {cat.name}</li>
{/each}
</ul>
<!-- With else block -->
{#each cats as cat}
<p>{cat.name}</p>
{:else}
<p>No cats found.</p>
{/each}
Attendez les blocs¶
<script>
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
if (!res.ok) throw new Error('Failed to fetch posts');
return await res.json();
}
let promise = getPosts();
</script>
{#await promise}
<p>Loading posts...</p>
{:then posts}
<ul>
{#each posts as post}
<li>{post.title}</li>
{/each}
</ul>
{:catch error}
<p>Error: {error.message}</p>
{/await}
HTML Rendu¶
<script>
let htmlContent = '<h1>This is raw HTML</h1><p>Use with caution.</p>';
</script>
<!-- Render raw HTML -->
{@html htmlContent}
<!-- Debugging -->
{@debug count, user}
Événements¶
DOM Événements¶
<script>
function handleClick() {
alert('Button clicked!');
}
function handleInput(event) {
console.log('Input value:', event.target.value);
}
</script>
<button on:click={handleClick}>Click me</button>
<input on:input={handleInput} placeholder="Type something" />
<!-- Inline handler -->
<button on:click={() => console.log('Clicked!')}>Inline</button>
Modificateurs d'événements¶
<!-- Prevent default action -->
<form on:submit|preventDefault={handleSubmit}></form>
<!-- Stop event propagation -->
<div on:click={handleOuter}>
<button on:click|stopPropagation={handleInner}>Inner</button>
</div>
<!-- Run handler only once -->
<button on:click|once={doSomething}>Once</button>
<!-- Run handler if event is not passive -->
<div on:wheel|passive={handleWheel}></div>
<!-- Run handler during capture phase -->
<div on:click|capture={handleCapture}></div>
<!-- Self modifier -->
<div on:click|self={handleSelf}></div>
Événements liés aux composantes¶
<!-- InnerComponent.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function notify() {
dispatch('message', {
text: 'Hello from the inner component!'
});
}
</script>
<button on:click={notify}>Notify Parent</button>
<!-- ParentComponent.svelte -->
<script>
import InnerComponent from './InnerComponent.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<InnerComponent on:message={handleMessage} />
Transmission des événements¶
<!-- CustomButton.svelte -->
<button on:click>
<slot />
</button>
<!-- ParentComponent.svelte -->
<script>
import CustomButton from './CustomButton.svelte';
function handleClick() {
console.log('Custom button clicked!');
}
</script>
<CustomButton on:click={handleClick}>Click me</CustomButton>
Reliures¶
Reliure de valeur¶
<script>
let name = 'John';
let message = 'Hello';
let selected = 'option2';
let checked = true;
let value = 5;
</script>
<!-- Text input -->
<input bind:value={name} />
<!-- Textarea -->
<textarea bind:value={message}></textarea>
<!-- Select -->
<select bind:value={selected}>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
</select>
<!-- Checkbox -->
<input type="checkbox" bind:checked={checked} />
<!-- Range slider -->
<input type="range" bind:value min="0" max="10" />
Reliures de groupe¶
<script>
let flavors = ['Vanilla', 'Chocolate', 'Strawberry'];
let selectedFlavors = ['Vanilla'];
let scoop = 'one';
</script>
<!-- Checkbox group -->
{#each flavors as flavor}
<label>
<input type="checkbox" bind:group={selectedFlavors} value={flavor} />
{flavor}
</label>
{/each}
<!-- Radio group -->
<label>
<input type="radio" bind:group={scoop} value="one" /> One scoop
</label>
<label>
<input type="radio" bind:group={scoop} value="two" /> Two scoops
</label>
Reliure des composants¶
<!-- InnerComponent.svelte -->
<script>
export let value;
</script>
<input bind:value />
<!-- ParentComponent.svelte -->
<script>
import InnerComponent from './InnerComponent.svelte';
let text = 'Initial value';
</script>
<InnerComponent bind:value={text} />
<p>Value: {text}</p>
Autres liaisons¶
<!-- bind:this -->
<script>
let myCanvas;
onMount(() => {
const ctx = myCanvas.getContext('2d');
// Draw on canvas
});
</script>
<canvas bind:this={myCanvas}></canvas>
<!-- bind:clientWidth, bind:clientHeight -->
<script>
let width, height;
</script>
<div bind:clientWidth={width} bind:clientHeight={height}>
Dimensions: {width} x {height}
</div>
Fonctions du cycle de vie¶
sur le mois¶
<script>
import { onMount } from 'svelte';
onMount(() => {
// Runs after the component is first rendered to the DOM
console.log('Component mounted');
const interval = setInterval(() => {
console.log('tick');
}, 1000);
// Cleanup function
return () => {
clearInterval(interval);
console.log('Component unmounted');
};
});
</script>
avant et après Mise à jour¶
<script>
import { beforeUpdate, afterUpdate } from 'svelte';
let count = 0;
beforeUpdate(() => {
// Runs before the DOM is updated
console.log('Before update');
});
afterUpdate(() => {
// Runs after the DOM is updated
console.log('After update');
});
</script>
<button on:click={() => count++}>Count: {count}</button>
surDestroy¶
<script>
import { onDestroy } from 'svelte';
onDestroy(() => {
// Runs when the component is about to be destroyed
console.log('Component destroyed');
});
</script>
cochez¶
<script>
import { tick } from 'svelte';
let text = 'Initial';
async function updateText() {
text = 'Updated';
// Wait for the DOM to update
await tick();
console.log('DOM updated');
}
</script>
<p>{text}</p>
<button on:click={updateText}>Update</button>
Magasins¶
Magasins¶
// stores.js
import { writable } from 'svelte/store';
export const count = writable(0);
export const user = writable({ name: 'Guest', loggedIn: false });
<!-- Component.svelte -->
<script>
import { count, user } from './stores.js';
function increment() {
count.update(n => n + 1);
}
function login() {
user.set({ name: 'John', loggedIn: true });
}
// Auto-subscription with $ prefix
$: console.log(`Count is ${$count}`);
</script>
<h1>Count: {$count}</h1>
<button on:click={increment}>Increment</button>
{#if $user.loggedIn}
<p>Welcome, {$user.name}!</p>
{:else}
<button on:click={login}>Login</button>
{/if}
Magasins lisibles¶
// stores.js
import { readable } from 'svelte/store';
export const time = readable(new Date(), (set) => {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return () => clearInterval(interval);
});
<!-- Clock.svelte -->
<script>
import { time } from './stores.js';
$: formattedTime = $time.toLocaleTimeString();
</script>
<p>Current time: {formattedTime}</p>
Magasins dérivés¶
// stores.js
import { derived } from 'svelte/store';
import { time } from './stores.js';
export const elapsed = derived(
time,
$time => Math.round(($time - new Date()) / 1000)
);
<!-- Elapsed.svelte -->
<script>
import { elapsed } from './stores.js';
</script>
<p>Elapsed time: {$elapsed}s</p>
Magasins personnalisés¶
// stores.js
import { writable } from 'svelte/store';
function createCount() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const count = createCount();
Transitions et Animations¶
Transitions¶
<script>
import { fade, fly, slide, scale } from 'svelte/transition';
let visible = true;
</script>
<label>
<input type="checkbox" bind:checked={visible} />
Toggle visibility
</label>
{#if visible}
<div transition:fade>Fades in and out</div>
<div transition:fly={{ y: 200, duration: 2000 }}>Flies in and out</div>
<div transition:slide>Slides in and out</div>
<div transition:scale>Scales in and out</div>
{/if}
Animations¶
<script>
import { flip } from 'svelte/animate';
let list = [1, 2, 3];
function shuffle() {
list = list.sort(() => Math.random() - 0.5);
}
</script>
<button on:click={shuffle}>Shuffle</button>
{#each list as item (item)}
<div animate:flip>{item}</div>
{/each}
Motion¶
<script>
import { spring } from 'svelte/motion';
let coords = spring({ x: 50, y: 50 }, {
stiffness: 0.1,
damping: 0.25
});
function handleMousemove(event) {
coords.set({ x: event.clientX, y: event.clientY });
}
</script>
<div on:mousemove={handleMousemove} class="container">
<div class="dot" style="transform: translate({$coords.x}px, {$coords.y}px)"></div>
</div>
SvelteKit¶
Routage¶
# File-based routing
src/routes/
├── +page.svelte # Homepage
├── about/+page.svelte # /about
├── blog/[slug]/+page.svelte # /blog/[slug]
└── +layout.svelte # Root layout
Chargement des données¶
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
export let data;
const { post } = data;
</script>
<h1>{post.title}</h1>
<div>{@html post.content}</div>
<!-- src/routes/blog/[slug]/+page.js -->
export async function load({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`);
const post = await res.json();
return { post };
}
Routes des API¶
// src/routes/api/posts/+server.js
export async function GET() {
const posts = await getPostsFromDB();
return new Response(JSON.stringify(posts));
}
export async function POST({ request }) {
const data = await request.json();
const newPost = await createPost(data);
return new Response(JSON.stringify(newPost), { status: 201 });
}
Mise en page¶
<!-- src/routes/+layout.svelte -->
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
</nav>
</header>
<main>
<slot />
</main>
<footer>
<p>© 2024 My App</p>
</footer>
Déploiement¶
Site statique (statique Adaptateur)¶
// svelte.config.js
import adapter from '@sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'index.html'
})
}
};
Vercel¶
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';
export default {
kit: {
adapter: adapter()
}
};
Netlifier¶
// svelte.config.js
import adapter from '@sveltejs/adapter-netlify';
export default {
kit: {
adapter: adapter()
}
};
Serveur Node.js¶
// svelte.config.js
import adapter from '@sveltejs/adapter-node';
export default {
kit: {
adapter: adapter()
}
};
Essais¶
Vitesse¶
// tests/Counter.test.js
import { render, fireEvent } from '@testing-library/svelte';
import Counter from '../src/lib/components/Counter.svelte';
it('increments counter', async () => {
const { getByText } = render(Counter);
const button = getByText('Increment');
await fireEvent.click(button);
expect(getByText('Count: 1')).toBeInTheDocument();
});
Dramaturge¶
// tests/e2e/home.spec.js
import { test, expect } from '@playwright/test';
test('homepage has correct title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/My App/);
});
Meilleures pratiques¶
- Organisation constituante: Gardez les composants petits et concentrés. Utilisez un répertoire
lib/components_ pour les composants réutilisables. - Réactivité: Utiliser des instructions réactives (
$:) pour calculer les valeurs. Évitez la logique complexe dans les modèles. - Stores: Utilisez des magasins pour la gestion globale de l'État. Créer des magasins personnalisés pour une logique complexe.
- Props: Utilisez des accessoires pour transmettre les données aux composants enfants. Utilisez
export letpour déclarer les accessoires. - Événements Utiliser les événements composants pour la communication entre l'enfant et le parent. Utilisez
createEventDispatcher. - ** Cycle de vie** : Utilisez
onMountpour récupérer les données et interagir avec le DOM. Utilisez des fonctions de nettoyage pour éviter les fuites de mémoire. - SvelteKit: Utilisez SvelteKit pour de nouveaux projets pour obtenir le routage, le chargement des données et le rendu côté serveur hors de la boîte.
Résumé¶
Svelte est un cadre puissant et efficace pour la construction d'applications web. Son approche de compilation-temps se traduit par des paquets plus petits et des performances plus rapides. Les principales caractéristiques sont les suivantes :
- Réactivité: Système de réactivité simple et puissant
- Component-based: Construire des applications avec des composants réutilisables
- Scoped Styles: CSS est appliqué aux composants par défaut
- Stores: solution de gestion intégrée de l'état
- Transitions et animations: Ensemble riche d'outils pour créer des UI fluides
- SvelteKit: Un cadre complet pour la construction des applications Svelte
En tirant parti des caractéristiques uniques de Svelte, vous pouvez construire des applications web rapides, modernes et durables avec une grande expérience de développeur.