Build a Micro‑Frontend Framework from Scratch with Vue3, React15/16 and TypeScript
This article walks through creating a full‑featured micro‑frontend framework using TypeScript, covering sub‑application registration, router interception, sandbox isolation, CSS scoping, inter‑app communication, global state management, caching, and prefetching, all demonstrated with Vue3 as the host and Vue2, React15, and React16 as child apps.
Overview
The guide details how to implement a lightweight micro‑frontend framework from zero, using TypeScript and a Vue3 host application that loads three child applications built with Vue2, React15, and React16. It covers registration, routing, sandboxing, CSS isolation, communication, global store, caching, and prefetching.
Key Features Implemented
Sub‑application registration and resource loading
Runtime isolation (sandbox)
CSS isolation
Application communication (props & custom events)
Global store for shared state
Prefetch and caching for performance
Project Structure
.
├── main
│ └── micro // framework core
├── vue2 // sub‑app
├── react15
├── react16
└── README.mdImplementation Highlights
1. Sub‑application registration
export interface IAppDTO {
name: string;
activeRule: string;
container: string;
entry: string;
}
export const subNavList: IAppDTO[] = [
{ name: 'react15', activeRule: '/react15', container: '#micro-container', entry: '//localhost:9002/' },
{ name: 'react16', activeRule: '/react16', container: '#micro-container', entry: '//localhost:9003/' },
{ name: 'vue2', activeRule: '/vue2', container: '#micro-container', entry: '//localhost:9004/' }
];2. Router interception
export const patchRouter = (globalEvent, eventName) => {
return function () {
const e = new Event(eventName);
globalEvent.apply(this, arguments);
window.dispatchEvent(e);
};
};
window.history.pushState = patchRouter(window.history.pushState, 'micro_push');
window.history.replaceState = patchRouter(window.history.replaceState, 'micro_replace');
window.addEventListener('micro_push', turnApp);
window.addEventListener('micro_replace', turnApp);
window.onpopstate = function () { turnApp(); };3. Lifecycle handling
export const registerMicroApps = (appList, lifeCycle) => {
setList(appList);
lifeCycle.beforeLoad[0]();
setTimeout(() => lifeCycle.mounted[0](), 2000);
setMainLifeCycle(lifeCycle);
};4. Sandbox isolation
Two sandbox strategies are provided.
Snapshot sandbox
export class SnapShotSandbox {
constructor() {
this.proxy = window;
this.active();
}
active() {
this.snapshot = new Map();
for (const key in window) {
this.snapshot[key] = window[key];
}
}
inactive() {
for (const key in window) {
if (window[key] !== this.snapshot[key]) {
window[key] = this.snapshot[key];
}
}
}
}Proxy sandbox
let defaultValue = {};
export class ProxySandbox {
constructor() {
this.active();
}
active() {
this.proxy = new Proxy(window, {
get(target, key) {
if (typeof target[key] === 'function') {
return target[key].bind(target);
}
return defaultValue[key] || target[key];
},
set(target, key, value) {
defaultValue[key] = value;
return true;
}
});
}
inactive() {
defaultValue = {};
}
}5. CSS isolation
Common approaches include CSS modules, Shadow DOM, and MiniCssExtractPlugin (used in this framework).
6. Communication
Props‑based example passes navStatus and changeNav from the main store to sub‑apps. Custom‑event bus example:
export class Custom {
on(name, cb) { window.addEventListener(name, e => cb(e.detail)); }
emit(name, data) { window.dispatchEvent(new CustomEvent(name, { detail: data })); }
}
window.custom = new Custom();7. Global store
export const createStore = (initData = {}) => {
let store = initData;
const observers = [];
const getStore = () => store;
const updateStore = newValue => new Promise(res => {
if (newValue !== store) {
const oldValue = store;
store = newValue;
res(store);
observers.forEach(fn => fn(newValue, oldValue));
}
});
const subscribeStore = fn => observers.push(fn);
return { getStore, updateStore, subscribeStore };
};8. Performance – caching & prefetch
const cache = {};
export const parseHtml = async (entry, name) => {
if (cache[name]) return cache[name];
const html = await fetchResource(entry);
// …resource extraction…
cache[name] = [dom, script];
return [dom, allScript];
};
export const prefetch = async () => {
const list = getList().filter(item => !window.location.pathname.startsWith(item.activeRule));
await Promise.all(list.map(item => parseHtml(item.entry, item.name)));
};Running the Demo
Start each sub‑application (e.g., cd react16 && yarn start) and then run the root yarn start script to launch all three apps together.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
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.
