ModuleFederationPlugin
The ModuleFederationPlugin allows a build to provide or consume modules with other independent builds at runtime.
import webpack from "webpack";
const { ModuleFederationPlugin } = webpack.container;
export default {
plugins: [
new ModuleFederationPlugin({
// options' typings in typescript
runtime: string | false,
}),
],
};Options
name
string
The name of the container this build produces. For the default script remote type it is also the global variable the container is assigned to, which is how a consumer reaches it once remoteEntry.js has loaded.
It must be unique among the builds on a page. It is a separate thing from output.uniqueName, which also has to be unique — see Every build needs its own unique name.
filename
string
The filename of the container entry, as a path relative to output.path. Conventionally remoteEntry.js.
This names the file this build emits. It has nothing to do with the filenames of the remotes it consumes — those are whatever each remote chose, and appear in the URLs under remotes.
remotes
string[] object
The containers this build consumes. Each entry maps a request scope — the name you import through — to a container location:
new ModuleFederationPlugin({
remotes: {
// request scope container location
app1: "app1@http://localhost:3001/remoteEntry.js",
},
});which is then imported as:
import Button from "app1/Button";The two halves of the location string are split at the first @:
| Part | Meaning |
|---|---|
before @ — app1 | the remote's name, i.e. the global its container is assigned to |
after @ — http://localhost:3001/remoteEntry.js | the URL its filename is served from |
So a name appearing "twice" in a config is usually two different things that happen to match: the key is what you import through and can be renamed freely, while the part before @ must match what the remote called itself. They are free to differ:
new ModuleFederationPlugin({
remotes: {
// imported as "checkout/…", served by a container that calls itself "shop"
checkout: "shop@http://localhost:3001/remoteEntry.js",
},
});Passing an array instead of an object drops the keys and lets webpack infer each request scope from the location.
For a statically configured remote you do not load anything yourself: webpack emits code that injects the <script>, waits for the global to appear, and reports ScriptExternalLoadError if it does not. Loading the container by hand is only needed for dynamic remotes, where the location is not known at build time.
An entry can also be an object with external (the location) and shareScope, when one remote needs a different share scope from the rest.
remoteType
string
How a remote is fetched, as an externalsType.
When unset it falls back to this plugin's own library.type, but only if that is a valid externals type, and otherwise to 'script' — the name@url form described above. Since library itself defaults to { type: 'var', name }, a config that sets neither gets 'script'.
runtime
Create a new runtime chunk with the specified name.
webpack.config.js
import webpack from "webpack";
const { ModuleFederationPlugin } = webpack.container;
export default {
plugins: [
new ModuleFederationPlugin({
runtime: "my-runtime-name",
}),
],
};exposes
The modules this container makes available to other builds. The property name is the public name a consumer imports (import Button from "remote/Button"), the value the module to expose:
webpack.config.js
new ModuleFederationPlugin({
name: "remote",
exposes: {
"./Button": "./src/components/Button",
},
});An entry can also be an object:
import(string): the module to expose.name(string): the name of the chunk webpack generates for it. Without it the chunk is named after the module's internal id, which changes as the build changes; setting it gives the file a stable name that can be referenced statically.
new ModuleFederationPlugin({
name: "remote",
exposes: {
"./Button": {
import: "./src/components/Button",
name: "button-chunk",
},
},
});Sharing libraries
With the shared key in the configuration you can define libraries that are shared between your federated modules. The package name is the same as the one found in the dependencies section of your package.json. However, by default webpack will only share the root level of a library.
import webpack from "webpack";
const { ModuleFederationPlugin } = webpack.container;
export default {
plugins: [
new ModuleFederationPlugin({
// adds date-fns as shared module
shared: ["date-fns"],
}),
],
};So in your application you could do something like
import { format } from "date-fns";
format(new Date(2014, 1, 11), "MM/dd/yyyy");and webpack will automatically share date-fns between all your federated modules that define date-fns as a shared library.
However, if you want to access something that is not located at the root level of the package, for example date-fns/locale/en-GB/index.js, you need to append / to the package name in your shared configuration:
import webpack from "webpack";
const { ModuleFederationPlugin } = webpack.container;
export default {
plugins: [
new ModuleFederationPlugin({
// adds date-fns as shared module
// all files of the package will be shared
shared: ["date-fns/"],
}),
],
};The / syntax allows you to access all files of a package. However, it should be used only where necessary, because it has an impact on performance especially in development mode.
Specify package versions
There are three ways to specify the versions of shared libraries.
Array syntax
This syntax allows you to share libraries with package name only. This approach is good for prototyping, but it will not allow you to scale to large production environment given that libraries like react and react-dom will require additional requirements.
import webpack from "webpack";
const { ModuleFederationPlugin } = webpack.container;
export default {
plugins: [
new ModuleFederationPlugin({
// adds lodash as shared module
// version is inferred from package.json
// requiredVersion is inferred from package.json as well
// (a warning is emitted if it can't be determined)
// a shared version that doesn't satisfy it falls back to the local copy
shared: ["lodash"],
}),
],
};Object syntax
This syntax provides you more control over each shared library in which you can define package name as the key and version (semver) as the value.
export default {
plugins: [
new ModuleFederationPlugin({
shared: {
// adds lodash as shared module
// version is inferred from package.json
// it will use the highest lodash version that is >= 4.17 and < 5
lodash: "^4.17.0",
},
}),
],
};Object syntax with sharing hints
This syntax allows you to provide additional hints to each shared package where you define the package name as the key, and the value as an object containing hints to modify sharing behavior.
import { readFileSync } from "node:fs";
const deps = JSON.parse(
readFileSync(new URL("./package.json", import.meta.url)),
).dependencies;
export default {
plugins: [
new ModuleFederationPlugin({
shared: {
// adds react as shared module
react: {
requiredVersion: deps.react,
singleton: true,
},
},
}),
],
};Sharing hints
eager
boolean
This hint will allow webpack to include the provided and fallback module directly instead of fetching the library via an asynchronous request. In other words, this allows to use this shared module in the initial chunk. Also, be careful that all provided and fallback modules will always be downloaded when this hint is enabled.
import
false | string
The provided module that should be placed in the shared scope. This provided module also acts as fallback module if no shared module is found in the shared scope or version isn't valid. (The value for this hint defaults to the property name.)
packageName
string
The package name that is used to determine required version from description file. This is only needed when the package name can't be automatically determined from request.
requiredVersion
false string
This field specifies the required version of the package. It accepts semantic versioning, such as "^1.2.3". Additionally, it retrieves the version if it's provided as a URL, for instance: "git+ssh://git@github.com:foo/bar.git#v1.0.0".
webpack implements the full range grammar, so a required version can be an exact version (1.2.3), a partial one (1.2, which matches any patch), a comparator (>=1.2.3 <2), a caret or tilde range (^1.2.3, ~1.2.3), a hyphen range (1.2.3 - 2.3.4) or several of those joined with ||. Set it to false to accept whatever the scope offers.
A share scope can hold several versions of the same module at once — each build provides the version it has, and a consumer gets the highest one that satisfies its own requiredVersion. That is what lets two builds that need different major versions of a library work on one page, at the cost of shipping both. singleton: true gives up that guarantee to keep one instance, and warns when the chosen version does not satisfy a consumer.
shareKey
string
The requested shared module is looked up under this key from the shared scope. It defaults to the key you used in shared, i.e. the request itself, so shared: ['lodash'] is stored and looked up as lodash.
Set it when the name a build imports and the name it should share under differ. Two builds only share a module when both the key and the scope match, so the key is what lets an application that imports lodash-es reuse what another build provided as lodash:
new ModuleFederationPlugin({
shared: {
"lodash-es": {
shareKey: "lodash",
},
},
});shareScope
string
The name of the shared scope, defaulting to the plugin's own shareScope option and, when that is unset, to 'default'. A scope is one namespace of shared modules: builds put the modules they provide into it and look the ones they need up from it, so builds using different scope names never share with each other even when their keys match.
Change it to keep groups of builds apart — for example when a page hosts two independent sets of remotes that should each get their own copy of a library, or when a widget must not adopt the host's version of one. Both the providing and the consuming side have to name the same scope.
singleton
boolean
This hint only allows a single version of the shared module in the shared scope (disabled by default). Some libraries use a global internal state (e.g. react, react-dom). Thus, it is critical to have only one instance of the library running at a time.
In cases where there are multiple versions of the same dependency in the shared scope, the highest semantic version is used.
strictVersion
boolean
This hint allows webpack to reject the shared module if version is not valid (defaults to true when local fallback module is available and shared module is not a singleton, otherwise false, it has no effect if there is no required version specified). Throws a runtime error if the required version is not found.
version
false | string
The version of the provided module. It allows webpack to replace lower matching versions, but not higher.
By default, webpack uses the version from the package.json file of the dependency.
Additional examples
export default {
plugins: [
new ModuleFederationPlugin({
// adds vue as shared module
// version is inferred from package.json
// it will always use the shared version, but print a warning when the shared vue is < 2.6.5 or >= 3
shared: {
vue: {
requiredVersion: "^2.6.5",
singleton: true,
},
},
}),
],
};export default {
plugins: [
new ModuleFederationPlugin({
// adds vue as shared module
// there is no local version provided
// it will emit a warning if the shared vue is < 2.6.5 or >= 3
shared: {
vue: {
import: false,
requiredVersion: "^2.6.5",
},
},
}),
],
};export default {
plugins: [
new ModuleFederationPlugin({
// adds vue as shared module
// there is no local version provided
// it will throw an error when the shared vue is < 2.6.5 or >= 3
shared: {
vue: {
import: false,
requiredVersion: "^2.6.5",
strictVersion: true,
},
},
}),
],
};export default {
plugins: [
new ModuleFederationPlugin({
shared: {
"my-vue": {
// can be referenced by import "my-vue"
import: "vue", // the "vue" package will be used as a provided and fallback module
shareKey: "shared-vue", // under this name the shared module will be placed in the share scope
shareScope: "default", // share scope with this name will be used
singleton: true, // only a single version of the shared module is allowed
strictVersion: true, // don't use shared version when version isn't valid. Singleton or modules without fallback will throw, otherwise fallback is used
version: "1.2.3", // the version of the shared module
requiredVersion: "^1.0.0", // the required version of the shared module
},
},
}),
],
};


