Comment créer vos propres plugins Vue.js

Les plugins Vue.js constituent un moyen puissant mais simple d’ajouter des fonctionnalités globales à votre application. Ils ont une variété d’utilisations, de la distribution de composants à l’échelle de l’application à l’ajout de capacités supplémentaires telles que le routage et les structures de données immuables à votre application. Dans cet article je vais vous expliquez

Comment créer vos propres plugins Vue.js
Réponse rapide

Un plugin Vue 3 est un objet doté d’une méthode install(app, options), installé avec app.use(). Il enregistre composants et directives, fournit des valeurs par app.provide() et configure l’application. Le code Vue 2 à base de Vue.prototype et Vue.mixin ne fonctionne plus.

Un plugin Vue ajoute des capacités à toute une application : un composant disponible partout, une directive, une valeur injectable, une configuration commune. C’est le mécanisme derrière Vue Router, Pinia et la plupart des bibliothèques de l’écosystème.

Ce qu’est un plugin

Un objet doté d’une méthode install, ou une simple fonction. Vue l’appelle avec l’instance d’application et les options passées à app.use().

src/plugins/monitor.js
export default {
  install(app, options) {
    // tout se passe ici
  }
}
Le code Vue 2 ne fonctionne plus

En Vue 2, install recevait le constructeur global Vue, et un plugin manipulait Vue.prototype, Vue.mixin ou Vue.component. En Vue 3, il n’y a plus de constructeur global : install reçoit l’instance créée par createApp, et tout passe par elle. Un plugin Vue 2 recopié tel quel échoue avec Cannot set properties of undefined (setting '$api') ou un plantage silencieux.

La table de correspondance :

javascript
// Vue 2                          // Vue 3
Vue.use(Plugin)                   app.use(Plugin)
Vue.component('X', X)             app.component('X', X)
Vue.directive('x', x)             app.directive('x', x)
Vue.mixin({ … })                  app.mixin({ … })
Vue.prototype.$api = api          app.config.globalProperties.$api = api
                                  app.provide('api', api)   // préférable

Un plugin complet

Voici un plugin qui journalise le montage des composants, expose ce journal et fournit une directive. Il illustre les quatre points d’accroche à connaître.

src/plugins/monitor.js
export default {
  install(app, options = {}) {
    const prefixe = options.prefixe ?? '[monitor]'
    const journal = []

    // 1. propriété globale, atteignable par this dans l’Options API
    app.config.globalProperties.$journal = journal

    // 2. injection : la voie recommandée avec <script setup>
    app.provide('journal', journal)

    // 3. directive globale
    app.directive('surligne', {
      mounted(el, binding) {
        el.style.backgroundColor = binding.value ?? '#fef08a'
      }
    })

    // 4. mixin global, exécuté au montage de chaque composant
    app.mixin({
      mounted() {
        journal.push(`${prefixe} ${this.$options.__name ?? 'anonyme'} monté`)
      }
    })
  }
}

Installation, avec ou sans options :

src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import monitor from './plugins/monitor.js'

const app = createApp(App)

app.use(monitor, { prefixe: '[gekkode]' })
app.mount('#app')

Un plugin installé deux fois n’est appliqué qu’une seule fois : Vue tient le registre des plugins déjà passés par app.use().

provide plutôt que globalProperties

app.config.globalProperties.$journal reproduit l’habitude Vue 2 du this.$quelqueChose. Cela fonctionne, mais this n’existe pas dans <script setup> : la propriété n’est atteignable que depuis le gabarit ou l’Options API. Elle échappe aussi à l’autocomplétion et au typage.

app.provide() est la bonne voie. Le composant récupère la valeur par inject :

src/components/Journal.vue
<script setup>
import { inject } from 'vue'

const journal = inject('journal')
</script>

<template>
  <p>{{ journal.length }} composants montés</p>
</template>

Pour éviter les collisions de noms de chaîne, utilisez un Symbol exporté par le plugin :

src/plugins/monitor.js
export const cleJournal = Symbol('journal')

export default {
  install(app) {
    app.provide(cleJournal, [])
  }
}

Distribuer des composants

Le cas le plus courant : rendre une bibliothèque de composants disponible sans import.

src/plugins/ui.js
import GkBouton from '../components/GkBouton.vue'
import GkCarte from '../components/GkCarte.vue'

export default {
  install(app, { prefixe = 'Gk' } = {}) {
    app.component(`${prefixe}Bouton`, GkBouton)
    app.component(`${prefixe}Carte`, GkCarte)
  }
}

Attention au compromis : un composant enregistré globalement est disponible partout, mais échappe au retrait de code mort. Il finit dans le bundle même si personne ne l’utilise. Réservez l’enregistrement global aux composants réellement omniprésents.

Un plugin peut être une simple fonction

Si l’objet à méthode install vous paraît cérémonieux, une fonction suffit : Vue l’appelle avec les mêmes arguments.

src/plugins/titre.js
export default function titrePlugin(app, options) {
  app.config.globalProperties.$titre = options.titre
}

Le paquet publiable

Testez-le d’abord dans un projet Vite neuf. Pour le distribuer sur npm, exportez-le par défaut et laissez la dépendance à Vue en peerDependencies, afin que le projet hôte fournisse sa propre copie :

package.json
{
  "name": "gekkode-monitor",
  "type": "module",
  "main": "./dist/index.js",
  "exports": { ".": "./dist/index.js" },
  "peerDependencies": { "vue": "^3.5.0" }
}
L’auto-installation n’a plus lieu d’être

Les plugins Vue 2 se terminaient souvent par if (window.Vue) window.Vue.use(Plugin), pour s’installer seuls quand la page chargeait Vue par une balise script. Il n’y a plus de window.Vue global en Vue 3 : ce bloc ne sert plus à rien et doit être retiré.

Le mixin global, en dernier recours

Le plugin d’exemple utilise app.mixin pour observer chaque montage. C’est le bon outil pour un besoin transversal comme la télémétrie, où l’on veut vraiment toucher tous les composants.

Pour tout le reste, préférez un composable : une fonction exportée que les composants intéressés appellent. Un mixin global s’applique partout, y compris là où on ne l’attend pas, et rend l’origine d’un comportement difficile à retrouver.

src/composables/useJournal.js
import { inject } from 'vue'

export function useJournal() {
  const journal = inject('journal')
  return {
    journal,
    ajouter: (message) => journal.push(message)
  }
}

Tester un plugin

Vue Test Utils accepte les plugins dans l’option global, avec leurs options :

javascript
import { mount } from '@vue/test-utils'
import monitor from '@/plugins/monitor.js'

mount(MonComposant, {
  global: { plugins: [[monitor, { prefixe: '[test]' }]] }
})

Sans options, la forme courte suffit : plugins: [monitor].

Pour aller plus loin

Beaucoup de bibliothèques de l’écosystème s’installent ainsi, de VueFire à unhead pour le référencement.

Les plugins prennent tout leur sens une fois les composants et l’injection maîtrisés. Si ces notions sont fraîches, reprenez le tutoriel Vue 3 pour les débutants, en particulier les chapitres sur les composants et les props. Pour un état partagé, Pinia est lui-même un plugin, et sa source est une bonne lecture.

Erreurs fréquentes

Recopier un plugin Vue 2 install reçoit l’instance d’application, plus le constructeur global. Vue.prototype n’existe plus : utilisez app.provide() ou app.config.globalProperties.
globalProperties avec script setup this n’existe pas dans <script setup> : la propriété n’est lisible que depuis le gabarit. Préférez provide et inject.
Garder le bloc d’auto-installation if (window.Vue) window.Vue.use(Plugin) n’a plus d’objet à trouver en Vue 3 et doit être retiré.
Enregistrer trop de composants globalement Ils échappent au retrait de code mort et alourdissent le bundle même inutilisés.

JavaScriptVue.js

Damien Flandrin Développeur web depuis 2010, créateur de Gekkode et d’Email Impact. Chaque article est testé sur un projet réel avant publication. Contact
Newsletter

Les nouveaux tests, tutoriels et projets, par e-mail.

Tests reproductibles, code versionné, résultats datés. Jamais de spam.