J’ai récemment découvert Svelte à l’occasion d’un POC. La documentation est bien faite et quelques articles existent à ce sujet mais je voulais ajouter ma pierre à l’édifice. Cet article a pour but de vous faire découvrir le Framework et de vous en apprendre les rudiments.
Svelte : Qu’est-ce que c’est ?
Svelte propose une approche différente de la plupart des Framework Front les plus populaires comme React, Vue ou encore Angular. Ceux-ci effectuant le plus gros du travail dans le navigateur de l’utilisateur, manipulation d’un DOM virtuel par exemple, Svelte passe par une étape de compilation. Il va ainsi générer un JavaScript “vanilla” optimisé.
Cela a plusieurs avantages comme la réduction du bundle envoyé au navigateur lors de l’arrivée sur votre site ou encore de meilleures performances dans l’exécution de manipulation du DOM.
Malgré ces avantages, le Framework reste plutôt “jeune”. La communauté n’est pas aussi développée que sur d’autres comme React ou Vue. Il y a forcément moins de plugins ou librairies associées. Et au moment où j’écris ces lignes, Svelte annonce que SvelteKit (équivalent de NextJs pour React ou Nuxt pour Vue) passe en bêta publique (What’s new in Svelte: April 2021).
Au-delà de ça, Svelte reste un compétiteur et est déjà adopté par plusieurs grosses plateformes du web comme The New York Times, Rakuten, Chess.com ou encore Les Echos.
Par où je commence ?
Svelte est un compilateur. On ne peut donc pas ajouter <script src="svelte.js">
dans une page pour tester directement.
Plusieurs choix s’offrent à nous pour découvrir ce Framework.
REPL
Tout d’abord, Svelte met à disposition un REPL (Read-Eval-Print Loop) que vous pouvez utiliser pour tester le framework. C’est la meilleure option si vous souhaitez commencer à coder peu importe votre environnement.
Vous pouvez directement vous y rendre ici : https://svelte.dev/repl
C’est d’ailleurs ce que je vous recommande pour suivre cet article.
Projet NodeJS
Pour cette option-ci vous aurez besoin de quelques prérequis :
- NodeJS
- NPM et NPX (gestionnaire de paquets et exécuteur de scripts fournis avec NodeJS)
Un template de projet est disponible pour créer un environnement préconfiguré. On va utiliser degit
pour créer notre dossier.
npx degit sveltejs/template mon-projet
Dans cette commande nous avons :
npx
: l’exécuteur de scriptdegit
: le paquet qui permet de télécharger le repo sans l’historique git (comme le feraitgit clone
)sveltejs/template
: le nom du template Svelte que nous allons utilisermon-projet
: le nom du dossier qui sera créé et dans lequel sera téléchargé le template
Un fois le dossier créé, il ne reste plus qu’à installer les dépendances et commencer à développer:
# On se place dans le dossier
cd mon-projet
# On installe les dépendances
npm install
# On lance le serveur de développement
npm run dev
Contenu du projet
Rollup
L’application utilise Rollup pour la génération du build final et pour l’exécution de l’environnement de développement. Le plugin Svelte est utilisé pour prendre en charge les fichiers .svelte
. On peut retrouver le fichier de configuration à la racine.
Dossier public
Les fichiers contenus dans ce dossier seront les points d’entrée de votre application. On retrouve:
index.html
: la page principale générée par l’applicationglobal.css
: la feuille de style importée dans le fichierindex.html
favicon.png
: l’image utilisée pour afficher le faviconbuild/bundle.css
: la feuille de style de notre projet compilébuild/bundle.js
: le fichier JavaScript contenant toute notre applicationbuild/bundle.js.map
: la source map de notrebundle.js
A noter que le dossier build
a été généré lors de la commande npm run dev
.
Dossier src
Ce dossier contient notre application.
src/main.js
: c’est le point d’entrée de l’application, il fait le lien entre le composantApp
(qui est la racine) et le fichierindex.html
src/App.svelte
: le composantApp
de l’application. C’est ce fichier qu’il faudra éditer pour étudier le Framework
Les bases de Svelte
Comme vous avez pu le voir plus haut, l’application sera composée de fichiers avec l’extension .svelte
. C’est là-dedans que la logique sera décrite.
Un fichier correspond à un composant et peut être décrit comme cela :
<script>
// La logique et le fonctionnement du composant en JS sera décrite ici
</script>
<!-- Les composants HTML ou Svelte seront placés ici -->
<style>
/* Le style en CSS sera placé ici */
</style>
La balise <script>
C’est dans celle-ci que l’on va pouvoir définir et modifier des variables, importer des librairies ou des composants ou encore déclarer les propriétés de notre composant.
Prenons un exemple :
<script>
// L'import des librairies et des composants Svelte se fait comme cela
import Component from './Component.svelte'
// Avec `export let` on va créer une propriété à notre composant
export let propName;
// Et ici une variable standard
const variable = 'hello';
</script>
La partie HTML
En dehors des balises de style
et de script
, c’est le code HTML qui sera le corps de votre composant. Pour intégrer des variables directement dedans, on utilisera {}
.
<script>
import Recette from './Recette.svelte'
export let nom;
</script>
Bonjour {nom}, voici votre recette :
<Recette />
D’autres tags existent pour interagir avec le DOM comme :
{#if variable}
pour conditionner l’affichage
<script>
export let nom;
</script>
{#if nom}
Bonjour {nom}
{/if}
{#each variable}
pour parcourir un tableau
<script>
const panier = ['banane', 'fraise', 'poire', 'orange']
</script>
<h1>Contenu du panier</h1>
<ul>
{#each panier as fruit}
<li>{fruit}</li>
{/each}
</ul>
{@html variable}
pour intégrer du HTML
<script>
const titre = `<h1> JE SUIS UN TITRE </h1>`
</script>
{@html titre}
- et d’autres comme
{#await variable}
,{#key...}
ou encore{@debug ...}
, la suite ici
La balise <style>
Les règles CSS
qui vont s’appliquer au composant et seulement à celui-là. Si les règles ciblent toutes les balises button
, ça sera toutes les balises button
du composant.
<h1>Titre</h1>
<style>
/*
Ici la couleur rouge ne s'appliquera qu'à la balise h1 au-dessus
et nulle part ailleurs dans l'application
*/
h1 {
color: red;
}
</style>
Un peu de pratique
Mais il reste pas mal de choses à voir. Pour la suite, on va commencer une petite application que vous pourrez étoffer par vous-même. On va partir sur un Pokédex, tout ce qu’il y a de plus simple.
Notre projet contiendra 3 fichiers :
App.svelte
: le point d’entrée de l’applicationPokeList.svelte
: la liste des PokémonsPokeDetails.svelte
: les détails du Pokémon choisi
Voici le résultat final si vous souhaitez y jeter un œil. Pokédex
Récupération de la liste : {#await}
, {:then}
et {:catch}
On commence avec de la récupération de données. Ça va nous permettre de faire un premier pas dans le cycle de vie de Svelte.
<!-- App.svelte -->
<script>
// La variable qui va stocker notre promesse
const promise = getPokemons();
// La fonction qui va récupérer les Pokémons
async function getPokemons() {
const res = await fetch('https://pokeapi.co/api/v2/pokemon');
const json = await res.json();
return json.results;
}
</script>
<h1>Pokédex</h1>
<!--
`#await` va permettre de gérer la promesse
directement dans le corps du composant
-->
{#await promise}
Chargement du Pokédex
<!-- Une fois résolue, on a le retour de notre fonction `GetPokemons` -->
{:then pokemons}
{pokemons}
<!-- Sinon, en cas d'erreur, on affiche un message -->
{:catch error}
Une erreur s'est produite : {error.message}
{/await}
Les informations sont récupérées à l’affichage du composant puis utilisées dans la partie HTML.
Le composant de liste PokeList : each
et import
On va maintenant créer le composant qui va traiter ces données.
<!-- PokeList.svelte -->
<script>
// On récupère en propriété le tableau des Pokémons
// et on lui donne [] comme valeur par défaut.
export let pokemons = []
</script>
<ul>
{#each pokemons as pokemon}
<li>
{pokemon.name}
</li>
{/each}
</ul>
On met à jour notre fichier App.svelte
pour prendre en compte notre nouveau composant.
<!-- App.svelte -->
<script>
// Récupération du composant
import PokeList from './PokeList.svelte'
const promise = getPokemons();
async function getPokemons() {
const res = await fetch('https://pokeapi.co/api/v2/pokemon');
const json = await res.json();
return json.results;
}
</script>
<h1>Pokédex</h1>
{#await promise}
Chargement du Pokédex
{:then pokemons}
<PokeList pokemons={pokemons} />
{:catch error}
Une erreur s'est produite : {error.message}
{/await}
Récupérer le Pokémon à afficher : on:
et dispatch
Maintenant que la liste est faite, on va vouloir sélectionner le Pokémon à afficher. On va donc devoir remonter l’information du composant PokeList
vers le composant App
le Pokémon sélectionné.
Et on commence par mettre des boutons sur chaque nom avec un comme propriété on:click
. On va attacher une fonction à l’évènement ‘click’ du bouton.
<!-- PokeList.svelte -->
<!-- [...] -->
<button on:click="{() => handlePokemonClick(pokemon)}" >
{pokemon.name}
</button>
<!-- [...] -->
Et dans la balise script
on va ajouter la déclaration de la fonction.
<!-- PokeList.svelte -->
<script>
// [...]
const handlePokemonClick = (pokemon) => {
// Ici on récupère bien le Pokémon sur lequel on a cliqué
console.log(pokemon)
}
</script>
<!-- [...] -->
Mais on souhaite faire passer cette information au composant App
. Il va donc falloir ajouter un nouvel évènement.
Côté App
on va écouter l’évènement pokemonSelect
.
<!-- App.svelte -->
<script>
// [...]
let pokemons = []
// On initialise la variable qui va contenir le Pokémon choisi
let selectedPokemon = null
// [...]
const handlePokemonSelect = (event) => {
// Les données spécifique envoyé dans l'évènement seront
// dans la propriété `detail`
selectedPokemon = event.detail
}
</script>
<h1>Pokédex</h1>
{#await promise}
Chargement du Pokédex
{:then pokemons}
<PokeList pokemons={pokemons} on:pokemonSelect="{handlePokemonSelect}" />
<!-- Le nom du Pokémon sélectionné est affiché -->
{selectedPokemon}
{:catch error}
Une erreur s'est produite : {error.message}
{/await}
Et côté PokeList
, on va implémenter le dispatch
de l’évènement.
<!-- PokeList.svelte -->
<script>
// import de la fonction createEventDispatcher qui va nous permettre
// d'envoyer l'évènement
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let pokemons = []
const handlePokemonClick = (pokemon) => {
// L'évènement `pokemonSelect` est envoyé avec les informations
dispatch('pokemonSelect', pokemon)
}
</script>
<!-- [...] -->
Maintenant on peut sélectionner notre Pokémon et afficher son nom. Mais bon, on aimerait bien avoir plus d’info.
Le composant de détail PokeDetails : $:
et if:else
Avec notre Pokémon issue de la liste, on ne va pas pouvoir aller bien loin. En effet, chaque Pokémon de notre liste ne contient que 2 propriétés (name
et url
).
L’url permet de récupérer un peu plus d’information. On va donc l’utiliser dans notre nouveau composant.
<!-- PokeDetails.svelte -->
<script>
export let pokemon
let promise
// Pour ré-exécuter du code après le changement d’une variable
// il faut ajouter `$:` devant.
// Ici la promesse sera ré-exécuté lorsque la variable `pokemon` changera
// Les blocks `$:` ne sont exécuté que lorsque qu’une variable présente
// dans le bloc change
$: promise = getPokemon(pokemon.url)
async function getPokemon(url) {
const res = await fetch(url);
return await res.json();
}
</script>
<div>
<!-- On va réutiliser `await` pour le traitement de la promesse -->
{#await promise}
Chargement de {pokemon.name}…
{:then pokemonDetails}
<h2>{pokemonDetails.name}</h2>
<img src={pokemonDetails.sprites.front_default} alt={pokemonDetails.name} />
{:catch error}
Une erreur s'est produite : {error.message}
{/await}
</div>
Ensuite, on va conditionner l’affichage de ce composant dans le fichier App.svelte
.
<!-- App.svelte -->
{#await promise}
Chargement du Pokédex...
{:then pokemons}
<PokeList pokemons={pokemons} on:selectPokemon={selectPokemon} />
<!-- Si on a séléctionné un Pokémon, on peut afficher notre composant -->
{#if selectedPokemon}
<PokeDetails pokemon={selectedPokemon} />
<!-- Sinon, une indication -->
{:else}
Sélectionnez un Pokémon
{/if}
{:catch error}
Une erreur s'est produite : {error.message}
{/await}
Un peu de style : style
Ici c’est plus pour vous amuser. Pour ma part, j’ai ajouté une balise style
sur le composant App
pour plus de lisibilité.
<!-- App.svelte -->
<!-- [...] -->
<style>
/*
Comme vu plus haut, ces styles n’affecteront que le composant App.
Les balises `div` et `h1` des autres composants ne seront pas touchées.
*/
h1 {
text-align: center;
}
div {
display: flex;
}
</style>
Suite et aller plus loin
Voilà un joli Pokédex! Mais il manque de fonctionnalités. Pour rappel, le mien se trouve ici : Pokédex.
Voici plusieurs idées pour aller plus loin :
- Ajouter un input pour rechercher un Pokémon (voir: bind: et Exemple d’input)
- Améliorer le style général de l’application (voir: style)
- Ajouter d’autres détails au Pokémon choisi
- Modifier les métadonnées du site comme le Titre de la page (voir: svelte:head )
Et si vous avez d’autres idées, n’hésitez pas à les partager (@rednetio).
Conclusion
Alors ? Ça y est ? Vous vous sentez à l’aise avec Svelte ?
En tout cas, j’espère que ça vous a donné un premier aperçu du Framework. Svelte est très orienté performance et, à mon sens, vaut la peine d’y jeter un coup d’œil.
L’architecture et le mode de développement sont assez proches du vanilla JS, CSS et HTML au premier abord. Cela permet une prise en main plus rapide pour celles et ceux qui ne sont pas spécialistes d’autres Frameworks à la syntaxe plus complexe.
L’objectif était de vous faire découvrir Svelte et vous apprendre les rudiments et j’espère que ça a attisé votre curiosité.
Si vous voulez poussez plus loin, vous pouvez vous rendre sur le tutoriel Svelte.
Et si cet article vous a plu, n’hésitez pas à nous le dire sur @rednetio ou directement à moi @thodubois. J’essayerai de faire un article un peu plus poussé.