Svelte - Getting started

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 Logo

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 script
  • degit : le paquet qui permet de télécharger le repo sans l’historique git (comme le ferait git clone)
  • sveltejs/template : le nom du template Svelte que nous allons utiliser
  • mon-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’application
  • global.css : la feuille de style importée dans le fichier index.html
  • favicon.png : l’image utilisée pour afficher le favicon
  • build/bundle.css : la feuille de style de notre projet compilé
  • build/bundle.js : le fichier JavaScript contenant toute notre application
  • build/bundle.js.map : la source map de notre bundle.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 composant App (qui est la racine) et le fichier index.html
  • src/App.svelte : le composant App 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 :

<script>
  export let nom;
</script>

{#if nom}
  Bonjour {nom}
{/if}
<script>
   const panier = ['banane', 'fraise', 'poire', 'orange']
</script>

<h1>Contenu du panier</h1>
<ul>
  {#each panier as fruit}
    <li>{fruit}</li>
  {/each}
</ul>
<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.

Résultat final du Pokédex

Notre projet contiendra 3 fichiers :

  • App.svelte : le point d’entrée de l’application
  • PokeList.svelte : la liste des Pokémons
  • PokeDetails.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é.

Liens