Micro-FEs Simplified
Tags: microfrontends, modulefederation, react, vanillajs
Published: 2020-12-24 01:42:19

Micro-Frontends, where you take large UI components and live share them between applications, have huge potential, but the mechanics of implementing them can get in the way of realizing that value. In this article, and in its accompanying video, I look at how to use Webpack 5’s built in Module Federation feature to make sharing Micro-Frontend code easy.

In fact, Module Federation makes sharing components so easy that we get to think about two follow-on issues that come with Micro-FEs:

1. How to have multiple Micro-FEs from the same source share state without having the page they are hosted on implement that state sharing.
2. How to all the host page subscribe or mutate the data store backing the Micro-FEs instantiated on the page.

If you want a complete walkthrough of an example three application Micro-FE demo setup where React components are shared with another React app and also a vanilla JS application check out the associated video.

{% youtube tFDvEITdJZ8 %}

This post is going to concentrate on explaining the three core concepts presented in the video in more detail.

# A Little Setup

The completed Micro-FEs Simplified project contains three distinct applications relating to selling growlers.

> Growlers are a PNW tradition. They are large refillable bottles we fill with beer, soda, coffee, kombucha, etc.

There is the `growlers` application that has the three shared Micro-FE components. There is a `Taps` component that shows all of the different beverages available for pouring into a growler. There is a `Search` component that allows you to run a search on the available beverages and the results are immediately shown in the `Taps` component. And then there is a `Cart` component that shows this list of beverages selected as the user presses the `Add to Cart` button. Shown below is the `growlers` application:

![Growlers application with three Micro-FEs](https://dev-to-uploads.s3.amazonaws.com/i/mwzgvw66eoio3hmt9nua.png)

On the left is the `Search` component, in the middle the `Taps` component, and on the right the `Cart` component.

These components are then consumed in two different applications; `host-react` which uses React, and `host-vanilla` which uses just Vanilla JS on the page. Shown below is the `host-react`:

![Micro-FEs in use in the host-react application](https://dev-to-uploads.s3.amazonaws.com/i/f3swpmh4lwti02o3x66c.png)

The `host-react` application shows the three Micro-FEs in a different layout and using a different [Chakra-UI](https://chakra-ui.com/) dark theme. In addition there is extra UI on the left hand side that is written in the `host-react` that connects to the Micro-FE store and shows a more compact representation of the beverages that match the current search parameters. This deeper integration between the host page and the Micro-FEs is made possible by Module Federation.

Now that we have a better understanding of the demo application, let’s dive into the mechanics.

# Using Module Federation for Micro-FEs

In Module Federation terms the growlers application is exposing modules. And you can find the [mechanism for that in the `webpack.config.js` file in the project](https://github.com/jherr/micro-fes-simplified-done/blob/master/growlers/webpack.config.js#L50-L75). With Webpack 5 it’s as simple as importing the ModuleFederationPlugin and configuring it.

```js
new ModuleFederationPlugin({
  name: "growlers",
  filename: "remoteEntry.js",
  remotes: {},
  exposes: {
    "./DataComponent": "./src/components/DataComponent",
    "./Cart": "./src/components/Cart",
    "./Search": "./src/components/Search",
    "./Taps": "./src/components/Taps",
    "./store": "./src/store",
    "./VanillaCart": "./src/vanilla/VanillaCart",
    "./VanillaSearch": "./src/vanilla/VanillaSearch",
    "./VanillaTaps": "./src/vanilla/VanillaTaps",
  },
  ...
```

The most important fields here are the name of the federated modules container, which we specify as `growlers` . Followed by the list of exposed modules. At the start we just expose the `Cart`, `Search` and `Taps` components, as well as the store which we use to specify which client data we wish to show.

The demo app then goes on to expose a `DataComponent` which React based hosts can use to show the current state of the store. As well as vanilla versions of the Micro-FE components that manage mounting each component on a specified selector (which makes it easy for vanilla JS applications to consume React components that look just like a function.

In a host application we then consume the growlers remote by using the ModuleFederationPlugin once again:

```js
new ModuleFederationPlugin({
  name: "hostreact",
  filename: "remoteEntry.js",
  remotes: {
    growlers: "growlers@http://localhost:8080/remoteEntry.js",
  },
  exposes: {},
  ...
```

In this case the `host-react` application is specifying that there is a remote out there, at the specified URL called `growlers`.

From there, consuming and using the components is as simple as using imports:

```js
import Search from "growlers/Search";
import Cart from "growlers/Cart";
import Taps from "growlers/Taps";
import DataComponent from "growlers/DataComponent";
import { load } from "growlers/store";
load("hv-taplist");
```

In this code inside `host-react` we are importing the React Micro-FE components, just as we would any other React component. As well as initializing the store with our customer ID so that the Micro-FEs know what beverages data to work with.

All of this works because Module Federation is giving you the real Javascript React code to run. It’s not wrapped in a Micro-FE container. Module Federation works with any type of code that can be wepbacked; React, Vue, Angular, vanilla JS, JSON, transpiled Typescript, etc. Whatever you want.

The three key differentiators here are:

1. Your Micro-FE code doesn’t need to be extracted and deployed separately from the application hosting it.
2. Your Micro-FE code doesn’t need to be wrapped or bundled in any way.
3. You can expose any type of code you want, not just visual components.

All of this comes with one big caveat though; Module Federation does not provide a view platform agnostic compatibility layer. It won’t help you embed a React component in a Vue application or vice versa. If you are looking for that you will want to look at something like [SingleSPA](https://single-spa.js.org/) (which also [recommends using Module Federation](https://single-spa.js.org/docs/recommended-setup/#module-federation) as a code transport layer.) But if all your applications are React, or you are ok with something like the thin vanilla JS shims as shown in this example, then you are good to go.

# Sharing State Between Micro-FEs

Since sharing code between applications is trivially easy using Module Federation the next thing our example setup looks at is how to share state between the different Micro-FEs even as they are located on different parts of the host page.

To make it even more interesting I’ll insist on the constraint that the host page should not have to implement any type of global state provider to make this work. A host application should be able to import the component and drop it on the page as-is and it should work (once the client store is specified).
To make this happen I’ll use a revolutionary new micro state manager named [Valtio](https://github.com/pmndrs/valtio) for two reasons. First, it’s incredibly easy to use. And second, because it doesn’t require a provider.

To set up the store in the `growlers` application we [simply import `proxy` from Valtio and then create a store with the initial state](https://github.com/jherr/micro-fes-simplified-done/blob/master/growlers/src/store.ts#L1-L18).

```ts
import { proxy, ... } from "valtio";
import { Beverage } from "./types";
export interface TapStore {
  taps: Beverage[];
  searchText: string;
  alcoholLimit: number;
  filteredTaps: Beverage[];
  cart: Beverage[];
}
const store = proxy<TapStore>({
  taps: [],
  searchText: "",
  alcoholLimit: 5,
  filteredTaps: [],
  cart: [],
});
```

The state contains the an array of all the available beverages, the search parameters, the beverages (or taps) that match those filters, as well as the cart.

To consume the store [we use the `useProxy` hook in any component](https://github.com/jherr/micro-fes-simplified-done/blob/master/growlers/src/components/Cart.tsx).

```jsx
import React from "react";
import { useProxy } from "valtio";
import store from "../store";
const Cart = () => {
  const snapshot = useProxy(store);
  return (
    <Box border={MFE_BORDER}>
      {snapshot.cart.map((beverage) => (
        ...
      ))}
      ...
   </Box>
  );
};
export default Cart;
```

You don’t need to specify any kind of provider at the top of the view hierachy. You simply create a `proxy` in a shared file, and then consume it using `useProxy`.

Setting values is just as easy, we can go back to the store and [look at the implementation of `setSearchText`](https://github.com/jherr/micro-fes-simplified-done/blob/master/growlers/src/store.ts#L39-L42) which is simply:

```js
export const setSearchText = (text: string) => {
  store.searchText = text;
  store.filteredTaps = filter();
};
```

To set a value on a store you simply, set it. It doesn’t get a whole lot cleaner than that.

# Connecting the Host Page with the Micro-FEs State

Because Valtio is so easy so use we can do even cooler things that push the boundaries of the Micro-FEs and their connection to the host page. For example we can [create a novel `DataProvider` component](https://github.com/jherr/micro-fes-simplified-done/blob/master/growlers/src/components/DataComponent.tsx).

```jsx
import React, { ReactElement } from "react";
import { useProxy } from "valtio";
import store, { TapStore } from "../store";
const DataComponent: React.FC<{
  children: (state: TapStore) => ReactElement<any, any>;
}> = ({ children }) => {
  const state = useProxy(store);
  return children(state);
};
export default DataComponent;
```

Where a host page that uses React can provide a child function that renders the store state any way the host page wants. For example, the demo `host-react` uses it to show much smaller beverage cards:

```jsx
<DataComponent>
  {({ filteredTaps }) =>
    filteredTaps.slice(0, 5).map((beverage) => (
      <SimpleGrid ...>
         ...
      </SimpleGrid>
     ))
  }
</DataComponent>
```

From a Micro-FE customer perspective this is great. Not only do I have ready to use Micro-FE components that I can put anywhere on the page without using a provider. And, if I don’t like the UI provided by one or more of the Micro-FEs, I have all the extension points I need to create my own components that work with the same store that’s used by the Micro-FEs.

# Providing Vanilla JS Compatibility

Another issue we tackled in the video is the ability to show these components on a VanillaJS page, which is as simple as [providing function wrappers around React-DOM](https://github.com/jherr/micro-fes-simplified-done/blob/master/growlers/src/vanilla/VanillaCart.tsx):

```jsx
import React from "react";
import ReactDOM from "react-dom";
import { ChakraProvider } from "@chakra-ui/react";
import Cart from "../components/Cart";
const App = () => (
  <ChakraProvider>
    <Cart />
  </ChakraProvider>
);
export default (selector: string): void => {
  ReactDOM.render(<App />, document.querySelector(selector));
};
```

Don’t be fooled by the `ChakraProvider` it’s just there to provide the CSS for the components.

Then on the VanillaJS side we can simply import those functions and then invoke them on a selector:

```js
import "./index.css";
import createTaps from "growlers/VanillaTaps";
import createCart from "growlers/VanillaCart";
import createSearch from "growlers/VanillaSearch";
import { load, subscribe } from "growlers/store";
// load("growlers-tap-station");
load("hv-taplist");

...

createTaps(".taps");
createCart(".cart");
createSearch(".search");
```

How are these Micro-FEs implemented? Whose to say? From the Vanilla JS applications perspective these are functions they invoke and UI appears on those selectors.

In this case Module Federation is not only handling getting the Micro-FE code to the page, but also `react` and `react-dom` so that the code can run. Even better, if you are lazy loading your Micro-FEs that will work just fine as well. Module Federation will bring the `remoteEntry.js` file on to the page, but that file is only references to the chunks required if and when you decide to import and invoke them. So the system is inherently lazy-loadable.

# Where To Go From Here

There is so much more to this example than I covered here, and to Module Federation more broadly. You can check out [my playlist on Module Federation](https://www.youtube.com/playlist?list=PLNqp92_EXZBLr7p7hn6IYa1YPNs4yJ1t1) on [my YouTube channel](https://www.youtube.com/user/jherr2006). Or you can check out [Practical Module Federation](https://module-federation.myshopify.com/products/practical-module-federation), it’s a book that Zack Jackson and I wrote that covers the both the practical use, and the internal mechanics, of this fascinating new technology for sharing code.