从零编写一个微前端框架
约 1389 字大约 5 分钟
微前端Micro Frontend框架实现
2026-04-16
概述
本文将从零实现一个简单的微前端框架,涵盖核心概念和实现原理。通过手写框架,可以深入理解 qiankun、single-spa 等成熟框架的设计思想。
项目初始化
目录结构
micro-pilot/
├── packages/
│ ├── core/ # 框架核心
│ │ ├── index.js
│ │ ├── apps.js # 应用管理
│ │ ├── lifeCycle.js # 生命周期
│ │ ├── router.js # 路由拦截
│ │ └── sandbox.js # JS 沙箱
│ └── plugins/ # 插件扩展
├── examples/
│ ├── main/ # 主应用
│ ├── vue-app/ # Vue 子应用
│ └── react-app/ # React 子应用
└── package.json核心模块
// packages/core/index.js
class MicroPilot {
constructor() {
this.apps = []; // 注册的应用列表
this.activeApp = null; // 当前激活的应用
}
}
export default new MicroPilot();.app 相关概念
应用注册对象
const appConfig = {
name: 'vue-app', // 应用唯一标识
entry: '//localhost:8080', // 子应用入口
container: '#micro-container', // 挂载容器
activeRule: '/vue', // 激活规则
props: {}, // 传递给子应用的props
sandbox: true, // 是否启用沙箱
};activeRule 规则类型
// 字符串规则 - 路径前缀匹配
{ activeRule: '/vue' }
// 函数规则 - 自定义匹配逻辑
{ activeRule: (location) => location.pathname.startsWith('/vue') }
// 正则规则
{ activeRule: /^\/vue(\/|$)/ }路由拦截
基于 history API 的拦截
// packages/core/router.js
class RouterInterceptor {
constructor() {
this.listeners = [];
this.originalPush = window.history.pushState;
this.originalReplace = window.history.replaceState;
}
// 拦截 pushState
interceptPushState() {
const self = this;
window.history.pushState = function (...args) {
const result = self.originalPush.apply(window.history, args);
self.notifyListeners('pushState', args);
return result;
};
}
// 拦截 replaceState
interceptReplaceState() {
const self = this;
window.history.replaceState = function (...args) {
const result = self.originalReplace.apply(window.history, args);
self.notifyListeners('replaceState', args);
return result;
};
}
// 监听 popstate 事件
listen(callback) {
this.listeners.push(callback);
window.addEventListener('popstate', (event) => {
callback('popstate', [event.state, event.title, event.url]);
});
}
// 通知所有监听器
notifyListeners(type, args) {
this.listeners.forEach(listener => listener(type, args));
}
}
export default new RouterInterceptor();劫持 history.go
// 扩展 RouterInterceptor
const originalGo = window.history.go;
window.history.go = function (...args) {
const result = originalGo.apply(window.history, args);
// 触发 popstate
window.dispatchEvent(new PopStateEvent('popstate', { state: window.history.state }));
return result;
};执行流程(核心)
应用生命周期
bootstrap() -> mount() -> unmount() -> destory()// packages/core/lifeCycle.js
// 全局生命周期钩子
export const globalLifeCycles = {
beforeLoad: [],
beforeMount: [],
afterMount: [],
beforeUnmount: [],
afterUnmount: [],
};
// 应用级生命周期
export function callGlobalLifecycle(hookName, app) {
const hooks = globalLifeCycles[hookName] || [];
return hooks.reduce((promise, hook) => {
return promise.then(() => hook(app));
}, Promise.resolve());
}
export async function bootstrapApp(app) {
if (app.bootstrap) {
await app.bootstrap(app.props);
}
}
export async function mountApp(app) {
if (app.mount) {
await app.mount(app.props);
}
}
export async function unmountApp(app) {
if (app.unmount) {
await app.unmount(app.props);
}
}应用加载流程
// packages/core/apps.js
import RouterInterceptor from './router.js';
import { callGlobalLifecycle, bootstrapApp, mountApp, unmountApp } from './lifeCycle.js';
class AppManager {
constructor() {
this.apps = [];
this.activeApp = null;
this.sandboxes = new Map();
}
// 注册应用
registerApp(appConfig) {
this.apps.push({
...appConfig,
status: 'NOT_BOOTSTRAPPED',
});
}
// 匹配活跃应用
getAppByRule(rule) {
return this.apps.find(app => {
if (typeof rule === 'function') {
return rule(window.location);
}
return window.location.pathname.startsWith(rule);
});
}
// 加载并激活应用
async activateApp(appName) {
const app = this.apps.find(a => a.name === appName);
if (!app) return;
// 1. 全局 beforeLoad 钩子
await callGlobalLifecycle('beforeLoad', app);
// 2. 沙箱初始化
const sandbox = this.initSandbox(app);
this.sandboxes.set(app.name, sandbox);
// 3. 获取子应用资源
const script = await fetchAppScript(app.entry);
// 4. 隔离全局变量
sandbox.instrument();
// 5. 执行 bootstrap
await bootstrapApp(app);
// 6. 全局 beforeMount 钩子
await callGlobalLifecycle('beforeMount', app);
// 7. 渲染到容器
const container = document.querySelector(app.container);
container.innerHTML = '<div id="app"></div>';
// 8. 执行 mount
await mountApp(app);
// 9. 全局 afterMount 钩子
await callGlobalLifecycle('afterMount', app);
app.status = 'MOUNTED';
this.activeApp = app;
}
// 卸载应用
async deactivateApp(appName) {
const app = this.apps.find(a => a.name === appName);
if (!app || !this.activeApp) return;
// 1. 全局 beforeUnmount 钩子
await callGlobalLifecycle('beforeUnmount', app);
// 2. 执行 unmount
await unmountApp(app);
// 3. 清空容器
const container = document.querySelector(app.container);
container.innerHTML = '';
// 4. 销毁沙箱
const sandbox = this.sandboxes.get(app.name);
if (sandbox) {
sandbox.destroy();
this.sandboxes.delete(app.name);
}
// 5. 全局 afterUnmount 钩子
await callGlobalLifecycle('afterUnmount', app);
app.status = 'NOT_MOUNTED';
this.activeApp = null;
}
// 初始化沙箱
initSandbox(app) {
return new ProxySandbox(app.name);
}
}
export default new AppManager();location 事件
监听 URL 变化
// packages/core/index.js - 完整初始化逻辑
import AppManager from './apps.js';
import RouterInterceptor from './router.js';
class MicroPilot {
constructor() {
this.appManager = AppManager;
this.routerInterceptor = RouterInterceptor;
}
// 注册子应用
registerMicroApps(apps) {
apps.forEach(app => this.appManager.registerApp(app));
}
// 启动框架
start() {
// 1. 拦截 history API
this.routerInterceptor.interceptPushState();
this.routerInterceptor.interceptReplaceState();
// 2. 监听路由变化
this.routerInterceptor.listen(async (type, args) => {
await this.handleRouteChange();
});
// 3. 处理初始路由
this.handleRouteChange();
}
// 处理路由变化
async handleRouteChange() {
const currentApp = this.appManager.activeApp;
const nextApp = this.appManager.getAppByRule(
window.location.pathname
);
if (!nextApp) {
// 没有匹配的应用,卸载当前应用
if (currentApp) {
await this.appManager.deactivateApp(currentApp.name);
}
return;
}
// 同一应用,跳转内部路由
if (currentApp && currentApp.name === nextApp.name) {
// 通知子应用路由变化
if (currentApp.onRouteChange) {
currentApp.onRouteChange(window.location);
}
return;
}
// 切换到新应用
if (currentApp) {
await this.appManager.deactivateApp(currentApp.name);
}
await this.appManager.activateApp(nextApp.name);
}
}
export default new MicroPilot();沙箱实现
快照沙箱(Legacy)
// packages/core/sandbox.js - 快照沙箱
class SnapshotSandbox {
constructor(name) {
this.name = name;
this.modifyMap = {}; // 记录的修改
}
// 激活 - 记录全局状态快照
active() {
this.windowSnapshot = {};
for (const key in window) {
this.windowSnapshot[key] = window[key];
}
// 恢复之前的修改
Object.keys(this.modifyMap).forEach(key => {
window[key] = this.modifyMap[key];
});
}
// 失活 - 记录修改并恢复快照
inactive() {
// 记录本次修改
for (const key in window) {
if (window[key] !== this.windowSnapshot[key]) {
this.modifyMap[key] = window[key];
window[key] = this.windowSnapshot[key];
}
}
}
}Proxy 沙箱
// packages/core/sandbox.js - Proxy 沙箱
class ProxySandbox {
constructor(name) {
this.name = name;
this.proxyWindow = null;
this.modifyMap = new Map();
this.init();
}
init() {
const self = this;
this.proxyWindow = new Proxy(window, {
get(target, prop) {
if (self.modifyMap.has(prop)) {
return self.modifyMap.get(prop);
}
return target[prop];
},
set(target, prop, value) {
self.modifyMap.set(prop, value);
return true;
},
});
}
// 获取代理后的全局对象
getProxy() {
return this.proxyWindow;
}
// 激活沙箱
active() {
// 可选:执行一些初始化逻辑
}
// 销毁沙箱
destroy() {
this.modifyMap.clear();
}
}沙箱在子应用中的使用
// 子应用 main.js
import Vue from 'vue';
import App from './App.vue';
let instance = null;
let globalProps = {};
// 获取主应用传递的 props
export function bootstrap(props) {
globalProps = props;
console.log('[vue-app] bootstrap', props);
}
// 设置全局对象
export function mount(props) {
globalProps = props;
// 使用 props 传入的 proxyWindow 作为全局对象
const proxyWindow = props.proxyWindow;
instance = new Vue({
el: proxyWindow.document.querySelector('#app'),
render: h => h(App),
});
}
export function unmount() {
instance.$destroy();
instance = null;
}
// 可选:监听路由变化
export function onRouteChange(url) {
console.log('[vue-app] route change:', url);
}主应用集成
完整示例
// main-app/src/micro-pilot.js
import MicroPilot from 'micro-pilot';
const microApp = MicroPilot;
// 注册子应用
microApp.registerMicroApps([
{
name: 'vue-app',
entry: '//localhost:8080',
container: '#micro-container',
activeRule: '/vue',
props: {
name: 'main-app',
onGlobalStateChange: (state) => {
console.log('[主应用] state change:', state);
},
setGlobalState: (state) => {
console.log('[主应用] set state:', state);
},
},
},
{
name: 'react-app',
entry: '//localhost:8081',
container: '#micro-container',
activeRule: '/react',
props: {
name: 'main-app',
},
},
]);
// 启动
microApp.start();