0

I have two Angular apps :

  • app1 = shellApp (the parent who consumes the micro front-end), running on port 4200
  • app2 = mfeApp (the one I want to consume in shellApp), running on port 4201

I would like to use a component of mfeApp in shellApp with Module Federation. I have been through a lot of tutorials (unsuccessfully) that uses this so I guess its the thing to do. Before event thinking of using the component, I am trying to route to it.

mfeApp is an app with one extra module containing one component, here is some code :
mfeApp - app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {AbcComponent} from "./test/abc/abc.component";

const routes: Routes = [
  {path: "abc", component: AbcComponent}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

mfeApp - test.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AbcComponent } from './abc/abc.component';



@NgModule({
  declarations: [
    AbcComponent
  ],
  imports: [
    CommonModule
  ]
})
export class TestModule { }

mfeApp - abc.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-abc',
  templateUrl: './abc.component.html',
  styleUrls: ['./abc.component.scss']
})
export class AbcComponent {

}

mfeApp - abc.component.html

<p>abc works!</p>

mfeApp - app.module.ts

import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import {TestModule} from "./test/test.module";

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    TestModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

and the most important mfeApp - webpack.config.ts

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: "mfeApp",
    publicPath: "auto"
  },
  optimization: {
    runtimeChunk: false
  },
  resolve: {
    alias: {
      ...sharedMappings.getAliases(),
    }
  },
  experiments: {
    outputModule: true
  },
  plugins: [
    new ModuleFederationPlugin({
        library: { type: "module" },

        //For remotes (please adjust)
        name: "mfeApp",
        filename: "remoteEntry.js",
        exposes: {
            './TestModule': './src/app/test/test.module.ts',
        },
        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()
  ],
};

shellApp is an empty app, no complexity, no components etc... It just has one module and a welcome page. Nothing fancy but still lets see some code.

shellApp - app.component.html

<h1>this is the shell app</h1>
<router-outlet></router-outlet>

shellApp - app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

shellApp - app.routing.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {loadRemoteModule} from "@angular-architects/module-federation";

const routes: Routes = [
  {
    path: 'mfeApp',
    loadChildren: () => loadRemoteModule({
      remoteEntry: 'http://localhost:4201/remoteEntry.js',
      remoteName: 'mfeApp',
      exposedModule: './TestModule',
    }).then(m => m.TestModule)
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

shellApp - webpack.config.ts

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: "shellApp",
    publicPath: "auto"
  },
  optimization: {
    runtimeChunk: false
  },
  resolve: {
    alias: {
      ...sharedMappings.getAliases(),
    }
  },
  experiments: {
    outputModule: true
  },
  plugins: [
    new ModuleFederationPlugin({
        library: { type: "module" },

        remotes: {
            "mfeApp": "http://localhost:4201/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()
  ],
};

Although http://localhost:4201/remonteEntry.js is accessibe, the http://localhost:4200/mfeApp throws a typescript error : container is undefined

I think I am doing the lazyloading wrong... I also realized that in my navigator networking tool that some requests are going from shellApp to mfeApp about all shared modules but none about the moduel I want to share... which is suspicious.

What am I doing wrong ? Do you see any typo in my code ? If you feel I am missing something important here, do you have any documentation or tutorial that could be helpful ? Thanks.

M Matthieu
  • 359
  • 3
  • 10

1 Answers1

0

I have find how to fix it. In shellApp, route should be as :

    path: 'mfeApp',
    loadChildren: () => loadRemoteModule({
      remoteEntry: 'http://localhost:4201/remoteEntry.js',
      exposedModule: './TestModule',
      type: "module",
    }).then(m => m.TestModule)
  }

It was missing

type:"module"

to work.

Found in the doc : https://github.com/angular-architects/module-federation-plugin/blob/main/libs/mf/tutorial/tutorial.md

M Matthieu
  • 359
  • 3
  • 10