Разработка плагинов для веб-клиента
Архитектура веб-клиента ПОЛИНОМ:MDM предоставляет механизм расширения базовой функциональности через пользовательские плагины, реализованные на базе технологии Webpack Module Federation.
1. Введение и архитектурные концепции
1.1. Что такое плагин
Плагин представляет собой удаленный модуль (Remote Module) на платформе Angular, который имеет набор команд, предназначенных для модификации и расширения стандартного контекстного меню host-приложения. Плагин разделяет контекст выполнения, роутинг и DI-контейнер с хостом, но может иметь собственное изолированное состояние.
1.2. Механизм загрузки
Загрузка плагина в runtime осуществляется динамически через файл remoteEntry.js, генерируемый сборщиком Webpack. Host-приложение загружает этот файл и получает доступ к экспортируемым модулям плагина через механизм Module Federation.
1.3. Базовые требования
- Node.js (версия, совместимая с host-приложением)
- Angular (версия должна совпадать с host-приложением)
- Webpack с поддержкой Module Federation
- Доступ к корпоративному npm-регистру ПОЛИНОМ для установки библиотек
@ascon.
Настройка реестра npm:
Для корректной загрузки пакетов скоупа @ascon необходимо настроить реестр. Откройте файл .npmrc в корне вашего проекта, перейдите по ссылке https://packages.ascon.ru и укажите необходимые данные.
1.4 Проверка настройки реестра и версий пакетов
После настройки файла .npmrc убедитесь, что конфигурация применилась корректно. Выполните в терминале:
npm config get @ascon:registry
Команда должна вернуть адрес корпоративного реестра, указанный на (https://packages.ascon.ru/). Если вывод отличается — проверьте, что файл .npmrc находится в корне проекта и не переопределен пользовательским .npmrc в домашней директории.
2. Создание и запуск плагина
2.1. Инициализация проекта
Скопируйте эталонный шаблон Angular-проекта из директории примеров: Samples/Client/web-client-plugin
Откройте конфигурацию сборщика webpack.config.js и верифицируйте настройки Module Federation. Выставьте строгие настройки для shared модулей, чтобы избежать дублирования фреймворка в памяти:
const { share, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
name: 'web-client-plugin',
exposes: {
'./PolynomPlugin': './src/models/polynom-plugin.model'
},
shared: share({
'@angular/core': { singleton: true, strictVersion: false, requiredVersion: 'auto' },
'@angular/common': { singleton: true, strictVersion: false, requiredVersion: 'auto' },
'@angular/router': { singleton: true, strictVersion: false, requiredVersion: 'auto' },
'@angular/forms': { singleton: true, strictVersion: false, requiredVersion: 'auto' },
rxjs: { singleton: true, strictVersion: false, requiredVersion: 'auto' },
'@ascon/primeng': { singleton: true, strictVersion: false, requiredVersion: 'auto' },
'@ascon/ui': { singleton: true, strictVersion: false, requiredVersion: 'auto' },
'@ascon/polynom-configuration': { singleton: true, strictVersion: false, requiredVersion: 'auto' },
'@ascon/polynom-context-menu': { singleton: true, strictVersion: false, requiredVersion: 'auto' },
})
});
Важно: Использование singleton: true для критичных библиотек (@angular/core, rxjs) обязательно.
Проведите аудит package.json: удалите неиспользуемые зависимости, оставив минимально необходимый набор библиотек для реализации бизнес-логики.
Выполните строгую сверку версий пакетов, объявленных в секции shared файла webpack.config.js, с актуальными версиями в package.json host-приложения (client). Использование несовместимых версий shared-модулей приведёт к загрузке дубликатов библиотек в runtime, нарушению работы синглтонов и непредсказуемым ошибкам инициализации DI-контейнера.
2.1.1 Регистрация плагина для API
Откройте файл src/plugin-info.json и укажите корректное название проекта в поле name. Имя должно в точности совпадать с именем, заданным в конфигурации module-federation, иначе плагин не будет корректно зарегистрирован и загружен системой.
Пример содержимого файла:
{
"name": "web-client-plugin",
"supplier": "",
"version": "1.0.0.0",
"isSystem": true
}
2.2. Создание первой команды
- В директории
commandсоздайте класс новой команды, используяSimpleCommandв качестве референсной реализации, либо переименуйте существующий класс. - Реэкспортируйте класс в barrel-файле
index.tsдля обеспечения корректного tree-shaking. - Зарегистрируйте команду в манифесте модуля
polynom-plugin.model.ts, добавив её класс в массивCOMMANDSдля автоматической регистрации в DI-контейнере при bootstrap:
const COMMANDS: Type<IPolynomCommand>[] = [SimpleCommand];
2.3. Сборка и деплой
Плагин является самодостаточным проектом, сборка которого не требует наличия исходного кода ПОЛИНОМ:MDM. Библиотеки @ascon подключаются через npm-регистр как стандартные пакеты.
Выполните установку зависимостей и запуск production-сборки находясь в папке проекта:
npm install
npm run build
Результат сборки размещается в директории dist/<ИмяПлагина> (параметр outputPath в project.json). Ключевой артефакт — файл remoteEntry.js, используемый Module Federation.
Скопируйте содержимое dist/<ИмяПлагина> в директорию плагинов инсталляции ПОЛИНОМ:
<каталог_установки>/apps/plugins/<ИмяПлагина>/
Убедитесь, что файл remoteEntry.js доступен для чтения процессом веб-сервера — именно этот файл резолвится host-приложением при инициализации remote-модуля.
2.4. Верификация
Проверьте доступность remoteEntry.js по прямому URL:
http[s]://<адрес_сервера>:<порт>/plugins/<ИмяПлагина>/remoteEntry.js
Отсутствие данной страницы свидетельствует о некорректном копировании артефактов или ошибке в конфигурации reverse-proxy / статического роутинга веб-сервера.
Для финальной валидации откройте веб-интерфейс ПОЛИНОМ:MDM, выберите произвольный бизнес-объект и подтвердите появление SampleCommand в контекстном меню.
3. Разработка команд меню
3.1. Архитектура базового класса команды
Все пользовательские команды наследуются от абстрактного класса BaseMenuItem, реализующего интерфейс IPolynomCommand.
/**
* Базовая модель для команд контекстного меню веб-клиента
*/
export abstract class BaseMenuItem implements IPolynomCommand {
/** Уникальный идентификатор команды в рамках модуля */
id: string;
/** Отображаемое наименование пункта меню */
label: string;
/** Индекс сортировки (order index) команды внутри группы */
order: number;
/** Идентификатор целевой группы контекстного меню */
group: string;
/** Конфигурация доступности команды (активна/заблокирована) */
isDisabled = (context: ICommandContext): Observable<boolean> => {
return of(false);
};
/** Конфигурация видимости команды в интерфейсе */
isVisible = (context: ICommandContext): boolean => {
return true;
};
constructor(groupConfig: IContextMenuBaseConfiguration) {
this.id = groupConfig.id;
this.label = translate(groupConfig.label);
this.order = groupConfig.order;
this.group = groupConfig.group;
}
/** Определение идентификатора типа выбранного бизнес-объекта */
protected typeEntity(entities: IContextMenuElement[]): IdentifiableObjectType {
return entities[0]?.entity.typeId;
}
}
3.2. Позиционирование и сортировка (order)
Параметр order задаёт индекс сортировки элемента в UI. Диапазоны стандартных групп жёстко зафиксированы в конфигурации host-приложения:
const GROUP_START_ORDERS = {
ACTIONS: 100,
GROUPS_AND_OBJECTS: 200,
DOCUMENTS: 300,
OBJECTS: 400,
VIEWPOINTS: 500,
EXTENSIONS: 600
} as const;
Дискретный шаг сортировки внутри базовых групп составляет 10 пунктов, что резервирует адресное пространство для интеграции пользовательских команд в произвольную позицию внутри группы.
3.3. Привязка к группе (group)
Свойство group определяет строковый идентификатор целевой группы контекстного меню. Значение должно строго соответствовать одной из системных констант:
const action = 'actions';
const groupAndObj = 'groupsAndObjects';
const doc = 'documents';
const obj = 'objects';
const view = 'viewpoints';
const ext = 'extensions';
3.4. Управление состоянием интерфейса
isDisabled(context: ICommandContext): Observable<boolean>— асинхронный предикат блокировки. Возвращает Observable, что позволяет выполнять HTTP-запросы к API ПОЛИНОМ:MDM для проверки прав доступа, лицензий или бизнес-правил перед активацией элемента.isVisible(context: ICommandContext): boolean— синхронный предикат рендеринга. Скрывает присутствие пункта в DOM на основе текущего контекста без сетевых издержек.
3.5. Бизнес-логика (execute)
Метод хранит полезную нагрузку, исполняемую при активации пункта меню. Поддерживает синхронный и асинхронный паттерны выполнения:
/** Логика выполнения действия при клике на пункт меню */
execute?(context: ICommandContext): void | Promise<void>;
4. Продвинутые сценарии
4.1. Подключение локального NgRx Store
При необходимости настроить собственный store для нужд плагина, внесите изменения в файл polynom-plugin.model.ts:
private readonly _pluginInjector: EnvironmentInjector;
public constructor(parentInjector: EnvironmentInjector) {
this._pluginInjector = createEnvironmentInjector(
[provideState({ name: 'simple', reducer: simpleReducer }), provideEffects([simpleEffects])],
parentInjector
);
}
Описание работы: createEnvironmentInjector создаёт дочерний инжектор окружения, наследующий зависимости от parentInjector (инжектор host-приложения). Использование provideState и provideEffects внутри него регистрирует изолированный срез state и side-эффекты в рамках самого плагина.
Важно: Этот store не интегрируется в глобальный store host-приложения. Он предназначен для управления внутренним состоянием UI плагина и доступен через стандартные селекторы NgRx только внутри компонентов, созданных в контексте _pluginInjector. Селекторы хоста не увидят этот стейт, а селекторы плагина не увидят стейт хоста.
4.2. Настройка shared-зависимостей
Для избежания конфликтов версий библиотек между хостом и плагином убедитесь, что в webpack.config.js указаны корректные настройки (см. п. 2.1):
- Флаг
singleton: trueгарантирует, что в runtime будет загружена только одна версия библиотеки. - Флаг
strictVersion: trueвызовет явную ошибку при несовместимости версий, что помогает избежать скрытых багов и дублирования фреймворка в памяти.
4.2.1. Проверка актуальных версий в ПОЛИНОМ
Для корректной настройки зависимостей плагина необходимо узнать точные версии пакетов, используемые в текущем клиенте:
- Откройте POLYNOM Client.
- В верхнем меню выберите Справка → О программе.
- Обратите внимание на строку с номером версии (например:
Версия клиента: 24.1.14404).
Эти данные (или соответствующие им версии пакетов @ascon/...) необходимо указать в файле package.json разрабатываемого плагина, чтобы обеспечить полную совместимость с host-приложением.
4.2.2. Поиск нужных версий в npm-регистре
Для поиска и проверки наличия необходимых версий пакетов в корпоративном реестре используйте команды npm CLI. Например, для пакета polynom-ui:
# Просмотр списка всех доступных версий пакета
npm view @ascon/polynom-ui versions
# Подробная информация о пакете (включая последнюю версию и зависимости)
npm info @ascon/polynom-ui
4.3. Работа с данными ПОЛИНОМ:MDM
Для чтения и изменения данных в командах используйте методы API веб-сервера. Описание API доступно в формате OpenAPI (Swagger) по адресу:
http[s]://<адрес_сервера>:<порт>/api/index.html
4.4. Чек-лист переименования плагина
После копирования шаблона выполните глобальную замену строки web-client-plugin → <ВашеИмяПлагина> по всем файлам внутри скопированной папки. Замена затронет:
project.json— имя проекта, пути,outputPathwebpack.config.js— полеnamesrc/plugin-info.json— полеnamepackage.json— полеnameglobal.ts- переменнаяPLUGIN_NAME
Несовпадение имён — главная причина того, что плагин не загружается или не отображается в интерфейсе.
5. Отладка и частые проблемы
| Проблема | Причина | Решение |
|---|---|---|
| Файл remoteEntry.js не отдаётся сервером | Плагин скопирован в неверную директорию или отсутствует в папке плагинов сервера | Убедитесь, что папка плагина размещена в <каталог_установки>/apps/plugins/ рядом с PrimaryComponents |
| В консоли браузера отображаются ошибки несовместимости версий библиотек | Версии библиотек в host-приложении (Polynom) и в плагине отличаются | Синхронизируйте версии зависимостей в package.json плагина с версиями в основном проекте |
| Плагин не отображается в интерфейсе после копирования | Сервер API не выполнил регистрацию нового плагина | Перезапустите Web.Server.exe для повторного сканирования директории плагинов |