Plugins Vue.js: como criar os teus próprios plugins

Os plugins Vue.js são uma forma poderosa mas simples de acrescentar funcionalidades globais à tua aplicação. Servem para muita coisa, desde distribuir componentes por toda a aplicação até acrescentar-lhe capacidades adicionais como o routing ou estruturas de dados imutáveis. Neste artigo explico-te como criar os teus

Plugins Vue.js: como criar os teus próprios plugins
Resposta rápida

Um plugin Vue 3 é um objeto com um método install(app, options), instalado com app.use(). Regista componentes e diretivas, fornece valores através de app.provide() e configura a aplicação. O código Vue 2 assente em Vue.prototype e Vue.mixin já não funciona.

Um plugin Vue acrescenta capacidades a uma aplicação inteira: um componente disponível em todo o lado, uma diretiva, um valor injetável, uma configuração comum. É o mecanismo por trás do Vue Router, do Pinia e da maior parte das bibliotecas do ecossistema.

O que é um plugin

Um objeto com um método install, ou simplesmente uma função. O Vue chama-o com a instância da aplicação e as opções passadas a app.use().

src/plugins/monitor.js
export default {
  install(app, options) {
    // é aqui que tudo acontece
  }
}
O código Vue 2 já não funciona

Em Vue 2, o install recebia o construtor global Vue, e um plugin mexia em Vue.prototype, Vue.mixin ou Vue.component. Em Vue 3 já não há construtor global: o install recebe a instância criada por createApp, e tudo passa por ela. Um plugin Vue 2 copiado tal e qual falha com Cannot set properties of undefined (setting '$api') ou estoira em silêncio.

A tabela de correspondência:

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)   // preferível

Um plugin completo

Eis um plugin que regista a montagem dos componentes, expõe esse registo e fornece uma diretiva. Cobre os quatro pontos de ligação que interessa conhecer.

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

    // 1. propriedade global, acessível por this na Options API
    app.config.globalProperties.$journal = journal

    // 2. injeção: a via recomendada com <script setup>
    app.provide('journal', journal)

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

    // 4. mixin global, executado na montagem de cada componente
    app.mixin({
      mounted() {
        journal.push(`${prefixe} ${this.$options.__name ?? 'anonyme'} monté`)
      }
    })
  }
}

Instalação, com ou sem opções:

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')

Um plugin instalado duas vezes só é aplicado uma: o Vue mantém o registo dos plugins que já passaram por app.use().

provide em vez de globalProperties

app.config.globalProperties.$journal reproduz o hábito do this.$qualquerCoisa do Vue 2. Funciona, mas o this não existe em <script setup>: a propriedade só é alcançável a partir do template ou da Options API. Também escapa ao autocompletar e à tipagem.

app.provide() é o caminho certo. O componente vai buscar o valor com 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>

Para evitar colisões de nomes em texto, usa um Symbol exportado pelo plugin:

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

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

Distribuir componentes

O caso mais comum: tornar uma biblioteca de componentes disponível sem 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)
  }
}

Atenção ao compromisso: um componente registado globalmente está disponível em todo o lado, mas escapa à eliminação de código morto. Acaba no bundle mesmo que ninguém o use. Reserva o registo global para os componentes verdadeiramente omnipresentes.

Um plugin pode ser uma simples função

Se o objeto com método install te parece cerimonioso, basta uma função: o Vue chama-a com os mesmos argumentos.

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

O pacote publicável

Testa-o primeiro num projeto Vite novo. Para o distribuir no npm, exporta-o por omissão e deixa a dependência do Vue em peerDependencies, para que o projeto anfitrião forneça a sua própria cópia:

package.json
{
  "name": "gekkode-monitor",
  "type": "module",
  "main": "./dist/index.js",
  "exports": { ".": "./dist/index.js" },
  "peerDependencies": { "vue": "^3.5.0" }
}
A auto-instalação deixou de fazer sentido

Os plugins Vue 2 terminavam muitas vezes com if (window.Vue) window.Vue.use(Plugin), para se instalarem sozinhos quando a página carregava o Vue por uma tag script. Em Vue 3 já não existe um window.Vue global: este bloco não serve para nada e deve ser retirado.

O mixin global, em último recurso

O plugin de exemplo usa app.mixin para observar cada montagem. É a ferramenta certa para uma necessidade transversal como a telemetria, em que queres mesmo tocar em todos os componentes.

Para tudo o resto, prefere um composable: uma função exportada que os componentes interessados chamam. Um mixin global aplica-se em todo o lado, incluindo onde não se espera, e torna difícil descobrir a origem de um comportamento.

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

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

Testar um plugin

O Vue Test Utils aceita plugins na opção global, com as respetivas opções:

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

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

Sem opções, basta a forma curta: plugins: [monitor].

Para ir mais longe

Muitas bibliotecas do ecossistema instalam-se assim, do VueFire ao unhead para o SEO.

Os plugins só ganham sentido depois de dominares os componentes e a injeção. Se estas noções ainda são recentes, volta ao tutorial de Vue 3 para principiantes, sobretudo aos capítulos sobre os componentes e as props. Para estado partilhado, o Pinia é ele próprio um plugin, e o seu código-fonte é uma boa leitura.

Erros frequentes

Copiar um plugin Vue 2 O install recebe a instância da aplicação, já não o construtor global. Vue.prototype deixou de existir: usa app.provide() ou app.config.globalProperties.
globalProperties com script setup O this não existe em <script setup>: a propriedade só se lê a partir do template. Prefere provide e inject.
Manter o bloco de auto-instalação if (window.Vue) window.Vue.use(Plugin) já não encontra nada em Vue 3 e deve ser retirado.
Registar componentes a mais globalmente Escapam à eliminação de código morto e engordam o bundle mesmo sem serem usados.

JavaScriptVue.js

Damien Flandrin Programador web desde 2010, criador da Gekkode e do Email Impact. Cada artigo é testado num projeto real antes de ser publicado. Contacto
Newsletter

Os novos testes, tutoriais e projetos, por e-mail.

Testes reproduzíveis, código versionado, resultados datados. Nunca spam.