You are on page 1of 7

Micro Front Ends — Doing It Angular Style

What is it and why do I need it?


Let’s start with the why part, when the days of single page applications started most
applications were considerably small and managed by a single FE team, all was well…
With time, applications have gotten larger and larger, and so have the teams managing
them. No need to say much about the problems of having large code bases and large
teams…
The term Micro Front Ends has been thrown around a lot lately, offering a concept similar
to Microservices where we can split a monolithic Front End application to micro
applications that can be loaded into a single container application running on the users
browser. Each application can have its own code base, be managed by a business oriented
team which can test and deploy their micro app independently.
(taken from https://micro-frontends.org/)
While the concept itself sounds promising, actual implementations of it are lacking.
Especially ones that can apply to a existing large application.

Alternatives
One possible solution is using the good old Iframe, which offers many advantages as far as
encapsulation and independence but is an old technology and suffers from a significant
scale issues. Other than Iframes, the term Web component has also been floating around
for a while.
Web components are a solution where you can create custom DOM elements that can run
independently and provide separation and css encapsulation, while this sounds like the
right direction, Web components are far from an actual solution. They are more of a concept
and the features behind it, such as shadow DOM still lack full browser support.

Our solution
At Outbrain we have been facing the issues that most veteran SPAs are facing, we have a
huge FE application, with a large team to manage it, and it’s getting rough. Seeing that
there are no outstanding solutions for MFE in the wild we decided to try to find a solution
of our own, one that is quick to implement on our current echo system. We defined several
key points that we saw as necessary for any solution to apply as a MFE .

Standalone mode
Each micro app should be capable of running completely in standalone mode, so each team
in charge of a given app should be able to run it independently from other applications.
This means each application should be hosted on a separate codebase and be able to run
locally on a developer’s computer, and in dev and tests environment.

Deployment
It should be possible to deploy each service independently to any environment including
production in order to allow freedom for the owner team to work without interfering with
other teams, If a bug fix needs to be deployed to production on the weekend no other team
should have to be involved.

Testing
Running tests independently on each micro app, that way a bug in one app is easily
identified and doesn’t reflect on other apps. That said It is necessary to have some
integration tests to check the interfaces between apps and make sure they are not broken,
these tests should be monitored by all teams.

One to many
We want to be able to use each micro app multiple times, a micro app should not care
where it runs, only be aware of its input and outputs.

Runtime separation and encapsulation


It is critical that each app is sandboxed in the runtime environment, so apps don’t interfere
with each other, this includes CSS encapsulation, JS namespacing and HTML separation.

Common resources sharing


Since we don’t want to have to load large modules like Angular, lodash and CSS styles
multiple times in the app it is important for micro apps to be able to share common resource
between them, that said, we also want to be able to allow them to encapsulate resources
that are only relevant to them, or encapsulate resources that have different versions across
apps, for instance app A uses lodash 3 and app B wants to migrate to lodash 4. We don’t
want to wait for all apps to migrate before it can upgrade.

Communication
There needs to be a decoupled way for the apps to communicate with each other without
actually knowing about each other, only via predefined interfaces and API’s.

Backwards compatibility
Since we are not going to rewrite our huge code base, we need something we can plugin
to our current system and gradually separate parts that can be managed by other teams.

Web Components
Finally, Any solution we go for should be aligned as much as possible with the web
components concept, even though it is currently just that, a concept, it seems that that is
the way the future is heading, and if any solution pop up in the future, aligning ourselves
with this will help us adopt future solution.
Enter Angular Lazy Loading Feature Modules
Angular has a built in concept of modules, which are basically declaration objects that
specify all the components, directives, services and other modules that are encapsulated in
a module.
@NgModule({
imports: [CommonModule],
declarations: [ WelcomeComponent],
bootstrap: [],
entryComponents: []
})export class AppB_Module {}
By specifying the module file as a Webpack entry point, this provided us with the ability to
bundle up the entire Angular module, including css, and html as a single standalone js file.
entry: {
'appB_module': './app/appB.prod.module.ts'
}
Using Angular lazy loading mechanism, we can dynamically load this js file and bootstrap
in into our current application.
const routes: Routes = [
{
path: appB,
loadChildren: '/appB/appB_Module#AppB_Module'
}
]
This is a big step towards our goal of separating our application to a mini application.

Moving from feature modules to mini apps


Angular feature modules along with Webpack bundling gives us the code separation we
need, but this is not enough, since Webpack only allows us to create bundles as part of a
single build process, what we want to be able to produce a separate JS bundle, that is built
at a different time, from a separate code base in a separate build system that can be loaded
into the application at runtime and share any common resources, such as Angular.
In order to resolve this we had to create our own Webpack loader which is called share-
loader.
Share-loader allows us to specify a list of modules that we would like to share between
applications, it will bundle a given module into one of the applications js bundle, and
provide a namespace in which other bundles access that modules.
Application A webpack.config:
rules: [
{
test: /\.js?$/,
use: [{
loader: 'share-loader',
options: {
modules: [/@angular/, /@lodash/],
namespace: 'container-app'
}
}]
}
Application B webpack.json
const {Externals} = require('share-loader');…externals: [
Externals({
namespace: 'container-app',
modules: [/@angular/, /@lodash/]
})
],
output: {
library: 'appB',
libraryTarget: 'umd'
},
In this example we are telling Webpack to bundle angular and lodash into application A
and expose it under the ‘container-app’ namespace.
In application B, we are defining that angular and lodash will not be bundled but rather be
pointed to by the namespace ‘container-app’.
This way, we can share some modules across applications, but maintain others that we wish
not to share.
So far we have tackled several of the key’s we specified in the previous post, We now have
two application that can be run independently or loaded remotely at runtime while
wrapped in a js namespace and have css and html encapsulation, They can also share
modules between then and encapsulate modules that shouldn’t be shared, now lets look
into some of the other key’s we mentioned.

DOM encapsulation
In order to tackle css encapsulation we wrapped each mini app with a generic angular
component, this component uses angular css encapsulation feature, we have two options,
we can use either emulated mode or native mode depending on the browser support we
require, either way we be sure that our css will not leak out.
@Component({
selector: 'ob-externals-wrapper',
template: require('./externals-wrapper.component.pug')(),
styleUrls: ['./externals-wrapper.component.less'],
encapsulation: ViewEncapsulation.Native
})
This wrapper component also serves as a communication layer between each mini app and
the other apps. all communication is done via an event bus instance that is hosted by each
wrapper instance, by using an event system we have a decoupled way to communicate data
in and out, which we can easily clear when a mini application is cleared from the main
application.
If we take a look at the situation we have so far, we can see that we have a solution that is
very much inline with the web component concept, each mini application is wrapped by a
standalone component, that encapsulates all js html and css, and all communication is done
by an event system.

Testing
Since each application can also run independently we can run test suites on each one
independently, this means each application owner knows when his changes have broken
the application and each team is concerned mostly with their own application.

Deployment and serving


In order to provide each application with its own deployment, we created a node services
for each application, each time a team created a new deployment of their application a js
bundle is created that encapsulates the application, each service exposes an endpoint that
returns the path to the bundle. At runtime, when a mini app is loaded into the container
app, a call to the endpoint is made and the js file is loaded to the app and bootstrapped to
the main application. This way each application can be built a deployed separately.
Example:
There is nothing better then an example, if you want to try out, you can go to the share-
loader repo and check out the example and example-cli sections in the Readme.
A demo is available in the repo itself, enjoy..

You might also like