Single-SPA
Core Concepts and Architecture
Single-SPA is one of the earliest micro frontend frameworks, defining the concept and basic architecture of micro frontends. Its core idea is to decompose a frontend application into multiple independent “sub-applications,” managed and coordinated by a “base application” responsible for loading, unloading, and lifecycle management.
The Single-SPA architecture includes three key components:
- Base Application (Bootstrap): Manages the control flow of the overall application, handling sub-application registration and lifecycle.
- Sub-Application (Application): Independent business modules that can be developed and deployed separately.
- Loader: Responsible for dynamically loading sub-application resources.
Single-SPA uses JavaScript module systems to organize code, leveraging SystemJS or native ES Modules for dynamic loading.
Application Registration and Lifecycle Management
Single-SPA uses the registerApplication method to register sub-applications:
import { registerApplication, start } from 'single-spa';
registerApplication({
name: 'app1',
app: () => System.import('app1'),
activeWhen: ['/app1'],
customProps: {
// Props passed to the sub-application
}
});
start();
The lifecycle of sub-applications is managed by Single-SPA, including:
bootstrap: Initializes the application.mount: Mounts the application to the DOM.unmount: Unmounts the application from the DOM.unload: Unloads the application resources.
Sub-applications need to export these lifecycle methods:
// app1.js
export const bootstrap = () => Promise.resolve();
export const mount = () => {
// Mounting logic
};
export const unmount = () => {
// Unmounting logic
};
Active Application Management Strategies
Single-SPA provides multiple strategies to determine which sub-application is active:
- URL Path Matching: Configures path rules using
activeWhen. - Custom Logic: Uses a function that returns a boolean to decide.
- Manual Control: Dynamically modifies using
setActiveWhen.
registerApplication({
name: 'app1',
app: () => System.import('app1'),
activeWhen: [
'/app1',
location => location.pathname.startsWith('/dashboard') && location.query.theme === 'dark'
]
});
Custom Loader Implementation
Single-SPA allows custom loaders to control how sub-applications are loaded:
function customLoader(appName) {
return {
loadApp: () => import(`./${appName}/app.js`),
unloadApp: () => Promise.resolve()
};
}
registerApplication({
name: 'custom-app',
app: () => customLoader('custom-app').loadApp(),
// ...
});
More complex loaders can implement caching, error handling, and other features:
const appCache = {};
function advancedLoader(appName) {
return {
loadApp: () => {
if (appCache[appName]) {
return Promise.resolve(appCache[appName]);
}
return import(`./${appName}/app.js`)
.then(app => {
appCache[appName] = app;
return app;
})
.catch(err => {
delete appCache[appName];
throw err;
});
},
unloadApp: () => {
delete appCache[appName];
return Promise.resolve();
}
};
}
Qiankun
Basic Usage and Configuration
Qiankun is an open-source micro frontend framework by Alibaba, built on Single-SPA, offering a more comprehensive solution.
Basic configuration example:
// Main application
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'app1',
entry: '//localhost:7100',
container: '#app1-container',
activeRule: '/app1',
},
{
name: 'app2',
entry: '//localhost:7101',
container: '#app2-container',
activeRule: '/app2',
},
]);
start();
Sub-applications need to expose lifecycle methods:
// Sub-application entry file
export async function bootstrap() {
console.log('app1 bootstraped');
}
export async function mount(props) {
console.log('app1 mount', props);
// Render application to container
}
export async function unmount() {
console.log('app1 unmounted');
// Unmount application
}
Main and Sub-Application Communication Mechanisms
Qiankun provides multiple communication methods:
- Props Passing: The main application passes data to sub-applications via props.
registerMicroApps([
{
// ...
props: { user: { name: 'admin' } }
}
]);
Sub-application receiving:
export async function mount(props) {
console.log(props.user); // { name: 'admin' }
}
- Custom Events: Use browser-native events or a publish-subscribe pattern.
// Main application sending
window.dispatchEvent(new CustomEvent('global-event', { detail: { data: 'test' } }));
// Sub-application listening
window.addEventListener('global-event', (e) => {
console.log(e.detail.data);
});
- Global State Management: Use libraries like Redux or Vuex to share state.
Sandbox Isolation Principles
Qiankun implements two sandbox mechanisms:
- Snapshot Sandbox:
- Records the initial state of global variables.
- Saves the current state before executing sub-application code.
- Restores the state after execution.
class SnapshotSandbox {
constructor() {
this.proxy = window;
this.modifyPropsMap = {};
this.windowSnapshot = {};
}
active() {
this.windowSnapshot = {};
for (const prop in window) {
if (window.hasOwnProperty(prop)) {
this.windowSnapshot[prop] = window[prop];
}
}
return this.proxy;
}
inactive() {
for (const prop in window) {
if (window.hasOwnProperty(prop) && window[prop] !== this.windowSnapshot[prop]) {
this.modifyPropsMap[prop] = window[prop];
window[prop] = this.windowSnapshot[prop];
}
}
Object.keys(this.modifyPropsMap).forEach(prop => {
window[prop] = this.modifyPropsMap[prop];
});
this.modifyPropsMap = {};
}
}
- Proxy Sandbox:
- Uses Proxy to intercept object operations.
- Creates an independent variable space.
class ProxySandbox {
constructor() {
this.proxy = new Proxy({}, {
get: (target, p) => {
if (p === 'window') return sandbox;
return sandbox.globalContext[p];
},
set: (target, p, value) => {
sandbox.globalContext[p] = value;
return true;
}
});
this.globalContext = {};
}
}
Performance Optimization Strategies
- Resource Preloading: Preload sub-application resources.
// Main application
import { prefetchApps } from 'qiankun';
prefetchApps();
- On-Demand Loading: Load sub-applications only when needed.
registerMicroApps([
{
// ...
props: { loadOnDemand: true }
}
]);
- Caching Optimization: Cache loaded sub-applications.
const appCache = new Map();
function loadApp(appName) {
if (appCache.has(appName)) {
return Promise.resolve(appCache.get(appName));
}
return import(`./${appName}/app.js`)
.then(app => {
appCache.set(appName, app);
return app;
});
}
- Code Splitting: Use Webpack’s dynamic imports.
const App = React.lazy(() => import('./App'));
Module Federation
Core Concepts of Webpack Module Federation
Module Federation, introduced in Webpack 5, is a micro frontend solution that allows different build systems to share code.
Core concepts:
- Host: The application that consumes remote modules.
- Remote: The application that provides modules.
- Shared: Dependencies shared across multiple applications.
Shared Dependency Management
Configure shared dependencies using the shared option:
// Main application Webpack configuration
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^17.0.2' },
'react-dom': { singleton: true, requiredVersion: '^17.0.2' }
}
});
// Sub-application Webpack configuration
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button'
},
shared: {
react: { singleton: true, requiredVersion: '^17.0.2' },
'react-dom': { singleton: true, requiredVersion: '^17.0.2' }
}
});
Remote Module Loading Mechanism
Dynamically import remote modules:
import('app1/Button').then(({ default: Button }) => {
// Use Button component
});
Webpack automatically handles the loading and caching of remote modules.
Dynamic and On-Demand Loading
Combine React.lazy and Suspense for on-demand loading:
const Button = React.lazy(() => import('app1/Button'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Button />
</Suspense>
);
}
Dependency Conflict Resolution Strategies
- Version Alignment: Ensure shared dependency versions are consistent.
- Singleton Mode: Enforce singleton instances for shared dependencies.
shared: {
react: { singleton: true }
}
- Isolated Loading: Create separate contexts for different dependency versions.
new ModuleFederationPlugin({
// ...
shared: {
lodash: {
requiredVersion: '^4.17.21',
import: 'lodash-es', // Use different package name
shareScope: 'lodash-v4'
}
}
});
- Dependency Hoisting: Elevate shared dependencies to the main application.
Wujie
Features and Advantages
Wujie is a next-generation micro frontend framework with the following features:
- Technology Stack Agnostic: Supports all frontend frameworks and pure HTML applications.
- High Performance: Optimized loading and rendering mechanisms.
- Robust Sandboxing: Provides multiple sandbox isolation solutions.
- Developer-Friendly Experience: Supports hot updates and source maps.
Comparison with Qiankun
| Feature | Wujie | Qiankun |
|---|---|---|
| Technology Stack Support | All frameworks + HTML | Major frameworks |
| Sandbox | Multiple solutions | Snapshot/Proxy |
| Performance | Optimized loading | Standard implementation |
| Developer Experience | Hot updates | Requires configuration |
| Community | Newer | Mature |
Advanced Feature Usage
- CSS Isolation:
wujie.start({
sandbox: {
strictStyleIsolation: true, // Enforce style isolation
experimentalStyleIsolation: true // Experimental style isolation
}
});
- Preloading:
wujie.microApp.preload('app1');
- Communication:
// Main application
wujie.start({
plugins: {
modules: {
'app1': {
onCreated(app) {
app.postMessage({ type: 'init', data: {} });
}
}
}
}
});
// Sub-application
window.addEventListener('message', (e) => {
if (e.data.type === 'init') {
// Handle initialization data
}
});
Other Frameworks
Piral
Piral is a micro frontend framework focused on building modular frontend applications.
Architecture and Usage
- Piral Core: The base application.
- Pilet: Sub-applications (plugins).
Installation:
npm install piral-core
Create a base application:
import { createInstance } from 'piral-core';
const instance = createInstance({
// Configuration
});
instance.run();
Register a Pilet:
{
"name": "my-pilet",
"version": "1.0.0",
"pilet": {
"entry": "./src/index.tsx"
}
}
Features
- React-based.
- Pluginized architecture.
- Hot update support.
- Dependency management.
Luigi
Luigi is an open-source micro frontend framework by SAP, designed for enterprise applications.
Architecture and Usage
- Micro Frontends: Sub-applications.
- Luigi Core: Coordinator.
Configuration example:
import { LuigiConfig } from 'luigi-client';
LuigiConfig.getConfigValue('clientOptions', {
routing: {
useHashRouting: true
}
});
Register sub-applications:
{
"applications": [
{
"pathSegment": "app1",
"viewUrl": "http://localhost:4200"
}
]
}
Features
- Enterprise-grade features.
- Permission management.
- Multi-language support.
- Deep integration with the SAP ecosystem.
Summary
Each micro frontend framework has its own strengths and use cases:
- Single-SPA: Ideal for highly customized projects, with a steeper learning curve.
- Qiankun: Open-source by Alibaba, with strong community support, suitable for most enterprise applications.
- Module Federation: Natively supported by Webpack, ideal for Webpack-based projects.
- Wujie: Technology stack agnostic with optimized performance, suitable for exploring new technologies.
- Piral: React ecosystem, pluginized architecture, suitable for React projects.
- Luigi: Enterprise applications with SAP ecosystem integration, suitable for large enterprises.
When choosing a micro frontend framework, consider:
- Technology stack compatibility.
- Team familiarity.
- Project scale.
- Performance requirements.
- Long-term maintenance needs.
While micro frontend architecture is powerful, it also increases system complexity, requiring careful consideration of trade-offs before adoption.



