Micro Frontend Solution 3 - Module Loader

The micro frontend module loader handles three main responsibilities:

  • Loading project configuration files
  • Loading module API files (used by the message bus, covered later)
  • Loading module entry files

These are three essential parts for every module.

Configuration File

In our micro frontend practice, each module has an external configuration file used when registering with Single-SPA.

{
    "name": "name",
    "path": "/project",
    "prefix": "/module-prefix/",
    "main": "/module-prefix/main.js",
    "store": "/module-prefix/store.js",
    "base": true
  }

When a module supports multiple URL prefixes, the path can be an array:

{
    "path": ["/project-url-path1/", "/project-url-path2/"],
  }

Auto Configuration

Each module has a configuration file as described above. When you have multiple modules, you need to aggregate all configs. I wrote a script for this:

micro-auto-config

Usage:

npm install micro-auto-config -g
pm2 start micro-auto-config

The idea: when a module is deployed and the server detects file changes, it scans all module configs, merges them, and outputs a unified project.config.js.

Module Loader

This file is included directly in the HTML — an upgraded version of single-spa-config.js from the previous article. We use SystemJS as our module loading tool.

"use strict";
import "../libs/es6-promise.auto.min";
import * as singleSpa from "single-spa";
import { registerApp } from "./Register";

async function bootstrap() {
  let projectConfig = await SystemJS.import("/project.config.js");

  projectConfig.projects.forEach((element) => {
    registerApp({
      name: element.name,
      main: element.main,
      url: element.prefix,
      store: element.store,
      base: element.base,
      path: element.path,
    });
  });

  singleSpa.start();
}

bootstrap();

Register.js

import "../libs/system";
import "../libs/es6-promise.auto.min";
import * as singleSpa from "single-spa";

export function hashPrefix(app) {
  return function (location) {
    let isShow = false;
    if (isArray(app.path)) {
      app.path.forEach((path) => {
        if (location.hash.startsWith(`#${path}`)) {
          isShow = true;
        }
      });
    }
    else if (location.hash.startsWith(`#${app.path || app.url}`)) {
      isShow = true;
    }
    return isShow;
  };
}

export function pathPrefix(app) {
  return function (location) {
    let isShow = false;
    if (isArray(app.path)) {
      app.path.forEach((path) => {
        if (location.pathname.indexOf(`${path}`) === 0) {
          isShow = true;
        }
      });
    }
    else if (location.pathname.indexOf(`${app.path || app.url}`) === 0) {
      isShow = true;
    }
    return isShow;
  };
}

export async function registerApp(params) {
  singleSpa.registerApplication(
    params.name,
    () => SystemJS.import(params.main),
    params.base ? () => true : pathPrefix(params)
  );
}

function isArray(o) {
  return Object.prototype.toString.call(o) == "[object Array]";
}

Related Articles

Demo

Article Link:

/en/archive/micro-frontend-solution-3-module-loader/

# Related Articles