Skip to content

Commit

Permalink
docs(misc): Add Manage Library Versions recipe
Browse files Browse the repository at this point in the history
  • Loading branch information
ndcunningham committed Oct 17, 2023
1 parent c053374 commit c1aa521
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 0 deletions.
24 changes: 24 additions & 0 deletions docs/generated/manifests/menus.json
Original file line number Diff line number Diff line change
Expand Up @@ -1940,6 +1940,14 @@
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "How to manage library versions",
"path": "/recipes/module-federation/manage-library-versions",
"id": "manage-library-versions",
"isExternal": false,
"children": [],
"disableCollapsible": false
}
],
"disableCollapsible": false
Expand Down Expand Up @@ -3322,6 +3330,14 @@
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "How to manage library versions",
"path": "/recipes/module-federation/manage-library-versions",
"id": "manage-library-versions",
"isExternal": false,
"children": [],
"disableCollapsible": false
}
],
"disableCollapsible": false
Expand Down Expand Up @@ -3350,6 +3366,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "How to manage library versions",
"path": "/recipes/module-federation/manage-library-versions",
"id": "manage-library-versions",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Enforce Module Boundaries",
"path": "/recipes/enforce-module-boundaries",
Expand Down
30 changes: 30 additions & 0 deletions docs/generated/manifests/nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -2418,6 +2418,16 @@
"isExternal": false,
"path": "/recipes/module-federation/federate-a-module",
"tags": []
},
{
"id": "manage-library-versions",
"name": "How to manage library versions",
"description": "A guide on how to use Nx to manage library versions in your Module Federation setup",
"file": "shared/recipes/module-federation/manage-library-versions",
"itemList": [],
"isExternal": false,
"path": "/recipes/module-federation/manage-library-versions",
"tags": []
}
],
"isExternal": false,
Expand Down Expand Up @@ -4142,6 +4152,16 @@
"isExternal": false,
"path": "/recipes/module-federation/federate-a-module",
"tags": []
},
{
"id": "manage-library-versions",
"name": "How to manage library versions",
"description": "A guide on how to use Nx to manage library versions in your Module Federation setup",
"file": "shared/recipes/module-federation/manage-library-versions",
"itemList": [],
"isExternal": false,
"path": "/recipes/module-federation/manage-library-versions",
"tags": []
}
],
"isExternal": false,
Expand Down Expand Up @@ -4178,6 +4198,16 @@
"path": "/recipes/module-federation/federate-a-module",
"tags": []
},
"/recipes/module-federation/manage-library-versions": {
"id": "manage-library-versions",
"name": "How to manage library versions",
"description": "A guide on how to use Nx to manage library versions in your Module Federation setup",
"file": "shared/recipes/module-federation/manage-library-versions",
"itemList": [],
"isExternal": false,
"path": "/recipes/module-federation/manage-library-versions",
"tags": []
},
"/recipes/enforce-module-boundaries": {
"id": "enforce-module-boundaries",
"name": "Enforce Module Boundaries",
Expand Down
6 changes: 6 additions & 0 deletions docs/map.json
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,12 @@
"name": "How to Federate a Module",
"description": "A guide on how to federate a module in your Nx workspace",
"file": "shared/recipes/module-federation/federate-a-module"
},
{
"id": "manage-library-versions",
"name": "How to manage library versions",
"description": "A guide on how to use Nx to manage library versions in your Module Federation setup",
"file": "shared/recipes/module-federation/manage-library-versions"
}
]
},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
130 changes: 130 additions & 0 deletions docs/shared/recipes/module-federation/manage-library-versions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Manage Library Versions

Federated modules are bundled and packaged independently with all the dependencies they need to run smoothly in federated applications called _remotes_. This means that if you have a federated module that depends on a library, the library will be bundled with the federated module within a remote. This independence provides much flexibility, allowing individual federated modules to function without relying on external resources.

A challenge arises when these federated modules are integrated into a _host_ or other _remotes_. Given that each federated module carries its own dependencies, the host application may have inadvertently downloaded multiple copies of the same dependency. This redundancy does two things:

1. Multiple copies of the same dependency can create bottlenecks and conflicts, resulting in unexpected behaviour.
2. With redundant dependencies, the host application can become bloated, increasing the bandwidth and consuming more memory and resources on the user's device.

To mitigate these issues, Module Federation has a shared API. Its primary function is to act as a gatekeeper, ensuring that only one copy of a dependency is downloaded, regardless of how many federated modules request it.

### How it works

The Shared API maintains a registry of all the downloaded dependencies. When a federated module requests a dependency, the Shared API checks the registry. If the dependency already exists, the module is directed to use the existing copy. If not, the dependency is downloaded and added to the registry.

![How Shared API works](/shared/guides/module-federation/shared-api.png)

{% callout type="info" title="Lost?" %}
If you are not familiar with the concepts of federated modules, remotes, and hosts, please read the [Faster builds with module federation](https://nx.dev/concepts/more-concepts/faster-builds-with-module-federation) for an introduction.
{% /callout %}

## Our Approach

Although the Shared API is a powerful tool, it can be challenging to manage. The Shared API is configured in the Module Federation Config File, which is a JavaScript or TypeScript file. This file is not part of the build process, so should you want to use a different version of a workspace dependency, you would have to manually record the change outside of the build process which can be a tedious and error-prone.

**Nx** follows the Single-Responsibility Principle (SRP) for managing library versions. Each library is responsible for its version. This approach is beneficial because it allows you to update a library without updating the entire application. Additionally, with this approach, consumers of the library (remotes/hosts) can dictate which version of the library they want to use, which can be further predicated by versioning schemes like SemVer.

## How are library versions managed?

With **Nx** there are two ways to manage how library versions are shared / managed with Module Federation:

### 1. Opt in to sharing library versions

This is the default behaviour for **Nx**. All dependencies are **singletons** and will be shared between remotes and hosts.

### 2. Opt out from sharing library versions

This means that the library will not be shared between remotes and hosts. Each remote and host will load its own version of the library.
A common use-case for this is if you want to enable tree-shaking for a library like _lodash_. If you share this library, it will be bundled with the remote and host, and tree-shaking will not be possible.

## Library Versions

**Nx** determines the version of a library by looking at a `package.json`. If the library is an npm package, the version is determined by the version declared in the workspace `package.json`. If the library is a workspace library, the version is determined by the version in the `package.json` of the project that consumes the shared library. RemoteA consumes Counter, which is a workspace library exposed and shared by RemoteB. The version of Counter is determined by the version in RemoteB's `package.json`. If the `package.json` does not exist or the library is not declared, Nx will use the version in the `package.json` of the workspace library.

![How Nx determines library versions](/shared/guides/module-federation/nx-library-version.png)

There are twos ways to manage library versions with **Nx**:

{% tabs %}
{% tab label="Opt out from sharing library" %}

```ts {% fileName="remote/module-federation.config.ts" %}
import { ModuleFederationConfig } from '@nx/webpack';

const config: ModuleFederationConfig = {
name: 'remote',
exposes: {
'./Module': './src/remote-entry.ts',
},
// Determine which libraries to share
shared: (packageName: string) {
// I do not want to share this package and I will load my own version
if(packageName === '@acme/utils') return false;
}
};
export default config;
```

This would result in the following webpack config:

```js {% fileName="webpack.config.js" %}
module.exports = {
plugins: [
new ModuleFederationPlugin({
// additional config
name: 'remote',
shared: {
react: { singleton: true, eager: true },
// acme/utils will not be shared
},
}),
],
};
```

{% /tab %}
{% tab label="Opt in to sharing library versions" %}

```ts {% fileName="remote/module-federation.config.ts" %}
import { ModuleFederationConfig } from '@nx/webpack';

const config: ModuleFederationConfig = {
name: 'remote',
exposes: {
'./Module': './src/remote-entry.ts',
},
// By not declaring a shared function, all dependencies will be shared
};
export default config;
```

This would result in the following webpack config:

```js {% fileName="webpack.config.js" %}
module.exports = {
// Additional config ignored for brevity
plugins: [
new ModuleFederationPlugin({
// ...
name: 'remote',
shared: {
react: { singleton: true, eager: true, version: '18.2.0' },
'acme/utils': { singleton: true, eager: true, version: '1.0.0' }, // <--- This version is determined by the logic discussed earlier
},
}),
],
};
```

### Example

Here is a working example of how to opt in and out of sharing library versions: https://github.com/jaysoo/module-federation-example

## Benefits

By taking advantage of **Nx**'s approach to managing library versions, you can:

- Streamline the process of updating a library version behind a versoning scheme like SemVer.
- Opt-in or out of sharing library versions with Module Federation based on your needs.
- Reduce the download size of your application by sharing workspace libraries between remotes and hosts.
1 change: 1 addition & 0 deletions docs/shared/reference/sitemap.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
- [How to create a Module Federation Host Application](/recipes/module-federation/create-a-host)
- [How to create a Module Federation Remote Application](/recipes/module-federation/create-a-remote)
- [How to Federate a Module](/recipes/module-federation/federate-a-module)
- [How to manage library versions](/recipes/module-federation/manage-library-versions)
- [Enforce Module Boundaries](/recipes/enforce-module-boundaries)
- [Ban Dependencies with Certain Tags](/recipes/enforce-module-boundaries/ban-dependencies-with-tags)
- [Tag in Multiple Dimensions](/recipes/enforce-module-boundaries/tag-multiple-dimensions)
Expand Down

0 comments on commit c1aa521

Please sign in to comment.