# Kiunzi Micro-frontend Tools

Kiunzi is a scaffolding framework for building microservice based applications.  The Kiunzi Micro-frontend Tools library provides support for Module Federation and Custom Elements to enable the development fully encapsulated micro-frontends

## 🏠Table of Contents

- [Acknowledgements](#acknowledgements)
- [Motivation](#motivation)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Getting Started](#getting-started)
- [Development](#development)
- [License](#license)

## 🎁Acknowledgements

This package uses and is inspired by `@angular-architects/module-federation` and `@angular-architects/module-federation-tools` by [Manfred Steyer](https://twitter.com/ManfredSteyer).

## 🤔Motivation

There were a few things in the angular architects packages that I felt could be expanded and improved on, and I also thought that more could be done with schematics to get a full implementation up and running without multiple tweaks to the generated code.

The key motivation is to simplify adoption of micro-frontends.

## 🔍Prerequisites

The general foundations:
- Module Federation requires Webpack 5+
- Angular's support of Webpack 5 requires Angular 12+
- This library is built using Angular 13+

Packages used and automatically added to the `package.json` of the angular workspace when Kiunzi is added.
- `angular-oidc-auth-client`
- `@angular-architects/module-federation`
- `@angular-architects/module-federation-tools`
- `@ng-bootstrap/ng-bootstrap`
- `@popperjs/core`
- `bootstrap`

## 📦Installation

This library is intended to be used at the start of establishing a project as it updates configurations and generates additional code and configurations.  In short it builds the scaffolding.

First, build the example application
```sh
# Create your angular workspace
ng new micro-frontend-workspace --create-application false

# Navigate into the micro-frontend-workspace folder
cd micro-frontend-workspace

# Create the shared library
ng generate library --project lib

# Create the shell
ng generate application --no-routing --style sass --project shell

# Create the micro-frontends
ng generate application --no-routing --style sass --project mfe1
ng generate application --no-routing --style sass --project mfe2

# Configure the library
ng add @jamarsto/kiunzi-micro-frontend-tools --project lib --type library --authority <your_oidc_server_url> --client <your_client_id>

# Configure the shell
ng add @jamarsto/kiunzi-micro-frontend-tools --project shell --type shell --port 8000 --library lib

# Configure the micro-frontends
ng add @jamarsto/kiunzi-micro-frontend-tools --project mfe1 --type microfrontend --port 8001 --library lib
ng add @jamarsto/kiunzi-micro-frontend-tools --project mfe2 --type microfrontend --port 8002 --library lib
```

Next, update the `tsconfig.json` to add the `resolveJsonModule` and `esModuleInterop` flags
```json
{
  "compileOnSave": false,
  "compilerOptions": {
    "resolveJsonModule": true,
    "esModuleInterop": true,
```

Finally, make sure the library is linked locally so that it is exposed as an `npm` package to the build
```sh
cd projects/lib
npm link
cd ../..
npm link lib
```

You may need to restart your ide for the `tsconfig.json` and `npm link` changes to be picked up

## 📀Getting Started

Update `projects/shell/src/app/app-routing.module.ts` to add the modules to ``customShellRoutes.moduleRoutes``
```ts
export const customShellRoutes: CustomShellRoutes = {
	headRoutes: [
		{ path: '', redirectTo: 'retail', pathMatch: 'full' },
		{ path: 'unauthorized', component: UnauthorisedComponent }
	],
	moduleRoutes: [
		{ title: 'Module One', name: 'mfe1', prefix: 'one', items: [], guards: [AutoLoginAllRoutesWithRoleGuard], roles: ['ADMIN', 'USER'] },
		{ title: 'Module Two', name: 'mfe2', prefix: 'two', items: [], guards: [AutoLoginAllRoutesWithRoleGuard], roles: ['ADMIN', 'USER'] }
	],
	tailRoutes: [
		{ path: '**', component: NotFoundComponent }
	]
}
```

Update the `src/app/remote-app/remote-app-routing.module.ts` for each module to reflect the routing required within the module
```ts
const customRoutes: CustomModuleRoutes = {
  headRoutes: [
    { path: '', redirectTo: shellModule.prefix, pathMatch: 'full' },
    { path: 'unauthorized', component: UnauthorisedComponent }
  ],
  moduleRoute: { component: RootComponent, guards: [AutoLoginAllRoutesWithRoleGuard], roles: ['ADMIN', 'USER'], children: [
    { path: '', component: HomeComponent },
    { path: 'example', component: ExampleComponent },
  ]},
  tailRoutes: []
}
```

Update the `src/assets/menu.json` for each module to reflect routes we want to add to the `shell` navigation bar
```json
{
    "menuItems": [
        { "title": "Home", "link": "/", "fullMatch": true },
        { "title": "Example", "link": "/example", "fullMatch": false }
    ]
}
```

Add a `proxy.conf.json` to the angular workspace, making sure the entries created match the modules created
```json
{
    "/mfe/mfe1": {
        "target": "http://localhost:8001",
        "pathRewrite": {
            "^/mfe/mfe1/": "/"
        },
        "secure": false,
        "changeOrigin": true
    },
    "/mfe/mfe2": {
        "target": "http://localhost:8002",
        "pathRewrite": {
            "^/mfe/mfe2/": "/"
        },
        "secure": false,
        "changeOrigin": true
    }
}
```

Add `proxyConfig` to the `serve` section of the `shell` project in `angular.json`
```json
"serve": {
    "builder": "ngx-build-plus:dev-server",
        "configurations": {
            "production": {
                "browserTarget": "shell:build:production",
                "extraWebpackConfig": "projects/shell/webpack.prod.config.js"
            },
            "development": {
                "browserTarget": "shell:build:development",
                "proxyConfig": "proxy.conf.json"
            }
        },
```

The modules and `shell` are now ready to be built and run as normal



## ✨Development

The `shellModule` constant in each `src/app/remote-app/remote-app-routing.module.ts` is used to simulate the `shell` when testing the micro-frontend in standalone mode
```ts
export const shellModule: Module = {name: 'mfe1', title: 'Module One', prefix: 'one', items: jsonMenuItems.menuItems as MenuItems};
```

The navigation bar in the `projects/src/shell/app/component/header/header.component.ts` is dynamically generated using the `src/assets/menu.json` from each module
```ts
ngOnInit(): void {
  this.modules.forEach((entry) => this
      .menuItemService
      .getMenuItemsForModule(entry.name)
      .subscribe((children) => entry.items = children));
}
```

To keep the routes from the `shell` and modules in sync Kiunzi uses events under the hood. To register the event handling we use `syncRouteShell.sync` in the `constructor` of `projects/shell/src/app/app.component.ts`

```ts
constructor(private router: Router, private syncRouteShell: SyncRouteShell) {
  this.syncRouteShell.sync(this.router, customShellRoutes);
}
```

and we use `syncRouteModule.sync` in `ngOnInit` of `src/app/remote-app/remote-app.component.ts` in each module

```ts
ngOnInit(): void {
  this.syncRouteModule.sync(this.router, shellModule.name);
}
```

## 📄License

This project is licensed under the MIT license.  See the [LICENSE](https://github.com/jamarsto/kiunzi-micro-frontend-tools/blob/master/LICENSE)
