Skip to content

Commit

Permalink
docs(angular): update mfe guide (#9500)
Browse files Browse the repository at this point in the history
* Revert "Revert "docs(angular): update the mfe guide (#9478)" (#9492)"

This reverts commit 1903f01.

* docs(angular): fix guide to use mfe.config.js
  • Loading branch information
Coly010 committed Apr 7, 2022
1 parent 35a5f82 commit 751e80b
Showing 1 changed file with 48 additions and 88 deletions.
136 changes: 48 additions & 88 deletions docs/shared/guides/setup-mfe-with-angular.md
Expand Up @@ -3,13 +3,13 @@
[Webpack 5](https://webpack.js.org/blog/2020-10-10-webpack-5-release/) introduced a [Module Federation Plugin](https://webpack.js.org/concepts/module-federation/#modulefederationplugin-high-level) enabling multiple, independently built and deployed bundles of code to form a single application. This is the foundation of Micro Frontend Architecture and the Module Federation Plugin makes implementing such an architecture much simpler.
With Angular 12 adding support for Webpack 5 it increases the viability of scaffolding a Micro Frontend architecture with Angular.

We made some changes to our `@nrwl/angular:app` generator to aid in the scaffolding of Module Federation configuration required for setting up a Micro Frontend Architecture.
We have added some generators to aid in the scaffolding of the Module Federation configuration required for setting up a Micro Frontend Architecture.

Therefore, using Nx it can be fairly straightforward to scaffold and build a Micro Frontend Architecture from a monorepo with all the additional benefits of Nx.
Therefore, using Nx, it can be fairly straightforward to scaffold and build a Micro Frontend Architecture from a monorepo with all the additional benefits of Nx.

In this guide, we'll show you to how setup a Micro Frontend Architecture with Nx and Angular.
In this guide, we'll show you to how to set up a Micro Frontend Architecture with Nx and Angular.

> **NOTE**: When serving Micro-Frontends (_MFEs_) in dev mode locally, there'll be an error output to the console, `import.meta` cannot be used outside of a module, and the script that is coming from is `styles.js`. It's a known error output, but it doesn't actually cause any breakages from as far as our testing has shown. It's because the Angular compiler attaches the `styles.js` file to the `index.html` in a `<script>` tag with `defer`.
> **NOTE**: When serving Micro-Frontends (_MFEs_) in dev mode locally, there'll be an error output to the console: `import.meta cannot be used outside of a module`, and the script that is coming from is `styles.js`. It's a known error output, but it doesn't actually cause any breakages from as far as our testing has shown. It's because the Angular compiler attaches the `styles.js` file to the `index.html` in a `<script>` tag with `defer`.
>
> It needs to be attached with `type=module`, but doing so breaks HMR. There is no way of hooking into that process for us to patch it ourselves.
>
Expand All @@ -20,11 +20,11 @@ In this guide, we'll show you to how setup a Micro Frontend Architecture with Nx
## What we'll build

We will put together a simple Admin Dashboard application that requires a user to log in to view the protected content.
To achieve this we will have two apps:
To achieve this we will have two applications:

- Admin Dashboard app
- Admin Dashboard application
- This will contain the layout of the dashboard and the content that can only be viewed by an authenticated and authorized user.
- Login app
- Login application
- This will contain the forms and logic for authenticating and authorizing a user

When the user tries to view a protected page within the Admin Dashboard, if they are not authenticated we will present them with a login form so that they can authenticate and view the contents of the page.
Expand Down Expand Up @@ -69,132 +69,103 @@ yarn add -D @nrwl/angular

Simple! You are now able to use Nx Generators to scaffold Angular applications and libraries.

### Creating our apps
### Creating our applications

We need to generate two applications. We also need to tell Nx that we want these applications to support Module Federation.
We need to generate two applications that support Module Federation.

We'll start with the Admin Dashboard application which will act as a host application for the Micro-Frontends (_MFEs_):

```bash
# Npm
npx nx g @nrwl/angular:app dashboard --mfe --mfeType=host --routing=true
npx nx g @nrwl/angular:host dashboard
```

```bash
# Yarn
yarn nx g @nrwl/angular:app dashboard --mfe --mfeType=host --routing=true
yarn nx g @nrwl/angular:host dashboard
```

You'll be prompted for some additional options. For this tutorial, just select the default options.
The application generator will create and modify the files needed to setup an Angular application.
The application generator will create and modify the files needed to set up the Angular application.

Now, let's generate the Login application as a remote application.

```bash
# Npm
npx nx g @nrwl/angular:app login --mfe --mfeType=remote --port=4201 --host=dashboard --routing=true
npx nx g @nrwl/angular:remote login --host=dashboard
```

```bash
# Yarn
yarn nx g @nrwl/angular:app login --mfe --mfeType=remote --port=4201 --host=dashboard --routing=true
yarn nx g @nrwl/angular:remote login --host=dashboard
```

_**Note:** We provided `remote` as the `--mfeType`. This tells the generator to create a Webpack configuration file that is ready to be consumed by a Host application._
_**Note:** We provided `--host=dashboard` as an option. This tells the generator that this remote application will be consumed by the Dashboard application. The generator will automatically link these two applications together in the `mfe.config.js` that gets used in the `webpack.config.js`._

_**Note:** We provided `4201` as the `--port`. This helps when developing locally as it will tell the `serve` target to serve on a different port reducing the chance of multiple remote apps trying to run on the same port._

_**Note:** We provided `--host=dashboard` as an option. This tells the generator that this remote app will be consumed by the Dashboard application. The generator will automatically link these two apps together in the `webpack.config.js`_

_**Note**: The `RemoteEntryModule` generated will be imported in `app.module.ts` file, however, it is not used in the `AppModule` itself. This it to allow TS to find the Module during compilation, allowing it to be included in the built bundle. This is required for the Module Federation Plugin to expose the Module correctly. You can choose to import the `RemoteEntryModule` in the `AppModule` if you wish, however, it is not necessary._
_**Note**: The `RemoteEntryModule` generated will be imported in `app.module.ts` file, however, it is not used in the `AppModule` itself. This is to allow TS to find the Module during compilation, allowing it to be included in the built bundle. This is required for the Module Federation Plugin to expose the Module correctly. You can choose to import the `RemoteEntryModule` in the `AppModule` if you wish, however, it is not necessary._

## What was generated?

Let's take a closer after generating each application.

For both apps, the generator did the following:
For both applications, the generator did the following:

- Created the standard Angular application files
- Added a `mfe.config.js` file
- Added a `webpack.config.js` and `webpack.prod.config.js`
- Added a `bootstrap.ts` file
- Moved the code that is normally in `main.ts` to `bootstrap.ts`
- Changed `main.ts` to dynamically import `bootstrap.ts` _(this is required for the Module Federation to correct load versions of shared libraries)_
- Updated the `build` target in the `project.json` to use the `@nrwl/angular:webpack-browser` executor _(this is required as it supports passing a custom webpack configuration to the Angular compiler)_
- Updated the `serve` target to use `@nrwl/angular:webpack-server`. _(this is required as we first need webpack to build the app with our custom webpack config)_
- Installed Manfred Steyer's `@angular-architects/module-federation` package _(Read more on it [here](https://github.com/angular-architects/module-federation-plugin))_
- Updated the `build` target in the `project.json` to use the `@nrwl/angular:webpack-browser` executor _(this is required as it supports passing a custom Webpack configuration to the Angular compiler)_
- Updated the `serve` target to use `@nrwl/angular:webpack-server` _(this is required as we first need Webpack to build the application with our custom Webpack configuration)_

The key differences reside within the configuration of the Module Federation Plugin within each app's `webpack.config.js`.
The key differences reside within the configuration of the Module Federation Plugin within each application's `mfe.config.js`.

We see the following within Login's webpack configuration:
We see the following within Login's micro frontend configuration:

```js
new ModuleFederationPlugin({
module.exports = {
name: 'login',
filename: 'remoteEntry.js',
exposes: {
'./Module': 'apps/login/src/app/remote-entry/entry.module.ts',
},
shared: {
'@angular/core': { singleton: true, strictVersion: true },
'@angular/common': { singleton: true, strictVersion: true },
'@angular/common/http': { singleton: true, strictVersion: true },
'@angular/router': { singleton: true, strictVersion: true },
...sharedMappings.getDescriptors(),
},
library: {
type: 'module',
},
}),
};
```

Taking a look at each property of the configuration in turn:

- `name` is the name that Webpack assigns to the remote appliction. It usually matches the name of the application.
- `filename` is the name given to the remote entrypoint that Webpack sets up to allow shell applications to consume the remote application.
- `name` is the name that Webpack assigns to the remote application. It **must** match the name of the application.
- `exposes` is the list of source files that the remote application provides consuming shell applications for their own use.
- `shared` is a list of libraries that should be shared between the remote and the shell application. By setting `singleton: true` we ensure that Webpack will only provide one instance of the library across the shell application and the remote application.

_**NOTE:** You may have some concerns that `remoteEntry.js` is not hashed as it could potentially lead to caching issues. This can be easily rectified with [ETags](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag). ETags are supported by a lot of hosting providers and servers with some ETagging static assets by default._

We can see the following in Dashboard's webpack configuration:
This config is then used in the `webpack.config.js` file:

```js
new ModuleFederationPlugin({
remotes: {
login: 'http://localhost:4201/remoteEntry.js',
},
shared: {
'@angular/core': { singleton: true, strictVersion: true },
'@angular/common': { singleton: true, strictVersion: true },
'@angular/common/http': { singleton: true, strictVersion: true },
'@angular/router': { singleton: true, strictVersion: true },
...sharedMappings.getDescriptors(),
},
library: {
type: 'module',
},
}),
const { withModuleFederation } = require('@nrwl/angular/module-federation');
const config = require('./mfe.config');
module.exports = withModuleFederation(config);
```

The key difference to note with the Dashboard's configuration is the `remotes` object. This is where you list the remote applications you want to consume in your host application.
We can see the following in Dashboard's micro frontend configuration:

You give it a name that you can reference in your code, in this case `login`.
Then you assign it a string value of the following pattern:
`{url}/{remoteEntrypointFilename}`
where:
```js
module.exports = {
name: 'dashboard',
remotes: ['login'],
};
```

The key difference to note with the Dashboard's configuration is the `remotes` array. This is where you list the remote applications you want to consume in your host application.

- `url` is the url where the remote application is hosted
- `remoteEntrypointFilename` is the filename supplied in the remote's webpack configuration
You give it a name that you can reference in your code, in this case `login`. Nx will find where it is served.

Now that we have our applications generated, let's move on to building out some functionality for each.

## Adding Functionality

We'll start by building the Login app, which will consist of a login form and some very basic and insecure authorization logic.
We'll start by building the Login application, which will consist of a login form and some very basic and insecure authorization logic.

### User Library

Let's create a user data-access library that we will share between the host application and the remote application. This will be used to determine if there is an authenticated user as well as providing logic for authenticating the user.
Let's create a user data-access library that will be shared between the host application and the remote application. This will be used to determine if there is an authenticated user as well as providing logic for authenticating the user.

```bash
nx g @nrwl/angular:lib shared/data-access-user
Expand Down Expand Up @@ -350,7 +321,7 @@ nx run login:serve

We can see if we navigate a browser to `http://localhost:4201` that we see the login form rendered:

![Login App](/shared/guides/login-app.png)
![Login Application](/shared/guides/login-app.png)

If we type in the correct username and password _(demo, demo)_, then we can also see the user gets authenticated!

Expand All @@ -360,22 +331,12 @@ Perfect! Our login application is complete.

Now let's create the Dashboard application where we'll hide some content if the user is not authenticated. If the user is not authenticated, we will present them with the Login application where they can log in.

To make this work, however, there are some initial steps we need to take. We need the state within `UserService` to be shared across both applications. To do this, we need to add a mapping within each application's `webpack.config.js` file.
For this to work, the state within `UserService` must be shared across both applications. Usually, with Module Federation in Webpack, you have to specify the packages to share between all the applications in your Micro Frontend solution.
However, by taking advantage of Nx's project graph, Nx will automatically find and share the dependencies of your applications.

Open up each and replace the `sharedMappings` config _(found near the top)_ with the following:

```js
sharedMappings.register(
tsConfigPath,
[
/* mapped paths to share */
'@ng-mfe/shared/data-access-user',
],
workspaceRootPath
);
```
_**Note:** This helps to enforce a single version policy and reduces the risk of [Micro Frontend Anarchy](https://www.thoughtworks.com/radar/techniques/micro-frontend-anarchy)_

Now, let's delete the `app.component.html` and `app.component.css` files in the Dashboard app. They will not be needed for this tutorial.
Now, let's delete the `app.component.html` and `app.component.css` files in the Dashboard application. They will not be needed for this tutorial.

Finally, let's add our logic to `app.component.ts`. Change it to match the following:

Expand Down Expand Up @@ -416,7 +377,7 @@ export class AppComponent implements OnInit {
}
```

We can run both the dashboard app and the login app and you can try it out using:
We can run both the dashboard application and the login application and you can try it out using:

```bash
nx run dashboard:serve-mfe
Expand All @@ -426,10 +387,9 @@ nx run dashboard:serve-mfe

As you can see, with this approach, your Login application can be deployed independently and developed independently without forcing you to have to rebuild or redeploy your Dashboard application. This can lead to a powerful micro frontend architecture that enables multiple teams to work independently in a single monorepo!

In this tutorial, we exposed a single module that was consumed dynamically as an Angular Route. You can see how that is done in more detail in [Manfred Steyer's](https://twitter.com/ManfredSteyer) article on Micro Fontends [here](https://www.angulararchitects.io/en/aktuelles/the-microfrontend-revolution-part-2-module-federation-with-angular/).
In this tutorial, we exposed a single module that was consumed dynamically as an Angular Route.

## References and Further Reading

- Module Federation: https://webpack.js.org/concepts/module-federation/
- Mirco Frontend Revolution Article Series: https://www.angulararchitects.io/aktuelles/the-microfrontend-revolution-module-federation-in-webpack-5/
- Repo containing the code in this example: https://github.com/Coly010/ng-mfe-example

0 comments on commit 751e80b

Please sign in to comment.