Webpack5 has an amazing feature called Module Federation, which provides a mechanism for scheduling and running projects between different builds. It’s like a micro front, but it’s not limited to that. This article introduces the basic application and principle of this feature.

Microfront like

We are all familiar with the concept of micro front end. Its essence is the separation and isolation of services to minimize the conflicts and collisions between services. Webpack’s module federation does much the same.

However, webPack module federation has more advantages:

  • Based on Webpack ecology, the cost of learning and implementation is low. After all, most projects are in WebPack
  • Naturally engineered, NPM offers a variety of packages to play with
  • The relevant concepts are clear and easy to understand
  • The configuration is simple and easy to use. The official version is also provided based on various frameworks

So, if you’ve had the idea to practice microservices on the front end before, but for one reason or another didn’t, now’s your chance!

Three concepts

First, there are three important concepts to understand:

  • Webpack build. A standalone project is packaged and compiled with WebPack to produce resource bundles.
  • remote. One exposed module for the othersWebpakc buildThe consumption ofWebpack build.
  • host. One consumes the otherremoteThe moduleWebpack build.

In short, a WebPack build can be remote, which is the provider of the service, or host, which is the consumer of the service, or both, depending on the architecture of the project.

The following figure shows the dependency between host and remote:

It should be noted that any Webpack build can act as either a host consumer or a Remote provider, depending on the responsibilities and Webpack configuration.

A case in field

Introduction to project dependencies

There are three microapps :lib-app, Component-app, and main-app. The roles are:

  • lib-appAs remote, exposes two modulesreactandreact-dom
  • component-appAs remote and host, dependentlib-app, exposed some components formain-appconsumption
  • main-appAs the host, rely onlib-appandcomponent-app

Lib-app exposes modules

//webpack.config.js
module.exports = {
    / /... omit
    plugins: [
        new ModuleFederationPlugin({
            name: "lib_app".filename: "remoteEntry.js".exposes: {
                "./react":"react"."./react-dom":"react-dom"}})]./ /... omit
}
Copy the code

The compiled results are as follows:

Apart from the generated map file, there are four files:main.js,remoteEntry.js,. react_index.js,. react-dom_index.js;

  • The first is the entry file for the project (the project only exposes the interface, so this file is empty)
  • The second is the remote entry file through which other WebPack builds use and access modules exposed by this project
  • The third and fourth are exposed modules for consumption by other projects

Component – app configuration

Depending on lib-app, expose three module components: Button, Dialog, and Logo

//webpack.config.js module.exports = { //... [new ModuleFederationPlugin({name: "component_app", filename: "remoteentry.js ", exposes: { "./Button":"./src/Button.jsx", "./Dialog":"./src/Dialog.jsx", "./Logo":"./src/Logo.jsx" }, remotes:{ "lib-app":"lib_app@http://localhost:3000/remoteEntry.js" } }), ] }Copy the code

Three exposed components:

//Button.jsx
import React from 'lib-app/react';
export default function(){
    return <button style={{color: "#fff",backgroundColor: "#409eff",borderColor: "#409eff}} ">Button component</button>
}
Copy the code
//Dialog.jsx
import React from 'lib-app/react';
export default class Dialog extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        if(this.props.visible){
            return (
                <div style={{position:"fixed",left:0.right:0.top:0.bottom:0.backgroundColor:"rgba(0.0.0.3.}}) ">
                    <button onClick={()= >this.props.switchVisible(false)} style={{position:"absolute",top:"10px",right:"10px"}}>X</button>
                    <div style={{ marginTop:"20% ",textAlign:"center}} ">
                        <h1>
                            What is your name ?
                        </h1>
                        <input style={{fontSize:"18px",lineHeight:2}} type="text" />
                    </div>
                    
                </div>
                );
        }else{
            return null; }}}Copy the code
// Logo.jsx
import React from 'lib-app/react';
import pictureData from './MF.jpeg'
export default function(){
    return <img src={pictureData} style={{width:"500px",borderRadius:"10px}} "/ >
}
Copy the code

The build results are similar to the last one. It should be noted that in order for the exposed components to work properly, tests need to be done locally, and main.js is the entry function for the tests. NPM run start: localhost:3001

And open the console network,react,react-domModules have been separated from the project:

The main – app configuration

Main-app relies on two projects, Lin-app and Component-app.

///webpack.config.js
module.exports = {
    / / to omit...
    plugins: [
        new ModuleFederationPlugin({
            name: "main_app".remotes: {"lib-app":"lib_app@http://localhost:3000/remoteEntry.js"."component-app":"component_app@http://localhost:3001/remoteEntry.js"}}),new HtmlWebpackPlugin({
          template: "./public/index.html",})]/ / to omit...
};
Copy the code

Because you need to wait for the base module to be loaded, you need to configure the lazy loading portal bootstrap.js.

  • Webpack packages entry files
import("./bootstrap.js")
Copy the code
  • bootstrap.js
import App from './App.jsx'
import ReactDOM from 'lib-app/react-dom';
import React from 'lib-app/react'
ReactDOM.render(<App />.document.getElementById("app"));
Copy the code
  • The root component App. JSX
import React from 'lib-app/react';
import Button from 'component-app/Button'
import Dialog from 'component-app/Dialog'
import Logo from 'component-app/Logo'
export default class App extends React.Component{
  constructor(props) {
    super(props)
    / / to omit...
  }
  / / to omit...
  render(){
    return (<div>/ / to omit...</div>)}}Copy the code

Run and open the browser http://localhost:3002:

Looking at the console, the resources are nicely separated:

The basic principle of

In this section, we’ll start with the host code and take a brief look at how all this interacts and works.

The program starts with a piece of code in main.js:

__webpack_require__.e("bootstrap_js").then(__webpack_require__.bind(__webpack_require__,"./bootstrap.js"))
Copy the code

__webpack_require__.e(“bootstrap_js”) loads all dependencies of the chunk with id bootstrap_js and returns a promise. Wait until all the dependencies are in place before fetching the./bootstrap.js module and executing it

Here is the code for __webpack_require__.e:

__webpack_require__.e = (chunkId) = > {
	return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) = > {
		__webpack_require__.f[key](chunkId, promises);
		returnpromises; } [])); };Copy the code

The code above does one thing: iterates over the __webpack_require__.f object and executes the member functions in the object in turn. The object has two members:

{
	remotes:(chunkId, promises) = > {
		// Find all the remote modules corresponding to chunkId bootstrap_js and load them
		var chunkMapping = {
                    "bootstrap_js": [
                        "webpack/container/remote/lib-app/react"."webpack/container/remote/component-app/Button"./ / to omit...]};var idToExternalAndNameMapping = {
                    "webpack/container/remote/lib-app/react": [
                        "default"."./react"."webpack/container/reference/lib-app"]."webpack/container/remote/component-app/Button": [
                        "default"."./Button"."webpack/container/reference/component-app"]./ /... omit
                };
	},
	j:(chunkId,promises) = >{
		// Responsible for loading the local module corresponding to chunkId}}Copy the code

Bootstrap_js is one of two promises:

  • One is responsible for remote dependency loading
  • The other takes care of local loading

Modules are not required and executed until all dependent modules are loaded and ready.

Of course, there are more details. There are also some interesting modules in the source code, For example, __webpack_require__.l is responsible for loading scripts as script tags, webpackJsonpCallback is responsible for updating promsie status of local modules, and __webpack_require__.f.j is responsible for hierarchical invocation of remote modules. Limited in space, can not do too much in-depth introduction, interested friends, welcome to leave a message to discuss!

The last

The cases covered in this article have been hosted on Github.

If you have any doubts, please leave a comment and if this article is helpful to you, you can like it and forward it to more people.