Table of Contents

Разработка плагинов для веб-клиента

Архитектура веб-клиента ПОЛИНОМ: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. Проверка актуальных версий в ПОЛИНОМ

Для корректной настройки зависимостей плагина необходимо узнать точные версии пакетов, используемые в текущем клиенте:

  1. Откройте POLYNOM Client.
  2. В верхнем меню выберите Справка → О программе.
  3. Обратите внимание на строку с номером версии (например: Версия клиента: 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 — имя проекта, пути, outputPath
  • webpack.config.js — поле name
  • src/plugin-info.json — поле name
  • package.json— поле name
  • global.ts - переменная PLUGIN_NAME

Несовпадение имён — главная причина того, что плагин не загружается или не отображается в интерфейсе.

5. Отладка и частые проблемы

Проблема Причина Решение
Файл remoteEntry.js не отдаётся сервером Плагин скопирован в неверную директорию или отсутствует в папке плагинов сервера Убедитесь, что папка плагина размещена в <каталог_установки>/apps/plugins/ рядом с PrimaryComponents
В консоли браузера отображаются ошибки несовместимости версий библиотек Версии библиотек в host-приложении (Polynom) и в плагине отличаются Синхронизируйте версии зависимостей в package.json плагина с версиями в основном проекте
Плагин не отображается в интерфейсе после копирования Сервер API не выполнил регистрацию нового плагина Перезапустите Web.Server.exe для повторного сканирования директории плагинов