I joined Razorpay last year on the frontend team building RazorpayX, the neo baking solution for startups and SMEs. Our front-end is very much a complex dashboard with multiple products, each handled by a different team. With multiple frontend devs working on the codebase, I have witnessed the codebase grow at an exponential rate.
It took longer to start the development server, tests ran for longer, building was slower and CI jobs ran for over an hour. So I asked the question, does every company face this? See their codebase turn into a monolith, with unwanted coupling between apps. And the answer seems to be a yes.
I want to ship the product faster. I don't want a text change to take 3 hours for deployment. I want deployments to be scoped to individual apps. But I also want to be able to share a common store between apps and share UI code easily. I dont want complex configurations and architectures.
So I started reading about the possible solutions to this problem. I found out about the following methods:
NPM Packages flow
Each micro app would be an NPM package published to the company package registry. The host app would be a shell that would have all of these packages listed as dependencies. The package versions would only increase by a minor (ie 0.0.x) so that NPM would always resolve to the latest version to download
To update an app, you would have to only publish a newer version for that particular app. Which is something that can be automated on a merge to master and can be done multiple times a day
To deploy a newer version of the dashboard, you'd have to run the pipeline for shell again. This can be done as many times as a new deployment was required.
There was still a problem here, I'd have to migrate all library code (or common UI code) into its own package to be shared between apps. I would also have to manually depulicate these dependencies myself for each package. This would be a major migration effort for a pretty sizeable codebase. Hard pass.
There was still another problem here. When I wanted to deploy a newer version for an app, I would have to trigger two deployments sequentially. First, publish the app itself. Then remember to also rerun the shell pipeline so that the latest app goes into production. All that effort would not be worth it for a minor improvement
Microfrontend frameworks
Then I started looking at the existing microfrontend frameworks. I went through the docs of the popular ones - SingleSPA, Open Components, and Mosaic.
In each of them, I found that it required a lot of configuration to set them up. Configuration would be required to handle routing, state sharing, code sharing, dependency deduplication.
Each project would have to be configured manually to deduplicate the dependencies between them. Each solution had its tradeoffs that I was either not comfortable making, or couldn't make due to the architecture of the existing dashboard that we have.
Module Federation
The new kid on the block. Module federation in the words of its creator:
Webpack 5 Module Federation aims to solve the sharing of modules in a distributed system, by shipping those critical shared pieces as macro or as micro as you would like. It does this by pulling them out of build pipeline and out of your apps.
Translated to simple words:
Module federation allows you to use code from other projects in your project, at runtime
It allows you to share code at runtime, as opposed to build time. This is the major difference between Module Federation and mono-repos. But then, Module Federation is not a microfrontend framework, it is simply a tool that allows you to share code at runtime.
The Module Federation plugin for Webpack 5 simplifies a lot of configuration that would otherwise be required to be done for the above-mentioned solutions:
- It simplifies dependency deduplication by allowing you to pass a list of dependencies via the
shared
parameter. - Exposing code and using it is simple, it looks something like this
// Remote App
new ModuleFederationPlugin({
name: "microapp1",
filename: "microapp1.remoteEntry.js",
exposes: {
"./index": "./src/index",
},
}),
// Host App
new ModuleFederationPlugin({
name: "host",
filename: "host.remoteEntry.js",
remotes: {
microapp1: "microapp1@http://localhost:3001/microapp1.remoteEntry.js",
},
}),
// Usage in host
import Remote from "microapp1";
And so, code sharing is pretty simple. Does not require you to make any changes to how your codebase is structured
Conclusion
So to conclude, I see the value proposition of Module Federation. It fits right into the build toolchain since its a part of Webpack. The configuration required is little and the learning curve is non existent. It does not seem to require any drastic changes to the codebase. If you are interested in trying out Module Federation hands-on, I have a demo available here - Module Federation with Webpack5
Did you like what you read? I try to publish two articles a month on this blog. You can subscribe for updates at https://burhanuday.com
Top comments (0)