Simplified Micro-Frontends in Vue
Jack Herrington
Posted on January 20, 2021
Vue 3 is a fantastic platform for creating Micro-Frontend components. And there are great frameworks out there to help with that, such as SingleSPA and OpenComponents.
Unless you plan on integrating your Vue components into applications that use a different framework you simplify your architecture dramatically by Webpack 5’s Module Federation to share and consume Micro-FE components.
Scenario
Let’s say that you and I work for a tap list service. That’s a service that bars and restaurants use to list their available beverages online. The bars enter the data into our service, and then use an iframe embedded on their site to show customers what’s on tap today. This is a real service by the way, with multiple vendors. Here is the tap list for my local Happy Valley Growlers.
An iframe, while easy to implement, doesn’t integrate well onto the page. We can do better using Micro-Frontends!
The Growlers App
Our starting repo has a single Vue 3 application (on Webpack 5 and using Typescript) called growlers. And when we start it up we see three Micro-FE components.
There are three Micro-Frontend components:
- Search — Has controls that allow the customer to refine the list of taps to their liking.
- Taps — Shows the current list of available beverages that match the Search filter.
- Cart — Shows a list of beverages the customer has added to their cart.
Each of these components is implement in its own .vue file. And they “talk” through a shared data store, implemented in store.ts, that uses the Vue 3 reactive feature to share data between the components. The advantage of this approach in a Micro-Frontend context is that the application that consumes these components do not need to implement the shared data store. You just drop the components on the page and they connect automatically through the store.
Turning these components into Micro-Frontend components is as easy as editing the ModuleFederationPlugin
in the webpack.config.js
. It starts off looking like this:
new ModuleFederationPlugin({
name: "starter",
filename: "remoteEntry.js",
remotes: {},
exposes: {},
shared: require("./package.json").dependencies,
}),
From there we can change the name from starter
to growlers
and then expose the components as well as the store:
new ModuleFederationPlugin({
name: "growlers",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./store": "./src/store",
"./Cart": "./src/components/Cart",
"./Search": "./src/components/Search",
"./Taps": "./src/components/Taps",
},
shared: require("./package.json").dependencies,
}),
This is telling Webpack 5 to expose these files for external consumption by other applications. We are exposing the store because it is used to load the customer data. Even if we didn’t expose the store code it would still be packaged up with the components because Module Federation is smart enough to package up all the code required to run any exposed module.
Setting Up The Consumer Application
Our next step in trying this out is to create a new Vue 3 project to consume these components. The simplest way to do that is to use degit
to clone the starter project:
npx degit https://github.com/jherr/wp5-starter-vue-3 hv-growlers
This will create a directory called hv-growlers
that has a Vue 3 application that uses Tailwind for CSS and supports Typescript.
The next thing to configure is the webpack.config.js
file to change the port number to 8081 (so that it doesn’t conflict with the growlers application). Then changing the ModuleFederationPlugin
configuration to:
new ModuleFederationPlugin({
name: "hvGrowlers",
filename: "remoteEntry.js",
remotes: {
growlers: "growlers@http://localhost:8080/remoteEntry.js",
},
exposes: {},
shared: require("./package.json").dependencies,
}),
This connects our new application with the growlers application and allows us to import the components using simple import
statements in the code.
With this configured we can add this code to bootloader.ts
:
import { load } from "growlers/store";
load("hv-taplist");
This will load the tap list for the client into the store.
Adding Micro-Frontends To The Page
Our next step is to change App.vue
to have a template for the new page with spots in it to host the Micro-Frontend components. There is a template in a gist associated with this demo. When we replace the template in App.vue
with this HTML we see this layout:
We can then replace the App.vue
code with this code:
import { Component } from "Vue";
import Taps from 'growlers/Taps';
import Search from 'growlers/Search';
import Cart from 'growlers/Cart';
export default {
components: {
Search,
Taps,
Cart,
},
}
Now the Micro-FE components from growlers are available to the template and we can replace the search
, taps
, and cart
divs with <Search />
, <Taps />
, and <Cart />
tags. The result looks like this:
How easy is that? And this is a live runtime connection. The moment that a new version of growlers
is pushed with a new implementation the consuming applications will update immediately.
Where To Go From Here
In the complete version of this example, which is shown in the YouTube video connected below, we continue on to:
- Show the Vue consumer application integrating with the store to show an additional level of integration.
- The growlers application upgraded to expose Vanilla Javascript wrappers around the Micro-Frontend components.
- Another consumer application, this time using Vanilla Javascript and consuming the components using the Vanilla Javascript wrappers
There is a YouTube version of this article if you want a video walkthrough of the process:
Conclusions
Vue is a great way to make reusable components. When you combine that that Module Federation you now have reusable components you can easily runtime share between applications Micro-Frontend style. Even better, because you have access to them as full fledged Vue components you have a much deeper level of integration that we’ve seen in other Micro-Frontend frameworks.
Posted on January 20, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.