Создание конструктора рабочих процессов на основе плагинов с помощью Angular и федерации модулей

В предыдущей статье этого цикла я показал, как использовать динамическую федерацию модулей. Это позволяет нам загружать неизвестные микрофронтенды — или пульты, что является более общим термином в Module Federation — во время компиляции. Нам даже не нужно заранее знать количество микрофронтов.

Если в предыдущей статье маршрутизатор использовался для интеграции доступных микрофронтендов или пультов, то в этой статье показано, как загружать отдельные компоненты. В качестве примера используется простой конструктор рабочих процессов на основе плагинов.

Английский перевод оригинальной статьи Манфреда Штайера «Building A Plugin-based Workflow Designer With Angular and Module Federation» обновлен 10-06-2021

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

Обратите внимание, что эти плагины предоставляются из разных источников (http://localhost:4201 и http://localhost:4202), а дизайнер рабочего процесса обслуживается из собственного источника (http://localhost:4200).

📂 Исходный код

Спасибо Заку Джексону и Джеку Херрингтону, которые помогли мне разобраться в новом API rater для Федерации динамических модулей.

Важно: Эта статья написана для Angular и Angular CLI 14.x и выше. Убедитесь, что у вас правильная версия, если вы пробуете описанные здесь примеры.

Создание плагинов

Плагины предоставляются через отдельные приложения Angular. Для простоты все приложения являются частью одного монорежима. Ваша конфигурация webpack использует Module Federation для раскрытия отдельных плагинов, как показано в предыдущих статьях этой серии:

const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');

 module.exports = withModuleFederationPlugin({

   name: 'mfe1',

   exposes: {
     './Download': './projects/mfe1/src/app/download.component.ts',
     './Upload': './projects/mfe1/src/app/upload.component.ts'
   },

   shared: {
     ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
   },

 });
Войдите в полноэкранный режим Выход из полноэкранного режима

Одно отличие от конфигураций, показанных в предыдущих статьях, заключается в том, что здесь мы напрямую раскрываем независимые компоненты. Каждый компонент представляет собой задачу, которая может быть помещена в рабочий процесс.

Комбинация singleton: true и strictVersion: true заставляет webpack выдавать ошибку времени выполнения, когда оболочка и микрофронтенд(ы) требуют разных несовместимых версий (т.е. двух разных основных версий). Если бы мы опустили strictVersion или установили значение false, webpack выдал бы только предупреждение во время выполнения.

Загрузка плагинов в конструктор рабочих процессов

Для загрузки плагинов в workflow designer я использую вспомогательную функцию loadRemoteModule, предоставляемую плагином @angular-architects/module-federation. Для загрузки мы используем loadRemoteModule следующим образом

import { loadRemoteModule } from '@angular-architects/module-federation';

 [...]

 const component = await loadRemoteModule({
     type: 'module',
     remoteEntry: 'http://localhost:4201/remoteEntry.js',
     exposedModule: './Download'
 })
Войдите в полноэкранный режим Выход из полноэкранного режима

Предоставление метаданных о плагинах

Во время выполнения нам необходимо предоставить конструктору рабочих процессов ключевые данные о плагинах. Тип, используемый для этого, называется PluginOptions и расширяет LoadRemoteModuleOptions, показанный в предыдущем разделе, с помощью displayName и componentName:

export type PluginOptions = LoadRemoteModuleOptions & {
     displayName: string;
     componentName: string;
 };
Войдите в полноэкранный режим Выход из полноэкранного режима

Альтернативой этому является расширение Module Federation Manifest, как показано в предыдущей статье о динамической федерации модулей.

В то время как displayName является именем, представляемым пользователю, componentName относится к классу TypeScript, который представляет данный компонент Angular.

Для загрузки этих ключевых данных дизайнер рабочего процесса использует LookupService:

@Injectable({ providedIn: 'root' })
 export class LookupService {
     lookup(): Promise<PluginOptions[]> {
         return Promise.resolve([
             {
                 type: 'module',
                 remoteEntry: 'http://localhost:4201/remoteEntry.js',
                 exposedModule: './Download',

                 displayName: 'Download',
                 componentName: 'DownloadComponent'
             },
             [...]
         ] as PluginOptions[]);
     }
 }
Войдите в полноэкранный режим Выход из полноэкранного режима

Для простоты LookupService предоставляет некоторые уже определенные записи, но в реальном мире он, скорее всего, будет запрашивать эти данные из соответствующей конечной точки HTTP.

Динамическое создание компонента плагина

Конструктор рабочих процессов представляет плагины с помощью PluginProxyComponent. Он принимает объект PluginOptions через вход, загружает описанный плагин через Dynamic Module Federation и отображает компонент плагина в placeHolder:

@Component({
     standalone: true,
     selector: 'plugin-proxy',
     template: `
         <ng-container #placeHolder></ng-container>
     `
 })
 export class PluginProxyComponent implements OnChanges {
     @ViewChild('placeHolder', { read: ViewContainerRef, static: true })
     viewContainer: ViewContainerRef;

     constructor() { }

     @Input() options: PluginOptions;

     async ngOnChanges() {
         this.viewContainer.clear();

         const Component = await loadRemoteModule(this.options)
             .then(m => m[this.options.componentName]);

         this.viewContainer.createComponent(Component);
     }
 }
Войдите в полноэкранный режим Выход из полноэкранного режима

В версиях, предшествующих Angular 13, нам нужно было использовать ComponentFactoryResolver для получения фабрики загруженного компонента:

// Before Angular 13, we needed to retrieve a ComponentFactory
 //
 // export class PluginProxyComponent implements OnChanges {
 //     @ViewChild('placeHolder', { read: ViewContainerRef, static: true })
 //     viewContainer: ViewContainerRef;

 //     constructor(
 //       private injector: Injector,
 //       private cfr: ComponentFactoryResolver) { }

 //     @Input() options: PluginOptions;

 //     async ngOnChanges() {
 //         this.viewContainer.clear();

 //         const component = await loadRemoteModule(this.options)
 //             .then(m => m[this.options.componentName]);

 //         const factory = this.cfr.resolveComponentFactory(component);

 //         this.viewContainer.createComponent(factory, null, this.injector);
 //     }
 // }
Войдите в полноэкранный режим Выход из полноэкранного режима

Подключение всех

Теперь пришло время соединить вышеупомянутые части. Для этого AppComponent дизайнера рабочих процессов получает массив плагинов и рабочих процессов. Первый представляет PluginOptions доступных плагинов и, следовательно, всех доступных задач, а второй описывает PluginOptions задач, выбранных в конфигурации:

@Component({ [...] })
 export class AppComponent implements OnInit {

   plugins: PluginOptions[] = [];
   workflow: PluginOptions[] = [];
   showConfig = false;

   constructor(
     private lookupService: LookupService) {
   }

   async ngOnInit(): Promise<void> {
     this.plugins = await this.lookupService.lookup();
   }

   add(plugin: PluginOptions): void {
     this.workflow.push(plugin);
   }

   toggle(): void {
     this.showConfig = !this.showConfig;
   }
 }
Войдите в полноэкранный режим Выход из полноэкранного режима

AppComponent использует внедренный LookupService для заполнения своего массива плагинов. Когда плагин добавляется в рабочий процесс, метод add помещает его объект PluginOptions в массив рабочего процесса.

Для отображения рабочего процесса дизайнер просто итерирует все элементы в массиве рабочего процесса и создает для них плагин-прокси:

<ng-container *ngFor="let p of workflow; let last = last">
     <plugin-proxy [options]="p"></plugin-proxy>
     <i *ngIf="!last" class="arrow right" style=""></i>
 </ng-container> 
Войдите в полноэкранный режим Выход из полноэкранного режима

Как упоминалось выше, прокси загружает плагин (по крайней мере, если он еще не загружен) и отображает его.

Кроме того, чтобы отобразить меню задач, показанное слева, он просматривает все записи в массиве плагинов. Для каждого из них отображается гиперссылка, вызывающая метод add:

<div class="vertical-menu">
     <a href="#" class="active">Tasks</a>
     <a *ngFor="let p of plugins" (click)="add(p)">Add {{p.displayName}}</a>
 </div>
Войдите в полноэкранный режим Выход из полноэкранного режима

Заключение

Хотя федерация модулей полезна для реализации микрофронтендов, ее также можно использовать для создания подключаемых архитектур. Это позволяет нам расширить существующее решение третьих лиц. Он также кажется подходящим для SaaS-приложений, которые необходимо адаптировать к потребностям различных клиентов.

Оцените статью
devanswers.ru
Добавить комментарий