← Курс/Плагины Vue: расширение приложения#232 из 257+25 XP

Плагины Vue: расширение приложения

Что такое плагин Vue

Плагин — это способ добавить глобальную функциональность в Vue-приложение. Это может быть глобальный компонент, директива, метод, provide/inject значение или любая инициализационная логика.

Структура плагина

Плагин — это объект с методом install или просто функция:

// Вариант 1: объект с install
const myPlugin = {
  install(app, options) {
    // app — экземпляр Vue приложения
    // options — параметры, переданные при app.use(plugin, options)

    // Добавить глобальный компонент
    app.component('MyButton', MyButtonComponent)

    // Добавить глобальную директиву
    app.directive('focus', { mounted: (el) => el.focus() })

    // Добавить глобальное свойство
    app.config.globalProperties.$http = axios

    // Provide для всего приложения
    app.provide('i18n', createI18n(options.locale))
  }
}

// Вариант 2: функция
const myPlugin = (app, options) => {
  app.provide('config', options)
}

Подключение через app.use()

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// Без опций
app.use(myPlugin)

// С опциями
app.use(i18nPlugin, { locale: 'ru' })
app.use(routerPlugin, { routes })

app.mount('#app')

Важно: app.use() вызывает install один раз. Повторный вызов с тем же плагином игнорируется.

Создание собственного плагина

// plugins/notification.js
export const NotificationPlugin = {
  install(app, options = {}) {
    const { timeout = 3000, position = 'top-right' } = options

    const notifications = ref([])

    // Глобальный метод
    app.config.globalProperties.$notify = (message, type = 'info') => {
      const id = Date.now()
      notifications.value.push({ id, message, type })
      setTimeout(() => {
        notifications.value = notifications.value.filter(n => n.id !== id)
      }, timeout)
    }

    // Доступ через inject
    app.provide('notifications', {
      list: notifications,
      add: app.config.globalProperties.$notify,
    })

    // Глобальный компонент
    app.component('NotificationContainer', NotificationContainerComponent)
  }
}
// Использование в компоненте
const { $notify } = getCurrentInstance().proxy
$notify('Файл сохранён!', 'success')

// Или через inject
const { add } = inject('notifications')
add('Ошибка!', 'error')

Популярные плагины экосистемы

  • **Vue Router** — официальный роутинг: app.use(router)
  • **Pinia** — управление состоянием: app.use(pinia)
  • **Vue i18n** — интернационализация
  • **Vueuse** — коллекция composables (не плагин, но часто используется)
  • **VeeValidate** — валидация форм
  • Плагин vs Composable

    | Плагин | Composable |

    |--------|-----------|

    | Глобальная регистрация | Локальное использование |

    | Регистрирует компоненты/директивы | Возвращает реактивное состояние |

    | Один раз через app.use() | Вызывается в каждом компоненте |

    | Для инфраструктуры (роутер, i18n) | Для логики (useForm, useFetch) |

    Composable предпочтительнее для переиспользуемой логики. Плагин нужен когда требуется глобальная регистрация или доступ к экземпляру приложения.

    Примеры

    Создание системы плагинов — реестр с install-функцией, аналог app.use() в Vue

    // Реализуем механику Vue-плагинов:
    // app.use() регистрирует плагин и вызывает install(app, options).
    
    class VueApp {
      constructor() {
        this._installedPlugins = new Set()
        this._components = {}
        this._directives = {}
        this._provides = {}
        this.config = { globalProperties: {} }
      }
    
      // Аналог app.use()
      use(plugin, options = {}) {
        const installer = typeof plugin === 'function' ? plugin : plugin.install
    
        // Защита от повторной установки
        if (this._installedPlugins.has(plugin)) {
          console.warn('[app.use] Плагин уже установлен, пропускаем')
          return this
        }
    
        installer(this, options)
        this._installedPlugins.add(plugin)
        return this  // для цепочки вызовов
      }
    
      // Аналог app.component()
      component(name, comp) {
        if (this._components[name]) {
          console.warn(`[app] Компонент "${name}" уже зарегистрирован`)
        }
        this._components[name] = comp
        console.log(`[app] Зарегистрирован компонент: ${name}`)
        return this
      }
    
      // Аналог app.directive()
      directive(name, def) {
        this._directives[name] = def
        console.log(`[app] Зарегистрирована директива: v-${name}`)
        return this
      }
    
      // Аналог app.provide()
      provide(key, value) {
        this._provides[key] = value
        console.log(`[app] Provide: "${String(key)}"`)
        return this
      }
    
      // Получить inject (для тестирования)
      inject(key) {
        return this._provides[key]
      }
    
      info() {
        console.log('\n=== Состояние приложения ===')
        console.log('Компоненты:', Object.keys(this._components))
        console.log('Директивы:', Object.keys(this._directives).map(d => 'v-' + d))
        console.log('Provides:', Object.keys(this._provides))
        console.log('Global props:', Object.keys(this.config.globalProperties))
        console.log('Плагинов установлено:', this._installedPlugins.size)
      }
    }
    
    // --- Создаём плагины ---
    
    // Плагин как объект
    const RouterPlugin = {
      install(app, { routes = [], mode = 'history' } = {}) {
        console.log(`[RouterPlugin] Инициализация (mode: ${mode}, маршрутов: ${routes.length})`)
    
        const router = {
          routes,
          currentRoute: routes[0] || null,
          push(path) { console.log('[Router] navigate to', path) },
          replace(path) { console.log('[Router] replace to', path) },
        }
    
        app.provide('router', router)
        app.config.globalProperties.$router = router
        app.config.globalProperties.$route = router.currentRoute
        app.component('RouterLink', { name: 'RouterLink' })
        app.component('RouterView', { name: 'RouterView' })
      }
    }
    
    // Плагин как функция
    const i18nPlugin = (app, { locale = 'en', messages = {} } = {}) => {
      console.log(`[i18nPlugin] Инициализация (locale: ${locale})`)
    
      const i18n = {
        locale,
        messages,
        t(key) {
          return messages[locale]?.[key] ?? messages['en']?.[key] ?? key
        },
      }
    
      app.provide('i18n', i18n)
      app.config.globalProperties.$t = (key) => i18n.t(key)
    }
    
    // Плагин для уведомлений
    const NotificationPlugin = {
      install(app, { timeout = 3000 } = {}) {
        console.log(`[NotificationPlugin] Инициализация (timeout: ${timeout}мс)`)
    
        const queue = []
        const notify = (msg, type = 'info') => {
          const item = { id: Date.now(), msg, type }
          queue.push(item)
          console.log(`  [notify] [${type.toUpperCase()}] ${msg}`)
          return item.id
        }
    
        app.provide('notifications', { notify, queue })
        app.config.globalProperties.$notify = notify
        app.component('ToastContainer', { name: 'ToastContainer' })
      }
    }
    
    // === Сборка приложения ===
    console.log('=== Создаём Vue приложение ===\n')
    const app = new VueApp()
    
    app
      .use(RouterPlugin, {
        mode: 'history',
        routes: [
          { path: '/', name: 'home' },
          { path: '/about', name: 'about' },
        ]
      })
      .use(i18nPlugin, {
        locale: 'ru',
        messages: {
          ru: { hello: 'Привет', save: 'Сохранить' },
          en: { hello: 'Hello', save: 'Save' },
        }
      })
      .use(NotificationPlugin, { timeout: 5000 })
    
    // Повторная установка — игнорируется
    app.use(RouterPlugin)
    
    app.info()
    
    // Тестируем функциональность
    console.log('\n=== Тест плагинов ===')
    const i18n = app.inject('i18n')
    console.log(i18n.t('hello'))  // Привет
    console.log(i18n.t('save'))   // Сохранить
    
    app.config.globalProperties.$notify('Данные сохранены!', 'success')
    app.config.globalProperties.$router.push('/about')
    console.log(app.config.globalProperties.$t('hello'))