In the previous article, we introduced single-SPA and the implementation of qiankun as a microfront-end solution. In this article, we’ll introduce another solution: Module federation in webpack5.
This article will be explained from the following aspects.
Module federation
What is theModule federation
How to use it- Source code analysis
Module federation
Implementation principle of Module federation
withqiankun
As well assingle-spa
The difference between
For ease of writing, we also use MF instead of Module Federation
What is Module Federation
Mf is a new plugin for webpack5, and its main function is that we can expose some or all of the components of the project to the outside world. We can also reference components exposed in other projects to enable reuse of modules. It sounds like MF is very similar to the NPM installation package in that both can expose components for external use and I can use components from other installation packages myself. What are the specific differences?
Let’s use an example to explain:
Let’s say we have a project A that uses some of the functionality in project B. In the form of NPM, we can package B’s project and let project A reference it. If project B suddenly finds that some of its written features are buggy, then project A should also update the dependency package and republish it. If it is in the form of MF, we only need to update project B. That’s the big difference.
How should Module Federation be used
Mf is based on WebPack 5, and if the build used in the project is based on WebPack 4 or other versions, you will need to upgrade the WebPack.
With the version of WebPack secured, the next thing we do is configure the packaging configuration for WebPack. We mentioned earlier that MF is a new plugin for Webpack5, so we need to introduce the configuration items in this plugin first.
new ModuleFederationPlugin({
name: "app_1",
filename: 'remoteEntry.js'
remotes: {
app_two: "app_2",
app_three: "app_3"
},
exposes: {
AppContainer: "./src/App"
},
shared: ["react", "react-dom", "react-router-dom"]
}),
Copy the code
Name: indicates the name of the application. When other applications look up, they look up the corresponding component in the scope of this name.
Remotes: A mapping manager that maps other remote names to local aliases. For example, we mapped the other remote project app_2 to the local app_two.
Filename: Specifies the file in which the exposed modules are stored.
Object: An exposed module. Only the corresponding exposed module functions can be used.
Shared: This parameter allows remotely loaded modules to use the React or ReactDOM of the local project instead.
So after configuring WebPack, how should we use it in our projects?
Used in the project, divided into two steps, the first step is to reference the corresponding module packaged script. For example, remoteentry.js of app_2. This reference can be deployed to the CDN with the corresponding module packaged script, and then referenced in template.html.
Once you’ve referenced it, it’s time to use the components in the referenced script.
const Button = React.lazy(() => import("app_three/Button"));
Copy the code
As you can see, app_three caused us to map the name of the application named APP_3 in the previous configuration, and we exposed the Button component in an object reception of APP_3. So for references, just use import(” Apply alias/components to use “);
Source code analysisModule federation
Implementation principle of
Above we explained how to configure WebPack and how to use MF. Let’s take a look at how this works.
Let’s take a look at what inde.html looks like when it’s packaged
<! DOCTYPE html> <html lang="en"> <head> <script src="http://localhost:3002/remoteEntry.js"></script> <script src="http://localhost:3003/remoteEntry.js"></script> </head> <body> <div id="root"></div> <script src="http://localhost:3001/remoteEntry.js"></script><script src="http://localhost:3001/main.js"></script></body> </html>Copy the code
You can see we need reference application is first loaded components (< script SRC = “http://localhost:3002/remoteEntry.js” > < / script >), then we will see what this js file output. We can see that first he defines an app_03 in the global scope, and then assigns the value of a self-executing function to app_03. Inside this self-executing function, we create a variable _webpack_modules__ with a property inside, okay? Eb9c, for later clarification, we will? Eb9c is called the module identifier of APP_03. The module identifier value of app_03 is again a self-executing function that defines a moduleMap variable that stores the exposed module. Next, there are two methods, the first is the get method, which determines whether the desired outer module exists in the moduleMap and throws an error if it does not. Finally, there is the shared merge operation. Finally, mount the two methods to the Exports property to expose them.
Var app_03; app_03 = (() => { // webpackBootstrap var __webpack_modules__ = ({ "? eb9c": ((__unused_webpack_module, exports, __webpack_require__) => { var moduleMap = { "Button": () => { return Promise.all([__webpack_require__.e("vendors-node_modules_styled-components_dist_styled-components_browser_esm_js"), __webpack_require__.e("src_Button_jsx-")]).then(() => () => __webpack_require__(/*! ./src/Button */ "./src/Button.jsx")); }}; var get = (module) => { return ( __webpack_require__.o(moduleMap, module) ? moduleMap[module]() : Promise.resolve().then(() => { throw new Error("Module " + module + " does not exist in container."); })); }; var override = (override) => { Object.assign(__webpack_require__.O, override); } // This exports getters to disallow modifications __webpack_require__.d(exports, { get: () => get, override: () => override }); / * * * /})}) ()Copy the code
After loading the scripts for the application components, we used the following methods in the project:
/ /... Const Button = react. lazy(() => import("app_03/Button")); / /... Get rid of unimportant codeCopy the code
Now let’s look at what this code looks like when packaged.
We can see that the above code is translated into
const Button = react__WEBPACK_IMPORTED_MODULE_2___default().lazy(() => __webpack_require__.e(/*! import() */ "-_c6ab").then(__webpack_require__.t.bind(__webpack_require__, /*! app_03/Button */ "? c6ab", 7))); // __webpack_require__. E __webpack_require__. F = {}; // __webpack_require__. __webpack_require__.e = (chunkId) => { return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { __webpack_require__.f[key](chunkId, promises); return promises; } [])); };Copy the code
From here we can see that he calls __webpack_require__.e and passes _c6ab, and then calls the. Then method. In the __webpack_require__.e method, we see that it first fetches all keys on __webpack_require__.f, and then executes the corresponding function to return an array of promises. There is a specific method on __webpack_require__.f to get remote related data as follows:
*/ (() => { /******/ var chunkMapping = { /******/ "-_95f2": [ /******/ "?95f2" /******/ ], /******/ "-_6133": [ /******/ "?6133" /******/ ], /******/ "-_c6ab": [ /******/ "?c6ab" /******/ ] /******/ }; /******/ var idToExternalAndNameMapping = { /******/ "? 95f2": [ /******/ "?5d41", /******/ "Dialog" /******/ ], /******/ "? 6133": [ /******/ "?5d41", /******/ "Tabs" /******/ ], /******/ "? c6ab": [ /******/ "?702f", /******/ "Button" /******/ ] /******/ }; /******/ __webpack_require__.f.remotes = (chunkId, promises) => { /******/ if(__webpack_require__.o(chunkMapping, chunkId)) { /******/ chunkMapping[chunkId].forEach((id) => { /******/ if(__webpack_modules__[id]) return; /******/ var data = idToExternalAndNameMapping[id]; /******/ promises.push(Promise.resolve(__webpack_require__(data[0]).get(data[1])).then((factory) => { /******/ __webpack_modules__[id] = (module) => { /******/ module.exports = factory(); /******/} /******/})) /******/}); /******/} /******/} /******/})Copy the code
__webpack_require__. F [key](chunkId, promises) . That is equivalent to the call __webpack_require__ f.r emotes (chunkId, promises), in this way, if the chunkId find then will go inside idToExternalAndNameMapping match, After the match, an array is returned. The first item in the array is the key of the container-reference/ APP_03 in the current context, and the second item is the specific item to load. Container-reference /app_03 is an identifier that indicates that the container refers to app_03 in the global variable.
To sum up:
- Load other application components through the MF package after exposing the file remoteentry.js
- Execute remoteentry. js to mount a global scope property named Name defined in mf, which exposes the get and Override methods
- When referenced in a component, passes
__webpack_require__.e
To quote. __webpack_require__.e
In the call__webpack_require__.f
To obtain the corresponding component.
Module federation
withqiankun
As well assingle-spa
The difference between
Thing in common:
Can achieve a micro front end.
Difference:
qiankun
withsingle-spa
Is application based, while MF is component based.- Mf is friendly to unlimited nesting mode support,
- Mf is not friendly to old projects, so the corresponding Webpack needs to be upgraded and the. HTML file cannot be used directly.
- with
single-spa
Also, the js sandbox environment is not supported and you need to implement it yourself. - The first time you need to front-load a referenced dependency, it becomes a load time problem.