Module federation brings dynamic code composition to JavaScript on both client and server

The equivalent shift for bundlers that Apollo delivered for GraphQL

Scalable code sharing across independent applications has never been effortless, and at enterprise scale it has been nearly unworkable. The closest alternatives—externals or the DLLPlugin—forced everything to rely on a centrally managed external file. This made sharing awkward, left applications short of truly independent, and in practice limited how many dependencies could be shared. And sharing actual feature code or UI components between separately bundled applications? That has been impractical, counterproductive, and unprofitable.

Prefer a shorter version of this material? Jack Herrington has put one together!


What we need is a scalable answer for sharing node modules and complete feature or application code. The sharing has to happen at runtime so it can stay responsive and dynamic. Externals is neither efficient nor flexible enough for this. Import maps don't solve problems of scale either. Simply fetching code and sharing a few dependencies isn't sufficient—we need an orchestration layer that can dynamically share modules at runtime, complete with fallbacks.

Some really exciting work going on here by @ScriptedAlchemy, aiming to make independently deployed webpack builds "look and feel like a monolith in the client."https://t.co/8rb4A5Ifyc

— Mark Dalgleish (@markdalgleish) February 18, 2020

What is Module Federation?

Module Federation is a JavaScript architecture I originally invented and built a prototype for. With the help of my co-creator and the founder of Webpack, it evolved into one of the most compelling features embedded in the Webpack 5 core (the release contains several strong additions, and the fresh API is notably powerful and tidy).

I'm proud to present, a long awaited leap forward in JavaScript application architecture. Our contribution to the open-source community:
Module Federation

Module Federation lets one JavaScript application pull in code from another application at runtime, and in that same process, share dependencies. When a consumer app that loads a federated module lacks a specific dependency required by that federated code, Webpack fetches the missing dependency directly from the build that provided the federated module.

Whenever possible, code gets shared, but each situation has its own fallback ready. Federated code is always able to load its own dependencies, though it will first try to use the consumer's existing dependencies before downloading any additional payload. The benefit is minimal code duplication and dependency sharing on par with a single monolithic Webpack build. While I initiated the system, it became part of Webpack 5 as a co-authored effort by myself (Zack Jackson) and Marais Rossouw, with extensive guidance, pair-programming, and support from Tobias Koppers. These developers were instrumental in rewriting and stabilizing Module Federation within the Webpack 5 core. Their ongoing collaboration and support have been invaluable.

Key Terminology

  • Module federation: the same principle as Apollo GraphQL federation — but designed for JavaScript modules. It operates both in the browser and in node.js. We refer to it as Universal Module Federation.
  • A host: the Webpack build that gets initialized first during a page load (when the onLoad event fires).
  • A remote: a Webpack build that partially gets consumed by a “host”.
  • Bidirectional-hosts: a bundle or Webpack build that can act as either a host or a remote. It can consume other applications or be consumed by them at runtime.

Webpack 5 Module Federation: A game-changer in JavaScript architecture — figure 1

It should be stressed that this system is built so each completely standalone build/app can live in its own repository, be independently deployed, and run as its own independent SPA.

Every one of these applications is a bi-directional host. Whichever application happens to load first automatically becomes the host. As you navigate through an app and change routes, it loads federated modules just like it would with dynamic imports. But if you refresh the page, the application that starts up first on that specific load becomes the host.

This is killer stuff Zack, thanks! Playing with the demos now and I feel like this stuff can be a real game-changer. This is how I've wanted to compose apps in the past, but the burden it put on the consumer through bundle size / UX always felt painful. Hoping this nails it.

— Kevin Saldaña (@kmsaldana1) March 2, 2020

Imagine each page of a website is compiled and deployed separately. I want this micro-frontend style architecture, but I absolutely cannot afford page reloads when switching routes. I'd also like to dynamically share code & vendors between pages, making it just as efficient as a single large Webpack build using code splitting.

If you land on the home page app, the "home" page becomes the "host". Navigating to an "about" page means the host (the home page SPA) is dynamically importing a module from another independent application—specifically the about page SPA. It doesn't load the main entry point or spin up the entire application; it only pulls in a few kilobytes of code. If you're on the "about" page and refresh the browser, then the "about" page becomes the "host". Going back to the home page means the about page "host" is fetching a slice of runtime from a "remote"—this time, the home page. Every application is both a remote and a host; each is a consumer and is consumed by other federated modules across the system.

For deeper technical details, check the GitHub discussion: https://github.com/webpack/webpack/issues/10352

Constructing a federated application

Let's begin with three standalone applications.

App One

Settings:

I'll use the app container <App> from App One. Other apps will use this component. To allow this, I expose its App as AppContainer.
App One will also pull in components from two other federated applications. To enable that, I define the remotes scope:

const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

module.exports = {
  // other webpack configs...
  plugins: [
    new ModuleFederationPlugin({
      name: "app_one_remote",
      remotes: {
        app_two: "app_two_remote",
        app_three: "app_three_remote"
      },
      exposes: {
        'AppContainer':'./src/App'
      },
      shared: ["react", "react-dom","react-router-dom"]
    }),
    new HtmlWebpackPlugin({
      template: "./public/index.html",
      chunks: ["main"]
    })
  ]
}

Configuring build orchestration:

At the top of my applications, I include app_one_remote.js. This connects you to other Webpack runtimes and sets up the orchestration layer at runtime. It functions as a carefully crafted Webpack runtime and entry point. It is not a typical application entry point and is only a few KB in size.

Keep in mind these are special entry points—they are just a few KB. They contain a specialized Webpack runtime that can interact with the host, so they are NOT standard entry points

<head>
  <script src="http://localhost:3002/app_one_remote.js"></script>
  <script src="http://localhost:3003/app_two_remote.js"></script>
</head>
<body>
  <div id="root"></div>
</body>

How to consume code from a remote

Within App One, there's a page that uses a dialog component originating from App Two.

const Dialog = React.lazy(() => import("app_two_remote/Dialog"));

const Page1 = () => {
    return (
        <div>
            <h1>Page 1</h1>
            <React.Suspense fallback="Loading Material UI Dialog...">
                <Dialog />
            </React.Suspense>
        </div>
    );
}

export default Page1;

The router itself looks quite standard:

import { Route, Switch } from "react-router-dom";

import Page1 from "./pages/page1";
import Page2 from "./pages/page2";
import React from "react";

const Routes = () => (
  <Switch>
    <Route path="/page1">
      <Page1 />
    </Route>
    <Route path="/page2">
      <Page2 />
    </Route>
  </Switch>
);

export default Routes;

App Two

Settings:

App Two will expose its Dialog component, which allows App One to use it. Additionally, App Two will use App One's <App> — so I define app_one as a remote, demonstrating bi-directional hosts:

const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "app_two_remote",
      filename: "remoteEntry.js",
      exposes: {
        Dialog: "./src/Dialog"
      },
      remotes: {
        app_one: "app_one_remote",
      },
      shared: ["react", "react-dom","react-router-dom"]
    }),
    new HtmlWebpackPlugin({
      template: "./public/index.html",
      chunks: ["main"]
    })
  ]
};

How it consumes:

The root App component looks like this:

import React from "react";
import Routes from './Routes'
const AppContainer = React.lazy(() => import("app_one_remote/AppContainer"));

const App = () => {
    return (
        <div>
            <React.Suspense fallback="Loading App Container from Host">
                <AppContainer routes={Routes}/>
            </React.Suspense>
        </div>
    );
}

export default App;

And the default page, which renders the Dialog, looks like this:

import React from 'react'
import {ThemeProvider} from "@material-ui/core";
import {theme} from "./theme";
import Dialog from "./Dialog";


function MainPage() {
    return (
        <ThemeProvider theme={theme}>
            <div>
                <h1>Material UI App</h1>
                <Dialog />
            </div>
        </ThemeProvider>
    );
}

export default MainPage

App Three

Predictably, App Three follows a similar pattern. The difference is that it does not consume the <App> from App One; it runs more like a standalone, self-sufficient component (no navigation or sidebar). Therefore, it does not define any remotes:

new ModuleFederationPlugin({
  name: "app_three_remote",
  library: { type: "var", name: "app_three_remote" },
  filename: "remoteEntry.js",
  exposes: {
    Button: "./src/Button"
  },
  shared: ["react", "react-dom"]
}),

What you see in the browser

Keep an eye on the network tab. The code is being federated across three separate servers: three separate bundles. Generally speaking, the advice is to avoid federating the whole application container unless you plan to use SSR or progressive loading. Nevertheless, the concept is remarkably powerful.

A more robust demo of Module Federation. 3 Apps sharing dependencies and modules at runtime! Nested federation, circular federated imports, component importing, page importing, and nested routing. #microfrontends #webpack5 #modulefederation pic.twitter.com/7FbbioAxZ6

— Zack Jackson (@ScriptedAlchemy) March 2, 2020

Duplication of Code

Dependency duplication is nearly non-existent. Thanks to the shared option — remotes will rely on host dependencies; if the host doesn't have a given dependency, the remote will fetch its own copy. Thus, no code duplication, but built-in redundancy.

Module Federation across three separately deployed applications. Only 30kb of Javascript downloaded when moving between the applications. ?

— Zack Jackson (@ScriptedAlchemy) March 1, 2020

Manually adding vendors or other modules to the shared field isn't practical on a large scale. This process can be automated with a custom-built function or through a supplementary Webpack plugin. We do have plans to release AutomaticModuleFederationPlugin and will maintain it separately from the Webpack Core. Now that we have integrated first-class code federation directly into Webpack, extending its capabilities is a straightforward task.

But the big question is – Does any of this even work with SSR??

Server-Side Rendering

We have built this to be Universal. Module Federation operates in any environment. Federating code for server-side rendering is completely within reach. You just need your server builds to use a commonjs library target. There are multiple ways to get federated SSR running: through S3 Streaming, ESI, or automating an npm publish to consume server-side variants. My own plan involves a commonly shared file volume or async S3 streaming to shuttle files across the filesystem. This would enable the server to require federated code in the same manner as the browser, using fs rather than http to load the federated code.

module.exports = {
 plugins: [
  new ModuleFederationPlugin({
   name: "container",
   library: { type: "commonjs-module" },
   filename: "container.js",
   remotes: {
    containerB: "../1-container-full/container.js"
   },
   shared: ["react"]
  })
 ]
};

“Module Federation also works with target: "node". In that case, you point to the other micro-frontends using file paths rather than URLs. This lets you do SSR with the same code base using a different webpack config for node.js builds. The properties of Module Federation remain true in node.js: e. g. Separate builds, Separate deploys” — Tobias Koppers

The Next.js Experiment with Webpack 5 Federation

Federation requires Webpack 5 — and Next.js doesn't officially support it yet. Even so, I managed to fork and update Next.js so it works with Webpack 5! There's still work to be done. Some middleware for development mode requires finishing touches. Production mode is functional, though I still need to retest some additional loaders.

Hello there beautiful. #nextjs upgraded and working with #webpack5 took a few hours but Module Federation with Next is going to be ?? pic.twitter.com/ZfTJ7mLjtO

— Zack Jackson (@ScriptedAlchemy) March 1, 2020

Let's talk, podcast, or get your feedback

I would love the chance to discuss this technology further. If you're planning to use Module Federation or a Federated architecture, we'd really like to hear about your experiences and any tweaks you've made to your current architecture. We are also eager to speak about it on Podcasts, at meetups, or in corporate settings. Reach me on Twitter: https://twitter.com/ScriptedAlchemy

You can also get in touch with my co-creator. Follow us for the latest updates on Module Federation, FOSA (Federation of Standalone Applications) Architecture, and other tools we are building—all designed to integrate with Federated Applications.

Module Federation in Action: Examples

The response from the community has been extremely positive! Both my co-creators and I have dedicated our efforts primarily to integrating this directly into Webpack 5. In the meantime, we hope some code samples are helpful while we work on polishing the remaining features and writing documentation: https://twitter.com/codervandal

[

Webpack 5 and Module Federation – A Microfrontend Revolution

Picture this: you’ve got yourself a pretty whiz-bang component, not just any component, but that clas…

Webpack 5 Module Federation: A game-changer in JavaScript architecture — figure 2The DEV CommunityMarais Rossouw

Webpack 5 Module Federation: A game-changer in JavaScript architecture — figure 3

](https://dev.to/marais/webpack-5-and-module-federation-4j1i)

As bandwidth allows, we will create SSR examples and more comprehensive demos. If you're interested in building a project that could serve as a demo, we are happy to accept pull requests into webpack-external-import

[

module-federation/module-federation-examples

Examples showcasing Webpack 5′s Module Federation. Contribute to module-federation/module-federation-examples development by creating an account on GitHub.

Webpack 5 Module Federation: A game-changer in JavaScript architecture — figure 4GitHubmodule-federation

Webpack 5 Module Federation: A game-changer in JavaScript architecture — figure 5

](https://github.com/module-federation/module-federation-examples)

[

module-federation/next-webpack-5

Contribute to module-federation/next-webpack-5 development by creating an account on GitHub.

Webpack 5 Module Federation: A game-changer in JavaScript architecture — figure 6GitHubmodule-federation

Webpack 5 Module Federation: A game-changer in JavaScript architecture — figure 7

](https://github.com/module-federation/next-webpack-5)

[

ScriptedAlchemy/mfe-webpack-demo

Contribute to ScriptedAlchemy/mfe-webpack-demo development by creating an account on GitHub.

Webpack 5 Module Federation: A game-changer in JavaScript architecture — figure 8GitHubScriptedAlchemy

Webpack 5 Module Federation: A game-changer in JavaScript architecture — figure 9

](https://github.com/ScriptedAlchemy/mfe-webpack-demo)

[

ScriptedAlchemy/webpack-external-import

Dynamically import modules from other webpack bundles. Painless code sharing between separate apps – ScriptedAlchemy/webpack-external-import

Webpack 5 Module Federation: A game-changer in JavaScript architecture — figure 10GitHubScriptedAlchemy

Webpack 5 Module Federation: A game-changer in JavaScript architecture — figure 11

](https://github.com/ScriptedAlchemy/webpack-external-import)