Plugin Vue 3: jak napisać własne rozszerzenie aplikacji

Plugin Vue rozszerza całą aplikację naraz: rejestruje komponenty i dyrektywy, wstrzykuje wspólne wartości, ustawia konfigurację. Ten artykuł pokazuje, jak napisać taki plugin w Vue 3 — metoda install(app, options), provide zamiast globalProperties, publikacja na npm i testy — oraz co trzeba zmienić w kodzie odziedziczonym po Vue 2.

Plugin Vue 3: jak napisać własne rozszerzenie aplikacji
Szybka odpowiedź

Plugin Vue 3 to obiekt z metodą install(app, options), instalowany przez app.use(). Rejestruje komponenty i dyrektywy, udostępnia wartości przez app.provide() i konfiguruje aplikację. Kod z Vue 2 oparty na Vue.prototype i Vue.mixin już nie działa.

Plugin Vue dodaje możliwości całej aplikacji naraz: komponent dostępny wszędzie, dyrektywę, wstrzykiwaną wartość, wspólną konfigurację. To ten sam mechanizm, który stoi za Vue Routerem, Pinią i większością bibliotek z ekosystemu.

Czym jest plugin

Obiektem z metodą install albo zwykłą funkcją. Vue wywołuje go z instancją aplikacji i opcjami przekazanymi do app.use().

src/plugins/monitor.js
export default {
  install(app, options) {
    // tu dzieje się wszystko
  }
}
Kod z Vue 2 już nie zadziała

W Vue 2 install dostawał globalny konstruktor Vue, a plugin grzebał w Vue.prototype, Vue.mixin czy Vue.component. W Vue 3 nie ma już globalnego konstruktora: install dostaje instancję utworzoną przez createApp i wszystko idzie przez nią. Plugin z Vue 2 przeniesiony bez zmian kończy się błędem Cannot set properties of undefined (setting '$api') albo cichą awarią.

Tabela odpowiedników:

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)   // zalecane

Kompletny plugin

Oto plugin, który zapisuje montowanie komponentów, udostępnia ten dziennik i dokłada dyrektywę. Pokazuje cztery punkty zaczepienia, które warto znać.

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

    // 1. globalna właściwość, dostępna przez this w Options API
    app.config.globalProperties.$journal = journal

    // 2. wstrzykiwanie: zalecana droga przy <script setup>
    app.provide('journal', journal)

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

    // 4. globalny mixin, uruchamiany przy montowaniu każdego komponentu
    app.mixin({
      mounted() {
        journal.push(`${prefixe} ${this.$options.__name ?? 'anonyme'} monté`)
      }
    })
  }
}

Instalacja, z opcjami albo bez:

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

Plugin zainstalowany dwa razy zostanie zastosowany tylko raz: Vue prowadzi rejestr pluginów, które już przeszły przez app.use().

provide zamiast globalProperties

app.config.globalProperties.$journal odtwarza nawyk z Vue 2, czyli this.$quelqueChose. Działa, ale w <script setup> nie ma this: właściwość jest osiągalna wyłącznie z szablonu albo z Options API. Wymyka się też autouzupełnianiu i typowaniu.

app.provide() to właściwa droga. Komponent odbiera wartość przez 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>

Żeby uniknąć kolizji nazw zapisanych jako zwykłe łańcuchy znaków, użyj Symbol eksportowanego przez plugin:

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

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

Rozdawanie komponentów

Najczęstszy przypadek: udostępnić bibliotekę komponentów bez importowania ich w każdym pliku.

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

Uwaga na kompromis: komponent zarejestrowany globalnie jest dostępny wszędzie, ale wymyka się usuwaniu martwego kodu. Ląduje w bundlu, nawet jeśli nikt go nie używa. Rejestrację globalną zostaw dla komponentów naprawdę wszechobecnych.

Plugin może być zwykłą funkcją

Jeśli obiekt z metodą install wydaje ci się przesadnie uroczysty, wystarczy funkcja: Vue wywoła ją z tymi samymi argumentami.

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

Paczka gotowa do publikacji

Najpierw przetestuj plugin w świeżym projekcie Vite. Żeby wypuścić go na npm, wyeksportuj go domyślnie i zostaw zależność od Vue w peerDependencies, dzięki temu własną kopię dostarczy projekt, który plugin instaluje:

package.json
{
  "name": "gekkode-monitor",
  "type": "module",
  "main": "./dist/index.js",
  "exports": { ".": "./dist/index.js" },
  "peerDependencies": { "vue": "^3.5.0" }
}
Autoinstalacja straciła rację bytu

Pluginy dla Vue 2 często kończyły się linijką if (window.Vue) window.Vue.use(Plugin), żeby instalować się same, gdy strona ładowała Vue znacznikiem script. W Vue 3 nie ma już globalnego window.Vue: ten blok do niczego nie służy i trzeba go usunąć.

Globalny mixin, w ostateczności

Przykładowy plugin sięga po app.mixin, żeby obserwować każde montowanie. To dobre narzędzie do potrzeb przekrojowych w rodzaju telemetrii, gdzie naprawdę chcesz dosięgnąć wszystkich komponentów.

Do wszystkiego innego wybierz composable: eksportowaną funkcję, którą wywołują zainteresowane komponenty. Globalny mixin działa wszędzie, także tam, gdzie nikt się go nie spodziewa, i utrudnia odnalezienie źródła danego zachowania.

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

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

Testowanie pluginu

Vue Test Utils przyjmuje pluginy w opcji global, razem z ich opcjami:

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

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

Bez opcji wystarczy krótka forma: plugins: [monitor].

Co dalej

Wiele bibliotek z ekosystemu instaluje się dokładnie w ten sposób, od VueFire po unhead odpowiadający za SEO.

Pluginy nabierają sensu dopiero wtedy, gdy masz opanowane komponenty i wstrzykiwanie zależności. Jeśli te pojęcia są jeszcze świeże, wróć do kursu Vue 3 dla początkujących, zwłaszcza do rozdziałów o komponentach i propsach. A jeśli szukasz współdzielonego stanu: Pinia sama jest pluginem i jej źródła to dobra lektura.

Częste błędy

Przeniesienie pluginu z Vue 2 install dostaje instancję aplikacji, a nie globalny konstruktor. Vue.prototype już nie istnieje: użyj app.provide() albo app.config.globalProperties.
globalProperties w script setup W <script setup> nie ma this: właściwość odczytasz tylko z szablonu. Lepsze są provide i inject.
Pozostawiony blok autoinstalacji if (window.Vue) window.Vue.use(Plugin) nie ma już czego szukać w Vue 3 i trzeba go usunąć.
Zbyt wiele komponentów rejestrowanych globalnie Wymykają się usuwaniu martwego kodu i pogrubiają bundle, nawet gdy nikt ich nie używa.

JavaScriptVue.js

Damien Flandrin Web developer od 2010 roku, twórca Gekkode i Email Impact. Każdy artykuł jest sprawdzany na prawdziwym projekcie przed publikacją. Kontakt
Newsletter

Nowe testy, poradniki i projekty — e-mailem.

Powtarzalne testy, wersjonowany kod, datowane wyniki. Nigdy spamu.