Ниже приведены некоторые из наиболее часто используемых подходов для обработки подключаемости в веб-интерфейсе:
- Основное приложение работает как макет для всех функций, которые оно содержит, где каждая функция имеет функцию включения / выключения. Если плагин присутствует, он будет отображаться в определенном месте. Но если вы хотите разработать новый плагин, вам нужно будет изменить основное приложение, чтобы оно было в курсе этого.
- Динамически загружайте плагины и добавляйте их в основное приложение в качестве вложенных приложений в iframe . Это дает определенную гибкость, поскольку вы можете использовать разные версии одних и тех же сторонних библиотек, но есть и некоторые затраты, в том числе:
- Размер пучка дует очень быстро. Все необходимые сторонние плагины должны быть снова включены в плагин.
- Чтобы повторно использовать уже написанную логику в базовом плагине, вы должны либо скопировать и вставить его, либо создать общий модуль с общей функциональностью и включить его в ядро и пользовательский плагин. В этом последнем сценарии, когда эта общая функциональность отличается от плагина к плагину, она может очень быстро стать беспорядком.
- Это не позволит вам вносить небольшие изменения в приложение, такие как замена кнопки новой на лету.
Помня об этих ограничениях, давайте взглянем на новый подход. Сначала я объясню это на простом примере, а затем на более продвинутом уровне.
Вам также может понравиться:
Основы Redux .
В качестве простого примера мы можем смоделировать приложение, в котором все пользовательские плагины будут автоматически зарегистрированы на панели навигации. Давайте посмотрим на панель навигации внутри нашего основного основного плагина пользовательского интерфейса:
Мы хотим, чтобы любой новый плагин был добавлен на панель. Мы понятия не имеем, как новый плагин будет взаимодействовать с основным приложением. Поэтому, чтобы заставить его работать, нам нужно определить некоторые условные обозначения и коммуникационные каналы:
- Мы должны определить, как мы собираемся обнаруживать плагины пользовательского интерфейса среди других типов плагинов.
- Каждый плагин должен содержать метаданные в едином формате:
- Дайте название плагину на панели навигации.
- Определите место для вкладки; в противном случае порядок пользовательских вкладок будет неуправляемым.
- Укажите расположение пакетов, которые будут загружены на страницу.
- Определите разрешения и загрузите плагины в зависимости от роли.
- Создайте канал связи на основе событий между основным приложением и плагином и между плагинами.
Кроме того, нам необходимо выполнить проверку того, что сторонние библиотеки должны быть одной и той же версии, так как плагины не будут включать стороннюю библиотеку, а, скорее, повторно использовать ее из основного приложения.
Имея в виду эти требования, давайте попробуем углубиться в детали и сделать нашу панель такой (после загрузки пользовательского плагина):
Как вы могли заметить, на второй позиции панели навигации добавлена дополнительная вкладка «Отчеты».
Мы должны определить, как мы собираемся обнаруживать плагины пользовательского интерфейса среди других типов плагинов.
Для этого мы можем добавить файловый дескриптор к каждому плагину пользовательского интерфейса, который будет указывать, что это пользовательский плагин пользовательского интерфейса, то есть custom-ui-extension.json . При этом, когда мы сканируем папку / classpath, мы можем фильтровать новые плагины.
Каждый плагин должен содержать метаданные в едином формате.
Давайте спроектируем, как это может выглядеть для вышеупомянутых требований:
JSON
1
{
2
"entry": "reports-bundle.js",
3
"name": "reports-plugin",
4
"weight": 15,
5
"permissions": [
6
"admin"
7
],
8
"title": "Reports"
9
}
10
Название определено очевидно.
Что касается положения вкладок, я исходил из предположения, что вкладки основного плагина были построены в том порядке, в котором первая вкладка имеет «вес» 10, вторая 20 и т. Д.
Пользовательские плагины могут идентифицировать себя, между какими вкладками они должны быть размещены. Еще есть место для добавления дополнительных плагинов, если между вкладками «Главная» и «Магазин» нужно вставить несколько пользовательских плагинов.
Если два плагина имеют одинаковый вес, их можно заказать с некоторой дополнительной логикой, например разместить их в алфавитном порядке, чтобы позиция не применялась случайным образом. Поле ввода содержит информацию о пакете и местонахождении . Это может быть, например: $ {plugin-name} / web / $ {entry}.
В качестве альтернативы вы можете определить в метаданных полный путь к файлу пакета и предоставить гибкость плагинов пользовательского интерфейса, чтобы иметь другую структуру. Поле разрешений, содержащее список имен ограниченных разрешений, для которых этот плагин будет иметь эффект. Если разрешения - это пустой список, то есть открытый доступ.
Создать канал связи на основе событий между основным приложением и плагином и между плагинами
Преимущество этого типа канала связи заключается в слабой связи между плагинами. Если некоторые события не могут быть обработаны, они будут проигнорированы, и система сможет нормально работать. Если система будет огромной или вы хотите, чтобы у клиента была возможность создавать свои собственные плагины пользовательского интерфейса, вам нужно создать подробный API. Что, в свою очередь, также повысит его качество, так как вам нужно будет обосновать в документах все события / области, зачем они нужны и что они делают.
Теперь у нас есть логика того, как мы собираемся получать информацию о плагинах и общаться между ними. Давайте посмотрим, как их нужно динамически добавлять в основное приложение.
Как только пакет плагина загружен, нам нужно динамически добавить скрипт в тело объекта документа для страницы:
xxxxxxxxxx
1
const script = document.createElement('script');
2
script.src = scriptUrl; // fully qualified path to the loaded script
3
script.async = true;
4
document.body.appendChild(script);
Теперь все скрипты, которые были в плагине, доступны из основного приложения. И для этого нам нужно иметь некоторое соглашение о том, как создаются пользовательские плагины. С текущего примера было бы достаточно иметь один объект экспорта, содержащий:
JavaScript
xxxxxxxxxx
1
{
2
component: ,
3
eventChannel:
4
}
Компонент - вы динамически визуализируете на вновь созданной вкладке (это также означает, что вам нужно иметь логику, которая будет динамически создавать вкладки в этом жизненном цикле), а также регистрироваться eventChannel
в системе. И это будет зависеть от технологии. Например, в Redux / Redux-saga вам потребуется зарегистрировать редукторы и запустить sagas (как это сделать с помощью этих технологий, я расскажу в другой статье о реализации этого подхода с использованием этих инструментов).
Когда вы создаете собственный плагин, вам нужно пометить все сторонние библиотеки как внешние, чтобы они не были включены в комплект. Как только код смешан, необходимые библиотеки будут взяты из основного приложения.
Одна вещь, которую вы могли бы рассмотреть, - это создать среду разработки для быстрой разработки, поскольку я могу предположить, что вы не хотите загружать всю инфраструктуру, но в то же время можете использовать канал связи из основного приложения для быстрого тестирования. , Для этого вы можете отправить эту часть из основного приложения в виде отдельного небольшого модуля и использовать его только в целях разработки.
Это в основном это; подход работает. Это дает вам действительно плавную интеграцию, так как она была разработана в одном репозитории git, не имеет дублированных загруженных библиотек или дублированного кода между ядром и пользовательским плагином, и вы можете легко общаться с каждым модулем изначально, без каких-либо мостов iframe.
Давайте теперь посмотрим на более сложный сценарий. Давайте предположим, что я хочу на домашней странице обновить существующую кнопку кнопкой с выпадающим списком.
Подход :
- Используйте иерархическую идентификацию элементов в основном приложении:
- Вкладка «Нравится» имеет атрибут: component-id = «home» .
- Кнопка имеет атрибут: component-id = «open-profile» .
- Укажите в плагине для каждого компонента, где он должен быть размещен. Итак, объект ввода будет выглядеть так:
JSON
xxxxxxxxxx
1
{
2
overrides: [{
3
replace-component-having-path: ‘home/open-profile’
4
component:
5
}]
6
}
И для метаданных потребуется меньше полей для плагина такого типа:
JSON
xxxxxxxxxx
1
{
2
"entry": “home-addons-bundle.js",
3
"name": “home-addons-plugin"
4
}
Затем логика состоит в том, чтобы найти этот компонент с помощью селекторов CSS. Я сделал оригинал невидимым и вместо него разместил новый компонент. То же самое можно сделать для всех кнопок, и тогда вам не нужна иерархическая структура. Вместо этого найдите все компоненты с нужным классификатором и замените их.
При таком подходе вы только связываете дополнения в этот плагин. Но это не обязательно техническое ограничение для разделения каждого плагина таким образом. Это может быть один пакет, содержащий и дополнения, и несколько вкладок. Я бы так не делал, но если кто-то предпочитает этот метод, то метаданные будут содержать массив записей, например:
JSON
xxxxxxxxxx
1
[{
2
"entry": "reports-bundle.js",
3
"name": "reports-plugin",
4
"weight": 15,
5
"permissions": [
6
"admin"
7
],
8
"title": "Reports"
9
}, {
10
"entry": “home-addons-bundle.js",
11
"name": “home-addons-plugin"
12
}]
Если вам не нужна настройка с переопределениями, вы можете рассмотреть возможность улучшения, когда вы хотите отобразить дополнительные виджеты вместе или предоставить эту возможность клиентам. Затем вы можете пометить все компоненты и предоставить им API, чтобы любое место в вашем приложении можно было настраивать и не было частью основного продукта.
Я с нетерпением жду ваших комментариев. Чтобы сделать это более понятным, я собираюсь создать приложение-песочницу с минимально возможной конфигурацией, чтобы показать случаи, описанные в этой статье, и продемонстрировать более сложные сценарии.
Я буду использовать Node.js, React, Redux и Webpack . Вы можете добиться того же с другими инструментами, и мне будет любопытно посмотреть, как вы можете это сделать, и побудить вас донести до меня ваши идеи и решения. Спасибо за Ваше внимание!