This article is licensed under a “CC BY 4.0” license. You are welcome to reprint or modify this article, but the source must be noted.
Author: Baiying front-end team @0o Howie O0 launched at juejin.cn/post/692756…
preface
Recently, the project team is upgrading the technology stack of the original old project, and the main contents of the upgrade are React and ANTD. After discussion, the final technical solution was to use Qiankun. The specific implementation plan is to build a master app based on Qiankun, and then deploy a new sub-app. The new sub-app uses the latest version of React and ANTD to gradually reconstruct the old business, and finally connect the old and new applications to the main app.
Qiankun is a micro-front-end implementation library based on single-SPA. With Qiankun, we don’t need to pay attention to the technology stack used between sub-applications. Sub-applications can also be independently developed, deployed, run independently and upgraded increments, which is very convenient. But in the process of using, found a small problem. If the child applications use the same technology stack and the same version, the dependent parts (react, React-DOM, ANTD, etc.) will be loaded repeatedly, which will affect the performance.
Is there a solution?
The answer is yes. The new Module Federation feature, released in WebPack5, helps to address these issues and provides a new micro front-end implementation.
This article will introduce Module Federation from the following dimensions:
- What is Module federation
- Module federation
- Brief analysis of module Federation principles
- Detailed description of module federation configuration items
- other
- conclusion
- Reference documentation
What is Module federation
The webpack5 official document describes Module federation as follows:
Multiple separate builds should form a single application. These separate builds should not have dependencies between each other, so they can be developed and deployed individually. This is often known as Micro-Frontends, but is not limited to that.
The explanation is as follows: An application can consist of multiple independent builds. These individual builds have no dependencies and can be developed and deployed independently.
The above explanation is actually the concept of a micro front end.
With Module Federation, you can dynamically load and run code from one javascript application to another and share dependencies between applications.
Module federation
After understanding the concept and usage of Module Federation, let’s learn how to use Module Federation.
At the end of the official Webpack5 document, there is a link to the moder-federation-examples, which contains a large number of demos that describe the application scenarios of Module Federation in detail.
This article introduces the usage of Module federation based on the /advanced-api/automatic-vendor-sharing example. (Interested students can use the above two links to view the specific demo).
The example automation-vendor-sharing is very simple, and two react-based applications, APP1 and App2, are provided internally. Module federation allows App1 and App2 to use each other’s Button components and share their React and react-dom dependencies.
The project structure of APP1 and App2 is the same, as follows:
|-- app1 |--app2
|-- package.json |-- package.json
|-- webpack.config.js |-- webpack.config.js
|-- public |-- public
| |-- index.html |-- index.html
|-- src |-- src
|-- App.js |-- App.js
|-- Button.js |-- Button.js
|-- bootstrap.js |-- bootstrap.js
|-- index.js |-- index.js
Copy the code
In app.js, the components of other applications are dynamically loaded as follows:
const RemoteButton = React.lazy(() => import("app2/Button")); // const RemoteButton = React. Lazy (() => import("app1/ button ")); // App2 uses app1's Button componentCopy the code
Start App1 and App2 as follows:
Looking at the example diagram, we can see that App1 successfully loads and renders the Button component provided by App2, and that the Button component and App1 share the same react, react-dom. The case for APP2 is the same as that for APP1.
The key to this is the configuration of Module federation in webpack.config.js, which is as follows:
const { ModuleFederationPlugin } = require("webpack").container; module.exports = { ... Plugins: [new ModuleFederationPlugin ({name: "app1", the corresponding configuration application app1 / / / / name: 'app2 / application/app2 corresponding configuration filename: "RemoteEntry. Js", remotes: {app2: "app2 @ http://localhost:3002/remoteEntry.js", corresponding configuration / / / / application app1 app1: "App1 @ http://localhost:3001/remoteEntry.js" / / application corresponding configuration app2}, exposes: {". / Button ":". / SRC/Button ",}, Shared: { "react": { singleton: true, }, "react-dom": { singleton: true, }, }, }), ... ] };Copy the code
Before explaining the configuration items for the ModuleFederationPlugin, let’s declare two names: host application and Remote Application.
Host applications that use components provided by other applications; Remote application, which provides components for other applications.
In the example/automation-vendor-sharing /app1, app1 uses the Button component provided by App2, so app1 is the host application and App2 is the remote application. Conversely, in the/automation-vendor-sharing /app2 example, app2 uses the Button component provided by App1, so app2 is the host application and App1 is the remote application.
An application can be either a host application or a remote application.
In the Configuration items of the ModuleFederationPlugin, the key configuration items are exposes, filename, remotes, and shared.
Exposes configuration items define the components of a Remote application that can be used by a host application. These components for the host application are packaged separately and an entry file – remoteentry.js – is generated for the host application to use. The name of the entry file is determined by the filename configuration item.
The remotes configuration defines which components of the Remote application can be used by the host application. When the host application uses components provided by the remote application, the remoteentry. js file generated by the remote application needs to be loaded.
The shared configuration item defines shared dependencies between host and Remote application components.
How Module Federation works
In the last section, we learned how to use Module federation. Now let’s learn how Module Federation works.
To better illustrate how Module Federation works, this section will analyze it step by step in the dimensions listed below:
- Common WebPack build application architecture;
- How a common WebPack build works;
- Use Module Federation’s Webpack to build the structure;
- How webpack builds with Module Federation work;
Common WebPack build applications
First, let’s take a look at what a common WebPack build application looks like.
During the construction of WebPack, the entry file corresponding to the entry configuration item is used as the starting point to collect all modules needed in the entire application, establish the dependency relationship between modules, and generate a module dependency graph. The module dependency graph is then split into chunks and output to the location specified in the Output configuration item.
In general, a complete WebPack build has the following structure:
The final build consists of main-chunk and async chunk. Main-chunk is the chunk in which the entry file (usually index.js) resides. Internal module includes Runtime module, index entry module, third-party dependency module (such as React, React-DOM, ANTD, etc.) and internal component module (such as COM-1, COM-2, etc.); Async-chunk is an asynchronous chunk that contains modules that need to be loaded asynchronously (lazily).
The sampleautomatic-vendor-sharingIf the Module federation feature is not used, their WebPack builds are as follows:
Looking at the figure above, we can see that the structure of Figure 2 and Figure 1 are not quite the same. This is because bootstrap.js is imported via import(“./bootstrap”) in the /automatic- vvendor -sharing/app1 project index.js. An async chunk containing bootstrap, App, Button, React, and react-dom is generated. Webpack will decouple async-chunk from async-chunk if it has third-party dependencies (i.e. react, react-dom) during the packaging process and build a new presumption-chunk.
How does a common WebPack build work
When an application built through WebPack starts, the chunk loading sequence is main-chuk and async-chunk.
If you have seen the results of a WebPack build, you will be familiar with the results below. For a brief look at how webPack builds work, let’s use the/automation-vvendor -sharing/app1 build result with Module Federation removed as an example.
The truncated part of the build code is as follows:
// main-chunk (() => { var __webpack_modules__ = { ... }; var __webpack_module_cache__ = {}; function __webpack_require__(moduleId) { if (__webpack_module_cache__[moduleId]) { return __webpack_module_cache__[moduleId].exports; } var module = __webpack_module_cache__[moduleId] = { exports: {} } __webpack_module__[moduleId](module, module.exports, __webpack_require); return module.exports; }... __webpack_require__.l = (url, done, key, chunkId) => { ... }... __webpack_require__.f.j = (chunkId, promises) => { ... }... var installedChunks = { ... } var webpackJsonpCallback = (parentChunkLoadingFunction, data) => { ... } var chunkLoadingGlobal = self["webpackChunk_automatic_vendor_sharing_app1"] = self["webpackChunk_automatic_vendor_sharing_app1"] || []; chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); . Promise.all(/* import() */[__webpack_require__.e(935), __webpack_require__.e(902)]).then(__webpack_require__.bind(__webpack_require__, 902)); }) ()Copy the code
// vendors-chunk
(self["webpackChunk_automatic_vendor_sharing_app1"] = self["webpackChunk_automatic_vendor_sharing_app1"] || []).push([[935],{
"./node_modules/react-dom/cjs/react-dom.development.js": (__unused_webpack_module, exports, __webpack_require__) => { ... },
...
}])
Copy the code
// async-chunk
(self["webpackChunk_automatic_vendor_sharing_app1"] = self["webpackChunk_automatic_vendor_sharing_app1"] || []).push([[902],{
"./src/App.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) => { ... },
"./src/Button.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) => { ... },
"./src/bootstrap.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) => { ... },
}])
Copy the code
In the build code above, there are a few key definitions to focus on, They are in turn __webpack_modules__, __webpack_module_cache__, __webpack_require__, installedChunks, webpackJsonpCallback, __webpack_require_ _. L, etc.
-
__webpack_modules__
__webpack_modules__ is an object that caches module execution methods. The key value corresponds to the module ID, and the value corresponds to the module execution method.
When packaged, WebPack treats each module as an execution method. When this execution method is run, the corresponding code for the module is executed and the output of the module is returned.
-
__webpack_module_cache__
__webpack_module_cache__ is an object that caches module output. The key value corresponds to the module ID, and the value corresponds to the output of the module.
-
__webpack_require__
__webpack_require__ is a method that gets the output of the module. “Import xx from ‘XXX ‘” in the source code will eventually be converted to __webpack_require__(…). In the form.
The __webpack_require__ method, when obtaining module output, first looks for the corresponding output in __webpack_module_cache based on the module ID. If not, go to __webpack_modules__ to get the module execution method, then run the module execution method to get the module output and cache it in __webpack_module_cache.
-
installedChunks
InstalledChunks is an object that is used to cache chunks. The key value corresponds to the CHUNK ID. The value is 0.
-
webpackJsonpCallback
WebpackJsonpCallback is a global method mounted to global variables (window, global, self) to install the chunk. When executed, this method collects the modules contained within the chunk and their execution methods into __webpack_modules__.
-
__webpack_require__.l
__webpack_require__.l is a method used to load async-chunk. __webpack_require__.l obtains the JS file of async-chunk by dynamically adding script according to the URL of async-chunk and then executes it.
These key points make up the Main-chunk Runtime module.
With these key points in mind, let’s briefly summarize the process of building WebPack:
- Open the application and load the JS file corresponding to main-chunk.
- To perform the main – the chunk. Js, define __webpack_modules__, __webpack_module_cache__, __webpack_require__, installedChunks, webpackJsonpCallback, __webpack_require_ _. L, etc;
- Execute entry module -index corresponding to the code;
- If a dependent module is encountered, get the output of the dependent module through the __webpack_require__ method (first from __webpack_module_cache, if not, run the corresponding execution method of the module in __webpack_modules__).
- If you encounter lazy loading modules, use the __webpack_modules__. L method to get the corresponding async-chunk and execute it, and then get the corresponding output.
The working process of a WebPack build is covered briefly in this article, followed by a detailed analysis in a separate article.
Build with WebPack for Module Federation
After app1 and App2 use module federation, the corresponding Webpack is built as follows:
Comparing figure 2, we can see that the package changes when we use the Module Federation feature.
In the new package file, We found new remoteentry-chunk, exposing -chunk with the Button module, shared-chunk1 with the React module, and shared-chunk2 with the React-DOM module.
Remoteentry-chunk mainly contains the Runtime module inside, and expoil-chunk contains button components that can be used by host applications
Why is that?
This is primarily related to exposes, shared configuration items of the ModuleFederationPlugin.
First, there are exposes of profiles. Exposes configuration items define which components of a remote application can be used by a host application. When packaged, WebPack generates a Remoteentry-chunk and exposes – chunks, based on exposes configuration items. After the application is started, the host application can load the Remoteentry-chunk and expoil-chunk of the remote application based on the URL specified by the Remotes configuration.
Second is the shared configuration item. The shared configuration defines shareable dependencies between the host application and the Expose -chunk of the Remote application. The react module and the react-dom module are separated into new shared-chunks.
How does the Webpack build with Module Federation work
Module Federation provides two main functions: component interoperability between applications, and dependency sharing between host and Remote applications. How do you achieve these two functions?
Let’s first look at the implementation logic for component interoperability between applications.
From the Results of the WebPack build in Figure 3, we can get a good idea of how Module Federation implements interoperability of components between applications.
Quite simply, the Remote application generates one Remoteentry-chunk and multiple expos-chunks. After the host application is started, the remote application’s Remoteentry-chunk and exposer-chunk components are loaded dynamically using the URL specified by the Remotes configuration item, and then the remote application’s components are executed and rendered.
Next, let’s look at the dependency sharing implementation logic for the host application and remote application components.
In the build results of how a common WebPack build works, we can see that multiple WebPack builds are actually isolated from each other and are not accessible to each other. So how does Module Federation break down this isolation?
The answer is sharedScope – share scope. After an application is started, a sharedScope is created between the remote chunk of the host application and the host application, which contains shareable dependencies.
Using the Module federation function, WebPack adds a piece of logic to the main-chunk Runtime module of the app1 application:
var __webpack_modules__ = { ... "webpack/container/reference/app2": (module, __webpack_exports, __webpack_require__) => { ... module.exports = new Promise((resolve, reject) => { ... __webpack_require__.l("http://localhost:3002/remoteEntry.js", (event) => {... }, "app2"); }).then(() => app2); }}... (() => { __webpack_require__.S = {}; __webpack_require__.I = (name, initScope) => { ... var scope = __webpack_require__.S[name]; . var register = (name, version, factory) => { var versions = scope[name] = scope[name] || {}; var activeVersion = versions[version]; if(! activeVersion || ! activeVersion.loaded && uniqueName > activeVersion.from) versions[version] = { get: factory, from: uniqueName } } var initExternal = (id) => { ... var module = __webpack_require__(id); if(! module) return; var initFn = (module) => module && module.init && module.init(__webpack_require__.S[name], initScope); if(module.then) return promises.push(module.then(initFn, handleError)); var initResult = initFn(module); if(initResult && initResult.then) return promises.push(initResult.catch(handleError)); }... switch(name) { case "default": {register("react-dom", "17.0.1", () => Promise. All ([__webpack_require__.e("vendors-node_modules_react-dom_index_js"), __webpack_require__.e("webpack_sharing_consume_default_react_react-_2997")]).then(() => () => __webpack_require__(/*! ./node_modules/react-dom/index.js */ "./node_modules/react-dom/index.js"))); Register ("react", "17.0.1", () => Promise. All ([__webpack_require__.e("vendors-node_modules_react_index_js"), __webpack_require__.e("node_modules_object-assign_index_js")]).then(() => () => __webpack_require__(/*! ./node_modules/react/index.js */ "./node_modules/react/index.js"))); initExternal("webpack/container/reference/app2"); } break; }... }}) ()Copy the code
Here’s a brief explanation of the new logic in the Runtime module:
-
__webpack_modules__ added “webpack/container/reference/app2” module and the corresponding implementation method.
When the execution method is run, the remoteentry.js of App2 is loaded dynamically.
-
__webpack_require__ has added an S object.
S is a common object that stores the shared content specified by the shared configuration item in App1.
/automatic-vendor-sharing/app1 During work, the structure of S is as follows:
{ default: { 'react': { from: '@automatic-vendor-sharing/app1', get: () => {... } }, 'react-dom': { from: '@automatic-vendor-sharing/app1', get: () => {... }}}}Copy the code
Default is the name of the default sharedScope; React and react-dom correspond to shared dependencies in the shared configuration item. From is the application from which the shared dependency is actually taken; Get is the method for obtaining shared dependencies.
-
__webpack_require__ has added the I method.
The __webpack_require__.I method is used to initialize app1’s __webpack_require__.S object.
__webpack_require__.I internally defines two key methods: register and initExternal. Register adds the shared dependencies specified by the shared configuration and the method of obtaining them to __webpack_require__.S. When initExternal is executed, the remoteentry. js provided by App2 is loaded and the __webpack_require__.S object for app2 is initialized using the __webpack_require__.S for app1.
The code snippet of the remote chunk of app2 is as follows:
var app2 = (() => { ... var __webpack_modules = ({ "webpack/container/entry/app2": (__unused_webpack_module, exports, __webpack_require__) => { ... var get = (module, getScope) => {... } var init = (shared, initScope) => { if (! __webpack_require__.S) return; var oldScope = __webpack_require__.S["default"]; var name = "default"; . __webpack_require__.S[name] = shareScope; return __webpack_require__.I(name, initScope); } __webpack_require__.d(exports, { get: () => get; init: () => init }); }})... / / and app1 main - in the middle of the chunk in the same runtime return __webpack_require (" webpack/container/entry/app2 ")}) ()Copy the code
When the above code executes, it returns a global variable, app2. App2 contains an init method that uses App1’s __webpack_require__.S to initialize App2’s __webpack_require__.S.
During initialization, we will update the shared dependencies in shareScope to the newer versions of both apps when comparing the react and React-DOM versions in App1 and App2.
If the react version of app1 is higher than app2, __webpeck_require__.S[default]. React from and init attributes correspond to app1. Instead, __webpeck_require__.S[default]. React from and init attributes correspond to App2.
To sum up, we make a simple review of the implementation logic of dependency sharing:
- Open app1 and load the JS file corresponding to main-chunk.
- To perform the main – the chunk. Js, define __webpack_modules__, __webpack_module_cache__, __webpack_require__, installedChunks, webpackJsonpCallback, __webpack_require_ L, __webpack_require__.S, __webpack_require__.
- Execute the code corresponding to the entry module index.js. When executed, __webpack_require__.I is triggered;
- The __webpack_require__.I method starts by initializing the __webpack_require__.S object using the register method, and then getting the remoteentry.js of App2 using the initExternal method. When retrieving app2’s remoteEntry. Js, it returns a Promise object. When app2’s remoteEntry.
- Load app2’s remoteentry. js dynamically and execute it. Return a global variable app2 containing get and init.
- The state of the Promise object in Step 4 becomes Resolved. The registered callback is triggered and app2.init is executed.
- Init method, using app1’s __webpack_require__.S to initialize app1’s __webpack_require__.S.
This enables dependency sharing.
After the application is started, the structure of App1 is as follows:
Detailed description of module federation configuration items
In this section, we’ll look at the module Federaion configuration in detail.
A complete Module federation configuration item contains the following:
new ModuleFederationPlugin({
name: 'xxx',
filename: 'xxx',
library: {
type: 'xxx',
name: 'xxx'
},
remotes: {
app2: 'app2@xxxx',
app3: 'app3@xxxx',
...
},
exposes: {
'./Button': './src/Button',
...
},
shared: {
'react': {
import: 'xxx',
singleton: true,
requiredVersion: 'xxx',
strictVersion: 'xxx',
shareScope: 'xxx',
packageName: 'xxx',
sharedKey: 'xxx',
eager: true
}
},
shareScope: 'xxx'
})
Copy the code
-
name
Alias of the current application.
-
filename
Entry file name. The name of the remote file provided by the remote application when the remote application is consumed by the host application.
-
exposes
What output can be used by the host application when the remote application is consumed by the host application?
Exposes are an object, key is the relative path of the output in the host application, and value is the relative (or absolute) path of the output in the current application.
new ModuleFederationPlugin({ ... exposes: { './Button': '.. /src/component/Button' } })Copy the code
Note: If we import(‘app2/Button’) in our host application, then the key in your exposes must be ‘./Button’; If import(‘app2/shared/Button’), then the key in your exposes must be ‘./shared/Button’.
-
library
The library defines how the remote application exposes output to the host application.
The value of a configuration item is an object, such as {type: ‘XXX ‘, name:’ XXX ‘}.
Where, name, the name of the variable exposed to the external application; Type, the way the variable is exposed.
The value of type, similar to the value of output.libraryTarget:
-
Var: The output of remote is assigned to a variable defined by var;
var app2; app2 = (() => { ... return __webpack_require__(...) ; }) ();Copy the code
-
Assign: Assign the output of remote to a variable not defined by var;
app2 = (() => { ... return __webpack_require__(...) ; }) ();Copy the code
-
This: The output of remote is an attribute of the current context this, and the value corresponding to the attribute name;
this["app2"] = (() => { ... return __webpack_require__(...) ; }) ()Copy the code
-
Window: The output of remote is a property of the window object, the value of the property name;
window["app2"] = (() => { ... return __webpack_require__(...) ; }) ()Copy the code
-
The output of self: remote is a property of the self object, the value of the property name;
self["app2"] = (() => { ... return __webpack_require__(...) ; }) ();Copy the code
-
Commonjs: The output of remote is an attribute of exports, and the value of name corresponds to the attribute name.
exports["app2"] = (() => { ... return __webpack_require__(...) ; }) ();Copy the code
-
Commonjs2: The output of remote is an attribute of module.exports, with the value of attribute name;
module.exports["app2"] = (() => { ... return __webpack_require__(...) ; }) ();Copy the code
-
Amd: remoteEntry. Js complies with the AMD specification;
define('app2', [], function() { return (() => {... }}) ());Copy the code
-
Umd: remoteEntry. Js complies with the UMD specification;
(function(root, factory){ if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["app2"] = factory(); else root["app2"] = factory(); }(window, function() { return (() => {... }) ()})Copy the code
-
Jsonp: Wraps the output of remote in a JSONP wrapper;
app2((() =>{... }) ())Copy the code
-
System: Remoteentry. js complies with the Systemjs specification;
System.register("app2", [], function(__WEBPACK_DYNAMIC_EXPORT__, __system_context__) { return { execute: function() { __WEBPACK_DYNAMIC_EXPORT__(...) }}}Copy the code
The common types of type are var and window. The default type is var.
-
-
remotes
Remote application consumed by the current host application.
Remotes is an object, and the key value is an alias for the application to consume. If we wanted to use the button component of the remote application in the host application, our code would look like this:
const RemoteButton = React.lazy(() => import('app2/button)); Copy the code
Where app2 in the import URL corresponds to the key value in the Remotes configuration item.
Value indicates the output and URL of the remote application. The format must be ‘obj@url’. Where, obj corresponds to the name configuration item in the library in the remote application, and URL corresponds to the link of the remoteEnter file in the remote application.
-
shared
The shared configuration indicates which dependencies can be shared between the output of the remote application and the host application. For shared to take effect, the host application and the remote application must have the same shared configuration dependencies.
-
import
The actual package name of the shared dependency.
{ ..., shared: { 'react-shared': { import: 'react' } } } Copy the code
If not specified, the default is a user-defined shared dependency name, called React-shared. If so, the WebPack package will throw an exception, since there is no react-shared package.
-
singleton
Whether to enable the singleton mode. If true, the singleton mode is enabled. If the value is false, the singleton mode is disabled.
The default value is false, that is, the singleton mode is not enabled
With singleton mode enabled, the dependencies shared by the remote application component and the host application are loaded only once, regardless of version. If the version is inconsistent, a warning is given.
The version of the dependency to be loaded is higher than that of the remote application and host application.
If the singleton mode is not enabled and the versions of shared dependencies of the remote application and host application are inconsistent, load the dependencies of the remote application and host application respectively.
-
requiredVersion
Specifies the version of the shared dependency. The default value is the version of the current application dependency.
If requiredVersion does not match the version of the actual application dependency, a warning is given.
-
strictVersion
Whether strict version control is required.
In singleton mode, if strictVersion is different from the version that the actual application depends on, an exception is thrown.
The default value is false.
-
shareKey
The default value is the key value of the shared configuration item.
-
shareScope
Specifies the scope domain of the current shared dependency. The default value is default.
In XXX, we learned that the structure of __webpack_require__.S at project runtime is:
{ default: { 'react': { from: '@automatic-vendor-sharing/app2', get: () => {... } }, 'react-dom': { from: '@automatic-vendor-sharing/app2', get: () => {... }}}}Copy the code
The default in __webpack_require__.S[“default”] is the default specified by shareScope.
-
eager
Whether shared dependencies are separated into Async Chunks during the packaging process.
If eager is false, the shared dependency is separated into async chunks. If eager is true, shared dependencies are packaged into main and remoteEntry, and will not be separated.
The default is false, and if set to true, sharing dependencies is actually meaningless.
-
-
shareScope
Name of the shared dependency scope. The default value is default.
If both shareScope and Share [” XXX “]. ShareScope exist, share[” XXX “]. ShareScope has higher priority.
other
-
Why do we need to import bootstrap.js with import() in index.js?
When learning Module Federation, I didn’t understand how to use import() to introduce bootstrap.js in the example index.js, so I pasted the content from bootstrap.js directly into index.js. During startup, an error is found, and the error is as follows:
After looking at the packaged code, it is found that the problem is caused by the react module’s execution method running at the wrong time.
With Module federation, React will act as an asynchronous module and will not load and run the corresponding execution methods until main.js of App1 and remote.js of App2 are loaded and sharedScope is initialized.
If you import React from ‘React’ directly in index.js, React is packaged as a static dependency in main-chunk. After the application is started, the runtime module code is executed, and then the entry file index.js code is executed. At this point, the React execution method is immediately run. When the execution method is run at the wrong time, the code throws the above exception.
-
Can Module federation be stack independent?
The answer is yes.
The host application uses the React stack and the remote application uses the Vue stack. The host application cannot be used directly when using components provided by the remote application. The vue.mount(‘# XXX ‘) method is required. The specified location to mount the remote component.
-
Versioning of shared dependencies
When module Federation initializes shareScope, it compares the versions of shared dependencies between the host app and the Remote app and updates the version of shared dependencies in shareScope to a higher version.
When loading a shared dependency, if it is found that the version actually required is inconsistent with the version of the shared dependency in shareScope, it will handle the problem according to the different share configuration items:
- If singleton is set to true and shared dependencies in shareScope are actually used, the console will print a version inconsistency warning.
- If Singleton is set to true and strictVersion is set to true, the versions must be consistent and an exception will be raised.
- If singleton is set to false, the app will not use the shared dependencies in shareScope and will load its own dependencies instead.
To sum up, if the version of the shared dependency between host application and remote application is compatible, you can set singleton to true. If the shared dependency version is incompatible, you need to set Singleton to False.
-
Can multiple apps (more than 2) share a shareScope?
App1 is the remote application of App1. App3 is the remote application of App2. App2 is the remote application of App2.
The answer is yes.
With Module Federation, all connected apps share a shareScope.
conclusion
This is the end of the description of Module Federation. I believe you have a more in-depth understanding of the usage and working principle of Module Federation. Due to my limited level, some places may not understand the explanation in place. If you have any questions or mistakes, welcome to comment and correct, learn together, and make progress together.
Finally, let’s conclude:
- With Module Federation, you can dynamically load and execute code from one application, regardless of the technology stack;
- Dependencies can be shared by apps that connect via Module federation and share a shareScope.
- The host application entry file must import(), otherwise an error will be reported.
- Using Module Federation requires webPack5;
Reference documentation
To complete this article, refer to the following documents:
-
Webpack 5 Module Federation: A Revolution in JavaScript Architecture
-
Webpack 5 Module Federation: A Game-Changer in JavaScript Architecture
-
Module Federation principles
-
Micro front-end
-
qiankun
-
module-federation-examples