English | 简体中文
lite-module-federation
is an external module dynamic loading scheme, which is more lightweight than Webpack 5 Module Federation. Provides the ability to dynamically load React
modules when runtime
is provided.
import { memorizedFetchBundle } from 'lite-module-federation';
const { config } = await memorizedFetchBundle(
'http://localhost:7001/cdn/remoteEntry.js'
);
const PluginOne = config.componentOne;
ReactDom.render(<PluginOne />, document.getElementById('app'));
Disadvantages of Module Federation
- There are prerequisites for use, and both sub-projects and parent projects need to be upgraded to Webpack 5
- The configuration is cumbersome, it is not easy for users to understand, the documentation is not clear enough, and the mental burden is heavy.
- If the
remoteEntry
module is loaded dynamically, additionalshared
activation logic needs to be added to the host application. In addition, in order to useModule Federation
, the host application entry file needs to be transformed into asynchronous loading, which is intrusive.
We want to be as intrusive as possible to the host application, and lightweight and easy to use.
npm install lite-module-federation
Dependencies can be shared between the host project and subprojects.
In the root directory of the host project, we create a new lite-module-federation.config.js
file, in which we configure the dependencies that need to be shared with sub-applications.
// lite-module-federation.config.js
module.exports = {
shared: {
react: require('react'),
},
};
After that, we need to add the alias
of lite-module-federation.config.js
to the webpack configuration of the host application
module.exports = {
resolve: {
alias: {
'lite-module-federation.config.js': path.resolve(
__dirname,
'lite-module-federation.config.js'
),
},
},
};
For the webpack.config.js
of the sub-project, you need to change the following 2 configurations
- Change output format to
commonjs
- Set external dependencies
externals
, so that the following dependencies will not be included in the bundle when packaging
module.exports = {
output: {
libraryTarget: 'commonjs',
},
externals: {
react: 'react',
},
};
This is a parent-child application scenario, there are 2 projects, one is the child application and the other is the parent application.
In the figure below, we start the parent application locally, and the plugin-1
and plugin-2
components are dynamically loaded remote sub-applications remoteEntry.js
In the sub-application, we export
out a config
, which is similar to the exposes
property of Module Federation, which can export multiple components. The difference here when bundling is that we will configure react
in externals
in webpack
. That is, do not package React
.
// ./src/plugin
import React from 'react';
import './test.css';
const PluginOne: React.FC<{}> = () => {
return <div className="sub-app-box">plugin-1</div>;
};
const PluginTwo: React.FC<{}> = () => {
return <div className="sub-app-box">plugin-2</div>;
};
export const config = {
componentOne: PluginOne,
componentTwo: PluginTwo,
};
The Webpack
configuration on the sub-application side is as follows:
const webpack = require('webpack');
module.exports = {
mode: 'development',
entry: {
remoteEntry: './src/plugin.tsx',
},
output: {
filename: '[name].js',
libraryTarget: 'commonjs',
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
],
externals: {
react: 'react',
},
devServer: {
hot: true,
port: 9001,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
};
Next, we package the sub-app
npm run build
After packaging, we get remoteEntry.js
, and we upload it to CDN
.
On the host side, we use the lite-module-federation
package to parse the packaged remoteEntry.js
just published to the CDN.
After executing the memorizedFetchBundle
method, we can load the injected dependency remoteEntry.js
, get the config
property, and render the component to the page.
import ReactDom from 'react-dom';
import React, { useEffect, useState } from 'react';
import { memorizedFetchBundle } from 'lite-module-federation';
import './app.css';
const App: React.FC<{}> = () => {
const [config, setConfig] = useState<Record<string, any>>({});
const [loading, setLoading] = useState(true);
useEffect(() => {
setTimeout(async() => {
const { config } = await memorizedFetchBundle(
'http://localhost:7001/cdn/remoteEntry.js'
);
setConfig(config);
setLoading(false);
}, 1000);
}, []);
if (loading) {
return <div>Loading sub-app.....</div>;
}
const PluginOne = config.componentOne;
const PluginTwo = config.componentTwo;
return (
<div className="main-box">
<div className="main-app">Main App</div>
<div className="sub-app-wrapper">
<PluginOne />
<PluginTwo />
</div>
</div>
);
};
ReactDom.render(<App />, document.getElementById('app'));
Module Federation usage scenarios:
-
It needs to run in the sandbox environment of the host application, and the child application wants to share the same context as the parent application
-
Parent-child applications do not require style environment isolation
The usage scenario of lite-module-federation
is also the same as Module Federation. When you encounter the above scenario and need a lighter solution, you can use lite-module-federation
.