Featured image of post Brincando com module federation e Blazor

Brincando com module federation e 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