Utiliser n'importe quelle bibliothèque Javascript avec Vue.js

Lodash, Moment, Axios, Async … Ce sont toutes des librairies JavaScript très utiles que vous aurez certainement besoin d’utiliser dans vos applications Vue.js.

Mais au fur et à mesure que votre projet grandit, vous allez de plus en plus devoir séparer votre code dans de multiples fichiers de composants et de modules. Vous pouvez aussi vouloir exécuter votre application dans différents environnements pour autoriser un rendu coté serveur.

Tant que vous n’aurez pas trouvé un moyen facile et robuste d’inclure ces librairies au niveau de vos composants et modules, elles vont devenir une véritable nuisance.

Ceci est la traduction et l’adaptation par mes soins d’un article paru sur le blog vuejsdevelopers.com le 22 avril 2017, car je tenais à partager ce genre de bonnes pratiques.

Comment ne pas inclure une bibliothèque dans un projet Vue

Variable globale

Le premier réflexe pour ajouter une librairie à votre projet est de définir une variable globale attachée à l’objet window :

entry.js

import window._ from 'lodash'

MyComponent.vue

export default {
  mounted () {
    console.log(_.isEmpty() ? 'Lodash est présent !' : 'Pas de Lodash ...')
  }
}

Les raisons pour lesquelles cette méthode est à déconseiller sont nombreuses, mais surtout, de manière plus spécifique à nos préoccupations, cela ne fonctionnera pas avec un rendu coté serveur : en effet, lorsque l’application est rendue sur le serveur, l’objet window ne sera pas défini et essayer d’accéder à une de ses propriétés aboutira à une erreur.

Importation dans chaque fichier

Une autre méthode pourrait être l’importation de la librairie dans chaque fichier :

MyComponent.vue

import _ from 'lodash'
export default {
  mounted () {
    console.log(_.isEmpty() ? 'Lodash présent ici !' : 'Pas de Lodash ...')
  }
}

Cela fonctionne, mais ce n’est pas très propre et c’est tout simplement un nid à problèmes : vous aurez à vous souvenir de l’importer dans chaque fichier où il y en a besoin, et ne pas oublier de l’enlever si vous ne vous en servez plus. De plus, si vous n’avez pas finement paramétré votre outil de build, vous avez de fortes chances de vous retrouver avec plusieurs copies de la même librairie dans votre application, qui vont faire grossir inutilement la taille des fichiers à transférer aux navigateurs.

La bonne méthode

Le moyen le plus propre et le plus robuste d’utiliser une librairie JavaScript dans un projet Vue est de l’attacher à une propriété de l’objet prototype de Vue. Voyons comment le faire pour ajouter par exemple la librairie de temps et date Moment à un projet Vue :

entry.js

import moment from 'moment'
Object.defineProperty(Vue.prototype, '$moment', { value : moment })

Comme tous les composants héritent leurs méthodes de l’objet prototype Vue, cela va automatiquement mettre Moment à disposition au niveau de tous les composants sans variable globale et sans rien importer manuellement. La librairie peut simplement être accédée dans chaque instance par this.$moment :

MyComponent.vue

export default {
  mounted () {
    console.log('Il est ' + this.$moment().format('HH:mm'))
  }
}

Prenons un moment pour en comprendre l’intérêt :

Object.defineProperty

On pourrait normalement définir une propriété d’objet prototype comme ceci :

import moment from 'moment'
Vue.prototype.$moment = moment

Vous pourriez faire cela, mais en utilisant Object.defineProperty à la place nous sommes capable de le faire avec un descripteur. Ce descripteur nous permet de définir certains détails de bas niveau comme le fait pour la propriété d’être en lecture seule ou pas, comment elle apparaît lors d’énumération dans une boucle for et plus encore.

On ne se préoccupe normalement pas de cela dans nos activités JavaScript de tous les jours car dans 99% des cas nous n’avons pas besoin de ce niveau d’assignation des propriétés. Mais ici, cela nous donne un net avantage : les propriétés déclarées avec un descripteur sont en lecture seule par défaut.

Cela signifie qu’un développeur en manque de café (probablement vous …) ne sera pas capable de faire quelque chose d’idiot et de tout casser comme cela dans un composant :

this.$http = 'Je fais une bêtise en assignant ceci à $http !'
this.$http.get('/')... 
/* => TypeError : this.$http.get is not a function */

$

Vous avez noté que nous avons préfixé le nom de la propriété avec le signe dollar « $ ». Vous avez probablement déjà vu d’autres propriétés et méthodes comme $refs, $on, $mount, etc. qui ont ce préfixe aussi.

Même s’il n’est pas obligatoire, ce préfixe est ajouté pour rappeler aux développeurs en manque de café (vous de nouveau …) que l’on a affaire à une propriété qui est une API publique que vous êtes invités à utiliser, au contraire d’autres propriétés de l’instance qui ne serviront probablement qu’à la cuisine interne de Vue.

Comme nous avons affaire à un langage basé sur les prototypes, il n’y a pas de (véritables) classes en JavaScript, et donc pas de variables « publiques » ou « privées » ni de méthodes « statiques ». Cette convention est un substitut « doux » qu’il est recommandable de suivre.

this

Vous aurez aussi noté que pour utiliser la librairie nous appelons this.nomDeLibrairie, ce qui n’est probablement pas une surprise puisque c’est maintenant une méthode de l’instance.

Une des conséquences est que contrairement à une variable globale vous devez vous assurer d’être dans le bon scope avant de l’utiliser. Notamment, à l’intérieur de méthodes de callback vous ne pourrez accéder à this où habite votre librairie…

On peut bien entendu passer une référence à l’objet this en paramètre du callback. Mais pour ceux qui codent en ES6, cela se fait automatiquement en écrivant le callback sous forme de fonction fat arrow, (param => {}) qui permet de conserver le bon scope :

this.$http.get('/').then(res => {
  if (res.status !== 200) {
    this.$http.get('/')...
    // Ne fonctionne que dans un callback fat arrow
  }
})

Pourquoi ne pas en faire un plugin ?

Si vous envisagez d’utiliser la librairie sur plusieurs projets Vue, ou si vous voulez partager cela avec le monde entier, vous pouvez l’intégrer dans votre propre plugin.

Un plugin nous abstrait de la complexité et nous permet de simplement faire la chose suivante pour ajouter notre librairie :

import MyLibraryPlugin from 'my-library-plugin'
Vue.use(MyLibraryPlugin)

Avec ces deux lignes, vous pouvez utiliser votre librairie dans n’importe quel composant comme il possible d’utiliser de cette manière Vue Router, VueX et d’autres plugins utilisant Vue.use.

Ecrire un plugin

Créez d’abord un fichier pour votre plugin. Dans cet exemple, nous allons faire un plugin pour ajouter la bibliothèque Axios à toutes vos instances Vue et composants.

La principale chose à retenir est qu’un plugin doit exposer une méthode install qui prend le constructeur Vue comme premier argument :

export default {
  install (Vue) {
    // faire des choses ...
  }
}

Nous pouvons maintenant utiliser notre méthode précédente pour ajouter la librairie à notre objet prototype :

axios.js

import axios from 'axios'
export default {
  install (Vue) {
    Object.defineProperty(Vue.prototype, '$http', { value: axios })
  }
}

Nous n’avons maintenant plus besoin que de la méthode use de Vue pour ajouter notre librairie à un projet. Par exemple, nous pouvons maintenant ajouter la bibliothèque Axios aussi facilement que cela :

import AxiosPlugin from './axios.js'
Vue.use(AxiosPlugin)

new Vue({
  mounted () {
    console.log(this.$http ? 'Axios fonctionne !' : 'Bof ...')
  }
})

En bonus : arguments optionnels de plugin

La méthode install de votre plugin peut prendre des arguments optionnels. Certains développeurs pourraient ne pas aimer appeler leur méthode d’instance Axios $http dans la mesure où Vue Resource utilise aussi ce nom. Utilisons donc un argument optionnel pour leur permettre de changer ce nom comme ils le veulent :

axios.js

import axios from 'axios'
export default {
  install (Vue, name = '$http') {
    Object.defineProperty(Vue.prototype, name, { value: axios })
  }
}

entry.js

import AxiosPlugin from './axios.js'
Vue.use(AxiosPlugin, '$axios')

new Vue({
  mounted () {
    console.log(this.$axios ? 'Axios fonctionne !' : 'Bof ...')
  }
})

Et voilà, on peut maintenant nommer la bibliothèque en $axios, ou autre chose, comme on le désire.

J’espère que cet article vous aura éclairé sur ces « bonnes pratiques ». Si vous avez des questions ou des remarques, n’hésitez pas à m’envoyer un message.

A bientôt !