Building and Publishing Design Systems | Part 3

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 (you are here)

4. Building shell application for micro frontends

In this part, we will create a react application that consumes the design system. The app will have the capability to display itself as a standalone app or as a micro frontend in shell application.

The idea here is to create a react app that does not automatically mount to DOM but rather assigns two functions mount and unmount to a property on the window object. The page that wants to display this app will call mount function and give it a host DOMElment plus some configuration. In our case, the configuration will be one property isStandAlone that will indicate if the app should display its own shell or as embedded micro fronted. We will also create an `index.html` file which will be deployed along with the application. If someone tries to access our app directly, the server will serve index.html. HTML will contain a small script that will set up our app in stand-alone mode.

Create directory nutrition-portal in the same folder as design-systme (next to each other).

Run following commands from nutrition-portal 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 ./.bablrc with the following content:

{
    presets: [
      [
        '@babel/preset-env',
        {
          modules: false
        }
      ],
      '@babel/preset-react'
    ],
    plugins: [
      'react-hot-loader/babel'
    ]
  }

Create file ./src/App.js with the following content:

import React from "react";
import    from "react-hot-loader/root";
import    from "../../design-system/dist/main";
import "../../design-system/dist/main.css";

class App extends React.Component {
  render() {
    return (
      <div style={{ margin: "20px" }}>
        <Button>
          Nutrition portal{" "}
          {this.props.isStandAlone
            ? "displayed as stand-alone app"
            : "displayed as micro frontend"}
        </Button>
      </div>
    );
  }
}

export default hot(App);


Make sure paths in lines 3 and 4 are reflecting where you placed your design system.

Create file ./src/index.js with the following content:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

function mount(mountNode, args) {
  ReactDOM.render(<App  />, mountNode);
}

function unmount(mountNode) {
  let res = ReactDOM.unmountComponentAtNode(mountNode);
}

window.nutritionPortalMount = mount;
window.nutritionPortalUnmount = unmount;


./src/index.js will be our entry point. The important part here is that it is not rendering our react app but rather defines two functions for mounting and unmounting our app. At the end mount and unmount functions are assigned to a property on window object.

Create file ./src/index.html with the following content:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title></title>
</head>

<body>
    <div id="app"></div>
    <script>
        document.addEventListener("DOMContentLoaded", function () {
            window.nutritionPortalMount(document.getElementById("app"), {isStandAlone: true})
        });
    </script>
</body>

</html>


After deployment, if anyone hits the correct domain he will see this micro frontend rendered in standalone mode. A little script at the end of body will mount application to the div. We also pass arguments to the app making clear it should render with its own shell.

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: 7001
  },
  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;
};

This is a standard webpack setup for an react app.

Finally add following script to ./package.json file (scripts object):

    "start": "cross-env NODE_ENV=development webpack serve --hot --mode development",
    "build-prod": "cross-env NODE_ENV=production webpack --mode production"

You can run the application in stand-mode by running npm start; it should bind to port 7001. Open http://localhost:7001. You should see something like this:

For the other micro frontends you can repeat the setup or simply copy this application and change the following things in the copy:

  • The application package name in package.json (property name at the top of the file). This is important for webpack. You won't be able to load two micro frontends with the same package name in one shell application.

  • Port number in webpack.config.js (the setting is in devServer section, line 45)

  • The name of mount and unmount functions in ./src/index.js (lines 13,14)

  • The name of mount function in ./src/index.html (line 13)

  • Micro frontend name in the App.js (label of the button, line 11).

In the repository with the code for this series I created additional copies: exercise-portal, meals-planner-portal, and recipes-portal.

What´s next?

In the next post, we will integrate micro frontends inside the shell application. It will be the last post in this series.

Zurück
Zurück

So, I wrote a book

Weiter
Weiter

Using a Skill/Will matrix for personal career development