Uma Victor Uma is a software developer based in Nigeria who is familiar with a variety of different web technologies, frameworks, and build tools. He is also keen on finding ways to explain things as simply as possible.

Building micro-frontends with webpack’s Module Federation

7 min read 2139

Deploying Micro-Frontends Using webpack's Module Federation

Good frameworks exist out there that help us create micro-frontends such as single-spa and OpenComponents. But what if we don’t want to integrate a different framework in our architecture? Let’s take a look at how to implement micro-frontends in our Vue apps.

In this article, we will discuss how to dramatically simplify our app architecture by using webpack 5’s Module Federation to consume and share micro-frontend components with example code.

What is module federation?

Module federation is a JavaScript architecture invented by Zack Jackson. This architecture allows the sharing of code and dependencies between two different application codebases.

The code is loaded dynamically, and if a dependency is missing, the dependency will be downloaded by the host application, which allows for less code duplication in the application.

What are micro-frontends?

The concept of micro-frontends has been gaining traction in recent times. The push for microservices has also brought about the same implementation to the modern web in the form of micro-frontends. As the monolith app scales, it becomes difficult to maintain, especially across several teams working on the same app.

We can look at micro-frontends as feature-based, where there are different teams and each team handles a particular feature component while another team handles something else. In the end, all teams merge the different components they have built to form one application.

Developers made use of frameworks like single-spa and OpenComponents to achieve this, but with the new release of webpack 5 and the module federation, we can easily achieve the same goal, but way easier.

The advantages of micro-frontends

Adopting a micro-frontend approach to building your web applications is probably the best strategy. This is especially true if you are building a large-scale web application with many moving parts or applications that are branched out into sub-applications where you want some consistency in the overall look.

Let me highlight a few reasons you might want to switch to the micro-frontend approach:

  • Adopting a micro-frontend approach will allow us to create an end-to-end feature architecture. This approach will allow us to develop and deploy features locally, without the need for large-scale deployment infrastructures
  • With a smaller and more optimized bundle size, micro-frontends provide an overall better developer and user experience as a result of shared components and dependencies that can be lazy loaded whenever we want
  • One of the biggest advantages for me is the ability for teams of developers working on that particular product to be able to pick a technology stack of their choice without fear of incompatibility with the other team’s code

How do we split our apps?

These are some ways developers split large apps:

We made a custom demo for .
No really. Click here to check it out.

  1. By page ⁠— In our frontend applications, sometimes having different pages run at the same time in the browser can cause a crash in older devices, so the safest way is to split by page. If you have good routing, you can run separate, specific micro-apps for every page, which is also good for the developers on your team, because they will always work on that one page assigned to them
  2. By functionality ⁠— If you have one page with multiple things features performing different operations, you can split those big features into smaller apps and make it an independent application running that specific feature
  3. By section ⁠— You can also split your applications by section, with different apps sharing the same section or components

Proof of concept

We have explained some concepts about micro-frontends and module federation. Now it’s time for a proof of concept.

Here, we will demonstrate how we can use the module federation to create micro-frontends in Vue. To test this out, we will be spinning up two different apps, so we can create a micro-frontend in one of them and share it with the other.

First, we create a folder to host the two Vue applications:

mkdir vue-mf 

It is in the vue-mf folder we will run our Vue application. We won’t be using the Vue CLI here. Instead, we will be using the new release of webpack, which is webpack 5, to set up the Vue application.

We will name the two applications we want to share components as Company and Shop respectively. We create a folder for each of them in the vue-mf folder and then grab a webpack starter file of Vue created by Jherr from GitHub into each folder:

git clone https://github.com/jherr/wp5-starter-vue-3.git

Let’s take a look at the file structure now that we have set up the app:

+-- vue-mf/
|   +-- Company/
|   +-- Shops/

When we open up one of the app folders, this is the structure:

+-- vue-mf/
|   +-- Company/
|       +-- src/
|           +-- App.vue
|           +-- bootloader.js
|           +-- index.css
|           +-- index.html
|           +-- index.js
|       +-- package.json
|       +-- webpack.config.js
|   +-- Shops/

Set up webpack config

So we have two apps, Company and Shop, which are exactly the same for now. When we survey the file structure, we take a look at the package.json. We have our webpack loader, CSS loader, and all the basic loaders and webpack stuff we need:

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const { VueLoaderPlugin } = require("vue-loader");
module.exports = {
  output: {
    publicPath: "http://localhost:8080/",
  },
  resolve: {
    extensions: [".vue", ".jsx", ".js", ".json"],
  },
  devServer: {
    port: 8080,
  },
  module: {
    rules: [
      {
        test: /.vue$/,
        loader: "vue-loader",
      },
      {
        test: /.css$/i,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new ModuleFederationPlugin({
      name: "starter",
      filename: "remoteEntry.js",
      remotes: {},
      exposes: {},
      shared: require("./package.json").dependencies,
    }),
    new HtmlWebPackPlugin({
      template: "./src/index.html",
    }),
  ],
};

If we take a look at the webpack.config.js file, we can see that our public path is set to port 8080. We can also see webpack checking for our file extensions and using the appropriate loaders.

The important thing to take note of here is our Vue loader plugin used in parsing our files and the Module federation plugin from webpack 5 we have imported and used, which will allow us to perform sharing functionality. We will get back to the configuration of the ModuleFederationPlugin later in this tutorial.

N.B., make sure to set the public path and dev server port in the other application (Shop) to port 8081, so we can be able to run both apps simultaneously.

Create components to be shared

In our application, the App.vue file will serve as the homepage, so let’s add some markup:

<template>
 <div>
     <h1>Our Application Homepage</h1>
 </div>
</template>

The header component is one part of an application we would like to share between applications. Let’s say one of the teams of developers decides to build the header, so we create a header component that we can share in the two applications.

In the src folder in our Company app, we will create a header component. To do this, we create a Header.vue file, and in it, we create the header component:

<template>
  <div>
      <header>
          App Header
      </header>
  </div>
</template>

After creating the header, navigate to App.vue and import the header component:

<template>
 <div>
     <Header />
     <h1>Our Application Homepage</h1>
 </div>
</template>
<script>
import Header from './Header.vue';
export default {
 components: {
     Header,
 },   
}
</script>

We can now start our development server by navigating to each folder and running:

yarn && yarn start

Right now our app looks like this in the Company app.

Sample App for a Vue Micro-Frontend

Exposing the header component through the Module Federation plugin

We now have our header in the Company app, we would like to use it in the Shop app. So we head over to the webpack configuration in the Company app:

 plugins: [
    new VueLoaderPlugin(),
    new ModuleFederationPlugin({
      name: "Company",
      filename: "remoteEntry.js",
      remotes: {},
      exposes: {
        "./Header": "./src/Header",
      },
      shared: require("./package.json").dependencies,
    }),
    new HtmlWebPackPlugin({
      template: "./src/index.html",
    }),
  ],

In the webpack Module Federation configuration, we set the name to the app name, which is Company, and remoteEntry.js to be our filename. When we navigate to the remoteEntry.js file name, we see the code related to the components and dependencies we want to share. We also exposed the header component with its location.

Now, if we restart our server and navigate to http://localhost:8080/remoteEntry.js, we will see this:

remoteentry.js Example for Vue Micro-Frontend

Now grab the remote entry URL and switch to the webpack configuration file in our Shop app:

plugins: [
    new VueLoaderPlugin(),
    new ModuleFederationPlugin({
      name: "Shop",
      filename: "remoteEntry.js",
      remotes: {
        Company: "[email protected]://localhost:8080/remoteEntry.js"
      },
      exposes: {},
      shared: require("./package.json").dependencies,
    }),
    new HtmlWebPackPlugin({
      template: "./src/index.html",
    }),
  ],

Here, we give the plugin a name of Shop and set the remote to remoteEntry URL. Then in our App.vue file in the Shop app, we import and use the header component from our Company app:

<template>
<div>
    <Header />
    <h2>Our Shop Page</h2>
</div>
</template>

<script>
import Header from 'Company/Header';
export default {
 components: {
     Header,
 },   
}
</script>

If we restart our server, we can see that the shop page now has the header component, meaning we have successfully shared the component between the two apps. Yay!

N.B., if the team working on the header decides to push a new update for the header component, the Shop app team will immediately see the update once the Shop app is refreshed.

Sharing app state between federation modules

Let’s say you are using a state manager in your Vue application like Vuex. You might be asking yourself how you might have state, share it between the two components, and also have it update. So, let’s install Vuex for both apps:

yarn install [email protected]

Once we have installed Vuex, navigate to the bootloader.js file in our Company app, where we initialize our Vue app.
Here, we import our store and create a state:

import { createApp } from "vue";
import { createStore } from 'vuex'
import "./index.css";
import App from "./App.vue";
const app = createApp(App)
const store = createStore({
    state () {
      return {
        cartItems: 0
      }
    }
  })
app.use(store)
app.mount("#app");

If, for instance, this is an ecommerce store where we want to display the number of cart items we have in our cart, we create a cartItems state and display it in our company header. Then go to our header component, access the state, and display it:

<template>
  <div>
      <header>
          <h2> App Header</h2>
          <p>items: {{cartCount}}</p>
      </header>
  </div>
</template>
<script>
export default {
    computed: {
        cartCount() {
            return this.$store.state.cartItems
        }
    },
}
</script>f

We have successfully set up our state, but the problem with this is if we start the server for both apps and check the Company app, we can see the header display with the state:

Header with State for Vue Micro-Frontend

But if we navigate to the Shop app, we can no longer see the shared header component anymore, much less the state we added. Instead, we get an error message that says we can’t read the state of undefined, because in our Shop app, we haven’t set up any store.

To rectify this problem, we copy all the code we have in the bootloader of the Company app and paste it into the bootloader.js file of the Shop app. This time, we changed the cartCount state to 12. If we restart the server, we now have our header in the Shop app, with cart items of 12.

Let’s say we want to mimic the addition of more shop items to the cart, so in the Shop app, we add a button that increments the cartCount state:

<template>
<div>
    <Header />
    <h2>Our Shop Page</h2>
    <div>
        <button @click="addItem">Add item</button>
    </div>
</div>
</template>

<script>
import Header from 'Company/Header';
export default {
 components: {
     Header,
 }, 
 methods: {
         addItem() {
             this.$store.state.cartItems += 1
         }
     },  
}
</script>

If we restart the Shop application, we can see that the items in the header now update. Yay!

Resources

Conclusion

We have come to the end of this tutorial.

Here, we discussed how to dramatically simplify our app architecture by using webpack 5’s Module Federation to consume and share micro-frontend components with example code.

Whether you should or should not adopt micro-frontends depends on the kind of project you are building, because this approach will not be the best for small applications or businesses. A micro-frontend architectural approach is your best bet when working on a large project with distributed teams.

Uma Victor Uma is a software developer based in Nigeria who is familiar with a variety of different web technologies, frameworks, and build tools. He is also keen on finding ways to explain things as simply as possible.

Testing accessibility with Storybook

One big challenge when building a component library is prioritizing accessibility. Accessibility is usually seen as one of those “nice-to-have” features, and unfortunately, we’re...
Laura Carballo
4 min read

2 Replies to “Building micro-frontends with webpack’s Module Federation”

Leave a Reply