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
remoteEntrymodule is loaded dynamically, additionalsharedactivation 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-federationDependencies 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 buildAfter 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.