How to Use Module Federation With Re.Pack 3
Re.Pack v.3 introduces stable support for Module Federation, a powerful feature enabling code splitting and sharing chunks between loosely coupled applications. Particularly beneficial for Micro-Frontend architecture, Module Federation allows teams to develop independently, deploy containers separately, and share code efficiently. The article guides you through implementing Module Federation with Re.Pack 3, emphasizing its utility for large development teams working on complex projects like super apps, enabling independent deployment and code sharing across applications.
Along with the release of Re.Pack v.3 there comes a huge update for the library - stable support for Module Federation.
This feature can be a powerful ally for development departments, especially when it comes to building complex apps with Micro-Frontend architecture, requiring multiple teams to deliver the whole thing.
In this article, you’ll find out
- what’s the idea behind Module Federation,
- and how to use Module Federation with Re.Pack 3.
The idea behind Module Federation
Module Federation was first introduced in Webpack 5. It’s a functionality that allows for code-splitting and sharing the split code parts (chunks) between loosely coupled applications. It also helps distributed teams to ship large applications faster. And, along with its latest update, Re.Pack 3 supports this functionality out-of-the-box.
Module Federation is one of the approaches to creating Micro-frontends architecture for your application which makes the functionality a great ally in, for example, super app development or creating features on demand.
Going briefly through the Micro-Frontends working scheme, there are:
- the host app that runs firstly on the device,
- and Micro-frontend (MFE) apps that are used by the host app.
That means each container (MFE) that is used by the host application could be deployed and maintained independently. The host apps are able to reflect changes in Micro-frontend immediately with no need for re-deploy until the changes are connected to the JS only.
Accessing remote JS code exposed by MFE is the key feature of Module Federation and the process is called Runtime Deployment.
How to use Module Federation in Re.Pack
If you are familiar with the Webpack config for Module Federation on the web environment, you will notice a similarity in the configuration using Re.Pack. The major difference is to take into account mobile platform / React Native specifics.
Let’s assume that we have the host application, two container applications, and one module used in the first container application. Each of the apps could be deployed independently, but all of them are bound using Webpack config.
The most interesting properties for us from this config are plugins. It is an array of all the required Webpack plugins. As you can see from the code snippet below, Re.Pack exports a <rte-code>ModuleFederationPlugin<rte-code> plugin that enables Module Federation functionality:
Code snippet 1. Host app Webpack config Module Federation plugin
Code snippet 2. Remote Container 1 Webpack config Module Federation plugin
Code snippet 3. Module app Webpack config Module Federation plugin
Going through the properties in the Module Federation plugin config, we can find the same ones in all of the apps. The <rte-code>name<rte-code> property is required, so we have to provide a unique name for each of our apps. Then, we will use it across the app for importing federated modules. The next common property is called <rte-code>shared<rte-code>. This array contains libraries and provides Webpack information regarding the libraries that should be shared in other applications. Also, they should have only one instance across the apps. Re.Pack provides default settings for React and React Native libraries in <rte-code>shared<rte-code> property.
So, until there are no more other libraries that should be shared, this property could be skipped. Dependency defined inside shared property might be also set as <rte-code>eager<rte-code>, which doesn’t put the modules in an async chunk, but provides them synchronously. That’s why it is combined with <rte-code>STANDALONE<rte-code> env which should be <rte-code>true<rte-code> when a remote app is run as its standalone version. Otherwise shared modules won’t be available in the initial bundle and the app is likely to crash.
As you can see on the code snippets above, all the plugins’ configs, except the host app, have <rte-code>exposes<rte-code> property. This is a list of modules that the application will export as remote to another application. The host app can only import remote modules and cannot have pre-defined ‘exposes’ or ‘remotes’ properties in Webpack config. However, MFEs can do both, expose and import remote modules at the same time.
While ‘exposes’ and ‘remotes’ properties are sufficient to support Module Federation in web apps, it’s not enough in a mobile environment. For Re.Pack, we have to add one more thing to handle fetched scripts from remote - a resolver. That’s why the <rte-code>ScriptManager<rte-code> was introduced in Re.Pack 3.
This is a manager that eases the resolution, downloading, and executing additional code from:
- Webpack chunks
- Webpack bundles
- Webpack MF containers
To make <rte-code>ScriptManager<rte-code> resolve our remote scripts properly, we need to pass URLs to remote locations that those scripts will be available to fetch. They should be passed in proper object shape accepted by the createURLResolver method from the Federated module. In a very basic example, URLs might be hardcoded and the implementation is presented in the snippet below:
Code snippet 4. ScriptManager usage inside the host application
However, there is an option to provide remote containers URLs dynamically. That means removing hardcoded URLs and fetching them from external service right in <rte-code>addResolve<rte-code>r. Implementing the external service is optional but might become very useful on a larger scale. It allows to manage remote containers' URLs from outside of the host app and to switch them without the need for the host app re-deployement. Furthermore, it might help to handle fetching theproper version of MFEs inside the host app when a version management system is required.
Code snippet 4.1. ScriptManager usage with dynamic URLs
After setting up the <rte-code>ScriptManager<rte-code>, we can use Module Federation functionality in the code. Here is the <rte-code>App.tsx<rte-code> file from the host application:
The imports are async, so we should wait till the script will be downloaded, resolved by <rte-code>ScriptManager<rte-code>, and ready to use. So, we added <rte-code>React.lazy<rte-code> and <rte-code>React.Suspense<rte-code> syntax for handling async imports. <rte-code>React.Suspense<rte-code> has the fallback property that allows showing loader component to the users during loading scripts. To dynamically import remote modules in the host app, we also used the utility function <rte-code>importModule<rte-code> from <rte-code>Federated<rte-code> namespace, to provide the correct container and module name.
But, let’s take a look at the previously presented code from code snippet 2. There is remotes property under the Module Federation plugin in Webpack config. It helps to use the same syntax as for static imports inside app1 when importing modules from remote containers. Also, it’s worth mentioning that the component name needs to be prefixed with the remote container name. In this case, it’s module1/Root. According to what was written before - the remotes property can be used only in MFEs but anyway, it should still use <rte-code>React.Suspense<rte-code> when rendering remote containers:
Code snippet 6. App.tsx code from app1 application
In code snippet 3 there is also a defined remotes property that allows importing modules exposed by app1. This pattern is called bi-directional import. It won’t be possible outside the Module Federation when importing module B in module A and module A in module B at the same time because of circular dependencies. However, Module Federation allows it and it’s totally fine until both modules are imported from different MFEs. The only thing that developers have to keep in mind is that it might be easy to end up with an endless loop e.g. by calling one function by another as presented in the code snippet below:
Code snippet 7. baz function inside module1 which uses foo function from app1
Please notice that the <rte-code>foo<rte-code> function from app1 uses <rte-code>baz<rte-code> function from module1. So as mentioned above, circular dependencies are working perfectly fine:
Code snippet 8. foo function from app1
On code snippet 7, there is a counter variable that prevents an endless loop caused by calling <rte-code>foo<rte-code> by <rte-code>baz<rte-code> and <rte-code>baz<rte-code> by <rte-code>foo<rte-code>
Module Federation is a powerful feature implemented in Webpack 5 and carefully migrated to Re.Pack 3 package.
Thanks to its functionalities, Module Federation can be a great ally for large development teams working on complex products, like super apps. The main benefits of Module Federation are
- Independent deployment. No need to re-deploy host application on remote containers changes and re-deployment
- Separating huge development teams. As every container could be deployed independently each team could work on their container
- Sharing the code between tightly loosely coupled applications