Micro front-end
Before we look at module federation in WebPack, let’s take a look at the micro front end. If you don’t know about the micro front end, you can refer to the two articles “Micro front End (1) – Concept” and “Micro Front End (2) – Implementation” to familiarize yourself with its basic concepts.
Generally, our micro front end model is as follows:
In the micro front end, there is a container application whose job is to load each micro application.
Microapplications need to do a few things:
- Provide two methods: one is
Mount method
, which the container will call to render the microapplication. The other one isUnloading method
, used to uninstall microapplications, and they will all be used withInterface methods
Provides the form to the container call; - provide
Remote entry file
The address of the container application is selected at the appropriate timeDynamic loading
The document is obtained atMount method
After that, perform microapplication rendering; - provide
Micro application ID
Is used to identify itself and is required for operations on microapplications. - One of the first
The routing address
After the micro-application is mounted, determine the view display of the micro-application.
The container application does the following:
- Load the remote microapplication (download the remote JS entry file) and perform the rendering;
- Uninstall microapplications at a reasonable opportunity.
These are the basic functions of the micro front end. Next, let’s look at Module federation for Webpack.
Module joint
Typically, modules produced in webPack builds are stored locally and used directly by the current application. The concept of remote modules was introduced in WebPack 5 to allow the runtime to asynchronously load remote modules by using the currently built application as a container. The abbreviation MF will be used to refer to module federation below.
Webpack provides a way to dynamically load modules. You can use import or the older methods require.ensure or require([…]). ).
Remember what the container application did in the previous section when we talked about the micro front end? By dynamically loading WebPack, you’ve already done what container applications are supposed to do. So it’s safe to assume that microapplications themselves can function as container applications.
When we apply a microapplication as a container, its architectural model shifts, resulting in the following model:
It can be seen that when our micro applications become container applications, each application exists equally in the architecture, and container applications can rely on each other and load and use each other.
In the microfront, there is no architectural constraint on the complexity of microapplications, which means it could just be a button that sets the planet on fire, which is a joke. But we should classify it commercially as a valuable reusable module that is not tied to the framework.
How to divide, share and load modules through building tools is the functional significance of WebPack 5 Module Federation.
MF VS micro front end
Let’s continue thinking about the difference between module federation and microfront ends.
In the microfront-end:
- Loading microapplications must be
predefined
Interface methods (Mounted, unmount, etc.) to achieve dynamic micro applicationsmount
anduninstall
And so on, which means every microapplication mustmanual
To achieve theseInterface methods
; - in”Micro Front End (2) – Implementation”In, we learned about microapplications in
Indie development model
The interface method is usually called manually to dynamically load the view. - If we want to share the modules of one microapplication to other microapplications, it is not easy. This means that you need to isolate the module and use it in a way that makes sense for other micro-applications
The remote loading
. - The application of micro
switch
Are usually made ofRoute Status change
To trigger it.
In module federation:
- Above we saw that modules combine each
Micro application
It could be aThe container application
So they can be mutualRely on
andloading
; - Each application allows
exposed
An object reception (iso) multiple interfaces, other applications available inDynamic remote loading
After the application, use its interface directly. This addresses the microfront mentioned aboveModule Shared
Problem; - Very flexible in module use when you
reference
aRemote module
You can use it just like a normal NPM package, and of course you canLazy loading
Module; - There is no connection between the remote module and the route, and the loading opportunity is completely determined by the host application itself.
It is worth noting that the federated module, as the technical extension of the micro-front-end, still has the characteristics of the micro-front-end, that is, each container application should be developed and deployed independently, and the team should be autonomous.
The architectural model for module federation is more like the one shown below, but not just this one, because it is very flexible. It depends on how you share modules and combine them.
In the architecture diagram above:
APP A
,APP B
,APP C
Are loaded and used remotelyThe UI component library
The exposedButton
andText
Components,Table
Components are not stabilized and we are not prepared to expose them to external use;APP B
andAPP C
In theList
Modules are shared toAPP A
(e.g., the order list for business B and the order list for business C can be integrated directly into business A);The authentication
Application as a public module, byAPP A
,APP B
andAPP C
Instead of adding an additional authentication module to the new application, it will serve as the base service.
ModuleFederationPlugin
Webpack 5 implements module interface exposure and remote module declarations through the ModuleFederationPlugin.
The ModuleFederationPlugin combines the ContainerPlugin and ContainerReferencePlugin.
The ContainerPlugin plugin uses the specified public module to create an additional container entry, which means additional container entry files are generated in addition to the configured outputs.
module.exports = {
output: {
filename: 'main.js',}};Copy the code
The ContainerReferencePlugin allows us to use the import standard syntax when using a remote module, so we need to declare the remote module in advance.
The ModuleFederationPlugin allows you to build a run-time stand-alone module as a provider or consumer concept, which each application can become.
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({/* options */})]};Copy the code
You can see all the options here.
Container entry file
First, we need to provide a container entry file that other applications can load remotely:
new ModuleFederationPlugin({
name: "ui_lib".// Container name
filename: 'ui.js'.// Container entry file
})
Copy the code
You need to provide a unique container name (name) and filename (filename). If filename is not provided, the build will generate a filename with the same name as the container name.
After the build, additional container entry files for UI.js are generated in the dist directory.
Expose multiple modules
You can expose any module you want to share. It can be network libraries, common business modules, UI components, routes, hooks, whatever you think you can share. It sounds exciting, and it is.
We expose the module via an expose option:
new ModuleFederationPlugin({
name: "ui_lib".// Container name
filename: 'ui.js'.// Container entry file
exposes: {
"./components": "./src/components/",}})Copy the code
If the entry to our UI library was in the project SRC /components/index.js file, it would look like this:
export { default as Button } from './button/index.jsx'
export { default as Text } from './text/index.jsx'
Copy the code
Shared module
Containers typically have underlying, repetitive dependency libraries (e.g. React, Vue, and so on). Like the shared libraries that introduced the micro front end article, we need to exclude them from our container and load them as asynchronous modules.
Not only that, MF does versioning management for the shared module, you can get relevant information in this PR exchange.
Also, we used the shared option in the ModuleFederationPlugin plugin to specify asynchronous module loading for common modules, which functions like webpack externals and allows external dependency libraries to be loaded at runtime.
new ModuleFederationPlugin({
name: "ui_lib".filename: 'ui.js'.exposes: {
"./components": "./src/components/",},shared: {
react: { singleton: true},
"react-dom": { singleton: true}}})Copy the code
You can see all the shared options here.
Pay attention to the point
1. If you want to use shared modules for local startup, specify the eager: true option, otherwise the following error will occur.
Uncaught Error: Shared module is not available for eager consumption
Copy the code
This option allows the shared module to be used directly during initialization, meaning that it is not loaded as an asynchronous module.
Note that the eager option is enabled, which pushes the module directly into the container file to be loaded and used as a synchronous module.
You can also fix this problem by manually loading the shared module asynchronously.
First, let’s make some changes to the original SRC /index.js file.
Your previous entry file looked like this:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />.document.getElementById('root'));
Copy the code
After the modification, we only retain the asynchronous loading function:
import('./bootstrap');
Copy the code
Then I create the bootstrap.js startup file in the same directory and copy in the contents of the original index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />.document.getElementById('root'));
Copy the code
This enables manual asynchronous loading.
2. We use the version of the specified shared module with the requiredVersion option.
It has two values: requiredVersion, when a string value, represents the semantic version number that follows the Semver specification.
You can use the version of the package name in the Dependencies field in package.json to keep the version of the shared module consistent with that in package.json. If inconsistent, a warning is printed.
const deps = require("./package.json").dependencies;
// other code...
new ModuleFederationPlugin({
name: "ui_lib".filename: 'ui.js'.exposes: {
"./components": "./src/components/",},shared: {
react: {
requiredVersion: deps.react,
singleton: true,},"react-dom": {
requiredVersion: deps['react-dom'].singleton: true,}}})Copy the code
If requiredVersion is a Boolean value, it indicates whether automatic version inference is enabled. When it is true (the default), the requesting module automatically inferences from the version corresponding to the package name in package.json.
Using remote Modules
First, we also use the ModuleFederationPlugin to pre-declare which modules are remote, which is set using the Remotes option:
new ModuleFederationPlugin({
name: "app_b".remotes: {
"@lumin-ui": 'ui_lib@http://localhost:3003/ui.js',}})Copy the code
When we use it at runtime, it is no different from the import syntax we normally use:
import { Button, Text } from '@lumin-ui/components';
Copy the code
It looks really cool! For an architectural upgrade, we didn’t need to change any code to replace the local build module with a remote one.
The demo source code
You can find the demo source code for the above use case at the following address. The use case is not complete, but shows the basic functionality of MF.
Github.com/dun-cat/web…
Start the project by following these steps:
# install dependencies
npm run bootstrap
# Start a project
npm run start
Copy the code
Other interesting use cases
One of the UI library use cases in MF was demonstrated above, and you can find more examples here.
shared-routing
I suggest you take a look at the shared-routing use case, which shows how a complete application can be partitioned and, more importantly, how each module gets the entire application.
You must understand that module partitioning also means project partitioning and task partitioning. When developing independently, it is often necessary to determine how to ensure the correctness of the entire application. So we expect our modules to run across the entire application, and this example provides a good solution.
Read more:
> webpack.docschina.org/concepts/mo…
> www.bilibili.com/video/BV1z5…
> www.youtube.com/watch?v=-ei…
> github.com/module-fede…
> www.nicolasdelfino.com/blog/micro-…