Архитектура плагинов

Архитектура плагинов в Tippy.js построена вокруг принципа композиции: базовое поведение тултипа расширяется за счёт независимых модулей, которые внедряются в жизненный цикл экземпляра. Каждый плагин — это объект с набором хуков, позволяющих вмешиваться в различные этапы работы тултипа: от инициализации до уничтожения.

Ключевая идея — инверсия управления. Библиотека предоставляет точки расширения, а плагины реализуют конкретную логику, не изменяя ядро.


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

Минимальная структура плагина:

const myPlugin = {
  name: 'myPlugin',
  defaultValue: true,
  fn(instance) {
    return {
      onCreate() {},
      onShow() {},
      onHide() {},
      onDestroy() {}
    };
  }
};

Основные элементы

  • name — уникальное имя плагина
  • defaultValue — значение по умолчанию для пользовательской опции
  • fn(instance) — фабрика, возвращающая объект с хуками

Экземпляр instance — это центральный объект Tippy, через который происходит взаимодействие с DOM, состоянием и настройками.


Жизненный цикл и хуки

Плагины интегрируются в жизненный цикл тултипа через набор событий.

Основные хуки

  • onCreate — вызывается при создании экземпляра
  • onMount — после добавления тултипа в DOM
  • onShow — перед отображением
  • onShown — после завершения анимации показа
  • onHide — перед скрытием
  • onHidden — после скрытия
  • onDestroy — при удалении

Пример использования

const logPlugin = {
  name: 'logPlugin',
  fn(instance) {
    return {
      onShow() {
        console.log('Tooltip is about to show');
      },
      onHide() {
        console.log('Tooltip is about to hide');
      }
    };
  }
};

Доступ к внутреннему API

Объект instance предоставляет доступ к ключевым компонентам:

  • instance.reference — элемент-источник
  • instance.popper — DOM-узел тултипа
  • instance.props — текущие настройки
  • instance.state — внутреннее состояние

Пример

fn(instance) {
  return {
    onCreate() {
      instance.popper.classList.add('custom-class');
    }
  };
}

Работа с состоянием

Плагины могут хранить собственное состояние через замыкание:

const statePlugin = {
  name: 'statePlugin',
  fn(instance) {
    let counter = 0;

    return {
      onShow() {
        counter++;
        console.log(counter);
      }
    };
  }
};

Такой подход гарантирует изоляцию состояния каждого экземпляра.


Расширение через props

Плагин может вводить собственные параметры, интегрированные в конфигурацию Tippy.

const colorPlugin = {
  name: 'color',
  defaultValue: 'blue',
  fn(instance) {
    return {
      onCreate() {
        instance.popper.style.backgroundColor = instance.props.color;
      }
    };
  }
};

Использование:

tippy(element, {
  color: 'red',
  plugins: [colorPlugin]
});

Композиция плагинов

Tippy.js поддерживает одновременное использование нескольких плагинов. Они выполняются последовательно.

tippy(element, {
  plugins: [pluginA, pluginB]
});

Порядок выполнения

Плагины вызываются в том порядке, в котором они переданы. Это важно при наличии зависимостей между ними.


Изоляция и независимость

Каждый плагин должен:

  • не изменять глобальное состояние
  • не полагаться на внутреннюю реализацию других плагинов
  • работать независимо от порядка подключения (по возможности)

Это достигается за счёт:

  • локального состояния
  • использования публичного API
  • отсутствия побочных эффектов вне instance

Взаимодействие с DOM

Плагины часто манипулируют DOM-элементами тултипа:

const arrowPlugin = {
  name: 'arrowPlugin',
  fn(instance) {
    return {
      onCreate() {
        const arrow = document.createElement('div');
        arrow.className = 'arrow';
        instance.popper.appendChild(arrow);
      }
    };
  }
};

Важно учитывать:

  • структура popper может изменяться
  • необходимо избегать конфликтов классов
  • желательно использовать уникальные селекторы

Асинхронная логика

Хуки могут содержать асинхронный код:

const asyncPlugin = {
  name: 'asyncPlugin',
  fn(instance) {
    return {
      async onShow() {
        const data = await fetch('/api/data');
        instance.setContent(await data.text());
      }
    };
  }
};

Следует учитывать:

  • асинхронность может влиять на UX
  • необходимо обрабатывать ошибки
  • важно не блокировать основной поток

Управление поведением через return

Некоторые хуки позволяют контролировать поведение тултипа:

onShow() {
  if (!shouldShow()) {
    return false;
  }
}

Возврат false отменяет действие.


Плагины и Popper.js

Tippy.js использует Popper.js для позиционирования, и плагины могут влиять на его конфигурацию:

fn(instance) {
  return {
    onCreate() {
      instance.setProps({
        popperOptions: {
          modifiers: [
            {
              name: 'offset',
              options: { offset: [0, 20] }
            }
          ]
        }
      });
    }
  };
}

Динамическое изменение свойств

Плагин может изменять props в процессе работы:

onShow() {
  instance.setProps({
    duration: 0
  });
}

Это позволяет адаптировать поведение в зависимости от контекста.


Переиспользование логики

Плагины можно комбинировать и переиспользовать:

const plugins = [pluginA, pluginB, pluginC];

Каждый из них решает узкую задачу:

  • управление стилями
  • обработка событий
  • интеграция с API

Встроенные плагины

Tippy.js включает ряд встроенных плагинов, реализованных по той же архитектуре:

  • animateFill
  • followCursor
  • inlinePositioning
  • sticky

Они демонстрируют лучшие практики:

  • минимальная связность
  • максимальная переиспользуемость
  • чёткое разделение ответственности

Оптимизация производительности

При разработке плагинов важно учитывать:

  • минимизацию DOM-операций
  • отказ от лишних перерисовок
  • использование кэширования

Пример:

fn(instance) {
  let cachedValue;

  return {
    onShow() {
      if (!cachedValue) {
        cachedValue = computeExpensiveValue();
      }
      instance.setContent(cachedValue);
    }
  };
}

Ошибки и отладка

Распространённые проблемы:

  • конфликт имён плагинов
  • неправильный порядок подключения
  • мутация instance вне хуков

Рекомендуется:

  • логирование внутри хуков
  • проверка props
  • изоляция тестирования каждого плагина

Расширение архитектуры

Архитектура позволяет создавать сложные системы:

  • плагины, управляющие другими плагинами
  • плагины с внутренними зависимостями
  • адаптивные плагины с реакцией на окружение

Пример паттерна:

const composedPlugin = {
  name: 'composed',
  fn(instance) {
    const subLogic = createSubLogic(instance);

    return {
      onShow() {
        subLogic.run();
      }
    };
  }
};

Принципы проектирования плагинов

  • Single Responsibility — один плагин = одна задача
  • Loose Coupling — слабая связанность
  • High Cohesion — высокая связность внутри модуля
  • Predictability — предсказуемое поведение

Такая архитектура делает Tippy.js гибким инструментом, пригодным как для простых тултипов, так и для сложных интерфейсных систем с расширяемой логикой.