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:
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
- Micro Frontend Solution 1 - Thinking
- Micro Frontend Solution 2 - Single-SPA
- Micro Frontend Solution 3 - Module Loader
- Micro Frontend Solution 4 - Message Bus
- Micro Frontend Solution 5 - Route Distribution
- Micro Frontend Solution 6 - Build and Deploy
- Micro Frontend Solution 7 - Static Data Sharing
- Micro Frontend Solution 8 - Secondary Build
Demo
- Micro Frontend Demo
- Micro Frontend Module Loader
- Micro Frontend Base App Source
- Micro Frontend Submodule Demo Source