How to implement Micro Frontends with Reactjs and qiankun
Tags: microfrontends, react, qiankun
Published: 2021-01-03 11:08:11

---
title: How to implement Micro Frontends with Reactjs and qiankun
published: true
description: Implementing Micro Frontends with Reactjs and Qiankun in 2 minutes
tags: MicroFrontends, Reactjs, qiankun
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/avgehx5v0vr94mpjb18l.png
---

`<Hello world/>`

Maybe you don’t know what is Micro Frontend and why we have to use it, because it is a new technology on frontend. there are few plugins and articles about it, and I think it is better to focus on some plugins that have no documents.
There is a good source about use cases and motivations of Micro frontend.

[Micro frontends](https://micro-frontends.org/)

What I want to work with, is Qiankun
[Qiankun](https://qiankun.umijs.org/)

###Our Scenario
Imagine that we have a project named mf-productthat will work like a basket, we will add some products in our basket (Dummy products) and DVA will reduce states to show them on our frontend.

[DVA js](https://github.com/dvajs/dva)

Now we want to implement it into parent project that is micro-frontend and we have to show the `mf-product` in `#mf-container` DOM. it is easy , isn’t it? but how?
<br/>
I just know that `mf-product` is runing on `localhost:8001` and we can fetch it from there.<br/>
You can pull the project from : [https://github.com/nimahkh/micro-frontend](https://github.com/nimahkh/micro-frontend)

###Start
First we have to install qiankun
mkdir micro-front && cd micro-front && yarn add qiankun
Then we will create our package.json to see what we need for start.
```json
{
  "name": "micro-front",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "devDependencies": {
    "@babel/core": "^7.7.2",
    "npm-run-all": "^4.1.5",
    "@babel/plugin-transform-react-jsx": "^7.7.0",
    "@babel/preset-env": "^7.7.1",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.2.0",
    "html-webpack-plugin": "^3.2.0",
    "less-loader": "^6.2.0",
    "style-loader": "^1.0.0",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.9.0",
    "cross-env": "^7.0.2"
  },
  "dependencies": {
    "qiankun": "^2.3.5",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "vue": "^2.6.11"
  }
}
```
#### Why we need these packages?

We need babel for transpiling our es6 codes and also we need webpack and less for lunching our main project. the main project is the project that will load `mf-product` from port `8001` into `8000` at div that has `#mf-container` DOM.
```bash 
yarn #install dependencies
```
Create main Project
```bash
$ vim index.html
```
Put the codes below in index.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Example</title>
</head>
<body>
  <div class="mainapp">
    <header class="mainapp-header">
      <h1>Micro Frontend</h1>
    </header>
    <div class="mainapp-main">
      <ul class="mainapp-sidemenu">
        <li onclick="push('/mf-product')">Products</li>
      </ul>
      <main id="mf-container"></main>
    </div>
  </div>
<script>
    function push(subapp) { history.pushState(null, subapp, subapp) }
  </script>
</body>
</html>
```
The script part will redirect to links.
Now we need `index.js` file to register our Micro Apps, here we will introduce to the application that we have a project that is lunched on port 8001 and I want to show it on `#mf-container` that has a url equal to `mf-product`
```jsx
import {
  registerMicroApps,
  start,
  setDefaultMountApp,
  prefetchApps,
  addGlobalUncaughtErrorHandler,
} from "qiankun";
import render from "./render/ReactRender";
render({ loading: true });
const loader = (loading) => render({ loading });
registerMicroApps([
  {
    name: "mf-product", // app name registered
    entry: "//localhost:8001",
    container: "#mf-container",
    loader,
    activeRule: "/mf-product",
  },
]);
prefetchApps([{ name: "mf-product", entry: "//locahost:7101" }]);
start();
setDefaultMountApp("/mf-product");
addGlobalUncaughtErrorHandler((event) => console.log(event));
```
####Oh Man , what is Render?
nothing, it is just a loader with Reactjs , and it is why we need babel.<br/>
create render folder and create one file named: `ReactRender.jsx`
```jsx
import React from "react";
import ReactDOM from "react-dom";
function Render(props) {
  const { loading } = props;
return (
    <>
      {loading && <h4 className="subapp-loading">Loading...</h4>}
      <div id="mf-container" />
    </>
  );
}
export default function render({ loading }) {
  const container = document.getElementById("mf-container");
  ReactDOM.render(<Render loading={loading} />, container);
}
```
Now we are showing one simple loading until sub app fetched. after fetching the sub app (the application that we want to show it) we have to render it on mf-container .
Just we need to setup our webpack.config.js for lunching the project :
```js
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { name } = require("./package");
module.exports = {
  entry: "./index.js",
  devtool: "source-map",
  devServer: {
    open: true,
    port: "8000",
    clientLogLevel: "warning",
    disableHostCheck: true,
    compress: true,
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
    historyApiFallback: true,
    overlay: { warnings: false, errors: true },
  },
  output: {
    publicPath: "/",
  },
  mode: "development",
  resolve: {
    extensions: [".js", ".jsx", ".ts", ".tsx"],
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env"],
            plugins: ["@babel/plugin-transform-react-jsx"],
          },
        },
      },
      {
        test: /\.(le|c)ss$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: "index.html",
      template: "./index.html",
      minify: {
        removeComments: true,
        collapseWhitespace: true,
      },
    }),
  ],
};
```
It is very easy, I am not going to explain it. just we are trying to transpile and compile our plugins and finaly run the main on port `8000`.
####Now what?
We need to pull the project that we want to implement it on main.
Change your directory to root project (where package.json is), then pull the project.
```bash 
git pull https://github.com/nimahkh/mf-product
```
Our project is created with UMI, so I will explain in two ways, one with UMI and one with pure React.
###UMI
it is not important what we did, and what are our components, just we have to describe our `.umirc.js`
```jsx
import { name } from "./package.json";
import { slave } from "umi-plugin-qiankun";
export default {
  base: name,
  publicPath: "/mf-product/",
  outputPath: "./dist",
  mountElementId: "mf-product",
  plugins: [
    [
      slave,
      {
        keepOriginalRoutes: true
      }
    ],
    [
      "umi-plugin-react",
      {
        title: "mf-product",
        antd: true,
        dva: {
          always: true,
          hmr: true
        },
        dynamicImport: true,
        routes: {
          exclude: [
            /models\//,
            /services\//,
            /model\.(t|j)sx?$/,
            /service\.(t|j)sx?$/
          ]
        }
      }
    ]
  ]
};
```
just we have to install umi-plugin-qiankun
```bash
$ yarn add @umijs/plugin-qiankun
```
###Pure React
At first we have to ovveride webpack with wired or rescript, I am going to do it with rescript. first create `.rescriptsrc.js`
```js
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;
  },
};
```
This code will create output library from your package.json name property.
Now inside your package.json , change start command inside scripts block : `"start": "rescripts start"` .
Add `.env` inside root directory:
```
SKIP_PREFLIGHT_CHECK=true
BROWSER=none
PORT=7100
WDS_SOCKET_PORT=7100
```
We are near to end, just we have to create `public-path.js`.
####why?
We have to introduce to qiankun that we have global variable with name of our app to load it.
`src/public-path.js`
```js
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
 __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
```
Now you have to just change index.js of the application like below:

`src/index.js`
```jsx
import './public-path';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
function render(props) {
  const { container } = props;
  ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}
function storeTest(props) {
  props.onGlobalStateChange((value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev), true);
  props.setGlobalState({
    ignore: props.name,
    user: {
      name: props.name,
    },
  });
}
if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}
export async function bootstrap() {
  console.log('[react16] react app bootstraped');
}
export async function mount(props) {
  console.log('[react16] props from main framework', props);
  storeTest(props);
  render(props);
}
export async function unmount(props) {
  const { container } = props;
  ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
```
It is done dude!
Now come to see what we did, but there is no scripts block in main `package.json`, so come to add it
```json
"scripts": {
    "start:product": "cd mf-product  && yarn start",
    "start:main": "webpack-dev-server",
    "start": "npm-run-all --parallel start:*",
    "start:main-m": "cross-env MODE=multiple webpack-dev-server",
    "start:multiple": "run-p start:main-m start:product",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
```
YESSSSSS!, now just run
```bash
$ yarn start
```
Now you can check `localhost:8000` and you will see the `mf-project` in our main layout, also you can check `localhost:8001` to see that `mf-product` is working on it’s port.
It is very nice, isn’t it?
I hope that I explained it clearly.
`<good by world />`