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