Dynamic Remotes, Webpack Module Federation

The webpack module federation post resulted in many folks asking me if remote URLs need to be known at build-time? Or is there a way to specify the remotes after building, as runtime configuration somewhere.

Well, we have an easy way to load the component dynamically, here is a suggested method from the docs:

function loadComponent(scope, module) {
return async () => {
// Initializes the shared scope. Fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__("default");
const container = window[scope]; // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default);
const factory = await window[scope].get(module);
const Module = factory();
return Module;
};
}

loadComponent("Comic", "XKCD");

Let us dive into some details of each line in the above function:

__webpack_init_sharing__ is defined as:

{
__webpack_init_sharing__: {
expr: RuntimeGlobals.initializeSharing,
req: [RuntimeGlobals.initializeSharing],
type: "function",
assign: true
}
}

Where RuntimeGlobals.intiialzeSharing is exported as:

/**
* The sharing init sequence function (only runs once per share scope).
* Has one argument, the name of the share scope.
* Creates a share scope if not existing
*/

exports.initializeSharing = "__webpack_require__.I";

__webpack_share_scopes is defined as:

{
__webpack_share_scopes__: {
expr: RuntimeGlobals.shareScopeMap,
req: [RuntimeGlobals.shareScopeMap],
type: "object",
assign: false
},
}

shareScopeMap would be:

/**
* an object with all share scopes
*/

exports.shareScopeMap = "__webpack_require__.S";

__webpack_share_scopes__.default would resolve into:

{
"react-dom": {
"17.0.2": {
"from": "home-app",
"eager": true,
"loaded": 1
}
},
"react": {
"17.0.2": {
"from": "home-app",
"eager": true,
"loaded": 1
}
}
}

In case you are more curios of __webpack_require__:

Object.keys(__webpack_require__);
// ["m", "c", "n", "d", "f", "e", "u", "g", "o", "l", "r", "nmd", "S", "I", "p"]

Getting back to window[scope], in this context scope is Comic

Object.getOwnPropertyNames(window[scope]);
// ["get", "init"]

init accepts the shared scope object which is used as a shared scope in the remote container and is filled with the provided modules from a host. It can be leveraged to connect remote containers to a host container dynamically at runtime.

await window[scope].get(module) would resolve into a function on invocation would result in the Module.

() => ((__webpack_require__(/*! ./app.jsx */ \"./app.jsx\")))
{__esModule: true, Symbol(Symbol.toStringTag): "Module"}

Module.default would be the component that is shared.

The most important point to be mindful is that we need to load the container before attempting to dynamically connect a remote container.

Here is the same sample code re-written with dynamic remotes:

So, basically it boils down to:

P.S: The patterns in the above sandbox is heavily influenced by dynamic-system-host sample, also the sandbox example uses System custom component and is very verbose for better understanding, in the real world useage you need not be so verbose and might just have a single function that does all the three steps.

Feel free to share this article. You may as well ping me on Twitter.

Published