Micro‑Frontend Design and Loading Strategies: From Monolithic Workbench to Single‑spa
This article analyzes the challenges of monolithic admin workbenches, explains how micro‑frontend architecture—especially the single‑spa library—addresses routing, state‑machine coordination, resource loading, and lifecycle management, and compares practical loader implementations using Ajax and tag‑based approaches for production environments.
Fast's operation platform provides a desktop‑style web workbench for many front‑line services, but its monolithic architecture leads to high risk of public module changes, difficult upgrades, poor multi‑team collaboration, and slow release cycles.
Micro‑frontend (Micro‑Frontend) is presented as the solution, allowing the workbench to be split into independent sub‑applications managed by a central host.
Division of Micro‑Frontend
Aggregation and division can both be realized with micro‑frontend; the host application controls the lifecycle of child apps based on browser routing.
single‑spa Overview
single‑spa is a core library for micro‑frontend routing and state management. It mounts or unmounts sub‑applications according to the browser location, using the activeWhen function.
Routing
Routing changes trigger sub‑application mount/unmount; the host must respond to rapid route changes, possibly skipping outdated mount commands.
State Machine
Each sub‑application goes through states such as NOT_LOADED , LOADING_SOURCE_CODE , BOOTSTRAPPING , NOT_MOUNTED , MOUNTING , MOUNTED , UNMOUNTING , etc., which single‑spa tracks to ensure correct sequencing.
Loader Design
The host must obtain a sub‑application’s interface object (bootstrap, mount, unmount). A typical dynamic import example:
// single-spa-config.js
import { registerApplication, start } from 'single-spa';
registerApplication(
'app2',
() => import('src/app2/main.js'),
(location) => location.pathname.startsWith('/app2'),
{ some: 'value' }
);
start();Production‑ready loaders also need to fetch CSS resources, which the simple example omits.
Resource Mapping
Sub‑applications expose an HTML entry or, more flexibly, a JSON assets map (e.g., { "js": [], "css": [] } ) that the host can request and parse.
// assets-map.json
{
"js": [],
"css": []
}A mapping from app name to assets‑map URL can be stored in sub-apps.json or generated programmatically.
// sub-apps.json
{
"live": "/lv/assets-map.json",
"workbench": "/wb/assets-map.json"
}Loading Strategies
Two main approaches are compared:
Ajax request: fetch JS/CSS as text, inject via <style> or eval , enabling sandboxing and retry logic but losing native error locations and relative‑path handling.
Tag request: create <link> and <script> tags, preserving native loading, error reporting, and relative paths, but cannot provide sandboxing.
For CSS errors, the tag approach can delete and recreate the <link> tag; for JS errors, the sub‑application is marked as permanently failed.
Async vs Initial Chunks
Initial chunks load on page start; async chunks are loaded on demand. Both need to be tracked for proper cleanup when a sub‑application is unmounted.
Asset‑map format is extended to distinguish initial and async resources:
{
"css": {
"initial": [],
"async": []
},
"js": {
"initial": [],
"async": []
}
}UMD vs ESM Loading
UMD modules are loaded by fetching all scripts, executing the first N‑1 via Ajax, then the entry script, often using eval for sandboxing.
ESM modules can be loaded natively with dynamic import(entryJS) , optionally with magic comments to bypass bundler processing:
import(/* @vite-ignore */ /* webpackIgnore: true */ entryJS).then(...)Ajax‑based ESM loading requires converting code to a data URI before importing.
The discussion of loader design, resource mapping, and loading mechanisms continues in the next part of the series.
Kuaishou Tech
Official Kuaishou tech account, providing real-time updates on the latest Kuaishou technology practices.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.