<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.
What I want to work with, is Qiankun
Qiankun
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.
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?
I just know that mf-product
is runing on localhost:8001
and we can fetch it from there.
You can pull the project from : 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.
{
"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.
yarn #install dependencies
Create main Project
$ vim index.html
Put the codes below in index.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
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.
create render folder and create one file named: ReactRender.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 :
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.
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
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
$ 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
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
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
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
"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
$ 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 />
Top comments (1)
that was very useful, thanks Nima , keep going