Article écrit par Tom Panier
Vous l’attendiez, vous en rêviez, voici la troisième et dernière partie de cette introduction au framework Vue.js ! Cette fois encore, nous allons travailler sur la merveilleuse application que nous avons ébauchée ensemble et qui nous permet, je vous le rappelle, de gérer facilement une collection d’URLs d’images ou de vidéos, et de copier ces dernières dans le presse-papiers en un clic pour pouvoir harceler votre entourage !
Aujourd’hui, nous allons transformer notre proof of concept en une application construite et industrialisée dans les règles de l’art. Sans perdre une minute de plus, entrons si vous le voulez bien dans le vif du sujet !
Une app, une vraie
Si notre unique fichier JavaScript a rempli son office jusqu’ici, il est pour nous grand temps de passer à l’étape suivante : du code correctement découplé, avec notamment un fichier .vue
par composant. L’ennui, c’est que la compilation de ces fichiers en code compréhensible par le navigateur est potentiellement chronophage à mettre en place à la main, à grands coups de configuration Webpack et Babel. Fort heureusement, l’écosystème Vue.js comprend un outil en ligne de commande permettant de construire un projet avec tout ce qu’il faut en un tournemain. Commençons donc par l’installer :
$ npm install -g vue-cli
Puis exécutons-le directement dans le dossier de notre application, et laissons-nous guider :
$ vue init webpack . ? Generate project in current directory? Yes ? Project name memebox ? Project description Meme collection displayer with easy copypasting ? Author Tom Panier <tpanier@synbioz.com> ? Vue build standalone ? Install vue-router? No ? Use ESLint to lint your code? No ? Set up unit tests No ? Setup e2e tests with Nightwatch? No ? Should we run `npm install` for you after the project has been created? (recommended) npm vue-cli · Generated "memebox". To get started: npm run dev Documentation can be found at https://vuejs-templates.github.io/webpack
Que s’est-il passé ? Nous constatons que beaucoup de fichiers ont été créés, et que notre index.html
, notamment, a été remplacé. Pour l’heure, lançons, comme il nous est indiqué, npm run dev
(ou npm start
, ce qui revient au même) :
DONE Compiled successfully in 3147ms I Your application is running here: http://localhost:8080
Rendons-nous donc sur http://localhost:8080
:
Effectivement, notre application a « disparu » au profit d’une page de garde, certes élégante, mais peu utile en pratique ! Pas de panique, nous allons voir ensemble comment la remettre sur pied.
Composant monofichier
Lors de la partie 2 de ce tutoriel, nous avions mis en place un composant Meme
destiné à isoler la logique d’un élément de notre grille d’images et de vidéos. Dans cette nouvelle configuration, ce dernier va donc fort logiquement être placé dans un fichier autonome, à savoir src/components/Meme.vue
:
<template> <div class="meme-container" :class="{ 'meme-container-clicked': clicked }" @click="copy" @mouseleave="hover" > <iframe v-if="isEmbed" class="meme" :src="previewUrl" frameborder="0" allowfullscreen ></iframe> <div v-else class="meme" :style="{ backgroundImage: 'url(' + previewUrl + ')' }" ></div> </div> </template> <script> export default { props: { url: String }, data: () => ({ clicked: false }), computed: { isEmbed() { return /youtu/.test(this.url); }, previewUrl() { return getEmbedUrl(this.url) || this.url; } }, methods: { copy() { copyToClipboard(this.url); this.clicked = true; }, hover() { this.clicked = false; } } }; </script>
Un composant monofichier (en anglais, SFC) est ainsi constitué de deux balises élémentaires :
<template>
, contenant comme de raison le squelette du composant (équivalent du champ éponyme dans notre ancien fichierapp.js
)<script>
, qui doit exposer un module ES6 contenant le reste de la configuration du composant (là aussi, c’est strictement identique à ce que nous avions précédemment écrit)
Il est également possible de déclarer une balise <style>
pour injecter du CSS (ou des langages préprocessés dérivés de ce dernier, d’ailleurs).
Il ne reste qu’un détail à régler : notre code fait référence à nos deux fonctions getEmbedUrl
et copyToClipboard
, qui ne sont pour le moment pas disponibles dans ce contexte ! Nous pourrions les insérer au début de la balise <script>
, mais n’avions-nous pas décidé de faire les choses proprement ? Créons donc deux fichiers dans le dossier src
, respectivement getEmbedUrl.js
:
export default function getEmbedUrl(url) { let ytId = url.match(/youtu.be/([a-zA-Z0-9_-]+)/); ytId = ytId || url.match(/youtube.com.*(?|&)v=([a-zA-Z0-9_-]+)/); return ytId ? "https://www.youtube.com/embed/" + ytId.pop() : null; }
Et copyToClipboard.js
:
export default function copyToClipboard(text) { const textArea = document.createElement("textarea"); textArea.value = text; document.body.appendChild(textArea); textArea.select(); document.execCommand("copy"); document.body.removeChild(textArea); }
Il ne nous reste plus qu’à importer ces deux nouveaux modules là où nous en avons besoin, à savoir dans notre composant :
import getEmbedUrl from "../getEmbedUrl"; import copyToClipboard from "../copyToClipboard"; export default { // ... };
Point d’entrée et composant racine
Si nous ôtons d’app.js
le code que nous venons de déplacer, et excluons le tableau où nous stockons nos URLs, il ne reste pas grand-chose :
new Vue({ el: "#app", data: { urls }, components: { Meme } });
Il s’agit, en somme, du point d’entrée de notre application ; nous pouvons retrouver du code très similaire dans le fichier src/main.js
généré par vue-cli
:
import Vue from "vue"; import App from "./App"; Vue.config.productionTip = false; new Vue({ el: "#app", render: h => h(App) });
Ouvrons maintenant le fichier src/App.vue
, référencé ci-dessus (et ôtons-en au passage la balise <style>
, inutile dans notre cas) :
<template> <div id="app"> <img src="./assets/logo.png" /> <HelloWorld /> </div> </template> <script> import HelloWorld from "./components/HelloWorld"; export default { components: { HelloWorld } }; </script>
Ce composant (car c’en est bien un), référencé directement par le point d’entrée, constitue la racine des vues de notre application : son rendu est effectué en premier, et de lui découle le rendu de tous les autres composants. Modifions-le un brin, en reprenant le code qui se trouvait précédemment dans notre index.html
:
<template> <div id="app" class="memebox"> <meme v-for="url in urls" :key="url" :url="url" /> </div> </template> <script> import Meme from "./components/Meme"; export default { components: { Meme } }; </script>
Le composant déclarant lui-même ses dépendances (en l’occurrence, le composant Meme
), nous n’avons pas besoin de modifier le code du point d’entrée !
Il reste toutefois, là encore, un problème de référence : la fameuse variable urls
censée contenir notre précieuse collection…
Configuration et coup de polish
Il est temps de dire au revoir pour de bon à notre fichier app.js
: tout son contenu a été déplacé, hormis le tableau contenant les URLs des images et vidéos à afficher sur l’application. Pour le remplacer, créons à la racine du projet un fichier urls.json
:
[ "http://sarakha63-domotique.fr/wp-content/uploads/2017/03/Shut-up-and-take-my-money.jpg", "https://www.youtube.com/watch?v=XMdoGqcnBoo" ]
Ensuite, chargeons-le directement comme un module au sein du composant racine, à savoir src/App.vue
, et référençons son contenu comme un membre de data
, là encore tel que nous le faisions dans index.html
précédemment :
import urls from "../urls"; import Meme from "./components/Meme"; export default { data: () => ({ urls }), components: { Meme } };
Notez que data
doit être une fonction qui retourne un objet, afin d’éviter que plusieurs instances d’un même composant partagent leurs données.
Vous pouvez dès lors supprimer app.js
, ainsi que le dossier src/assets
et le fichier src/components/HelloWorld.vue
. Si vous avez laissé tourner la commande npm run dev
tout à l’heure, retournez sur votre navigateur, et…
L’application générée par vue-cli
dispose en effet du hot reloading : en développement, toute modification dans le code est automatiquement répercutée dans le navigateur, et ce en conservant l’état courant !
Comme vous le constatez, il ne manque guère que le CSS à notre application pour fonctionner de nouveau comme avant ; plusieurs choix s’offrent à nous, notamment la balise <style>
des composants que j’ai mentionnée plus haut, mais nous allons opter ici pour la simplicité et référencer directement app.css
dans index.html
.
Déplacez donc le fichier dans le dossier static
, prévu à cet effet :
$ mv app.css static/
Et ajoutez cette ligne à l’intérieur de la balise <head>
:
<link rel="stylesheet" href="/static/app.css" />
Bingo !
Bonus 1 : versioning de la configuration
Ce serait dommage de versionner urls.json
: votre collection ne sera pas celle d’un·e autre utilisateur·rice. Un pattern simple à appliquer dans un tel cas est le suivant :
- versionner un fichier d’exemple, nommé
urls.json.dist
- ajouter
urls.json
au.gitignore
, pour éviter que lui-même soit versionné - indiquer dans la documentation et/ou automatiser via un script la copie du premier vers le second
Bonus 2 : grid finesse
Nous allons améliorer un brin le rendu de notre page, afin d’éviter qu’un élément se retrouve seul en dernière ligne dans certains cas. Ajoutez le code suivant à app.css
:
.meme-container:nth-last-child(2):nth-child(10n) { // 10 éléments par ligne min-width: 50%; }
Bonus 3 : support direct des embed YouTube
Je vous propose une ultime retouche : le support des URLs de la forme https://www.youtube.com/embed/
, y compris avec des paramètres en query string. Cela aura un avantage incommensurable : le fait de pouvoir préciser un début et une fin à la vidéo YouTube partagée !
Voici un exemple pas piqué des hannetons.
Afin de pouvoir nager définitivement dans le bonheur, voici la modification à apporter à src/getEmbedUrl.js
:
// Avant : let ytId = url.match(/youtu.be/([a-zA-Z0-9_-]+)/); // Après : let ytId = url.match(/(youtu.be|youtube.com/embed)/([a-zA-Z0-9_-]+)/);
Ayé
Nous en avons fini ! Notre application est désormais fonctionnelle et construite selon les standards de l’industrie — en tout cas, c’est un bon début : nous pourrions envisager de tester unitairement nos méthodes et composants, ou encore d’automatiser les vérifications syntaxiques et stylistiques grâce à ESLint (dans les deux cas, je le rappelle, vue-cli
nous mâche le travail de mise en place des outils nécessaires). Nous explorerons certainement ces possibilités lors de prochains articles.
Si cela vous intéresse, vous pouvez retrouver sur Github l’application terminée.
Quant à moi, je vous dis à très bientôt pour de nouvelles aventures avec Vue.js !