Featured image of post Apresentando module federation para Blazor

Apresentando module federation para Blazor

Expondo seus componentes Blazor como microfrontends com module federation

Follow me

IntroduĆ§Ć£o Link to this section

Com o release do .NET 7, a Microsoft incluiu uma funcionalizade para renderizar componentes Blazor em aplicaƧƵes JavaScript (RegisterCustomElement<T>). Isso ajuda quem quer migrar lentamente de aplicaƧƵes JavaScript para Blazor, mas infelizmente, nĆ£o funciona para expor componentes Blazor como microfrontends, pois funciona apenas para aplicaƧƵes JavaScript hospedadas junto com a aplicaĆ§Ć£o Blazor.

Neste post, vou apresentar um pacote nuget que criei como protĆ³tipo para tentar resolver esse problema, expondo componentes Blazor com module federation para o consumo de outras aplicaƧƵes.

O que Ć© Module Federation? Link to this section

Module Federation Ć© uma funcionalidade do webpack que nos possibilita expor mĆ³dulos JavaScript JavaScript para que outras aplicaƧƵes consumam. Esses mĆ³dulos federados podem ser implantados de forma independente e sĆ£o isolados uns dos outros, possibilitando construir aplicaƧƵes com a arquitetura de microfrontends.

O que Ć© Arquitetura de Microfrontend? Link to this section

A arquitetura de Microfrontend Ć© similar Ć  arquitetura de MicrosserviƧos, mas aplicada Ć s aplicaƧƵes frontend. Uma aplicaĆ§Ć£o desenvolvida com a arquitetura de microfrontend Ć© composta por uma ou mais microfrontends, componentes autocontidos, publicados individualmente e que podem ser desenvolvidos usando tecnologias diferentes uns dos outros.

Na imagem abaixo, podemos ver uma aplicaĆ§Ć£o com quatro microfrontends. Cada um desenvolvido por um time diferente e com tecnologias diferentes.

Imagem de https://tautorn.github.io/micro-frontends/

Mais sobre microfrontends nesse post do Martin Fowler.

Angular Module Federation wrapper para Blazor Link to this section

Ressalva: Esse pacote Ć© um protĆ³tipo e nĆ£o estĆ” pronto para ser usado em produĆ§Ć£o.

ApĆ³s a instalaĆ§Ć£o do pacote nuget, ele irĆ” gerar uma aplicaĆ§Ć£o Angular em tempo de compilaĆ§Ć£o, expondo os componentes Blazor atravĆ©s do module federation.

Os componentes expostos aceitam parĆ¢metros de entrada e inscriĆ§Ć£o a eventos.

ConfiguraƧƵes na aplicaĆ§Ć£o Blazor Link to this section

Primeiro, instale o pacote nuget Blazor.ModuleFederation.Angular:

Install-Package Blazor.ModuleFederation.Angular

O cĆ³digo fonte da aplicaĆ§Ć£o Angular Ć© gerado por uma tarefa do MSBuild. Para isso, precisamos configurar quais componentes serĆ£o expostos.

PokemonCards.razor: Link to this section

No arquivo .razor do componente, inclua o atributo GenerateModuleFederationComponent.

1
2
3
4
@attribute [GenerateModuleFederationComponent]
@using Blazor.ModuleFederation.Angular;

...

Program.cs: Link to this section

No arquivo Program.cs, registre o componente com o mƩtodo RegisterForModuleFederation.

1
2
3
4
5
var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.RootComponents.RegisterForModuleFederation<PokemonCards>();

...

Project.csproj: Link to this section

No arquivo .csproj, configure os seguintes parĆ¢metros:

  • ModuleFederationName: Nome do mĆ³dulo que serĆ” exposto;
  • MicroFrontendBaseUrl: URL onde a aplicaĆ§Ć£o Blazor serĆ” publicada;
  • BuildModuleFederationScript: Habilitar ou desabilitar a geraĆ§Ć£o do wrapper Angular na compilaĆ§Ć£o;
  • IsProduction: Se a aplicaĆ§Ć£o Angular serĆ” compilada com configuraƧƵes de produĆ§Ć£o;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<PropertyGroup>
    <ModuleFederationName>blazormodule</ModuleFederationName>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'DEBUG'">
    <MicroFrontendBaseUrl>http://localhost:5289/</MicroFrontendBaseUrl>
    <BuildModuleFederationScript>False</BuildModuleFederationScript>
    <IsProduction>False</IsProduction>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'RELEASE'">
    <MicroFrontendBaseUrl>http://localhost:8080/</MicroFrontendBaseUrl>
    <BuildModuleFederationScript>True</BuildModuleFederationScript>
    <IsProduction>True</IsProduction>
</PropertyGroup>

ConfiguraƧƵes na aplicaĆ§Ć£o Host Link to this section

PokemonCardsLoaderComponent: Link to this section

Crie um componente para carregar o componente Blazor remoto. NĆ£o esqueƧa de incluĆ­-lo no mĆ³dulo da aplicaĆ§Ć£o.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import {
  Component,
  OnInit,
  ViewContainerRef,
  ComponentRef,
  EventEmitter
} from '@angular/core';

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

@Component({
  selector: 'pokemon-cards-loader',
  template: ''
})
export class PokemonCardsLoaderComponent implements OnInit {
  constructor(
    private vcref: ViewContainerRef
  ) {}

  async ngOnInit() {
    const { PokemonCardsComponent } = await loadRemoteModule({
      remoteEntry: 'http://localhost:8080/js/remoteEntry.js',
      remoteName: 'blazormodule',
      exposedModule: './PokemonCards',
    });

    const componentRef: ComponentRef<{
        startFromId: number;
        onDataLoaded: EventEmitter<any>;
      }> = this.vcref.createComponent(PokemonCardsComponent);

    componentRef.instance.startFromId = 810;
    componentRef.instance.onDataLoaded.subscribe(evt => console.log('API Data Loaded'));
  }
}

AppComponent: Link to this section

Inclua o componente no HTML.

1
2
3
4
5
6
7
<div class="toolbar" role="banner">
  <span class="header-title">Host App</span>
</div>

<div class="content" role="main">
  <pokemon-cards-loader></pokemon-cards-loader>
</div>

webpack.config.js Link to this section

Importe o componente Blazor na seĆ§Ć£o ModuleFederationPlugin.remotes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const mf = require("@angular-architects/module-federation/webpack");
const path = require("path");
const share = mf.share;

const sharedMappings = new mf.SharedMappings();
sharedMappings.register(
  path.join(__dirname, 'tsconfig.json'),
  [/* mapped paths to share */]);

module.exports = {
  output: {
    uniqueName: "hostApp",
    publicPath: "auto"
  },
  optimization: {
    runtimeChunk: false
  },
  resolve: {
    alias: {
      ...sharedMappings.getAliases(),
    }
  },
  experiments: {
    outputModule: true
  },
  plugins: [
    new ModuleFederationPlugin({
        library: { type: "module" },

        remotes: {
            blazormodule: 'blazormodule@http://localhost:8080/js/remoteEntry.js'
        },

        shared: share({
          "@angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
          "@angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
          "@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
          "@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' },

          ...sharedMappings.getDescriptors()
        })

    }),
    sharedMappings.getPlugin()
  ],
};

Application de exemplo Link to this section

AplicaĆ§Ć£o host em Angular carregando componente em Blazor

https://github.com/dgenezini/BlazorModuleFederationSample

Problemas e limitaƧƵes atuais Link to this section

  • Funciona apenas com Blazor WebAssembly;
  • Apenas uma aplicaĆ§Ć£o Blazor pode ser carregada pelo host (a aplicaĆ§Ć£o pode expor diversos componentes);
  • Servidor da aplicaĆ§Ć£o Blazor precisa ter CORS habilitado.
šŸ’¬ Like or have something to add? Leave a comment below.
Ko-fi
GitHub Sponsor
Licensed under CC BY-NC-SA 4.0
Criado com Hugo
Tema Stack desenvolvido por Jimmy