Plugin Vue.js: come creare il tuo plugin per Vue 3

I plugin Vue.js sono un modo potente ma semplice di aggiungere funzionalità globali alla tua applicazione. Gli usi sono molti: distribuire componenti a livello di applicazione, aggiungere capacità come il routing o strutture dati immutabili. In questo articolo ti spiego come crearli.

Plugin Vue.js: come creare il tuo plugin per Vue 3
Risposta rapida

Un plugin Vue 3 è un oggetto con un metodo install(app, options), installato con app.use(). Registra componenti e direttive, fornisce valori con app.provide() e configura l'applicazione. Il codice Vue 2 basato su Vue.prototype e Vue.mixin non funziona più.

Un plugin Vue aggiunge funzionalità a un’intera applicazione: un componente disponibile ovunque, una direttiva, un valore da iniettare, una configurazione comune. È il meccanismo dietro Vue Router, Pinia e gran parte delle librerie dell’ecosistema.

Che cos’è un plugin

Un oggetto con un metodo install, oppure una semplice funzione. Vue lo chiama passandogli l’istanza dell’applicazione e le opzioni date a app.use().

src/plugins/monitor.js
export default {
  install(app, options) {
    // qui succede tutto
  }
}
Il codice Vue 2 non funziona più

In Vue 2 install riceveva il costruttore globale Vue, e un plugin lavorava su Vue.prototype, Vue.mixin o Vue.component. In Vue 3 il costruttore globale non esiste più: install riceve l’istanza creata da createApp e tutto passa da lì. Un plugin Vue 2 ricopiato tale e quale fallisce con Cannot set properties of undefined (setting '$api') o si pianta in silenzio.

La tabella di corrispondenza:

javascript
// 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)   // preferibile

Un plugin completo

Ecco un plugin che registra il mount dei componenti, espone questo registro e fornisce una direttiva. Illustra i quattro punti di aggancio da conoscere.

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

    // 1. proprietà globale, raggiungibile con this nella Options API
    app.config.globalProperties.$journal = journal

    // 2. inject: la via consigliata con <script setup>
    app.provide('journal', journal)

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

    // 4. mixin globale, eseguito al mount di ogni componente
    app.mixin({
      mounted() {
        journal.push(`${prefixe} ${this.$options.__name ?? 'anonyme'} monté`)
      }
    })
  }
}

Installazione, con o senza opzioni:

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 installato due volte viene applicato una volta sola: Vue tiene traccia dei plugin già passati da app.use().

provide invece di globalProperties

app.config.globalProperties.$journal ricalca l’abitudine Vue 2 del this.$qualcosa. Funziona, ma this non esiste in <script setup>: la proprietà è raggiungibile solo dal template o dalla Options API. Ed è fuori dalla portata dell’autocompletamento e della tipizzazione.

app.provide() è la strada giusta. Il componente recupera il valore con 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>

Per evitare collisioni tra nomi in forma di stringa, usa un Symbol esportato dal plugin:

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

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

Distribuire componenti

Il caso più frequente: rendere una libreria di componenti disponibile senza 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)
  }
}

Attenzione al compromesso: un componente registrato globalmente è disponibile ovunque, ma sfugge al tree-shaking. Finisce nel bundle anche se non lo usa nessuno. Riserva la registrazione globale ai componenti davvero onnipresenti.

Un plugin può essere una semplice funzione

Se l’oggetto con il metodo install ti sembra troppo cerimonioso, basta una funzione: Vue la chiama con gli stessi argomenti.

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

Il pacchetto pubblicabile

Provalo prima in un progetto Vite nuovo. Per distribuirlo su npm esportalo come default e lascia la dipendenza da Vue nelle peerDependencies, così è il progetto ospite a fornire la propria copia:

package.json
{
  "name": "gekkode-monitor",
  "type": "module",
  "main": "./dist/index.js",
  "exports": { ".": "./dist/index.js" },
  "peerDependencies": { "vue": "^3.5.0" }
}
L’autoinstallazione non ha più senso

I plugin Vue 2 finivano spesso con if (window.Vue) window.Vue.use(Plugin), per installarsi da soli quando la pagina caricava Vue con un tag script. In Vue 3 non esiste più un window.Vue globale: quel blocco non serve a niente e va tolto.

Il mixin globale, come ultima risorsa

Il plugin di esempio usa app.mixin per osservare ogni mount. È lo strumento giusto per un’esigenza trasversale come la telemetria, dove vuoi davvero toccare tutti i componenti.

Per tutto il resto scegli un composable: una funzione esportata che chiamano i componenti interessati. Un mixin globale si applica ovunque, anche dove non te lo aspetti, e rende difficile risalire all’origine di un comportamento.

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

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

Testare un plugin

Vue Test Utils accetta i plugin nell’opzione global, con le loro opzioni:

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

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

Senza opzioni basta la forma breve: plugins: [monitor].

Per approfondire

Molte librerie dell’ecosistema si installano così, da VueFire a unhead per il posizionamento.

I plugin acquistano senso una volta padroneggiati i componenti e l’injection. Se questi concetti sono ancora freschi, riprendi il tutorial Vue 3 per principianti, in particolare i capitoli su componenti e props. Per uno stato condiviso, Pinia è a sua volta un plugin, e leggerne il sorgente è tempo ben speso.

Errori frequenti

Ricopiare un plugin Vue 2 install riceve l'istanza dell'applicazione, non più il costruttore globale. Vue.prototype non esiste più: usa app.provide() o app.config.globalProperties.
globalProperties con script setup this non esiste in <script setup>: la proprietà è leggibile solo dal template. Meglio provide e inject.
Tenere il blocco di autoinstallazione if (window.Vue) window.Vue.use(Plugin) in Vue 3 non trova più nessun oggetto e va tolto.
Registrare troppi componenti globalmente Sfuggono al tree-shaking e appesantiscono il bundle anche se inutilizzati.

JavaScriptVue.js

Damien Flandrin Sviluppatore web dal 2010, creatore di Gekkode e di Email Impact. Ogni articolo è testato su un progetto reale prima della pubblicazione. Contatti
Newsletter

I nuovi test, tutorial e progetti, via e-mail.

Test riproducibili, codice versionato, risultati datati. Mai spam.