EXPEDIA GROUP TECHNOLOGY — SOFTWARE

Using Webpack Module Federation to Create an App Shell

Keep modules in sync across different web applications at scale

Chris Nienhuis
Expedia Group Technology

--

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?

Webpack module federation logo in front of Vrbo screenshots

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:

Diagram showing a consuming app interacting with other applications to obtain its modules

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.

Diagram showing a header and footer in a component that is depended on by other micro frontends

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!”

Screenshot of a web page with headers and footers and a placeholder reading “Content here!” between them

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:

Screenshot of web page with same footers as before, but now with a property details page for an island resort

We can repeat this same process for search and favorites:

Search and favorites pages in app shell

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 shell
  • localhost: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.js entry point from localhost: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 Koppersrefactor 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.

Diagram showing how web servers identify CDN bundles using hashes
Building, publishing, and deploying immutable assets to end-users

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.

Diagram illustrating linkage to remote immutable remoteEntry.js asset

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.

Diagram showing an artifact built by CI/CD in a registry which is consulted by web servers
Building, publishing, and deploying immutable assets with asset registry to end-users

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.

Decorative separator

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.

--

--