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.