Skip to content

Commit

Permalink
docs(nxdev): update module federation guide (#10176)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaysoo committed May 6, 2022
1 parent ac76a7e commit 7da1913
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 39 deletions.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/shared/guides/module-federation/dep-graph.png
Binary file not shown.
103 changes: 64 additions & 39 deletions docs/shared/guides/module-federation/faster-builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,36 @@ With Module Federation, a large application is split into:
1. A single **Host** application that references external...
2. **Remote** applications, which handle a single domain or feature.

In the next section, we will see an example with a host app (`shell`) and three remotes (`shop`, `cart`, `about`). Although all the applications are independently built, thus have no dependency between them, conceptually you can think of them in the following hierarchy.
In the next section, we will see an example with a host app (`host`) and three remotes (`shop`, `cart`, `about`). Although all the applications are independently built, thus have no dependency between them, conceptually you can think of them in the following hierarchy.

![Shell with implicit dependencies to remotes](/shared/guides/module-federation/dep-graph.png)
![Host with implicit dependencies to remotes](/shared/guides/module-federation/dep-graph-2.png)

## Creating an example workspace

The best way to understand the setup is through an example. In this section, we will create a `shell` (host) application with three remotes under these routes:
The best way to understand the setup is through an example. In this section, we will create a `host` application with three remotes under these routes:

1. `/shop`
1. `/cart`
1. `/about`

First, create an empty Nx workspace.
But before we begin, we've put together a couple of example repos for you to inspect if you want to skip ahead.

- Angular: https://github.com/nrwl/ng-module-federation
- React: https://github.com/nrwl/react-module-federation

These examples have fully functioning [CI](https://github.com/nrwl/react-module-federation/blob/main/.github/workflows/ci.yml) [workflows](https://github.com/nrwl/ng-module-federation/blob/main/.github/workflows/ci.yml) that are simple to set up. You can see what the CI does by viewing the sample pull requests in each repo. Also notice the [Nx Cloud](https://nx.app) integration, which gives you insight into each pipeline. We'll touch on this [later in this guide](#distributed-computation-caching-with-nx-cloud).

![Nx Cloud integration for GitHub](/shared/guides/module-federation/pull-request.png)

Now, let's continue by creating an empty Nx workspace.

```bash
# Replace acme with desired scope
npx create-nx-workspace acme --preset=empty
cd acme
```

**Note:** You will be prompted to enable Nx Cloud in the workspace. For the best experience, we highly recommend using Nx Cloud to take advantage of distributed caching and other features it provides. We'll discuss this [later in the guide](#distributed-caching-with-nx-cloud).
> Note: You will be prompted to enable Nx Cloud in the workspace. For the best experience, we highly recommend using Nx Cloud to take advantage of distributed caching and other features it provides.
Then, for React users, install the `@nrwl/react` plugin; and for Angular users, install the `@nrwl/angular` plugin.

Expand All @@ -67,44 +76,44 @@ Next, generate the host and remote applications.

```bash
# React
nx g @nrwl/react:host shell --remotes=shop,cart,about
nx g @nrwl/react:host host --remotes=shop,cart,about

#a Angular
nx g @nrwl/angular:host shell --remotes=shop,cart,about
nx g @nrwl/angular:host host --remotes=shop,cart,about
```

**Note:** You can leave off the `--remotes` option and add them later with `nx g @nrwl/react:remote shop --host=shell` or `nx g @nrwl/angular:remote shop --host=shell`.
> Note: You can leave off the `--remotes` option and add them later with `nx g @nrwl/react:remote shop --host=host` or `nx g @nrwl/angular:remote shop --host=host`.
Now, serve `shell` to view it in your browser.
Now, serve `host` to view it in your browser.

```bash
nx serve shell --open
nx serve host --open
```

The above command serves `shell` in development mode, whereas the remotes are built and served statically. That is, changes to `shell` will update its bundle, but changes to remotes will not update.
The above command serves `host` in development mode, whereas the remotes are built and served statically. That is, changes to `host` will update its bundle, but changes to remotes will not update.

To run one or more remotes in development mode, use the `--devRemotes` option.

```bash
nx serve shell --open --devRemotes=shop,cart
nx serve host --open --devRemotes=shop,cart
```

The above command starts the `shop` and `cart` remotes in development mode, but `about` will remain static.

> Note, both commands serve the whole system. By passing devRemotes, you configure what parts of it you will be changing. For instance, in the example above, you can go to the about page and back. This is different from having different versions of the app for every team.
> Note: Both commands serve the whole system. By passing `--devRemotes`, you configure what parts of it you will be changing. For instance, in the example above, you can go to the about page and back. This is different from having different versions of the app for every team.
## What was generated?

To understand how Module Federation works with Nx, let's take a look at three files that control this feature.

### `apps/shell/project.json`
### `apps/host/project.json`

The `build` target uses `@nrwl/web:webpack` for React, and `@nrwl/angular:webpack-browser` for Angular. This is the same as a normal SPA that uses custom webpack configuration (`webpackConfig`), but difference is in the webpack configuration file.

If you use Module Federation to speed up your CI and improve your local development, and not to deploy different remotes independently, you need to create implicit dependencies from the host to all the remotes. Semantically, the host and the remotes comprise one application, so you cannot build the host without the remotes. Adding implicit dependencies also makes distributed builds possible ([see below](#production-build-and-deployment)). To create these dependencies, add the `implicitDependencies` configuration.

```text
// apps/shell/project.json
// apps/host/project.json
{
//...
"implicitDependencies": ["about", "shop", "cart"]
Expand All @@ -113,7 +122,7 @@ If you use Module Federation to speed up your CI and improve your local developm

In the future, Nx may automatically handle this for you.

### `apps/shell/webpack.config.js`
### `apps/host/webpack.config.js`

The webpack configuration uses an utility function that Nx provides: `withModuleFederation`.

Expand All @@ -129,20 +138,20 @@ module.exports = withModuleFederation({

We'll talk about [what `withModuleFederation` does](#what-does-withmodulefederation-do) in a bit, but for now the important part of the configuration is the use of `module-federation.config.js` which we will examine next.

### `apps/shell/module-federation.config.js`
### `apps/host/module-federation.config.js`

This file is the main configuration for the `shell`, and you'll see `module-federation.config.js` for the generated remotes as well.
This file is the main configuration for the `host`, and you'll see `module-federation.config.js` for the generated remotes as well.

```javascript
module.exports = {
name: 'shell',
name: 'host',
remotes: ['shop', 'cart', 'about'],
};
```

The required `name` property is the magic to link the host and remotes together. Since `shell` is a host, it references the three remotes by their names.
The required `name` property is the magic to link the host and remotes together. The `host` application references the three remotes by their names.

**Note:** It is important that the values in `remotes` property matches the `name` property of the remote applications. Otherwise, webpack will throw an error. Nx handles this automatically for you so there shouldn't be an issue unless it was modified manually.
> Note: It is important that the values in `remotes` property matches the `name` property of the remote applications. Otherwise, webpack will throw an error. Nx handles this automatically for you so there shouldn't be an issue unless it was modified manually.
## What does `withModuleFederation` do?

Expand All @@ -159,7 +168,7 @@ If you want to opt-out of the shared, singleton behavior of libraries you can us

```javascript
module.exports = {
name: 'shell',
name: 'host',
remotes: ['shop', 'cart', 'about'],
shared: (name, config) => {
if (name === 'react' || name === 'react-dom') {
Expand All @@ -172,29 +181,29 @@ module.exports = {

The `shared` function can return a [shared config](https://webpack.js.org/plugins/module-federation-plugin/#sharing-hints) that webpack supports, `undefined` to use Nx's default value, or `false` to exclude it from being shared.

**Note:** We discourage users from overriding the shared configuration. If you have any feedback regarding this feature, we'd love to hear from you--check our [community page](https://nx.dev/community) for links to our Slack and Twitter.
> Note: We discourage users from overriding the shared configuration. If you have any feedback regarding this feature, we'd love to hear from you--check our [community page](https://nx.dev/community) for links to our Slack and Twitter.
## Distributed caching with Nx Cloud
## Distributed computation caching with Nx Cloud

To use Module Federation well, we recommend you to enable Nx Cloud. If you haven't enabled it yet when using `create-nx-workspace`, you can do the following.
To use Module Federation well, we recommend that you enable [Nx Cloud](https://nx.app). If you haven't enabled it yet when using `create-nx-workspace`, you can do the following.

```bash
nx connect-to-nx-cloud
```

With Nx Cloud enabled, a large set of builds can be skipped entirely when running the application locally (and in CI/CD). When you run builds through Nx + Nx Cloud, the artifacts are stored in the distributed cache, so as long as the source of a given remote hasn't changed, it will be served from cache.

You can see this behavior locally if you serve the `shell` twice.
You can see this behavior locally if you serve the `host` twice.

```bash
nx serve shell
nx serve host

# (kill server)

nx serve shell
nx serve host
```

The second serve should start up much faster, because the three remotes (`shop`, `cart`, `about`) are read from cache. Not only that, any other copy of the workspace will also benefit from the cache if they haven't changed a particular remote. If, say, someone is working on `shop`, they will get the `cart` and `about` builds from the cache.
The second serve starts up much faster, because the three remotes (`shop`, `cart`, `about`) are read from cache. Not only that, any other copy of the workspace will also benefit from the cache if they haven't changed a particular remote. If, say, someone is working on `shop`, they will get the `cart` and `about` builds from the cache.

If you inspect the terminal output, you'll see something like this, even if you are on different machines.

Expand All @@ -209,18 +218,26 @@ If you inspect the terminal output, you'll see something like this, even if you

```

> This caching behavior is _crucial_. If you don't have a build system supporting distributed computation caching, using Module Federation will likely be slower. It takes longer to build `shop`, `cart` and `about` separately than building all of them together as part of the same process. **When using Nx, you rarely have to build all of them because most of the time you work on one remote, other remotes will be retrieved from cache.**
> This caching behavior is _crucial_. If you don't have a build system supporting distributed computation caching, using Module Federation will be slower. It takes longer to build `shop`, `cart` and `about` separately than building all of them together as part of the same process. **When using Nx, you rarely have to build all of them because most of the time you work on one remote, other remotes will be retrieved from cache.**
This also helps things like end-to-end (E2E) testing because testing against a static server is much more efficient than starting many servers in development mode. When the CI pipeline runs E2E tests, all the remotes should be served statically from cache.

In addition to computation caching, Nx Cloud also comes with:

- Distributed task execution, which simplifies your CI/CD setup, and speeds up your builds.
- GitHub integration, so you can easily access important information without digging through a bunch of CI/CD logs.
- Actionable insights, which improve caching and task distribution.

![Nx Cloud run details](/shared/guides/module-federation/nx-cloud.png)

## Production build and deployment with Nx Cloud

In this section, we'll examine how to set up your production build and simulate a deployment to `http://localhost:3000`.

First, make sure you have implicit dependencies from `shell` to each remote. In case you didn't already set this up, add the following line to the `shell`'s project configuration.
First, make sure you have implicit dependencies from `host` to each remote. In case you didn't already set this up, add the following line to the `host`'s project configuration.

```text
// apps/shell/project.json
// apps/host/project.json
{
//...
"implicitDependencies": ["about", "shop", "cart"]
Expand All @@ -230,7 +247,7 @@ First, make sure you have implicit dependencies from `shell` to each remote. In
Next, open up the production webpack configuration file and update the remote URLs to their own subfolder under `http://localhost:3000`.

```javascript
// apps/shell/webpack.config.prod.js
// apps/host/webpack.config.prod.js
const withModuleFederation = require('@nrwl/react/module-federation');
const moduleFederationConfig = require('./module-federation.config');

Expand All @@ -244,17 +261,17 @@ module.exports = withModuleFederation({
});
```

Now you can run `nx build shell` to build all the `shell` and all the implicit dependencies in production mode.
Now you can run `nx build host` to build all the `host` and all the implicit dependencies in production mode.

> Again, if you don't use [Nx Cloud's Distributed Tasks Execution](/using-nx/dte) using Module Federation will likely be slower than building everything in a single process. It's only if you enable Distributed Tasks Execution, your CI will be able to build each remote on a separate machine, in parallel, (or not build it at all and retrieve it from cache), which will reduce the CI time.
> Again, if you don't use [Nx Cloud's Distributed Tasks Execution](/using-nx/dte) using Module Federation will be slower than building everything in a single process. It's only if you enable Distributed Tasks Execution, your CI will be able to build each remote on a separate machine, in parallel, (or not build it at all and retrieve it from cache), which will reduce the CI time.
After running that command you'll see the following artifacts in `dist` folder.

```treeview
dist/apps
├── about
├── cart
├── shell
├── host
└── shop
```

Expand All @@ -263,11 +280,11 @@ Now, we can add a simple deploy command to simulate deploying this folder to pro
```bash
nx g nx:run-commands \
deploy \
--project=shell \
--command="rm -rf production && mkdir production && cp -r dist/apps/shell/* production && cp -r dist/apps/{shop,cart,about} production && http-server -p 3000 -a localhost production"
--project=host \
--command="rm -rf production && mkdir production && cp -r dist/apps/host/* production && cp -r dist/apps/{shop,cart,about} production && http-server -p 3000 -a localhost production"
```

You can then run `nx deploy shell` to see the application running on `http://localhost:3000`. If you inspect the `production` folder you'll see the following files.
You can then run `nx deploy host` to see the application running on `http://localhost:3000`. If you inspect the `production` folder you'll see the following files.

```treeview
production/
Expand All @@ -286,6 +303,9 @@ production/

The above command is just an example. You'll need to use what make sense for your team and workspace.

For examples of how CI/CD pipelines can be configured using Nx Cloud and GitHub, see our [React](https://github.com/nrwl/react-module-federation)
and [Angular](https://github.com/nrwl/ng-module-federation) examples.

## Using buildable libs

By using Module Federation you essentially split your application build process vertically. You can also split it horizontally by making some libraries buildable.
Expand All @@ -303,3 +323,8 @@ Module Federation allows you to split a single build process into multiple proce
When a developer runs say `nx serve host --devRemotes=cart`, they still run the whole application, but `shop` and `about` are served statically, from cache. As a result, the serve time and the time it takes to see the changes on the screen go down, often by an order of magnitude.

When a CI machine runs say `nx build host --configuration=production`, the `shop`, `about` and `cart` remotes will either be build on separate machines or retrieved from cache. Once all of them are built, the build process for `host` will combine the file artifacts from all the remotes. Nx Cloud takes care of distributing the tasks and moving file artifacts across machines. As a result, the worst case scenario build time (when nothing is cached) goes from building all the code to building the largest remote, which is often an order of magnitude faster.

## Resources

- [React Module Federation example](https://github.com/nrwl/react-module-federation)
- [Angular Module Federation example](https://github.com/nrwl/ng-module-federation)
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

1 comment on commit 7da1913

@vercel
Copy link

@vercel vercel bot commented on 7da1913 May 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-git-master-nrwl.vercel.app
nx-dev-nrwl.vercel.app
nx-five.vercel.app
nx.dev

Please sign in to comment.