Svelte, encore un framework front ?
Non, Svelte est une nouvelle librairie qui permet de gérer les composants front-end d’une application. Mais il existe son grand frère, Sapper, qui lui est un framework basé sur Svelte et qui permet de créer une application de type SPA.
A la grande différence des dernières librairies et frameworks Front-End, où la plupart du travail est directement exécuté au niveau du navigateur Web via le VirtualDOM, Svelte a décidé d’effectuer le gros du travail lors de la compilation afin de générer la logique de manipulation du DOM. Cette particularité offre deux avantages :
- Des performances accrues car il y a moins de code à exécuter lors d’un changement d’état au sein d’un composant.
- Le poids du code final est largement inférieur et dépend du nombre de fonctionnalités utilisées dans le code.
Générer un premier composant Svelte
Alors là, pas de magie comme avec les différents CLI des autres librairies ou framework. Svelte ne propose pas encore de CLI digne de ce nom. Il faut générer le projet en clonant un template présent sur Github / en utilisant leur RPL (un fichier ZIP) ou en utilisant degit
. C’est cette méthode que nous allons utiliser ici.
npx degit sveltejs/template counter-button
Une fois le projet généré, il faut installer les dépendances via votre gestionnaire de package préféré.
yarn install // ou npm install
Toutes les dépendances sont installées, nous sommes prêts pour le développement de notre composant.
Contrairement aux autres librairies / frameworks, Svelte se démarque encore une fois dans l’outil de “build” qu’il utilise. Là où les autres ont l’habitude d’utiliser Webpack, Svelte utilise par défaut Rollup.
Je dois avouer que la configuration de Rollup est un jeu d’enfant comparé à la configuration de Webpack.
Donc pas de surprise, on démarre le serveur de développement avec un script présent dans le fichier package.json.
yarn dev // ou npm run dev
Voilà. Notre composant Svelte est servi en localhost
sur le port 5000
.
Création de notre counter-button
Anatomie d’un composant Svelte
Ok, nous avons un composant avec des fichiers .svelte
, mais que contient-il ?
<script> export let name; </script> <main> <h1>Hello {name}!</h1> <p>Visit the <a href="<https://svelte.dev/tutorial>">Svelte tutorial</a> to learn how to build Svelte apps.</p> </main> <style> main { text-align: center; padding: 1em; max-width: 240px; margin: 0 auto; } h1 { color: #ff3e00; text-transform: uppercase; font-size: 4em; font-weight: 100; } @media (min-width: 640px) { main { max-width: none; } } </style>
Un peu comme un fichier React ou VueJS, le fichier est divisé en 3 blocs:
- un bloc contenant le code JavaScript
- un bloc contenant le template HTML
- et un dernier bloc contenant le style CSS
Modifions un peu le template de base pour obtenir notre composant counter-bouton
qui va pouvoir prendre en propriété d’entrée un nombre qui par défaut vaut 0.
// main.js import App from "./App.svelte"; const app = new App({ target: document.body, props: { count: 0, }, }); export default app; <script> export let count; function handleClick() { count = +count + 1; } </script> <style> .button { display: flex; flex-direction: row; margin: 0.75rem; padding: 0.75rem 1.5rem; border: none; border-radius: 0.2rem; outline: none; background-color: tomato; color: white; font-family: inherit; font-size: 1.25rem; font-weight: 400; line-height: 1.5rem; text-decoration: none; text-align: center; cursor: pointer; transition: all 150ms ease-out; } button:hover { background-color: tint(tomato, 10%); box-shadow: 0 0 0 (1.5rem / 8) white, 0 0 0 (1.5rem / 4) tint(tomato, 10%); } button:active { background-color: shade(tomato, 5%); box-shadow: 0 0 0 (1.5rem / 8) shade(tomato, 5%), 0 0 0 (1.5rem / 4) shade(tomato, 5%); transition-duration: 150ms / 2; } .button span { display: block; min-width: 24px; height: 24px; line-height: 24px; text-align: center; margin: 0 0 0 0.5rem; background: white; font-size: 0.825rem; color: tomato; border-radius: 50%; } @media (min-width: 640px) { main { max-width: none; } } </style> <svelte:options tag="counter-button" /> <main> <button class="button" on:click={handleClick}> Click Me !! <span>{count}</span> </button> </main>
On obtient le résultat suivant :
Builder son composant Svelte en WebComponent
Pour builder notre application Svelte en WebComponent, il faut modifier légèrement la configuration de Rollup et placer une option <svelte:options tag:"NOM_DU_WEBCOMPONENT"/>
dans notre fichier App.svelte
afin que celui-ci soit construit en tant que WebComponent avec un nom bien spécifique (ici “counter-button“).
// App.svelte <svelte:options tag="counter-button" /> // Rollup.config.js ... svelte({ // enable run-time checks when not in production dev: !production, **customElement: true,** // we'll extract any component CSS out into // a separate file - better for performance css: (css) => { css.write("public/build/bundle.css"); }, }), ...
Maintenant que la configuration est terminée, le build d’un composant est relativement simple, il suffit d’exécuter la commande build
:
yarn build // ou npm run build
Voilà le composant est buildé sous forme d’un fichier Javascript dans le dossier public/build
Intégrer le WebComponent dans une page HTML
Pour cela rien de plus simple :
- créez une page HTML classique dans un nouveau dossier, voire même dans un nouveau projet.
// index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>My page with Svelte WebComponent</title> <script src="./js/bundle.js"></script> </head> <body> <h1>My WebComponent</h1> <div class="content"> <counter-button count="1"></counter-button> </div> </body> </html>
- Copiez le fichier
bundle.js
qui est le résultat du build précédent et collez-le dans le dossier js.
Il faut maintenant servir la page HTML avec votre serveur web préféré. (Pour ma part j’utilise http-server
, installé en global sur ma machine).
On obtient le résultat suivant :
On peut maintenant ajouter plusieurs composants <counter-button>
dans une même page. Ils sont tous totalement indépendants.
// index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>My page with Svelte WebComponent</title> <script src="./js/bundle.js"></script> </head> <body> <h1>My WebComponent</h1> <div class="content"> <div class="component"> <span>My Counter Button 1</span> <counter-button count="1"></counter-button> </div> <div class="component"> <span>My Counter Button 2 start at 10</span> <counter-button count="10"></counter-button> </div> </div> </body> </html>
Communication entre WebComponents
Créer un WebComponent avec Svelte s’avère très simple et surtout assez pratique.
Mais comment les composants peuvent communiquer avec la page qui les utilise ou avec d’autres WebComponents ?
Deux possibilités :
- la première via les évènements dit
custom
- la seconde via un bus d’évènements hébergés au niveau de la page HTML.
Nous allons tester la première solution.
Pour cela, créons un second WebComponent sur le même principe que le premier et qui affichera la somme des différents boutons présents dans la page.
Composant Sum
<script> export let sum; </script> <style> main { text-align: center; padding: 1em; max-width: 240px; margin: 0 auto; } span { color: #ff3e00; text-transform: uppercase; font-size: 4em; font-weight: 100; } @media (min-width: 640px) { main { max-width: none; } } </style> <svelte:options tag="counter-sum" /> <main> <p> The Sum is : <span>{sum}</span> </p> </main>
On intègre le nouveau composant dans notre page HTML :
// index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>My page with Svelte WebComponent</title> <script src="./js/bundle.js"></script> <script src="./js/counter-sum.js"></script> </head> <body> <h1>My WebComponent</h1> <div class="content"> <div class="component"> <span>My Counter Button 1</span> <counter-button count="1" id="buttonOne"></counter-button> </div> <div class="component"> <span>My Counter Button 2 start at 10</span> <counter-button count="10" id="buttonTwo"></counter-button> </div> <div class="sum"> <counter-sum id="sumComponent" sum="0"></counter-sum> </div> </div> <script src="./js/main.js"></script> </body> </html>
Exportation des évènements depuis les composants <counter-button>
Pour exporter la donnée d’un WebComponent créé via Svelte, il faut créer un émetteur d’évènement custom via la méthode dispatch
.
// CounterButton // App.svelte <script> import { createEventDispatcher } from "svelte"; const dispatch = createEventDispatcher(); ... function handleClick() { count = +count + 1; exportData(); } function exportData() { dispatch("data", { count: count }); } ... </script> ...
Remarquez le
+count
; qui nous permet de transformer unstring
ennumber
car on passe unstring
en valeur à la propriétécount
du webComponent.
Il faut construire à nouveau le composant et l’intégrer dans la page HTML.
On récupère ensuite l’évènement custom créé par notre composant au niveau d’un fichier main.js
// main.js // HTML Elements const buttonOne = document.getElementById("buttonOne"); const buttonTwo = document.getElementById("buttonTwo"); const sumElem = document.getElementById("sumComponent"); // Listeners buttonOne.$on("data", (event) => { updateCountOne(event); }); buttonTwo.$on("data", (event) => { updateCountTwo(event); }); // Variables let countOne = 1; let countTwo = 10; let buttonSum = 0; // Init all attribute buttonOne.setAttribute("count", countOne); buttonTwo.setAttribute("count", countTwo); // Methods function updateCountOne(event) { countOne = event.detail.count; computeSum(); } function updateCountTwo(event) { countTwo = event.detail.count; computeSum(); } function computeSum() { buttonSum = countOne + countTwo; sumElem.setAttribute("sum", buttonSum); } computeSum();
Détaillons un peu le fichier main.js
:
// HTML Elements const buttonOne = document.getElementById("buttonOne"); const buttonTwo = document.getElementById("buttonTwo"); const sumElem = document.getElementById("sumComponent");
On commence par récupérer les différents WebComponent de notre page et on les assigne à des constantes.
// Listeners buttonOne.$on("data", (event) => { updateCountOne(event); }); buttonTwo.$on("data", (event) => { updateCountTwo(event); });
Puis on ajoute des écouteurs d’évènements qui vont récupérer la donnée émise par chacun de nos composants <counter-button>
et qui vont faire appel à des méthodes à chaque fois qu’un évènement est émis.
// Variables let countOne = 1; let countTwo = 10; let buttonSum = 0;
On définit les variables qui nous servent à initialiser les propriétés count
de nos deux composants <counter-button>
mais également qui vont servir à calculer la somme de nos deux composants.
// Init all attribute buttonOne.setAttribute("count", countOne); buttonTwo.setAttribute("count", countTwo);
On initialise les props
de chacun de composant <counter-button>
// Methods function updateCountOne(event) { countOne = event.detail.count; computeSum(); } function updateCountTwo(event) { countTwo = event.detail.count; computeSum(); } function computeSum() { buttonSum = countOne + countTwo; sumElem.setAttribute("sum", buttonSum); } computeSum();
Enfin nous avons trois méthodes qui vont nous permettre de mettre à jour chacune des variables et de calculer la somme des valeurs de nos différents composants <counter-button>
.
Voici le résultat attendu:
En conclusion, Svelte est une librairie intéressante et s’avère être une bonne alternative aux autres librairies front. Son principe de fonctionnement est vraiment bien pensé et performant. C’est une librairie à surveiller de près.
Nous verrons dans un prochain article comment utiliser son grand frère (Sapper) pour créer un site complet.