EXPEDIA GROUP TECHNOLOGY — SOFTWARE
Using Webpack Module Federation to Create an App Shell
Keep modules in sync across different web applications at scale
Webpack 5.0 will ship a powerful new feature for javascript application architecture: module federation (committed in v5.0.0-beta.16). Module federation provides several powerful architectural benefits:
- It keeps common modules synchronized across different web applications at scale, enabling evergreen releases and boosting performance
- Teams can independently deploy updates of code they own without requiring additional releases from other teams
- Works server- and client-side without any additional required code
This article explores an implementation of a federated app shell micro front-end (MFE) working with an example travel shopping experience.
What is webpack module federation?
Module federation allows a JavaScript application to dynamically run code from another bundle/build, on both client and server. This means that javascript applications can both consume and provide code:
Any javascript application that enables this feature can be considered as a “host”. Any host application can:
- Expose code to be shared with other applications
- Consume code from other host applications
- If doing both above, the host is considered to be “bidirectional”
With module federation, federated dependencies are dynamically loaded and rendered both on the client and server. If additional dependencies are needed for a federated module from a remote host, webpack will continue to download required dependencies from the remote host.
For a deeper introduction to webpack module federation, check out Jack Herrington’s Overview Of Federated Modules (10 min).
Let’s apply this pattern to create a standalone MFE that shares a simple application shell consumed by the shopping experience.
Building a federated app shell
Here’s a demo of applying module federation to create a shared app shell in a travel shopping experience.
Let’s say your shopping experience consists of a search page, a property details page, and a favorites page that contains user preferences and saved favorite destinations. Those pages will likely have the same content at the top and bottom of the page (a header and a footer).
Let’s take the header and footer and create a standalone federated module called “app shell” and use webpack federation to share it with search, property, and favorite pages.
Let’s look at the code.
Define the app shell
First, we write our module resource that we we would like to share. It should render a header and footer and pass content through to its body.
appshell/src/App.jsx
Here is our app shell with the dummy content “Your content here!”
Expose an interface
Let’s create an interface called StandardLayout which will act as a default app shell that renders a header and footer.
First, we write the React component which will define required properties and act as our interface. Consider this a representation of appshell/src/App.jsx.
appshell/src/StandardLayout.jsx
Then we expose this component in our webpack configuration.
appshell/src/webpack.config.js
This will expose a version of our app shell to consumers that implements what we are defining as our standard layout.
Consume app shell from application
Finally, we’ll consume our app shell from another application, called property.
property/src/App.jsx
Configure the appshell remote in the webpack configuration.
property/src/webpack.config.js
Include a link to the deployed remoteEntry chunk to enable loading of the federated app shell.
property/public/index.html
Here is our app:
We can repeat this same process for search and favorites:
To run the demo for yourself, check out my demo application.
How does it work?
When you run the above demo, the following servers are run:
localhost:3001— app shelllocalhost:3002|3003|3004— property details, search, favorites (uses app shell)
If you access localhost:3002 (property details), you can observe how module federation works:
- HTML downloads and parses
main.jsentry point from localhost:3002 - HTML downloads and parses
remoteEntry.jsentry point fromlocalhost:3001
When both of these assets are resolved, the webpack container intelligently resolve dependencies at runtime and requests any remaining required dependencies from localhost:3001 to complete the app shell (vendors and StandardLayout)
Note: the exact details of how the webpack container resolves its dependencies are still a work in progress (see Tobias Koppers’ refactor of shared modules in federation plugin, for example).
Bonus Feature: Federated Dashboard
Another powerful feature is the federated dashboard plugin (included in demo), currently in development by Zack Jackson. The dashboard enables:
- Dependency graphs for visualizing cohesion of federated modules
- Dependency tables to monitor deployed versions of components and drift between consuming applications
- Enables control plane for deploying new versions
- Usage and health statistics of federated modules
The federated dashboard plugin looks like a key feature to enable powerful observability features and deployment controls that enable intelligent management of federated modules.
Caveat: Immutable remoteEntry.js chunks
Many sites (including Vrbo.com™, part of Expedia Group™) optimize the download of assets using the following recipe:
- Upload content hashed assets to a content delivery network (CDN) (i.e.,
myapp.636afebbf02818aa1fa6.min.js) - Set time-to-live (TTL) to one year
- Ensure web applications render links to correct content hashed assets
When an application is prepared for release, assets are content-hashed and references to these hashes are stored with the web server so it can render the correct links. This ensures that clients get the optimal download performance while maintaining the ability to update them by simply updating the HTML to new CDN locations.
With module federation, federated modules can be independently deployed because the implementation of remoteEntry.js provides indirection to where the assets live. The problem is that this asset itself, when named remoteEntry.js, implies a short TTL duration, which is suboptimal for the user’s performance.
At Vrbo.com, we’ve created an internal proposal for solving this problem by creating an asset registry that provides an API and a simple UI dashboard for managing content-hashed files. We are also making general proposals around observability and control of remote federated deployments. A system like this might be best built in the open in collaboration with other companies who have the same sorts of problems.
Next steps are to reach out to Zack Jackson (creator of module federation), who would be a key person to work with to build this sort of community solution, assuming there is demand for it. There is also an open merge proposal for module federation on the official webpack repo that describes patterns to support immutable asset support.
Update 6/9: Caught up with Zack and he is currently developing first-class support in module federation that will enable intelligent management of assets.
Advantages of module federation
In this exploration, we created a MFE to act as a dedicated host for sharing an app shell module using module federation. Federated module architecture offers several benefits to teams:
- Streamlines software development lifecycle for teams that own shared code that is distributed to many different applications
- Shrinks monolithic JavaScript applications that inconsistently implement common customer experiences
- Enables efficient code sharing that supports universal rendering for a faster customer experience
Challenges of module federation
Likely challenges with pursuing this kind of application architecture include:
- Single point of failure for rolling out new versions across sites (due to logical errors, broken interfaces, etc)
- Infrastructure for monitoring these kinds of deployments requires investment
- New CI/CD processes require time to mature and scale
Thoughtful consideration of required engineering investment and added risk/complexity should be evaluated when considering these challenges in building support for module federation.
Webpack module federation is a fresh new approach to sharing modules across applications that has exciting potential to increase developer efficiency while providing a better customer experience. Even though this feature is still new, it is already drawing a lot of attention and excitement from the web development community. A serious implementation of MFE federated applications requires careful consideration of how the remoteEntry.js chunk is deployed to a CDN and how to link to it, among other design considerations.