Webpack module federation — ready for primetime?

Context

Recently I’ve been researching a way to allow multiple teams to work on a complex web based product. While the product is supposed to look like a single coherent single page application to users, ~8 distributed, cross-functional teams are supposed to work on individual USPs in parallel. “Parallel” means that teams should be able to deploy updates to their feature independent from other teams, without any risk of breaking the whole application or other team’s features. At the same time, teams need to be able to re-use components built by other teams. To reach a coherent user experience and to allow people to switch to other teams without hassle, ensuring consistency across teams was an important consideration as well.

Considered approaches:

Research into module federation with webpack 5

To research, whether module federation is already mature and feature-complete enough, I set up a small example project.

The repository is more or less a concatenation of multiple module federation examples:

Detected downsides of federated modules & remedies

  1. To work on one of the federated modules, all remotes need to be reachable. It’s not possible to work on one application locally without running all applications locally or at least having access to a hosting environment exposing all remote applications.
    This is not a big issue. Being able to hook in the production or staging hosted remote applications is comfortable enough.
  2. If one of the remotes is not responding, the accessed module crashes at the first non-lazy import of a federated module.
    To resolve, free the host application’s bootstrapping from non-lazy imports of remote application’s modules. This e.g. could likely be archived by changing the import of settingsApp/routes in host/src/App.tsx to be lazy, and adding a second router <Switch> wrapped in its own <Suspense> and <ErrorBoundary>, which would render the Routes of the lazy loaded settings application. A failing import of a remote application should then only affect routes exposed by the remote application instead of the whole accessed application.
  3. Applications need to load their own remoteEntry.js to resolve circular dependencies. An example: when starting the host app and accessing /settings, host renders the remote module settings/page/SettingsPage.tsx, which in turn imports host/utils/cookie.ts via the import path hostApp/cookie. The host environment doesn’t know how to resolve imports starting with hostApp/ without the import of its own remoteEntry.js. host having to load its own remoteEntry.js is a duplication of other JS bundles loaded because remoteEntry.js (also) contains the exported modules and shared eagerly loaded dependencies.
    That redundant imports are the only solution seems unlikely given that every application is aware of its own name, as specified in ModuleFederationPlugins configuration object’s name property. This is such a basic case that it seems likely that there is a better way.
  4. There’s no polished best practises for stuff like sharing module type definitions (but at least one example and discussions).
    Given webpack’s buy-in and support by other module bundlers, best practises should quickly appear. There needs to be a willingness to adapt those as they come up.
  5. Tree shaking is not possible for shared dependencies. Using different dependency versions in different applications could introduce subtle issues in some cases, and updating synchronously could counter the idea of application development independence.
  6. Recent webpack beta updates still changed module federation plugin capabilities, defaults, and configuration. Adopting module federation would likely mean having to migrate through a couple of smaller breaking changes in the coming months.
    Locking in the webpack version and only upgrading with the stable release would mean only a single adaptation would be required. The expected costs are not high, because the amount of configuration is limited.

Conclusion

In the end we decided not to go forward with webpack module federation. While it seemed that most issues could be overcome or would be ironed out during the remaining beta phase of webpack 5, the expected required time investment and the remaining insecurities didn’t make it contender number one for a project with delivery pressure.

Footnotes

  1. There’s promising signs that rollup will support it. At that point it’s safe to call module federation an — albeit unofficial – “JS ecosystem standard”.

  2. That use case has been solved much better. My solution had stopped to work with the webpack 5 beta 21 update anyway.