Building Shell Application for Micro Frontend | PART 4
This post is part of the "Building design system and micro frontends" series. See the first post for context. Source code for all the parts of this series can be found in this GitHub repository.
1. Building design system and micro frontends
2. Building and publishing design system
3. Building micro frontend consuming design system
4. Building shell application for micro frontends (you are here)
In this post, we will create the last piece of our micro frontends with the design system PoC. We now have a design system, several micro frontends consuming this design system, and we need now a shell application that will import micro frontends and display them. This shell application will import micro frontends using script
tags and script URLs hardcoded in index.html
in the real project, it would be driven by some sort of configuration.
This series will not cover two aspects: module federation and mounting microseconds to isolated virtual DOM. Module federation helps to lower the size of included script by requesting modules, used by more than one micro frontend, only once. If we want to increase isolation between micro frontend we can create a web component that will mount micro frontend to isolated virtual dom.
Create directory fitness-portal-shell
in the same directory as design-system
and micro frontends. Run the following commands, from the new directory to setup project dependencies:
npm init -y npm i -P react@17.0.2 react-dom@17.0.2 react-hot-loader@4.13.0 npm i -D webpack@5.38.1 webpack-cli@4.7.0 webpack-dev-server@3.11.2 html-webpack-plugin@5.3.1 cross-env@7.0.3 babel-loader@8.2.2 @hot-loader/react-dom@17.0.1+4.13.0 @babel/preset-react@7.13.13 @babel/preset-env@7.14.4 @babel/core@7.14.3 mini-css-extract-plugin@1.6.0 css-loader@5.2.6
Now we need a few files.
Create file ./src/App.js
with the following content:
import React from "react"; import from "react-hot-loader/root"; import MicroFrontend from "./MicroFrontend"; class App extends React.Component { render() { return [ "nutritionPortal", // Uncomment following lines if you created additional micro frontends // "exercisePortal", // "mealsPlannerPortal", // "recipesPortal" ].map((name) => ( <MicroFrontend key= name=></MicroFrontend> )); } } export default hot(App);
Create file ./src/MicroFrontend.js
with the following content:
import React, from 'react' export default class MicroFrontend extends Component { get containerId() { return `mf-$ render() { return <div id= componentDidMount() Mount`](document.getElementById(this.containerId), {isStandAlone: false}) } componentWillUnmount() Unmount`](document.getElementById(this.containerId), {isStandAlone: false}) } }
Create file ./src/index.js
with the following content:
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; var mountNode = document.getElementById("app"); ReactDOM.render(<App />, mountNode);
Create file ./src/index.html
with the following content:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script defer="" src="http://localhost:7001/main.js" ></script> <script defer="" src="http://localhost:7001/vendors.js" ></script> <script defer="" src="http://localhost:7001/runtime.js" ></script> <link rel="stylesheet" href="http://localhost:7001/main.css"> <!-- Uncomment following lines if you created additional micro frontends --> <!-- <script defer="" src="http://localhost:7002/main.js" ></script> <script defer="" src="http://localhost:7002/vendors.js" ></script> <script defer="" src="http://localhost:7002/runtime.js" ></script> <link rel="stylesheet" href="http://localhost:7002/main.css"> <script defer="" src="http://localhost:7003/main.js" ></script> <script defer="" src="http://localhost:7003/vendors.js" ></script> <script defer="" src="http://localhost:7003/runtime.js" ></script> <link rel="stylesheet" href="http://localhost:7003/main.css"> <script defer="" src="http://localhost:7004/main.js" ></script> <script defer="" src="http://localhost:7004/vendors.js" ></script> <script defer="" src="http://localhost:7004/runtime.js" ></script> <link rel="stylesheet" href="http://localhost:7004/main.css"> --> </head> <body> <div id="app"></div> </body> </html>
This is a naive implementation where we hard-code links to micro frontends' resources in a real project it would come from the configuration.
No we will configure build.
Create file ./.bablrc
with the following content:
{ presets: [ [ '@babel/preset-env', { modules: false } ], '@babel/preset-react' ], plugins: [ 'react-hot-loader/babel' ] }
Create file ./webpack.config.js
with the following content:
const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const config = { entry: ["react-hot-loader/patch", "./src/index.js"], output: { path: path.resolve(__dirname, "dist"), filename: "[name].js", }, module: { rules: [ { test: /\.(js|jsx)$/, use: "babel-loader", exclude: /node_modules/, }, { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, { loader: "css-loader", options: { importLoaders: 1, }, }, ], }, ], }, resolve: { extensions: [".js"], alias: { "react-dom": "@hot-loader/react-dom", }, }, devServer: { contentBase: "./src", watchContentBase: true, port: 7000, headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS" }, }, plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html", }), new MiniCssExtractPlugin(), ], optimization: { runtimeChunk: "single", splitChunks: { cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: "vendors", chunks: "all", }, }, }, }, }; module.exports = (env, argv) => { if (argv.hot) { // Cannot use 'contenthash' when hot reloading is enabled. config.output.filename = "[name].js"; } return config; };
Open ./package.json
and add following script to scripts
section:
"start": "cross-env NODE_ENV=development webpack serve --hot --mode development"
You should be able to build the shell app now. Run npm start
. It should bind to port 7000
. Remember to build and run micro frontends first.