Skip to main content

Analysis of the principle of module federation

· 7 min read

Module federation is implemented via the ModuleFederationPlugin plugin provided natively by Webpack, which has two main concepts: Host (consuming other Remote) and Remote (being consumed by Host). Each project can be Host or Remote, or both.

  • As a Host, you need to configure the remote list and shared module.

  • As Remote, you need to configure the project name, packaging method, packaged filename, provided module, and Host shared module.

Webpack packaging principles

Module federation is an optimization based on WebPack, so we need to know how WebPack does packaging before we dive into module federation.

Reading the WebPack results, you can see that each webPack wraps all the resources in one immediately executed function, which avoids global contamination but also prevents external access to internal modules.

In the immediate execution function, Webpack uses the __webpack_modules__ object to save all module code, and then loads modules from __webpack_modules__ using the internally defined __webpack_require__ method.  In addition, an array of webpackChunk is exposed globally to communicate with multiple Webpack resources in both asynchronous loading and file splitting. This array is overridden by webpack's push method,  It synchronously adds content to __webpack_modules__ when other resources add content to the 'webpackChunk' array to implement module integration.

Module federation is based on this mechanism, modifying the partial implementation of __webpack_require__ to remotely load resources at require and then merge them into __webpack_modules__.

Tips

  • webpackChunk can be modified through configuration output.chunkLoadingGlobal

Webpack builds resources

JS modular mechanism has many standards, but in webpack all unified conversion to their own implementation of __webpack_require__

Webpack single file structure

In the webpack single-file structure, the source code for all modules is stored in the __webpack_modules__ object, which is then uniformly loaded using the internally defined __webpack_require__ method.

In this case, the external has no access to the internal running resources at all

(() => {
var __webpack_modules__ = {
"./src/other.js": () => {
// ...
},
};

var __webpack_module_cache__ = {}; // The cache of module export results

function __webpack_require__() {
// Internal module import method
// .....
}
// .....

// Entry file content
(() => {
__webpack_require__("./src/other.js");
})();
})();

webpack multi-file structure

In a multi-file structure, there are two places to store the module's source code, an internal __webpack_modules__ object and a global webpackChunk array.

The webpackChunk array acts as a bridge to load modules from other files into the __webpack_modules__ object of the entry file by overriding the push method of the webpackChunk array.

// index.js
(() => {
var __webpack_modules__ = {
"./src/other.js": () => {
// source code
},
};

var __webpack_module_cache__ = {}; // The cache of module export results

function __webpack_require__() {} // Internal module import method

// Expose an object globally
self["webpackChunk"].push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
(() => {
__webpack_require__("./src/other.js");
})();
})();
// ./src/other2.js
(self["webpackChunk"] = self["webpackChunk"] || []).push([
["src_other2_js"],
{
"./src/other2.js": () => {
console.log("in other2.js");
},
},
]);

ModuleFederationPlugin

new ModuleFederationPlugin({
name: "app1",
library: { type: "var", name: "app1" },
filename: "remoteEntry.js",
remotes: {
app2: "app2",
app3: "app3",
},
remoteType: "var",
exposes: {
antd: "./src/antd",
button: "./src/button",
},
shared: ["react", "react-dom"],
shareScope: "default",
});

The following describes the property configuration

  • name,required,Unique ID, used as the output module name (container), with name/{name}/name/{expose}.

  • library,optional,default value is { type: "var", name: options.name },Where name is the name of the umD, which is the name of the variable mounted globally.

  • filename,optional,The file name after packaging.

  • remotes,optional,Indicates that the current application is a Host and can reference the Expose module in Remote.

  • remoteType,optional,default is var,("var"|"module"| "assign"|"this"|"window"|"self"|"global"|"commonjs"|"commonjs2"| "commonjs-module"|"amd"|"amd-require"|"umd"|"umd2"|"jsonp"|"system"|"promise"|"import"|"script"),The external type of the remote container.

  • exposes,optional,Indicates that the current application is a Remote, exposes module that can be referenced by other hosts as import(name/{name}/name/{expose}).

  • shared,optional,It is mainly used to avoid multiple public dependencies in the project. If this attribute is configured, WebPack will first check whether the corresponding package exists in the local application when loading. If not, it will load the dependency package of the remote application.

  • shareScope,optional,Name of the shared scope used for all shared modules.

Tips

  • When using module Federation, you must remember to configure public dependencies into shared. In addition, two items must be configured with shared at the same time, otherwise an error will be reported

  • Entry file index JS itself should not have any logic. Put the logic in bootstrap JS, index JS to dynamically load bootstrap js。 If you put the logic directly into index In JS, an error will be reported. If it is a public dependency configuration shared, it can be solved with the eager parameter. The main reason is that if directly in the index JS execution logic will depend on JS exposed by remote. At this time, remote JS has not been loaded, there will be a problem.

Principle of ModuleFederationPlugin

Three main things have been done by ModuleFederationPlugin.

  1. How to share dependencies: using SharePlugin
  2. How to expose a module: using ContainerPlugin
  3. How to reference a module: using ContainerReferencePlugin

SharePlugin

This plugin makes common dependencies shareable

ContainerPlugin

The plug-in creates an entry for the specified public moduleentry. JS after execution, an object will be hung on the window. The object has two methods, get 'and init'The get method is used to get the moduleThe init method is used to initialize the container, which can provide shared modules.

// entry.js
var remote;
remote = (() => {
// ...
})();
// Get and init methods in remote object
var get = (module, getScope) => {
__webpack_require__.R = getScope;
getScope = __webpack_require__.o(moduleMap, module)
? moduleMap[module]()
: Promise.resolve().then(() => {
throw new Error('Module "' + module + '" does not exist in container.');
});
__webpack_require__.R = undefined;
return getScope;
};
var init = (shareScope, initScope) => {
if (!__webpack_require__.S) return;
var oldScope = __webpack_require__.S["default"];
var name = "default";
if (oldScope && oldScope !== shareScope)
throw new Error("Container initialization failed as it has already been initialized with a different share scope");
__webpack_require__.S[name] = shareScope;
return __webpack_require__.I(name, initScope);
};

// This exports getters to disallow modifications
__webpack_require__.d(exports, {
get: () => get,
init: () => init,
});

When using the remote module, write its own shared into the remote through init, and then get the exposed components in the remote. When it is a remote, judge whether there are available shared dependencies in the host. If so, load this part of the host's dependencies. If not, load its own dependencies.

ContainerReferencePlugin

The plug-in adds specific references to containers that are external resources and allows remote modules to be imported from these containers. When importing, the remote provided by the container user will be called for overloading.

Modules defined through remotes will also be displayed in __webpack_modules__ But there will be no concrete implementation, which is similar to asynchronous import. New in webpack5 __webpack_require__.e method, execute the following three functions for the modules imported through the secondary method, and return only after all of them are successful.

  • __webpack_require__.f.consumes is used to judge and consume the shared module. If this module already exists in the current environment, it will not request from the remote
  • __webpack_require__.f.remotes is used to connect containers
  • __webpack_require__.f.j used to load JS

Usage scenario

  1. It is suitable for creating special component application services to manage all components and applications. The rest of the business layers only need to load the corresponding components and functional modules according to their own business needs

  2. Unified module management, high code quality and fast construction speed. It is applicable to matrix app, visual page construction and other scenarios