Featured image of post Introducing module federation for Blazor components

Introducing module federation for Blazor components

Expose your Blazor components as microfrontends with module federation

Follow me

Introduction Link to this section

With the release of .NET 7, Microsoft included a feature to render Blazor components in JavaScript applications (RegisterCustomElement<T>). This helps those who want to slowly migrate JavaScript applications to Blazor, but unfortunately, won’t work for exposing Blazor components as microfrontends, as it works only for JavaScript applications deployed together with the Blazor application.

In this post, I’ll present a nuget package that I’ve created as a prototype to try to solve this problem, exposing Blazor components with module federation for other applications to consume.

What is Module Federation? Link to this section

Module Federation is a webpack feature that enables us to expose JavaScript modules for other applications to consume. These federated modules can be deployed independently and are isolated from one another, allowing us to build an application using the Microfrontend architecture.

What is Microfrontend architecture? Link to this section

Microfrontend architecture is similar to the microservices architecture, but applied to frontend applications. An application developed using microfrontend architecture is composed of one or more microfrontends, components that are self contained, individually deployed and that can be developed in different technologies from each other.

In the image below, we can see an application with four microfrontends. Each one is developed by a different team and in a different technology.

Credits to https://micro-frontends.org/

More on microfrontends in this Martin Fowler’s post.

Angular Module Federation wrapper for Blazor Link to this section

Disclaimer: This package is a prototype and not ready for production.

After installing Angular Module Federation wrapper for Blazor nuget package, it will generate an Angular application at compile time, exposing the registered Blazor components through module federation.

The exposed components accept input parameters and subscription to events.

Blazor Configuration Link to this section

First, install the Blazor.ModuleFederation.Angular Nuget package:

Install-Package Blazor.ModuleFederation.Angular

The source code for the Angular application is generated by an MSBuild task. For this, we need to configure which component will be exposed.

PokemonCards.razor: Link to this section

In the .razor file of the component, include the attribute GenerateModuleFederationComponent at the top.

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

...

Program.cs: Link to this section

In the Program.cs file, register the component with the RegisterForModuleFederation method.

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

builder.RootComponents.RegisterForModuleFederation<PokemonCards>();

...

Project.csproj: Link to this section

In the .csproj file, configure the following parameters:

  • ModuleFederationName: Name of the module that will be exposed;
  • MicroFrontendBaseUrl: The URL where the Blazor application will be published to;
  • BuildModuleFederationScript: Enable or disable the Angular module federation wrapper generation;
  • IsProduction: If the Angular app will be compiled with production configuration;
 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>

Host Configuration Link to this section

PokemonCardsLoaderComponent: Link to this section

Create a loader component to load the remote Blazor component. Don’t forget to include it in the app module.

 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

Include the loader component in the 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

Import the Blazor exposed component in the 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()
  ],
};

Sample application Link to this section

Angular host application loading a Blazor component

https://github.com/dgenezini/BlazorModuleFederationSample

Current issues and limitations Link to this section

  • Only works with Blazor WebAssembly;
  • Only one Blazor app can be loaded by a host (the app may expose several components);
  • Blazor App server needs to have CORS enabled.
💬 Like or have something to add? Leave a comment below.
Ko-fi
GitHub Sponsor
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy