Устройство проекта
Разберём, что лежит в стартовом шаблоне, какие файлы вы будете менять, а какие лучше не трогать.
Карта файлов
my-plugin/
├── plugin.json ← манифест плагина. Меняете всегда
├── Makefile ← команды сборки (make dist, make deploy…)
├── .env.example ← образец настроек для make deploy
├── go.mod, go.sum ← зависимости серверной части
├── assets/ ← иконка плагина и другие файлы, которые попадут в архив
├── build/ ← служебные программы сборки. Не трогайте
│ ├── manifest/ ← читает plugin.json и копирует его в код
│ ├── pluginctl/ ← загружает плагин на сервер для make deploy
│ ├── setup.mk, custom.mk ← части Makefile
├── server/ ← серверная часть (Go)
│ ├── main.go ← точка входа. Обычно не трогаете
│ ├── manifest.go ← создаётся при сборке из plugin.json. Не редактируйте
│ ├── plugin/
│ │ ├── plugin.go ← структура Plugin, запуск и остановка
│ │ ├── configuration.go ← настройки из Системной консоли
│ │ ├── api.go ← HTTP-маршруты плагина
│ │ ├── store.go ← место для работы с хранилищем
│ │ └── telemetry.go ← отправка статистики (выключена, пока не заданы ключи)
│ ├── telemetry/ ← клиент статистики
│ └── utils/rest_client.go ← помощник для запросов во внешние сервисы
├── webapp/ ← клиентская часть (React + TypeScript)
│ ├── package.json ← зависимости и скрипты
│ ├── eslint.config.mjs ← правила проверки стиля кода
│ ├── vite.config.ts ← настройки сборщика Vite
│ ├── i18n/en.json, ru.json ← переводы интерфейса
│ └── src/
│ ├── index.tsx ← точка входа: регистрирует плагин в Loop
│ ├── registerApp.tsx ← что и куда плагин добавляет в интерфейс
│ ├── manifest.ts ← создаётся при сборке из plugin.json. Не редактируйте
│ ├── components/ ← ваши React-компоненты
│ ├── store/ ← Redux: actions и reducers
│ ├── types/ ← типы TypeScript
│ └── utils/ ← запросы к серверу, переводы, помощники
└── .gitea/workflows/ ← пример CI для Gitea Actions
Серверная часть
server/main.go
Точка входа — то, что запускается первым:
func main() {
p := &plug.Plugin{}
p.InitApi()
plugin.ClientMain(p)
}
plugin.ClientMain подключает плагин к Loop и дальше управление переходит к Loop: он сам вызывает нужные функции (хуки) вашего плагина. Здесь обычно ничего менять не нужно.
server/plugin/plugin.go
Главный файл. В нём структура Plugin — «объект плагина», у которого есть все его данные и функции:
type Plugin struct {
plugin.MattermostPlugin // даёт доступ к p.API и p.Driver
IsReady bool
bundlePath string // папка, куда Loop распаковал плагин
configurationLock sync.RWMutex
configuration *Configuration
sdk *pluginapi.Client // удобная обёртка над p.API
router *mux.Router // HTTP-маршруты
// ...
}
Сюда вы будете добавлять свои поля: ID бота, клиенты внешних сервисов и т. п.
Здесь же две главные функции:
OnActivate— вызывается, когда плагин включают (и при каждом перезапуске сервера). Здесь создают ботов, регистрируют slash-команды, подключаются к внешним сервисам. Если функция вернёт ошибку, плагин не запустится, а ошибку будет видно в логах.OnDeactivate— вызывается, когда плагин выключают. Здесь останавливают фоновые задачи и закрывают соединения.
server/plugin/configuration.go
Всё про настройки из Системной консоли. Вам нужно только добавлять поля в структуру Configuration — по одному на каждую настройку из plugin.json. Остальной код (блокировки, OnConfigurationChange) уже написан и следит, чтобы настройки безопасно обновлялись на лету. Подробнее — в Серверная часть › Настройки.
server/plugin/api.go
HTTP API плагина. В функции InitApi перечислены маршруты:
p.router.HandleFunc("/example", p.handleExample).Methods("GET")
p.router.HandleFunc("/assets/{fileName}", p.handleAssetFile).Methods("GET")
Маршрут /example доступен по адресу https://your-loop-server.ru/plugins/<ID плагина>/example. Перед всеми маршрутами стоит проверка, что запрос пришёл от вошедшего в Loop пользователя. Помощники sendRes и sendError отвечают в едином формате:
{"status": "OK", "data": {...}}
{"status": "error", "error": "описание ошибки"}
server/plugin/store.go, telemetry.go, server/utils/
store.go— пустой файл, куда удобно складывать функции работы с KV-хранилищем.telemetry.goи папкаtelemetry/— отправка статистики использования через RudderStack. Включается, только если при сборке заданы ключиMM_RUDDER_CALLS_PRODиMM_RUDDER_DATAPLANE_URLи на сервере разрешена отправка диагностики. Если статистика вам не нужна, ничего делать не надо.utils/rest_client.go— функцияMakeRequestдля запросов во внешние сервисы с JSON.
Клиентская часть
webapp/src/index.tsx
Точка входа. Создаёт объект плагина и регистрирует его в Loop:
window.registerPlugin(manifest.id, new Plugin());
Когда Loop загружает плагин в браузере, он вызывает у этого объекта метод initialize(registry, store):
registry— реестр, через который плагин добавляет свои элементы в интерфейс;store— Redux-хранилище Loop, где лежат данные о пользователе, каналах, сообщениях.
Метод initialize в шаблоне передаёт управление в registerApp.tsx.
webapp/src/registerApp.tsx
Здесь вы описываете, что плагин добавляет в интерфейс. В шаблоне:
registry.registerReducer(reducer)— подключает хранилище плагина;registry.registerTranslations(getTranslations)— подключает переводы;registry.registerRootComponent(...)— добавляет корневой компонентRootComponent. Сейчас он ничего не показывает; он удобен для того, что должно работать всегда: модальных окон, фоновой логики. Если он не нужен — уберите.
Компонент Providers оборачивает ваши компоненты в поставщиков переводов (IntlProvider) и хранилища (Provider). Оборачивайте им всё, что регистрируете, — иначе внутри не будут работать FormattedMessage и useSelector.
webapp/src/store/
Хранилище состояния плагина на Redux: reducers.ts описывает данные и как они меняются, actions.ts — события, которые их меняют. Данные плагина лежат в общем хранилище Loop под ключом plugins-<ID плагина>; достать их помогает функция getPluginStoreFromState из utils/utils.ts.
webapp/src/utils/
| Файл | Что внутри |
|---|---|
api.ts | Класс ApiClient для запросов к серверной части плагина через Client4 |
utils.ts | Путь к ресурсам плагина, переводы, определение десктоп-приложения и другие помощники |
usePlugin.ts | Хук React, который возвращает объект плагина |
Сборка: webapp/vite.config.ts
Клиентская часть собирается Vite в один файл webapp/dist/main.js. Главная особенность — библиотеки React, ReactDOM, Redux, React Redux, React Intl, React Bootstrap, React Router и PropTypes не попадают в сборку: плагин использует те же экземпляры, что и сам Loop. Благодаря этому плагин весит меньше и работает с общим хранилищем Loop.
Не удаляйте эти библиотеки из списка external в vite.config.ts и не подключайте другую версию React. Две копии React на одной странице ломают хуки и приводят к непонятным ошибкам.
loop-plugin-sdk
Пакет с типами TypeScript и функциями Loop для плагинов: типы реестра (PluginRegistry), селекторы Redux, HTTP-клиент Client4. Он указан в webapp/package.json и скачивается из открытого репозитория Loop при yarn install.
Генерируемые файлы
Перед каждой сборкой команда make читает plugin.json и создаёт из него два файла:
server/manifest.go— манифест для серверной части;webapp/src/manifest.ts— манифест для клиентской части. Отсюда берётсяmanifest.id.
Оба файла в .gitignore и перезаписываются при каждой сборке. Меняйте plugin.json, а не их.
Команды Makefile
| Команда | Что делает |
|---|---|
make | Проверяет стиль кода, запускает тесты и собирает архив — всё сразу. Удобно перед выпуском версии |
make dist | Собирает серверную и клиентскую части и упаковывает всё в dist/<ID>-<версия>.tar.gz. Основная команда |
make server | Собирает только серверную часть — быстро проверить, что Go-код компилируется |
make webapp | Собирает только клиентскую часть |
make deploy | make dist + загрузка и включение плагина на сервере. Нужен .env |
make watch | Собирает плагин и дальше пересобирает клиентскую часть при каждом изменении файлов в webapp/src. На сервер ничего не отправляет |
make deploy-from-watch | Упаковывает и загружает на сервер то, что собрал make watch. Запускайте в соседнем терминале после каждого изменения |
make enable / make disable | Включает или выключает плагин на сервере |
make reset | Выключает и снова включает плагин — «перезапуск» |
make test | Запускает тесты Go (и webapp, если они есть) |
make check-style | Проверяет стиль кода: golangci-lint и eslint |
make deps | Устанавливает инструменты для check-style: golangci-lint и goi18n |
make clean | Удаляет всё собранное. Помогает, когда сборка ведёт себя странно |
make attach-headless | Подключает отладчик Delve к работающему плагину — см. Отладка |
make help | Список всех команд |
Для каких систем собирается серверная часть
Серверная часть — это программа, и собирать её нужно под систему сервера Loop. По умолчанию make dist собирает пять вариантов: Linux (amd64 и arm64), macOS (amd64 и arm64) и Windows (amd64). Loop сам выбирает подходящий.
Сборка пяти вариантов занимает время. Если в .env задать MM_SERVICESETTINGS_ENABLEDEVELOPER=true (так сделано в .env.example), соберутся только два: для вашего компьютера и для Linux amd64. Это быстрее, но плагин не запустится на сервере с процессором ARM (linux-arm64). Для итоговой сборки уберите эту переменную.
Переменные .env
| Переменная | Зачем |
|---|---|
MM_SERVICESETTINGS_SITEURL | Адрес сервера Loop для make deploy, например https://your-loop-server.ru |
MM_ADMIN_TOKEN | Персональный токен системного администратора для make deploy |
MM_ADMIN_USERNAME, MM_ADMIN_PASSWORD | Вместо токена можно указать логин и пароль администратора |
MM_LOCALSOCKETPATH | Путь к сокету локального режима — тогда токен не нужен |
MM_SERVICESETTINGS_ENABLEDEVELOPER | true — собирать серверную часть только под ваш компьютер и Linux amd64 |
MM_DEBUG | true — собирать без оптимизаций, для пошаговой отладки, и с исходниками для браузера |
MM_RUDDER_CALLS_PROD, MM_RUDDER_DATAPLANE_URL | Ключи RudderStack для серверной телеметрии. Пустые — телеметрия выключена |
CI
В папке .gitea/workflows/ лежит пример сборки для Gitea Actions: при создании релиза плагин собирается и публикуется. Он рассчитан на инфраструктуру команды Loop, поэтому используйте его как образец и адаптируйте под свою систему CI. Минимальный вариант для любой системы — выполнить cd webapp && yarn install && cd .. && make dist и сохранить файл из dist/.