Go to Hackademy website

Introduction à Vue.js, partie 3 — du POC à l'état de l'art

Tom Panier

Posté par Tom Panier dans les catégories front

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 :

Capture%20d%E2%80%99e%CC%81cran%202018-03-23%20a%CC%80%2014.47.00_thumb_650.png

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 fichier app.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…

Capture%20d%E2%80%99e%CC%81cran%202018-03-23%20a%CC%80%2016.13.28_thumb_650.png

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 !

Capture%20d%E2%80%99e%CC%81cran%202018-03-23%20a%CC%80%2017.30.51_thumb_650.png

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 !


L’équipe Synbioz.
Libres d’être ensemble.

Articles connexes

Jeu de la vie et rendus de la mort

09/09/2019

Bonjour à tous, aujourd’hui je vous propose de revoir un classique du monde du développement, le jeu de la vie. Automate cellulaire plus qu’un vrai jeu, c’est avant tout un algorithme qui va nous...

Houdini, CSS by JS

21/03/2019

Bonjour à tous, bienvenue dans le monde magique de l’illusion et des faux-semblants, où un background peut souvent en cacher un autre. Que le rideau se lève le temps d’apercevoir ce qui se cache...

Architecture en trois tiers d'une application Vue.js

21/02/2019

Ça commence à faire un petit moment que je vous bassine avec Vue.js, et que je vous fais construire des single-page applications en s’appuyant dessus. Néanmoins, nous n’avons jamais réellement parlé...

Une bibliothèque pour gérer l'authentification avec Vue.js, partie 2 — en route vers HTTP

08/02/2019

À l’heure où j’écris ces lignes, il m’est difficile de prévoir le temps qu’il fera quand vous les lirez. Laissons donc cette fois-ci les considérations météorologiques de côté et replongeons-nous...