Hello everyone! This is my first time doing a tutorial so I hope you like it! In this tutorial, we'll learn how to create a very simple web application with Firebase, based on the Micro Frontend Architecture.
Defining Our Micro Frontend
There are two ways to create your own micro frontend. The first one is a horizontal split which basically means that you will have multiple applications running on 1 page/view. The second one is the vertical split which is one application running on 1 page/view.
For the sake of this tutorial, we will be using the vertical split.
Let's start by cloning the starters repo:
run
npm install
in the root folder.
Overview
In the starters repo, we have the main shell which is made with the Vue framework. Our main shell will show our sub/micro applications. We have two sub-applications. Theyβre called reactapp
and vueapp
and are written in React and in Vue respectively.
Steps
We are going to divide this tutorial in multiple steps. First, we are going to implement the react micro app. Afterwards, we'll implement the vue micro app. Lastly, we are going to configure the main shell to show our micro apps.
STEP 1 - React Micro App
We need to setup 4 things before implementing the Firebase connectivity:
- Add
.rescriptsrc.js
file - Add
.env
file - Add
public-path.js
file - Edit the
index.js
file
.rescriptsrc.js
The .rescriptsrc.js
is a file where we can configure settings for launching the micro application. Create the file in the root folder of the react micro app and add the following code:
const { name } = require('./package');
module.exports = {
webpack: config => {
config.output.library = `${name}-[name]`;
config.output.libraryTarget = 'umd';
config.output.jsonpFunction = `webpackJsonp_${name}`;
config.output.globalObject = 'window';
return config;
},
devServer: _ => {
const config = _;
config.headers = {
'Access-Control-Allow-Origin': '*',
};
config.historyApiFallback = true;
config.hot = false;
config.watchContentBase = false;
config.liveReload = false;
return config;
},
};
.env
We also need to create a .env
file to store our keys and port.
Create the file and add the following code:
SKIP_PREFLIGHT_CHECK=true
BROWSER=none
PORT=7100
WDS_SOCKET_PORT=7100
REACT_APP_FIREBASE_API_KEY=<key>
REACT_APP_FIREBASE_AUTH_DOMAIN=<key>
REACT_APP_FIREBASE_PROJECT_ID=<key>
REACT_APP_FIREBASE_STORAGE_BUCKET=<key>
REACT_APP_FIREBASE_MESSAGING_SENDER_ID=<key>
REACT_APP_FIREBASE_APP_ID=<key>
public-path.js
In the source (src
) folder of the reactapp
folder. We need to define the public path for the application. Create the file with the same name as the title and add the following code:
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
Qiankun will define the public path through the main shell.
Qiankun is a micro frontend framework that helps us by setting everything up in our main shell.
Note The Qiankun dependecy is only needed in the main shell and not in the micro apps. The window object will handle all the rest.
index.js
Now we need to edit the index.js
file so that the main shell will find this application.
Copy the code below and paste it in the index.js file:
import './public-path';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
function render (props) {
const { container } = props;
ReactDOM.render(
<App />,
container
? container.querySelector('#root')
: document.querySelector('#root')
);
}
// This is to render the micro application when not going through the main shell
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
export async function bootstrap () {
console.log('react app bootstraped');
}
export async function mount (props) {
console.log('react props from main framework', props);
render(props);
}
export async function unmount (props) {
const { container } = props;
ReactDOM.unmountComponentAtNode(
container
? container.querySelector('#root')
: document.querySelector('#root')
);
}
serviceWorker.unregister();
bootstrap
, mount
and unmount
are three lifecycle
hooks that must be implemented in any Qiankun-powered micro application.
A lifecycle function is a function or array of functions that Qiankun will call on a registered application. Qiankun calls these by finding specific named exports from the registered application's main file.
Without those 3 hooks, the micro app will not load through the main shell.
Firebase
in the src
folder of reactapp
, we need to create a new folder called firebase. In that folder we'll create a new javascript file with the name firebaseConfig.js
.
Add the following code to setup your firebase and import extra packages if needed (like storage, firestore,...) In this tutorial, we only need the auth package.
import firebase from 'firebase/app';
import 'firebase/auth';
const config = {
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_FIREBASE_APP_ID,
};
var fb = null;
// Check how many times the firebase app is initialized.
if (firebase.apps.length === 0) {
fb = firebase.initializeApp(config);
console.log('Firebase [react] Initialized');
} else {
console.log('Firebase [react] is already Initialized');
}
export default fb;
App.js
now we can start editing our App.js
to show two input elements for registering a new user.
First, let's handle the imports. These 3 are the only ones needed so you can remove the other ones.
import React, {useState} from 'react';
import './App.css';
import fb from './firebase/firebaseConfig';
Afterwards, let's setup a state for an email and a password and a function to register:
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const onSubmit = () => {
fb.auth()
.createUserWithEmailAndPassword(email, password)
.then(() => {
console.log("route to vue app.");
window.history.pushState(null, "/vueapp", "/vueapp");
})
.catch((error) => {
console.log(error);
});
};
Now we'll edit the html code to show 2 inputs and a button:
<div className="app-main">
<h1>React App</h1>
<label for="email">Email</label>
<input
name="email"
type="text"
value={email}
onChange={(event) => setEmail(event.target.value)}
></input>
<label for="password">Password</label>
<input
name="password"
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
></input>
<button onClick={onSubmit}>Register</button>
</div>
That should be it for the reactapp
micro application.
STEP 2 - Vue Micro App
After signing up we want to redirect the user to our vue micro app. In that application we'll show the user's email.
Navigate to the vueapp
folder.
Now we only need to do 5 things:
- Create a
vue.config.js
file - Add a
public-path.js
file - Edit the
main.js
file - Configure Firebase
- Edit the
Home.vue
vue.config.js
Let's create vue.config.js
in our root folder of the vue micro app. The public path should be the name of your micro app. In our case, we'll put /vueapp
.
const { name } = require('../package.json');
module.exports = {
publicPath: '/vueapp',
chainWebpack: config => config.resolve.symlinks(false),
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`
}
},
devServer: {
port: process.env.VUE_APP_PORT,
headers: {
'Access-Control-Allow-Origin': '*'
}
}
};
public-path.js
In the src
folder of vueapp
, we'll specify the public path using the following code:
(function() {
if (window.__POWERED_BY_QIANKUN__) {
if (process.env.NODE_ENV === 'development') {
__webpack_public_path__ = `//localhost:${process.env.VUE_APP_PORT}${process.env.BASE_URL}`;
return;
}
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
})();
main.js
We need to setup the 3 things that a micro app always needs. So to do that, we need to remove:
const router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? routerBase : process.env.BASE_URL,
mode: 'history',
routes
});
new Vue({
render: h => h(App),
}).$mount('#app')
and add:
let instance = null;
function render(props = {}) {
const { container, routerBase } = props;
const router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? routerBase : process.env.BASE_URL,
mode: 'history',
routes
});
instance = new Vue({
router,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
console.log('[vue] props from main framework', props);
render(props);
}
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
}
Firebase
This is pretty much the same as in the reactapp
micro application.
We'll create a new folder called Firebase in the vueapp src
folder. After that we'll create a new file called firebaseConfig.js
with following code:
import firebase from 'firebase/app';
import 'firebase/auth';
const config = {
apiKey: '<key>',
authDomain: '<key>',
projectId: ' <key>',
storageBucket: '<key>',
messagingSenderId: '<key>',
appId: '<key>',
measurementId: '<key>'
};
var fb = null;
if (firebase.apps.length === 0) {
fb = firebase.initializeApp(config);
console.log('Firebase [Vue] Initialized');
} else {
console.log('Firebase [Vue] is already Initialized');
}
export default fb;
Home.vue
Now we need to configure our Home.vue
First, we'll import the firebase config
import fb from '../firebase/firebaseConfig';
After that we want to add two fields [user, isLoaded]
.
data() {
return {
user: {},
isLoaded: false
};
},
In the created method of Home.vue
we are going to add the firebase onAuthStateChanged
to wait for the user.
created() {
fb.auth().onAuthStateChanged(user => {
this.user = user;
this.isLoaded = true;
});
}
The only thing left to do is edit the template tag to show the email.
<template>
<div>
<h1>Vue App</h1>
<h1 v-if="isLoaded">Welcome! {{ user.email }}</h1>
<h6 v-if="!isLoaded">Loading...</h6>
</div>
</template>
Quick Note
You have to specify a port in a .env
file, so create one in the root of the vue
micro application add the following line:
VUE_APP_PORT=7777
That's it for the vue micro app!
STEP 3 - Main Shell
When working with a Micro Frontend Architecture, we need to have one main-shell
that will be used to show our other micro applications. Our main shell will be written in Vue.
Creation
Navigate to the main-shell
folder.
We will be using the package Qiankun
(This is a micro frontend framework)
Now we only need to do 4 things:
- Create a
vue.config.js
file - Add a
micro-apps.js
file - Edit the
main.js
file - Edit the
App.vue
vue.config.js
In the root folder of the main-shell
, we'll create a new file called vue.config.js
. In that file we'll add the following code:
module.exports = {
configureWebpack: {
module: {
rules: [
{
test: /\.(ttf|otf|eot|woff|woff2)$/,
use: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[ext]'
}
}
}
]
}
}
};
This will enable custom fonts in your application.
micro-apps.js
In the src
folder of the main-shell
, we'll create a new file called micro-apps.js
. This file will be used to define our micro applications.
// This is where we define our micro applications
const microApps = [
{
name: 'reactapp',
entry: '//localhost:7100',
activeRule: '/reactapp'
},
{
name: 'vue',
entry: '//localhost:7777/vueapp',
activeRule: '/vueapp'
}
];
const apps = microApps.map((item) => {
return {
...item,
// this will be the element where the micro application will be in
container: "#subapp-viewport",
props: {
routerBase: item.activeRule,
},
};
});
export default apps;
main.js
This is where the fun begins! ππ
First, we need to import methods from Qiankun and the micro-app.js
into the main.js
file.
import { registerMicroApps, start, setDefaultMountApp } from "qiankun";
import microApps from "./micro-apps";
Secondly, we need to register our micro apps and set a default route. After that we just need to run the start method. Add the following code at the end of the file.
const apps = microApps.map((item) => {
return {
...item,
};
});
registerMicroApps(apps, {
beforeLoad: (app) => {
console.log("before load app.name====>>>>>", app.name);
},
beforeMount: [
(app) => {
console.log("[LifeCycle] before mount %c%s", "color: green;", app.name);
},
],
afterMount: [
(app) => {
console.log("[LifeCycle] after mount %c%s", "color: green;", app.name);
},
],
afterUnmount: [
(app) => {
console.log("[LifeCycle] after unmount %c%s", "color: green;", app.name);
},
],
});
setDefaultMountApp("/reactapp");
start();
App.vue
The last one!
First, let's change the HTML code.
Change the HTML to this:
<template>
<div id="subapp-viewport"></div>
</template>
This will put our current micro application inside the div element.
Next up, the script tag in App.vue
will have 2 methods. We also need to import micro-app.js
again to bind it to the current route and return some fields to our data function.
import microApps from "./micro-apps";
data() {
return {
microApps,
current: "/reactapp",
};
},
Two methods are needed. The first one binds the current path to the current Path field in the App.vue
. The second method will listen to any router changes.
methods: {
// Binds the current path to the current Path in the main shell
bindCurrent() {
const path = window.location.pathname;
if (this.microApps.findIndex((item) => item.activeRule === path) >= 0) {
this.current = path;
}
},
// Will listen to any router changes
listenRouterChange() {
const _wr = function (type) {
const orig = history[type];
return function () {
const rv = orig.apply(this, arguments);
const e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _wr("pushState");
// will listen when navigating to another micro application
window.addEventListener("pushState", this.bindCurrent);
window.addEventListener("popstate", this.bindCurrent);
this.$once("hook:beforeDestroy", () => {
window.removeEventListener("pushState", this.bindCurrent);
window.removeEventListener("popstate", this.bindCurrent);
});
},
},
Lastly, we need to call our bindCurrent
method in created
and the
listenRouterChange method in mounted
:
created() {
this.bindCurrent();
},
mounted() {
this.listenRouterChange();
},
That's it!
Now to see it in action. Run the main-shell
and the 2 micro applications with the command npm run start
.
Navigate to http://localhost:8080/
where you should be seeing this:
You can find the complete code for this tutorial on the completed/tutorial branch of the starters Repo
I hope you find this tutorial somewhat useful. I apologize for my writing skills as I am still learning on how to explain things in a better way π π .
I recommend reading this medium article If you are interested in learning more about micro frontends!
Thank you! π
Top comments (2)
Hey dude, nice article. Just add dont forget run a command to install firebase as dependency inside of vueapp directory. This could generate unexpected build errors β
I will and Thank you!!