mfe-runtime-z 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -79
- package/build/index.esm.js +65 -30
- package/build/index.js +65 -30
- package/build/index.min.js +1 -1
- package/build/micro-fe/host/MFEHost.d.ts +20 -8
- package/build/micro-fe/store/createSharedStore.d.ts +3 -1
- package/build/micro-fe/types/MFEContext.d.ts +4 -5
- package/build/micro-fe/types/RemoteApp.d.ts +4 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,11 +24,11 @@ This library focuses on **application-level integration**, not component sharing
|
|
|
24
24
|
#### What it solves
|
|
25
25
|
|
|
26
26
|
- Load and mount remote applications dynamically
|
|
27
|
-
- Isolate application lifecycles (mount
|
|
27
|
+
- Isolate application lifecycles (mount/unmount/reload) ✅ via Shadow DOM
|
|
28
28
|
- Share state safely between apps
|
|
29
29
|
- Synchronize routing without sharing router instances
|
|
30
30
|
- Communicate via a central event bus
|
|
31
|
-
- Work with **any framework** (React, Vue, Svelte, Vanilla JS)
|
|
31
|
+
- Work with **any framework** (React, Vue, Svelte, Vanilla JS...)
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
@@ -55,13 +55,11 @@ yarn add mfe-runtime-z
|
|
|
55
55
|
Micro-frontends expose a simple runtime contract:
|
|
56
56
|
|
|
57
57
|
```ts
|
|
58
|
-
window.myRemoteApp = {
|
|
59
|
-
mount(el, ctx),
|
|
60
|
-
unmount(el)
|
|
61
|
-
}
|
|
58
|
+
window.myRemoteApp = { mount(el, ctx), unmount(el) }
|
|
62
59
|
```
|
|
63
60
|
|
|
64
|
-
No framework or bundler assumptions.
|
|
61
|
+
- No framework or bundler assumptions.
|
|
62
|
+
- Compatible with React, Vue, Vanilla JS
|
|
65
63
|
|
|
66
64
|
---
|
|
67
65
|
|
|
@@ -72,112 +70,93 @@ No framework or bundler assumptions.
|
|
|
72
70
|
```ts
|
|
73
71
|
import { MFEHost, createSharedStore } from "mfe-runtime-z"
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
})
|
|
73
|
+
// shared store example
|
|
74
|
+
const authStore = createSharedStore({ user: null })
|
|
78
75
|
|
|
76
|
+
// create host with isolate: true for Shadow DOM + error isolation
|
|
79
77
|
const host = new MFEHost({
|
|
80
78
|
stores: { auth: authStore },
|
|
81
|
-
navigate: (path) => history.pushState({}, "", path)
|
|
79
|
+
navigate: (path) => history.pushState({}, "", path),
|
|
80
|
+
isolate: false, // => shadow
|
|
81
|
+
onRemoteError: (err) => console.error("Remote error:", err)
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)
|
|
84
|
+
// load remote apps
|
|
85
|
+
const productRemote = await host.load("http://localhost:3001/remote.js", "productApp")
|
|
86
|
+
const cartRemote = await host.load("http://localhost:3002/remote.js", "cartApp")
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
// mount remotes into host container divs
|
|
89
|
+
host.mount(productRemote, document.getElementById("product-root")!, "productApp", { isolate: true }) // shadow
|
|
90
|
+
host.mount(cartRemote, document.getElementById("cart-root")!, "cartApp")
|
|
90
91
|
|
|
91
|
-
//
|
|
92
|
-
|
|
92
|
+
// // Unmount Shadow DOM remote
|
|
93
|
+
// host.unmount(productRemote, document.getElementById("product-root")!, "productApp")
|
|
94
|
+
// // Unmount normal remote
|
|
95
|
+
// host.unmount(cartRemote, document.getElementById("cart-root")!, "cartApp")
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
```
|
|
95
98
|
|
|
96
99
|
##### Remote application (framework-agnostic)
|
|
97
100
|
|
|
98
101
|
```ts
|
|
99
|
-
export function mount(el
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
})
|
|
111
|
-
})
|
|
112
|
-
})
|
|
102
|
+
export function mount(el: ShadowRoot) {
|
|
103
|
+
let count = 0
|
|
104
|
+
const div = document.createElement("div")
|
|
105
|
+
const btn = document.createElement("button")
|
|
106
|
+
const p = document.createElement("p")
|
|
107
|
+
btn.textContent = "Add Item"
|
|
108
|
+
p.textContent = count.toString()
|
|
109
|
+
btn.onclick = () => (p.textContent = (++count).toString())
|
|
110
|
+
div.appendChild(btn)
|
|
111
|
+
div.appendChild(p)
|
|
112
|
+
el.appendChild(div)
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
export function unmount(el) {
|
|
115
|
+
export function unmount(el: ShadowRoot) {
|
|
116
116
|
el.innerHTML = ""
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
window.
|
|
119
|
+
;(window as any).cartApp = { mount, unmount }
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
##### Remote app (framework-react-library)
|
|
123
123
|
|
|
124
124
|
```ts
|
|
125
|
-
// src/app.tsx
|
|
126
125
|
import React from "react"
|
|
127
126
|
import ReactDOM from "react-dom/client"
|
|
128
127
|
|
|
129
128
|
type AuthStore = {
|
|
130
129
|
user: { id: string; name: string } | null
|
|
131
|
-
subscribe: (
|
|
132
|
-
setState: (
|
|
130
|
+
subscribe: (cb: (state:any)=>void)=>void
|
|
131
|
+
setState: (s:any)=>void
|
|
133
132
|
}
|
|
134
133
|
|
|
135
|
-
// React component
|
|
136
134
|
function App({ store }: { store: AuthStore }) {
|
|
137
135
|
const [user, setUser] = React.useState(store.user)
|
|
138
|
-
|
|
139
136
|
React.useEffect(() => {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
})
|
|
143
|
-
return () => unsubscribe?.()
|
|
137
|
+
const unsub = store.subscribe((state:any) => setUser(state.user))
|
|
138
|
+
return () => unsub?.()
|
|
144
139
|
}, [store])
|
|
145
|
-
|
|
146
|
-
const handleLogin = () => {
|
|
147
|
-
store.setState({ user: { id: "1", name: "Alice" } })
|
|
148
|
-
}
|
|
149
|
-
|
|
150
140
|
return (
|
|
151
141
|
<div>
|
|
152
|
-
{user ? (
|
|
153
|
-
<span>Hello {user.name}</span>
|
|
154
|
-
) : (
|
|
155
|
-
<button id="login" onClick={handleLogin}>
|
|
156
|
-
Login
|
|
157
|
-
</button>
|
|
158
|
-
)}
|
|
142
|
+
{user ? <span>Hello {user.name}</span> : <button onClick={()=>store.setState({user:{id:"1",name:"Alice"}})}>Login</button>}
|
|
159
143
|
</div>
|
|
160
144
|
)
|
|
161
145
|
}
|
|
162
146
|
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const root = ReactDOM.createRoot(el)
|
|
147
|
+
// mount/unmount compatible with Shadow DOM
|
|
148
|
+
export function mount(el: HTMLElement | ShadowRoot, ctx: { stores: { auth: AuthStore } }) {
|
|
149
|
+
const root = ReactDOM.createRoot(el as unknown as HTMLElement)
|
|
167
150
|
;(el as any)._reactRoot = root
|
|
168
151
|
root.render(<App store={ctx.stores.auth} />)
|
|
169
152
|
}
|
|
170
153
|
|
|
171
|
-
export function unmount(el: HTMLElement) {
|
|
154
|
+
export function unmount(el: HTMLElement | ShadowRoot) {
|
|
172
155
|
const root = (el as any)._reactRoot
|
|
173
|
-
if (root)
|
|
174
|
-
root.unmount()
|
|
175
|
-
}
|
|
156
|
+
if (root) root.unmount()
|
|
176
157
|
el.innerHTML = ""
|
|
177
158
|
}
|
|
178
159
|
|
|
179
|
-
|
|
180
|
-
// expose mount/unmount to window
|
|
181
160
|
;(window as any).productApp = { mount, unmount }
|
|
182
161
|
```
|
|
183
162
|
|
|
@@ -187,17 +166,11 @@ export function unmount(el: HTMLElement) {
|
|
|
187
166
|
|
|
188
167
|
```ts
|
|
189
168
|
const store = createSharedStore({ count: 0 })
|
|
190
|
-
|
|
191
|
-
store.subscribe((state) => {
|
|
192
|
-
console.log(state.count)
|
|
193
|
-
})
|
|
194
|
-
|
|
169
|
+
store.subscribe((state) => console.log(state.count))
|
|
195
170
|
store.setState({ count: 1 })
|
|
196
171
|
```
|
|
197
172
|
|
|
198
|
-
- Push-based
|
|
199
|
-
- No Redux
|
|
200
|
-
- No shared framework state
|
|
173
|
+
- Push-based, no Redux, no shared framework state
|
|
201
174
|
|
|
202
175
|
---
|
|
203
176
|
|
|
@@ -207,12 +180,9 @@ store.setState({ count: 1 })
|
|
|
207
180
|
import { createSharedRouter } from "mfe-runtime-z"
|
|
208
181
|
|
|
209
182
|
const router = createSharedRouter(ctx)
|
|
210
|
-
|
|
211
183
|
router.go("/cart")
|
|
184
|
+
router.onChange((path) => console.log("navigated to", path))
|
|
212
185
|
|
|
213
|
-
router.onChange((path) => {
|
|
214
|
-
console.log("navigated to", path)
|
|
215
|
-
})
|
|
216
186
|
```
|
|
217
187
|
|
|
218
188
|
- Intent-based navigation
|
|
@@ -251,7 +221,7 @@ import { mount, unmount } from "./app"
|
|
|
251
221
|
;(window as any).productApp = { mount, unmount }
|
|
252
222
|
```
|
|
253
223
|
|
|
254
|
-
👉
|
|
224
|
+
👉 Copy-paste ✅: minimal remote build, browser-loadable, no Module Federation.
|
|
255
225
|
|
|
256
226
|
---
|
|
257
227
|
|
|
@@ -315,6 +285,7 @@ if (import.meta.env.DEV) {
|
|
|
315
285
|
- Shared state with subscriptions
|
|
316
286
|
- Router synchronization
|
|
317
287
|
- Hot reload support for remote apps
|
|
288
|
+
- Shadow DOM + error isolation (optional via isolate: true)
|
|
318
289
|
- No build-time federation required
|
|
319
290
|
|
|
320
291
|
---
|
|
@@ -327,6 +298,22 @@ if (import.meta.env.DEV) {
|
|
|
327
298
|
|
|
328
299
|
---
|
|
329
300
|
|
|
301
|
+
### Micro-Frontend Runtime Comparison
|
|
302
|
+
|
|
303
|
+
| Feature / Lib | **mfe-runtime-z** | Module Federation (Webpack 5) | single-spa | qiankun |
|
|
304
|
+
| ----------------------------------- | -------------------| ------------------------------- | ---------------- | ---------------- |
|
|
305
|
+
| **Framework-agnostic** | ✅ Yes | ❌ Mostly Webpack/JS | ✅ Yes | ✅ Yes |
|
|
306
|
+
| **Runtime loading** | ✅ Yes | ❌ Build-time federation only | ✅ Yes | ✅ Yes |
|
|
307
|
+
| **Shadow DOM support** | ✅ Optional | ❌ Not built-in | ❌ Not built-in | ✅ Partial |
|
|
308
|
+
| **Shared state / EventBus** | ✅ Yes | ❌ Requires external | ✅ Yes | ✅ Yes |
|
|
309
|
+
| **Dynamic mount/unmount** | ✅ Yes | ❌ Build-time | ✅ Yes | ✅ Yes |
|
|
310
|
+
| **Dev HMR support** | ✅ Yes | ❌ Limited | ⚠️ Needs plugin | ⚠️ Needs plugin |
|
|
311
|
+
| **Bundle size** | 🟢 Lightweight | ⚠️ Depends on Webpack setup | 🟡 Medium | 🟡 Medium |
|
|
312
|
+
| **Learning curve** | 🟢 Simple | ⚠️ Medium | ⚠️ Medium | ⚠️ Medium |
|
|
313
|
+
| **Build-time federation required?** | ❌ No | ✅ Yes | ❌ No | ❌ No |
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
330
317
|
### License
|
|
331
318
|
|
|
332
319
|
MIT
|
package/build/index.esm.js
CHANGED
|
@@ -167,16 +167,19 @@ function enableRouterSync(eventBus, navigate) {
|
|
|
167
167
|
var MFEHost = /** @class */ (function () {
|
|
168
168
|
function MFEHost(options) {
|
|
169
169
|
if (options === void 0) { options = {}; }
|
|
170
|
-
var _a;
|
|
170
|
+
var _a, _b;
|
|
171
171
|
this.eventBus = new EventBus();
|
|
172
|
+
this.isolate = false;
|
|
172
173
|
this.stores = (_a = options.stores) !== null && _a !== void 0 ? _a : {};
|
|
173
174
|
this.navigate = options.navigate;
|
|
174
175
|
this.onRemoteError = options.onRemoteError;
|
|
176
|
+
this.fallback = options.fallback;
|
|
177
|
+
this.isolate = (_b = options.isolate) !== null && _b !== void 0 ? _b : false;
|
|
175
178
|
if (this.navigate) {
|
|
176
179
|
enableRouterSync(this.eventBus, this.navigate);
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
|
-
/**
|
|
182
|
+
/** Load remote JS only */
|
|
180
183
|
MFEHost.prototype.load = function (url, global) {
|
|
181
184
|
var _a, _b;
|
|
182
185
|
return __awaiter(this, void 0, void 0, function () {
|
|
@@ -205,58 +208,85 @@ var MFEHost = /** @class */ (function () {
|
|
|
205
208
|
});
|
|
206
209
|
});
|
|
207
210
|
};
|
|
208
|
-
/**
|
|
209
|
-
MFEHost.prototype.mount = function (remote, el, name) {
|
|
210
|
-
var _a;
|
|
211
|
+
/** Mount remote with optional Shadow DOM isolation */
|
|
212
|
+
MFEHost.prototype.mount = function (remote, el, name, options) {
|
|
213
|
+
var _a, _b;
|
|
214
|
+
var isolate = (_a = options === null || options === void 0 ? void 0 : options.isolate) !== null && _a !== void 0 ? _a : this.isolate;
|
|
215
|
+
var mountEl = el;
|
|
216
|
+
// Shadow DOM isolation
|
|
217
|
+
if (isolate) {
|
|
218
|
+
if (el._shadowRoot)
|
|
219
|
+
el._shadowRoot.innerHTML = "";
|
|
220
|
+
var mode = (_b = options === null || options === void 0 ? void 0 : options.shadowMode) !== null && _b !== void 0 ? _b : "open";
|
|
221
|
+
// @ts-ignore
|
|
222
|
+
var shadow = el.attachShadow({ mode: mode })(el)._shadowRoot = shadow;
|
|
223
|
+
// For React/Vue remotes, create container div
|
|
224
|
+
var container = document.createElement("div");
|
|
225
|
+
shadow.appendChild(container);
|
|
226
|
+
// @ts-ignore
|
|
227
|
+
mountEl = container(el)._mountEl = container;
|
|
228
|
+
}
|
|
211
229
|
var ctx = {
|
|
212
230
|
name: name,
|
|
213
|
-
eventBus: this.eventBus,
|
|
214
231
|
stores: this.stores,
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
},
|
|
232
|
+
eventBus: this.eventBus,
|
|
233
|
+
host: { navigate: this.navigate },
|
|
218
234
|
};
|
|
219
235
|
try {
|
|
220
|
-
remote.mount(
|
|
221
|
-
console.log("[MFE] mounted ".concat(name));
|
|
236
|
+
remote.mount(mountEl, ctx);
|
|
237
|
+
console.log("[MFE] mounted ".concat(name, " ").concat(isolate ? "(shadow)" : ""));
|
|
222
238
|
}
|
|
223
239
|
catch (err) {
|
|
224
|
-
|
|
225
|
-
|
|
240
|
+
var error = err;
|
|
241
|
+
console.error("[MFE] remote ".concat(name, " mount failed:"), error);
|
|
242
|
+
if (this.onRemoteError)
|
|
243
|
+
this.onRemoteError(error);
|
|
244
|
+
if (this.fallback) {
|
|
245
|
+
this.fallback(mountEl, name, error, ctx);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
mountEl.innerHTML = "<div style=\"color:red\">Failed to load ".concat(name, "</div>");
|
|
249
|
+
}
|
|
226
250
|
}
|
|
227
251
|
};
|
|
252
|
+
/** Unmount remote */
|
|
228
253
|
MFEHost.prototype.unmount = function (remote, el, name) {
|
|
229
|
-
var _a, _b;
|
|
254
|
+
var _a, _b, _c, _d;
|
|
230
255
|
try {
|
|
231
|
-
(_a =
|
|
232
|
-
|
|
256
|
+
var mountEl = (_a = el._mountEl) !== null && _a !== void 0 ? _a : (this.isolate ? (_b = el.shadowRoot) !== null && _b !== void 0 ? _b : el : el);
|
|
257
|
+
(_c = remote.unmount) === null || _c === void 0 ? void 0 : _c.call(remote, mountEl);
|
|
258
|
+
delete el._mountEl;
|
|
259
|
+
if (name)
|
|
260
|
+
console.log("[MFE] unmounted ".concat(name));
|
|
233
261
|
}
|
|
234
262
|
catch (err) {
|
|
235
|
-
(
|
|
263
|
+
(_d = this.onRemoteError) === null || _d === void 0 ? void 0 : _d.call(this, err);
|
|
236
264
|
}
|
|
237
265
|
};
|
|
266
|
+
/** Get central event bus */
|
|
238
267
|
MFEHost.prototype.getEventBus = function () {
|
|
239
268
|
return this.eventBus;
|
|
240
269
|
};
|
|
241
|
-
/**
|
|
270
|
+
/** Reload remote: unmount + reload script + mount again */
|
|
242
271
|
MFEHost.prototype.reloadRemote = function (options) {
|
|
243
|
-
var _a;
|
|
272
|
+
var _a, _b, _c;
|
|
244
273
|
return __awaiter(this, void 0, void 0, function () {
|
|
245
|
-
var name, url, global, el, oldRemote, newRemote;
|
|
246
|
-
return __generator(this, function (
|
|
247
|
-
switch (
|
|
274
|
+
var name, url, global, el, isolate, oldRemote, mountEl, newRemote;
|
|
275
|
+
return __generator(this, function (_d) {
|
|
276
|
+
switch (_d.label) {
|
|
248
277
|
case 0:
|
|
249
|
-
name = options.name, url = options.url, global = options.global, el = options.el;
|
|
250
|
-
oldRemote = window[global];
|
|
278
|
+
name = options.name, url = options.url, global = options.global, el = options.el, isolate = options.isolate;
|
|
251
279
|
try {
|
|
252
|
-
|
|
280
|
+
oldRemote = window[global];
|
|
281
|
+
mountEl = (_a = el._mountEl) !== null && _a !== void 0 ? _a : ((isolate !== null && isolate !== void 0 ? isolate : this.isolate) ? (_b = el.shadowRoot) !== null && _b !== void 0 ? _b : el : el);
|
|
282
|
+
(_c = oldRemote === null || oldRemote === void 0 ? void 0 : oldRemote.unmount) === null || _c === void 0 ? void 0 : _c.call(oldRemote, mountEl);
|
|
253
283
|
}
|
|
254
|
-
catch (
|
|
284
|
+
catch (_e) { }
|
|
255
285
|
delete window[global];
|
|
256
286
|
return [4 /*yield*/, this.load("".concat(url, "?t=").concat(Date.now()), global)];
|
|
257
287
|
case 1:
|
|
258
|
-
newRemote =
|
|
259
|
-
this.mount(newRemote, el, name);
|
|
288
|
+
newRemote = _d.sent();
|
|
289
|
+
this.mount(newRemote, el, name, { isolate: isolate });
|
|
260
290
|
return [2 /*return*/];
|
|
261
291
|
}
|
|
262
292
|
});
|
|
@@ -272,12 +302,17 @@ function createSharedStore(initial) {
|
|
|
272
302
|
getState: function () {
|
|
273
303
|
return state;
|
|
274
304
|
},
|
|
305
|
+
/** Update state (shallow merge) */
|
|
275
306
|
setState: function (partial) {
|
|
276
307
|
state = __assign(__assign({}, state), partial);
|
|
277
|
-
listeners.forEach(function (
|
|
308
|
+
listeners.forEach(function (listener) { return listener(state); });
|
|
278
309
|
},
|
|
279
|
-
|
|
310
|
+
/** Subscribe to state changes */
|
|
311
|
+
subscribe: function (fn, callImmediately) {
|
|
312
|
+
if (callImmediately === void 0) { callImmediately = true; }
|
|
280
313
|
listeners.add(fn);
|
|
314
|
+
if (callImmediately)
|
|
315
|
+
fn(state); // Immediately call listener with current state
|
|
281
316
|
return function () { return listeners.delete(fn); };
|
|
282
317
|
},
|
|
283
318
|
};
|
package/build/index.js
CHANGED
|
@@ -173,16 +173,19 @@
|
|
|
173
173
|
var MFEHost = /** @class */ (function () {
|
|
174
174
|
function MFEHost(options) {
|
|
175
175
|
if (options === void 0) { options = {}; }
|
|
176
|
-
var _a;
|
|
176
|
+
var _a, _b;
|
|
177
177
|
this.eventBus = new EventBus();
|
|
178
|
+
this.isolate = false;
|
|
178
179
|
this.stores = (_a = options.stores) !== null && _a !== void 0 ? _a : {};
|
|
179
180
|
this.navigate = options.navigate;
|
|
180
181
|
this.onRemoteError = options.onRemoteError;
|
|
182
|
+
this.fallback = options.fallback;
|
|
183
|
+
this.isolate = (_b = options.isolate) !== null && _b !== void 0 ? _b : false;
|
|
181
184
|
if (this.navigate) {
|
|
182
185
|
enableRouterSync(this.eventBus, this.navigate);
|
|
183
186
|
}
|
|
184
187
|
}
|
|
185
|
-
/**
|
|
188
|
+
/** Load remote JS only */
|
|
186
189
|
MFEHost.prototype.load = function (url, global) {
|
|
187
190
|
var _a, _b;
|
|
188
191
|
return __awaiter(this, void 0, void 0, function () {
|
|
@@ -211,58 +214,85 @@
|
|
|
211
214
|
});
|
|
212
215
|
});
|
|
213
216
|
};
|
|
214
|
-
/**
|
|
215
|
-
MFEHost.prototype.mount = function (remote, el, name) {
|
|
216
|
-
var _a;
|
|
217
|
+
/** Mount remote with optional Shadow DOM isolation */
|
|
218
|
+
MFEHost.prototype.mount = function (remote, el, name, options) {
|
|
219
|
+
var _a, _b;
|
|
220
|
+
var isolate = (_a = options === null || options === void 0 ? void 0 : options.isolate) !== null && _a !== void 0 ? _a : this.isolate;
|
|
221
|
+
var mountEl = el;
|
|
222
|
+
// Shadow DOM isolation
|
|
223
|
+
if (isolate) {
|
|
224
|
+
if (el._shadowRoot)
|
|
225
|
+
el._shadowRoot.innerHTML = "";
|
|
226
|
+
var mode = (_b = options === null || options === void 0 ? void 0 : options.shadowMode) !== null && _b !== void 0 ? _b : "open";
|
|
227
|
+
// @ts-ignore
|
|
228
|
+
var shadow = el.attachShadow({ mode: mode })(el)._shadowRoot = shadow;
|
|
229
|
+
// For React/Vue remotes, create container div
|
|
230
|
+
var container = document.createElement("div");
|
|
231
|
+
shadow.appendChild(container);
|
|
232
|
+
// @ts-ignore
|
|
233
|
+
mountEl = container(el)._mountEl = container;
|
|
234
|
+
}
|
|
217
235
|
var ctx = {
|
|
218
236
|
name: name,
|
|
219
|
-
eventBus: this.eventBus,
|
|
220
237
|
stores: this.stores,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
},
|
|
238
|
+
eventBus: this.eventBus,
|
|
239
|
+
host: { navigate: this.navigate },
|
|
224
240
|
};
|
|
225
241
|
try {
|
|
226
|
-
remote.mount(
|
|
227
|
-
console.log("[MFE] mounted ".concat(name));
|
|
242
|
+
remote.mount(mountEl, ctx);
|
|
243
|
+
console.log("[MFE] mounted ".concat(name, " ").concat(isolate ? "(shadow)" : ""));
|
|
228
244
|
}
|
|
229
245
|
catch (err) {
|
|
230
|
-
|
|
231
|
-
|
|
246
|
+
var error = err;
|
|
247
|
+
console.error("[MFE] remote ".concat(name, " mount failed:"), error);
|
|
248
|
+
if (this.onRemoteError)
|
|
249
|
+
this.onRemoteError(error);
|
|
250
|
+
if (this.fallback) {
|
|
251
|
+
this.fallback(mountEl, name, error, ctx);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
mountEl.innerHTML = "<div style=\"color:red\">Failed to load ".concat(name, "</div>");
|
|
255
|
+
}
|
|
232
256
|
}
|
|
233
257
|
};
|
|
258
|
+
/** Unmount remote */
|
|
234
259
|
MFEHost.prototype.unmount = function (remote, el, name) {
|
|
235
|
-
var _a, _b;
|
|
260
|
+
var _a, _b, _c, _d;
|
|
236
261
|
try {
|
|
237
|
-
(_a =
|
|
238
|
-
|
|
262
|
+
var mountEl = (_a = el._mountEl) !== null && _a !== void 0 ? _a : (this.isolate ? (_b = el.shadowRoot) !== null && _b !== void 0 ? _b : el : el);
|
|
263
|
+
(_c = remote.unmount) === null || _c === void 0 ? void 0 : _c.call(remote, mountEl);
|
|
264
|
+
delete el._mountEl;
|
|
265
|
+
if (name)
|
|
266
|
+
console.log("[MFE] unmounted ".concat(name));
|
|
239
267
|
}
|
|
240
268
|
catch (err) {
|
|
241
|
-
(
|
|
269
|
+
(_d = this.onRemoteError) === null || _d === void 0 ? void 0 : _d.call(this, err);
|
|
242
270
|
}
|
|
243
271
|
};
|
|
272
|
+
/** Get central event bus */
|
|
244
273
|
MFEHost.prototype.getEventBus = function () {
|
|
245
274
|
return this.eventBus;
|
|
246
275
|
};
|
|
247
|
-
/**
|
|
276
|
+
/** Reload remote: unmount + reload script + mount again */
|
|
248
277
|
MFEHost.prototype.reloadRemote = function (options) {
|
|
249
|
-
var _a;
|
|
278
|
+
var _a, _b, _c;
|
|
250
279
|
return __awaiter(this, void 0, void 0, function () {
|
|
251
|
-
var name, url, global, el, oldRemote, newRemote;
|
|
252
|
-
return __generator(this, function (
|
|
253
|
-
switch (
|
|
280
|
+
var name, url, global, el, isolate, oldRemote, mountEl, newRemote;
|
|
281
|
+
return __generator(this, function (_d) {
|
|
282
|
+
switch (_d.label) {
|
|
254
283
|
case 0:
|
|
255
|
-
name = options.name, url = options.url, global = options.global, el = options.el;
|
|
256
|
-
oldRemote = window[global];
|
|
284
|
+
name = options.name, url = options.url, global = options.global, el = options.el, isolate = options.isolate;
|
|
257
285
|
try {
|
|
258
|
-
|
|
286
|
+
oldRemote = window[global];
|
|
287
|
+
mountEl = (_a = el._mountEl) !== null && _a !== void 0 ? _a : ((isolate !== null && isolate !== void 0 ? isolate : this.isolate) ? (_b = el.shadowRoot) !== null && _b !== void 0 ? _b : el : el);
|
|
288
|
+
(_c = oldRemote === null || oldRemote === void 0 ? void 0 : oldRemote.unmount) === null || _c === void 0 ? void 0 : _c.call(oldRemote, mountEl);
|
|
259
289
|
}
|
|
260
|
-
catch (
|
|
290
|
+
catch (_e) { }
|
|
261
291
|
delete window[global];
|
|
262
292
|
return [4 /*yield*/, this.load("".concat(url, "?t=").concat(Date.now()), global)];
|
|
263
293
|
case 1:
|
|
264
|
-
newRemote =
|
|
265
|
-
this.mount(newRemote, el, name);
|
|
294
|
+
newRemote = _d.sent();
|
|
295
|
+
this.mount(newRemote, el, name, { isolate: isolate });
|
|
266
296
|
return [2 /*return*/];
|
|
267
297
|
}
|
|
268
298
|
});
|
|
@@ -278,12 +308,17 @@
|
|
|
278
308
|
getState: function () {
|
|
279
309
|
return state;
|
|
280
310
|
},
|
|
311
|
+
/** Update state (shallow merge) */
|
|
281
312
|
setState: function (partial) {
|
|
282
313
|
state = __assign(__assign({}, state), partial);
|
|
283
|
-
listeners.forEach(function (
|
|
314
|
+
listeners.forEach(function (listener) { return listener(state); });
|
|
284
315
|
},
|
|
285
|
-
|
|
316
|
+
/** Subscribe to state changes */
|
|
317
|
+
subscribe: function (fn, callImmediately) {
|
|
318
|
+
if (callImmediately === void 0) { callImmediately = true; }
|
|
286
319
|
listeners.add(fn);
|
|
320
|
+
if (callImmediately)
|
|
321
|
+
fn(state); // Immediately call listener with current state
|
|
287
322
|
return function () { return listeners.delete(fn); };
|
|
288
323
|
},
|
|
289
324
|
};
|
package/build/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).MFERuntimeZ={})}(this,function(t){"use strict";var e=function(){return e=Object.assign||function(t){for(var e,n=1,o=arguments.length;n<o;n++)for(var r in e=arguments[n])Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t},e.apply(this,arguments)};function n(t,e,n,o){return new(n||(n=Promise))(function(r,i){function
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).MFERuntimeZ={})}(this,function(t){"use strict";var e=function(){return e=Object.assign||function(t){for(var e,n=1,o=arguments.length;n<o;n++)for(var r in e=arguments[n])Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t},e.apply(this,arguments)};function n(t,e,n,o){return new(n||(n=Promise))(function(r,i){function a(t){try{l(o.next(t))}catch(t){i(t)}}function u(t){try{l(o.throw(t))}catch(t){i(t)}}function l(t){var e;t.done?r(t.value):(e=t.value,e instanceof n?e:new n(function(t){t(e)})).then(a,u)}l((o=o.apply(t,e||[])).next())})}function o(t,e){var n,o,r,i={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]},a=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return a.next=u(0),a.throw=u(1),a.return=u(2),"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function u(u){return function(l){return function(u){if(n)throw new TypeError("Generator is already executing.");for(;a&&(a=0,u[0]&&(i=0)),i;)try{if(n=1,o&&(r=2&u[0]?o.return:u[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,u[1])).done)return r;switch(o=0,r&&(u=[2&u[0],r.value]),u[0]){case 0:case 1:r=u;break;case 4:return i.label++,{value:u[1],done:!1};case 5:i.label++,o=u[1],u=[0];continue;case 7:u=i.ops.pop(),i.trys.pop();continue;default:if(!(r=i.trys,(r=r.length>0&&r[r.length-1])||6!==u[0]&&2!==u[0])){i=0;continue}if(3===u[0]&&(!r||u[1]>r[0]&&u[1]<r[3])){i.label=u[1];break}if(6===u[0]&&i.label<r[1]){i.label=r[1],r=u;break}if(r&&i.label<r[2]){i.label=r[2],i.ops.push(u);break}r[2]&&i.ops.pop(),i.trys.pop();continue}u=e.call(t,i)}catch(t){u=[6,t],o=0}finally{n=r=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,l])}}}"function"==typeof SuppressedError&&SuppressedError;var r=new Set;function i(t,e){return void 0===e&&(e={}),n(this,void 0,void 0,function(){var n,i,u,l,c,s;return o(this,function(o){switch(o.label){case 0:if(n=e.timeout,i=void 0===n?15e3:n,u=e.retries,l=void 0===u?1:u,r.has(t))return[2];c=0,o.label=1;case 1:if(!(c<=l))return[3,6];o.label=2;case 2:return o.trys.push([2,4,,5]),[4,a(t,i)];case 3:return o.sent(),r.add(t),[2];case 4:if(s=o.sent(),c===l)throw s;return[3,5];case 5:return c++,[3,1];case 6:return[2]}})})}function a(t,e){return new Promise(function(n,o){var r=document.createElement("script");r.src=t,r.async=!0;var i=setTimeout(function(){a(),o(new Error("Timeout loading ".concat(t)))},e);function a(){clearTimeout(i),r.remove()}r.onload=function(){a(),n()},r.onerror=function(){a(),o(new Error("Failed to load ".concat(t)))},document.body.appendChild(r)})}var u=function(){function t(){this.map=new Map}return t.prototype.on=function(t,e){this.map.has(t)||this.map.set(t,new Set),this.map.get(t).add(e)},t.prototype.off=function(t,e){var n;null===(n=this.map.get(t))||void 0===n||n.delete(e)},t.prototype.emit=function(t,e){var n;null===(n=this.map.get(t))||void 0===n||n.forEach(function(t){return t(e)})},t}(),l="ROUTER:REQUEST_NAVIGATE",c="ROUTER:NAVIGATE";function s(t,e){t.on(l,function(n){var o=n.path;e(o),t.emit(c,{path:o})})}var f=function(){function t(t){var e,n;void 0===t&&(t={}),this.eventBus=new u,this.isolate=!1,this.stores=null!==(e=t.stores)&&void 0!==e?e:{},this.navigate=t.navigate,this.onRemoteError=t.onRemoteError,this.fallback=t.fallback,this.isolate=null!==(n=t.isolate)&&void 0!==n&&n,this.navigate&&s(this.eventBus,this.navigate)}return t.prototype.load=function(t,e){var r,a;return n(this,void 0,void 0,function(){var n,u,l;return o(this,function(o){switch(o.label){case 0:return o.trys.push([0,2,,3]),[4,i(t,{retries:1})];case 1:return o.sent(),[3,3];case 2:throw n=o.sent(),null===(r=this.onRemoteError)||void 0===r||r.call(this,n),n;case 3:if(!(null==(u=window[e])?void 0:u.mount))throw l=new Error('Remote "'.concat(e,'" not found')),null===(a=this.onRemoteError)||void 0===a||a.call(this,l),l;return[2,u]}})})},t.prototype.mount=function(t,e,n,o){var r,i,a=null!==(r=null==o?void 0:o.isolate)&&void 0!==r?r:this.isolate,u=e;if(a){e._shadowRoot&&(e._shadowRoot.innerHTML="");var l=null!==(i=null==o?void 0:o.shadowMode)&&void 0!==i?i:"open",c=e.attachShadow({mode:l})(e)._shadowRoot=c,s=document.createElement("div");c.appendChild(s),u=s(e)._mountEl=s}var f={name:n,stores:this.stores,eventBus:this.eventBus,host:{navigate:this.navigate}};try{t.mount(u,f),console.log("[MFE] mounted ".concat(n," ").concat(a?"(shadow)":""))}catch(t){var d=t;console.error("[MFE] remote ".concat(n," mount failed:"),d),this.onRemoteError&&this.onRemoteError(d),this.fallback?this.fallback(u,n,d,f):u.innerHTML='<div style="color:red">Failed to load '.concat(n,"</div>")}},t.prototype.unmount=function(t,e,n){var o,r,i,a;try{var u=null!==(o=e._mountEl)&&void 0!==o?o:this.isolate&&null!==(r=e.shadowRoot)&&void 0!==r?r:e;null===(i=t.unmount)||void 0===i||i.call(t,u),delete e._mountEl,n&&console.log("[MFE] unmounted ".concat(n))}catch(t){null===(a=this.onRemoteError)||void 0===a||a.call(this,t)}},t.prototype.getEventBus=function(){return this.eventBus},t.prototype.reloadRemote=function(t){var e,r,i;return n(this,void 0,void 0,function(){var n,a,u,l,c,s,f,d;return o(this,function(o){switch(o.label){case 0:n=t.name,a=t.url,u=t.global,l=t.el,c=t.isolate;try{s=window[u],f=null!==(e=l._mountEl)&&void 0!==e?e:(null!=c?c:this.isolate)&&null!==(r=l.shadowRoot)&&void 0!==r?r:l,null===(i=null==s?void 0:s.unmount)||void 0===i||i.call(s,f)}catch(t){}return delete window[u],[4,this.load("".concat(a,"?t=").concat(Date.now()),u)];case 1:return d=o.sent(),this.mount(d,l,n,{isolate:c}),[2]}})})},t}();t.EventBus=u,t.MFEHost=f,t.createSharedRouter=function(t){var e=t.eventBus,n=t.name;return{go:function(t){e.emit(l,{from:n,path:t})},onChange:function(t){return e.on(c,function(e){t(e.path)})}}},t.createSharedStore=function(t){var n=t,o=new Set;return{getState:function(){return n},setState:function(t){n=e(e({},n),t),o.forEach(function(t){return t(n)})},subscribe:function(t,e){return void 0===e&&(e=!0),o.add(t),e&&t(n),function(){return o.delete(t)}}}},t.enableRouterSync=s,t.loadScript=i,Object.defineProperty(t,"__esModule",{value:!0})});
|
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
import { EventBus } from "../event-bus/EventBus";
|
|
2
2
|
import type { RemoteApp } from "../types/RemoteApp";
|
|
3
|
-
type
|
|
4
|
-
|
|
3
|
+
import type { MFEContext } from "../types/MFEContext";
|
|
4
|
+
type HostOptions<S extends Record<string, any> = any> = {
|
|
5
|
+
stores?: S;
|
|
5
6
|
navigate?: (path: string) => void;
|
|
6
7
|
onRemoteError?: (error: Error) => void;
|
|
8
|
+
isolate?: boolean;
|
|
9
|
+
fallback?: (el: HTMLElement, name: string, error: Error, ctx: MFEContext<S>) => void;
|
|
7
10
|
};
|
|
8
|
-
|
|
11
|
+
interface MountOptions {
|
|
12
|
+
isolate?: boolean;
|
|
13
|
+
shadowMode?: ShadowRootMode;
|
|
14
|
+
}
|
|
15
|
+
export declare class MFEHost<S extends Record<string, any> = any> {
|
|
9
16
|
private eventBus;
|
|
10
17
|
private stores;
|
|
11
18
|
private navigate?;
|
|
12
19
|
private onRemoteError?;
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
private fallback?;
|
|
21
|
+
private isolate;
|
|
22
|
+
constructor(options?: HostOptions<S>);
|
|
23
|
+
/** Load remote JS only */
|
|
15
24
|
load(url: string, global: string): Promise<RemoteApp>;
|
|
16
|
-
/**
|
|
17
|
-
mount(remote: RemoteApp, el: HTMLElement, name: string): void;
|
|
25
|
+
/** Mount remote with optional Shadow DOM isolation */
|
|
26
|
+
mount(remote: RemoteApp, el: HTMLElement, name: string, options?: MountOptions): void;
|
|
27
|
+
/** Unmount remote */
|
|
18
28
|
unmount(remote: RemoteApp, el: HTMLElement, name?: string): void;
|
|
29
|
+
/** Get central event bus */
|
|
19
30
|
getEventBus(): EventBus<any>;
|
|
20
|
-
/**
|
|
31
|
+
/** Reload remote: unmount + reload script + mount again */
|
|
21
32
|
reloadRemote(options: {
|
|
22
33
|
name: string;
|
|
23
34
|
url: string;
|
|
24
35
|
global: string;
|
|
25
36
|
el: HTMLElement;
|
|
37
|
+
isolate?: boolean;
|
|
26
38
|
}): Promise<void>;
|
|
27
39
|
}
|
|
28
40
|
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export type Unsubscribe = () => void;
|
|
2
2
|
export declare function createSharedStore<T extends object>(initial: T): {
|
|
3
3
|
getState(): T;
|
|
4
|
+
/** Update state (shallow merge) */
|
|
4
5
|
setState(partial: Partial<T>): void;
|
|
5
|
-
|
|
6
|
+
/** Subscribe to state changes */
|
|
7
|
+
subscribe(fn: (s: T) => void, callImmediately?: boolean): Unsubscribe;
|
|
6
8
|
};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { EventBus } from "../event-bus/EventBus";
|
|
2
|
-
export
|
|
3
|
-
/** identity của microFE hiện tại */
|
|
2
|
+
export interface MFEContext<S = any> {
|
|
4
3
|
name: string;
|
|
4
|
+
stores: S;
|
|
5
5
|
eventBus: EventBus<any>;
|
|
6
|
-
|
|
7
|
-
host?: {
|
|
6
|
+
host: {
|
|
8
7
|
navigate?: (path: string) => void;
|
|
9
8
|
};
|
|
10
|
-
}
|
|
9
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export type RemoteApp = {
|
|
1
|
+
export interface RemoteApp<C = any> {
|
|
3
2
|
name?: string;
|
|
4
|
-
mount: (el: HTMLElement, ctx:
|
|
5
|
-
unmount?: (el: HTMLElement) => void;
|
|
6
|
-
}
|
|
3
|
+
mount: (el: HTMLElement | ShadowRoot, ctx: C) => void;
|
|
4
|
+
unmount?: (el: HTMLElement | ShadowRoot) => void;
|
|
5
|
+
}
|
package/package.json
CHANGED