mfe-runtime-z 1.0.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/LICENSE +21 -0
- package/README.md +332 -0
- package/build/dev.d.ts +1 -0
- package/build/index.d.ts +8 -0
- package/build/index.esm.js +305 -0
- package/build/index.js +320 -0
- package/build/index.min.js +1 -0
- package/build/micro-fe/dev/devNotify.d.ts +1 -0
- package/build/micro-fe/dev/devServer.d.ts +5 -0
- package/build/micro-fe/dev/index.d.ts +3 -0
- package/build/micro-fe/dev/reloadServer.d.ts +7 -0
- package/build/micro-fe/event-bus/EventBus.d.ts +7 -0
- package/build/micro-fe/host/MFEHost.d.ts +28 -0
- package/build/micro-fe/host/RemoteRegistry.d.ts +13 -0
- package/build/micro-fe/host/routerSync.d.ts +1 -0
- package/build/micro-fe/loader/loadScript.d.ts +6 -0
- package/build/micro-fe/router/createSharedRouter.d.ts +9 -0
- package/build/micro-fe/router/events.d.ts +4 -0
- package/build/micro-fe/store/createSharedStore.d.ts +6 -0
- package/build/micro-fe/types/MFEContext.d.ts +10 -0
- package/build/micro-fe/types/RemoteApp.d.ts +6 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Delpi.Kye
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<h1>mfe-runtime-z</h1>
|
|
3
|
+
<p>Framework-agnostic micro-frontend runtime</p>
|
|
4
|
+
<a href="https://codesandbox.io/p/sandbox/c57cwd" target="_blank">LIVE EXAMPLE</a>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
[](https://www.npmjs.com/package/mfe-runtime-z)
|
|
11
|
+
[](https://standardjs.com)
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
### Description
|
|
17
|
+
|
|
18
|
+
**mfe-runtime-z** is a lightweight, framework-agnostic **micro-frontend runtime**.
|
|
19
|
+
|
|
20
|
+
It provides the minimal building blocks needed to run multiple independent frontend applications (micro-frontends) together **at runtime**, without coupling them through framework internals or build-time federation.
|
|
21
|
+
|
|
22
|
+
This library focuses on **application-level integration**, not component sharing.
|
|
23
|
+
|
|
24
|
+
#### What it solves
|
|
25
|
+
|
|
26
|
+
- Load and mount remote applications dynamically
|
|
27
|
+
- Isolate application lifecycles (mount / unmount / reload)
|
|
28
|
+
- Share state safely between apps
|
|
29
|
+
- Synchronize routing without sharing router instances
|
|
30
|
+
- Communicate via a central event bus
|
|
31
|
+
- Work with **any framework** (React, Vue, Svelte, Vanilla JS)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
### Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install mfe-runtime-z
|
|
39
|
+
# or
|
|
40
|
+
yarn add mfe-runtime-z
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### Core Concepts
|
|
46
|
+
|
|
47
|
+
##### Host-driven architecture
|
|
48
|
+
|
|
49
|
+
- Each micro-frontend is an **independent app**
|
|
50
|
+
- The host coordinates loading, routing, and shared services
|
|
51
|
+
- No direct imports between micro-frontends
|
|
52
|
+
|
|
53
|
+
##### Runtime contract
|
|
54
|
+
|
|
55
|
+
Micro-frontends expose a simple runtime contract:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
window.myRemoteApp = {
|
|
59
|
+
mount(el, ctx),
|
|
60
|
+
unmount(el)
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
No framework or bundler assumptions.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### Usage
|
|
69
|
+
|
|
70
|
+
##### Host application
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { MFEHost, createSharedStore } from "mfe-runtime-z"
|
|
74
|
+
|
|
75
|
+
const authStore = createSharedStore({
|
|
76
|
+
user: null
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const host = new MFEHost({
|
|
80
|
+
stores: { auth: authStore },
|
|
81
|
+
navigate: (path) => history.pushState({}, "", path)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const remote = await host.load(
|
|
85
|
+
"http://localhost:3001/remote.js",
|
|
86
|
+
"productApp"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
host.mount(remote, document.getElementById("app"), "productApp")
|
|
90
|
+
|
|
91
|
+
// host.unmount(remote, document.getElementById("app"), "productApp")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
##### Remote application (framework-agnostic)
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
export function mount(el, ctx) {
|
|
100
|
+
const authStore = ctx.stores.auth
|
|
101
|
+
|
|
102
|
+
authStore.subscribe((state) => {
|
|
103
|
+
el.innerHTML = state.user
|
|
104
|
+
? `Hello ${state.user.name}`
|
|
105
|
+
: `<button id="login">Login</button>`
|
|
106
|
+
|
|
107
|
+
el.querySelector("#login")?.addEventListener("click", () => {
|
|
108
|
+
authStore.setState({
|
|
109
|
+
user: { id: "1", name: "Alice" }
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function unmount(el) {
|
|
116
|
+
el.innerHTML = ""
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
window.productApp = { mount, unmount }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
##### Remote app (framework-react-library)
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// src/app.tsx
|
|
126
|
+
import React from "react"
|
|
127
|
+
import ReactDOM from "react-dom/client"
|
|
128
|
+
|
|
129
|
+
type AuthStore = {
|
|
130
|
+
user: { id: string; name: string } | null
|
|
131
|
+
subscribe: (callback: (state: any) => void) => void
|
|
132
|
+
setState: (newState: any) => void
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// React component
|
|
136
|
+
function App({ store }: { store: AuthStore }) {
|
|
137
|
+
const [user, setUser] = React.useState(store.user)
|
|
138
|
+
|
|
139
|
+
React.useEffect(() => {
|
|
140
|
+
const unsubscribe = store.subscribe((state: any) => {
|
|
141
|
+
setUser(state.user)
|
|
142
|
+
})
|
|
143
|
+
return () => unsubscribe?.()
|
|
144
|
+
}, [store])
|
|
145
|
+
|
|
146
|
+
const handleLogin = () => {
|
|
147
|
+
store.setState({ user: { id: "1", name: "Alice" } })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div>
|
|
152
|
+
{user ? (
|
|
153
|
+
<span>Hello {user.name}</span>
|
|
154
|
+
) : (
|
|
155
|
+
<button id="login" onClick={handleLogin}>
|
|
156
|
+
Login
|
|
157
|
+
</button>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// index.js
|
|
164
|
+
// mount/unmount functions
|
|
165
|
+
export function mount(el: HTMLElement, ctx: { stores: { auth: AuthStore } }) {
|
|
166
|
+
const root = ReactDOM.createRoot(el)
|
|
167
|
+
;(el as any)._reactRoot = root
|
|
168
|
+
root.render(<App store={ctx.stores.auth} />)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function unmount(el: HTMLElement) {
|
|
172
|
+
const root = (el as any)._reactRoot
|
|
173
|
+
if (root) {
|
|
174
|
+
root.unmount()
|
|
175
|
+
}
|
|
176
|
+
el.innerHTML = ""
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
// expose mount/unmount to window
|
|
181
|
+
;(window as any).productApp = { mount, unmount }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### Shared State
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
const store = createSharedStore({ count: 0 })
|
|
190
|
+
|
|
191
|
+
store.subscribe((state) => {
|
|
192
|
+
console.log(state.count)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
store.setState({ count: 1 })
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- Push-based
|
|
199
|
+
- No Redux
|
|
200
|
+
- No shared framework state
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
### Router Synchronization
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { createSharedRouter } from "mfe-runtime-z"
|
|
208
|
+
|
|
209
|
+
const router = createSharedRouter(ctx)
|
|
210
|
+
|
|
211
|
+
router.go("/cart")
|
|
212
|
+
|
|
213
|
+
router.onChange((path) => {
|
|
214
|
+
console.log("navigated to", path)
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- Intent-based navigation
|
|
219
|
+
- Host owns the URL
|
|
220
|
+
- No shared router instances
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### How remote applications are built
|
|
225
|
+
|
|
226
|
+
Remote applications only need to build a single JavaScript file. No Module Federation required.
|
|
227
|
+
- What a remote must provide
|
|
228
|
+
- Build a browser-loadable file (e.g. remote.js)
|
|
229
|
+
- Expose itself on window
|
|
230
|
+
- Implement mount / unmount
|
|
231
|
+
- Minimal remote build (Vite)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
// vite.config.ts
|
|
236
|
+
export default {
|
|
237
|
+
build: {
|
|
238
|
+
lib: {
|
|
239
|
+
entry: "src/index.ts",
|
|
240
|
+
name: "productApp",
|
|
241
|
+
formats: ["umd"],
|
|
242
|
+
fileName: () => "remote.js",
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/index.ts
|
|
248
|
+
import { mount, unmount } from "./app"
|
|
249
|
+
|
|
250
|
+
// see above
|
|
251
|
+
;(window as any).productApp = { mount, unmount }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
👉 That’s it. `This file can be served from any CDN or server and loaded at runtime.`
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
#### Dev HMR section
|
|
259
|
+
|
|
260
|
+
###### Host dev reload
|
|
261
|
+
```js
|
|
262
|
+
import { MFEHost } from "mfe-runtime-z"
|
|
263
|
+
import { createMFEReloadServer } from "mfe-runtime-z/dev"
|
|
264
|
+
|
|
265
|
+
// Initialize the MFE host runtime
|
|
266
|
+
const host = new MFEHost()
|
|
267
|
+
|
|
268
|
+
// Create a dev reload server to automatically reload remote apps on changes
|
|
269
|
+
const startDevReload = createMFEReloadServer(host, {
|
|
270
|
+
productApp: {
|
|
271
|
+
url: "http://localhost:3001/remote.js", // URL of the remote JS file
|
|
272
|
+
global: "productApp", // Name of the global mount/unmount object exposed by remote
|
|
273
|
+
el: () => document.getElementById("product-root")!, // DOM container to mount the remote
|
|
274
|
+
},
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
// Only start the dev reload server in development mode
|
|
278
|
+
if (process.env.NODE_ENV === "development") {
|
|
279
|
+
startDevReload()
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
###### Remote dev notify.
|
|
285
|
+
```ts
|
|
286
|
+
// Only run in development
|
|
287
|
+
if (import.meta.env.DEV) {
|
|
288
|
+
// Connect to the host's WebSocket dev reload server
|
|
289
|
+
const ws = new WebSocket("ws://localhost:3000/__mfe_reload")
|
|
290
|
+
|
|
291
|
+
// Accept HMR updates from Vite
|
|
292
|
+
import.meta.hot?.accept(() => {
|
|
293
|
+
// Notify the host that this remote app has changed and should be reloaded
|
|
294
|
+
ws.send(JSON.stringify({ type: "RELOAD", name: "productApp" }))
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
✅ Summary of Comments
|
|
300
|
+
|
|
301
|
+
- Host side: sets up a WebSocket-based dev reload server that listens for changes from remotes and automatically reloads them without refreshing the host page.
|
|
302
|
+
|
|
303
|
+
- Remote side: connects to host WebSocket and sends a message when HMR triggers (code changed), so the host can remount the updated remote.
|
|
304
|
+
|
|
305
|
+
- Purpose: smooth dev experience for micro-frontends, framework-agnostic, avoids full page reload.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
### Features
|
|
310
|
+
|
|
311
|
+
- Framework-agnostic micro-frontend runtime
|
|
312
|
+
- Dynamic remote loading
|
|
313
|
+
- Safe lifecycle management
|
|
314
|
+
- Central event bus
|
|
315
|
+
- Shared state with subscriptions
|
|
316
|
+
- Router synchronization
|
|
317
|
+
- Hot reload support for remote apps
|
|
318
|
+
- No build-time federation required
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
### What this library is NOT
|
|
323
|
+
|
|
324
|
+
- ❌ Not a UI component library
|
|
325
|
+
- ❌ Not a replacement for React/Vue routers
|
|
326
|
+
- ❌ Not a build-time module federation tool
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### License
|
|
331
|
+
|
|
332
|
+
MIT
|
package/build/dev.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./micro-fe/dev";
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { MFEHost } from "./micro-fe/host/MFEHost";
|
|
2
|
+
export { enableRouterSync } from "./micro-fe/host/routerSync";
|
|
3
|
+
export { loadScript } from "./micro-fe/loader/loadScript";
|
|
4
|
+
export { EventBus } from "./micro-fe/event-bus/EventBus";
|
|
5
|
+
export { createSharedStore } from "./micro-fe/store/createSharedStore";
|
|
6
|
+
export { createSharedRouter } from "./micro-fe/router/createSharedRouter";
|
|
7
|
+
export type { RemoteApp } from "./micro-fe/types/RemoteApp";
|
|
8
|
+
export type { MFEContext } from "./micro-fe/types/MFEContext";
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
Copyright (c) Microsoft Corporation.
|
|
3
|
+
|
|
4
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
5
|
+
purpose with or without fee is hereby granted.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
8
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
9
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
10
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
11
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
12
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
13
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
14
|
+
***************************************************************************** */
|
|
15
|
+
|
|
16
|
+
var __assign = function() {
|
|
17
|
+
__assign = Object.assign || function __assign(t) {
|
|
18
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
19
|
+
s = arguments[i];
|
|
20
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
|
21
|
+
}
|
|
22
|
+
return t;
|
|
23
|
+
};
|
|
24
|
+
return __assign.apply(this, arguments);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
28
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
29
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
30
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
31
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
32
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
33
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function __generator(thisArg, body) {
|
|
38
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
39
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
40
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
41
|
+
function step(op) {
|
|
42
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
43
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
44
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
45
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
46
|
+
switch (op[0]) {
|
|
47
|
+
case 0: case 1: t = op; break;
|
|
48
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
49
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
50
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
51
|
+
default:
|
|
52
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
53
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
54
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
55
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
56
|
+
if (t[2]) _.ops.pop();
|
|
57
|
+
_.trys.pop(); continue;
|
|
58
|
+
}
|
|
59
|
+
op = body.call(thisArg, _);
|
|
60
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
61
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
66
|
+
var e = new Error(message);
|
|
67
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// loader/loadScript.ts
|
|
71
|
+
var loaded = new Set();
|
|
72
|
+
function loadScript(url, options) {
|
|
73
|
+
if (options === void 0) { options = {}; }
|
|
74
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
75
|
+
var _a, timeout, _b, retries, attempt, err_1;
|
|
76
|
+
return __generator(this, function (_c) {
|
|
77
|
+
switch (_c.label) {
|
|
78
|
+
case 0:
|
|
79
|
+
_a = options.timeout, timeout = _a === void 0 ? 15000 : _a, _b = options.retries, retries = _b === void 0 ? 1 : _b;
|
|
80
|
+
if (loaded.has(url))
|
|
81
|
+
return [2 /*return*/];
|
|
82
|
+
attempt = 0;
|
|
83
|
+
_c.label = 1;
|
|
84
|
+
case 1:
|
|
85
|
+
if (!(attempt <= retries)) return [3 /*break*/, 6];
|
|
86
|
+
_c.label = 2;
|
|
87
|
+
case 2:
|
|
88
|
+
_c.trys.push([2, 4, , 5]);
|
|
89
|
+
return [4 /*yield*/, loadOnce(url, timeout)];
|
|
90
|
+
case 3:
|
|
91
|
+
_c.sent();
|
|
92
|
+
loaded.add(url);
|
|
93
|
+
return [2 /*return*/];
|
|
94
|
+
case 4:
|
|
95
|
+
err_1 = _c.sent();
|
|
96
|
+
if (attempt === retries) {
|
|
97
|
+
throw err_1;
|
|
98
|
+
}
|
|
99
|
+
return [3 /*break*/, 5];
|
|
100
|
+
case 5:
|
|
101
|
+
attempt++;
|
|
102
|
+
return [3 /*break*/, 1];
|
|
103
|
+
case 6: return [2 /*return*/];
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function loadOnce(url, timeout) {
|
|
109
|
+
return new Promise(function (resolve, reject) {
|
|
110
|
+
var script = document.createElement("script");
|
|
111
|
+
script.src = url;
|
|
112
|
+
script.async = true;
|
|
113
|
+
var timer = setTimeout(function () {
|
|
114
|
+
cleanup();
|
|
115
|
+
reject(new Error("Timeout loading ".concat(url)));
|
|
116
|
+
}, timeout);
|
|
117
|
+
function cleanup() {
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
script.remove();
|
|
120
|
+
}
|
|
121
|
+
script.onload = function () {
|
|
122
|
+
cleanup();
|
|
123
|
+
resolve();
|
|
124
|
+
};
|
|
125
|
+
script.onerror = function () {
|
|
126
|
+
cleanup();
|
|
127
|
+
reject(new Error("Failed to load ".concat(url)));
|
|
128
|
+
};
|
|
129
|
+
document.body.appendChild(script);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
var EventBus = /** @class */ (function () {
|
|
134
|
+
function EventBus() {
|
|
135
|
+
this.map = new Map();
|
|
136
|
+
}
|
|
137
|
+
EventBus.prototype.on = function (event, handler) {
|
|
138
|
+
if (!this.map.has(event)) {
|
|
139
|
+
this.map.set(event, new Set());
|
|
140
|
+
}
|
|
141
|
+
this.map.get(event).add(handler);
|
|
142
|
+
};
|
|
143
|
+
EventBus.prototype.off = function (event, handler) {
|
|
144
|
+
var _a;
|
|
145
|
+
(_a = this.map.get(event)) === null || _a === void 0 ? void 0 : _a.delete(handler);
|
|
146
|
+
};
|
|
147
|
+
EventBus.prototype.emit = function (event, payload) {
|
|
148
|
+
var _a;
|
|
149
|
+
(_a = this.map.get(event)) === null || _a === void 0 ? void 0 : _a.forEach(function (fn) { return fn(payload); });
|
|
150
|
+
};
|
|
151
|
+
return EventBus;
|
|
152
|
+
}());
|
|
153
|
+
|
|
154
|
+
var ROUTER_EVENTS = {
|
|
155
|
+
REQUEST_NAVIGATE: "ROUTER:REQUEST_NAVIGATE",
|
|
156
|
+
NAVIGATE: "ROUTER:NAVIGATE",
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
function enableRouterSync(eventBus, navigate) {
|
|
160
|
+
eventBus.on(ROUTER_EVENTS.REQUEST_NAVIGATE, function (_a) {
|
|
161
|
+
var path = _a.path;
|
|
162
|
+
navigate(path);
|
|
163
|
+
eventBus.emit(ROUTER_EVENTS.NAVIGATE, { path: path });
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
var MFEHost = /** @class */ (function () {
|
|
168
|
+
function MFEHost(options) {
|
|
169
|
+
if (options === void 0) { options = {}; }
|
|
170
|
+
var _a;
|
|
171
|
+
this.eventBus = new EventBus();
|
|
172
|
+
this.stores = (_a = options.stores) !== null && _a !== void 0 ? _a : {};
|
|
173
|
+
this.navigate = options.navigate;
|
|
174
|
+
this.onRemoteError = options.onRemoteError;
|
|
175
|
+
if (this.navigate) {
|
|
176
|
+
enableRouterSync(this.eventBus, this.navigate);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** load script only */
|
|
180
|
+
MFEHost.prototype.load = function (url, global) {
|
|
181
|
+
var _a, _b;
|
|
182
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
183
|
+
var err_1, remote, err;
|
|
184
|
+
return __generator(this, function (_c) {
|
|
185
|
+
switch (_c.label) {
|
|
186
|
+
case 0:
|
|
187
|
+
_c.trys.push([0, 2, , 3]);
|
|
188
|
+
return [4 /*yield*/, loadScript(url, { retries: 1 })];
|
|
189
|
+
case 1:
|
|
190
|
+
_c.sent();
|
|
191
|
+
return [3 /*break*/, 3];
|
|
192
|
+
case 2:
|
|
193
|
+
err_1 = _c.sent();
|
|
194
|
+
(_a = this.onRemoteError) === null || _a === void 0 ? void 0 : _a.call(this, err_1);
|
|
195
|
+
throw err_1;
|
|
196
|
+
case 3:
|
|
197
|
+
remote = window[global];
|
|
198
|
+
if (!(remote === null || remote === void 0 ? void 0 : remote.mount)) {
|
|
199
|
+
err = new Error("Remote \"".concat(global, "\" not found"));
|
|
200
|
+
(_b = this.onRemoteError) === null || _b === void 0 ? void 0 : _b.call(this, err);
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
return [2 /*return*/, remote];
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
/** mount with identity */
|
|
209
|
+
MFEHost.prototype.mount = function (remote, el, name) {
|
|
210
|
+
var _a;
|
|
211
|
+
var ctx = {
|
|
212
|
+
name: name,
|
|
213
|
+
eventBus: this.eventBus,
|
|
214
|
+
stores: this.stores,
|
|
215
|
+
host: {
|
|
216
|
+
navigate: this.navigate,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
try {
|
|
220
|
+
remote.mount(el, ctx);
|
|
221
|
+
console.log("[MFE] mounted ".concat(name));
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
(_a = this.onRemoteError) === null || _a === void 0 ? void 0 : _a.call(this, err);
|
|
225
|
+
throw err;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
MFEHost.prototype.unmount = function (remote, el, name) {
|
|
229
|
+
var _a, _b;
|
|
230
|
+
try {
|
|
231
|
+
(_a = remote.unmount) === null || _a === void 0 ? void 0 : _a.call(remote, el);
|
|
232
|
+
name && console.log("[MFE] unmounted ".concat(name));
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
(_b = this.onRemoteError) === null || _b === void 0 ? void 0 : _b.call(this, err);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
MFEHost.prototype.getEventBus = function () {
|
|
239
|
+
return this.eventBus;
|
|
240
|
+
};
|
|
241
|
+
/** 🔥 reload = unmount + reload script + mount (KEEP name) */
|
|
242
|
+
MFEHost.prototype.reloadRemote = function (options) {
|
|
243
|
+
var _a;
|
|
244
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
245
|
+
var name, url, global, el, oldRemote, newRemote;
|
|
246
|
+
return __generator(this, function (_b) {
|
|
247
|
+
switch (_b.label) {
|
|
248
|
+
case 0:
|
|
249
|
+
name = options.name, url = options.url, global = options.global, el = options.el;
|
|
250
|
+
oldRemote = window[global];
|
|
251
|
+
try {
|
|
252
|
+
(_a = oldRemote === null || oldRemote === void 0 ? void 0 : oldRemote.unmount) === null || _a === void 0 ? void 0 : _a.call(oldRemote, el);
|
|
253
|
+
}
|
|
254
|
+
catch (_c) { }
|
|
255
|
+
delete window[global];
|
|
256
|
+
return [4 /*yield*/, this.load("".concat(url, "?t=").concat(Date.now()), global)];
|
|
257
|
+
case 1:
|
|
258
|
+
newRemote = _b.sent();
|
|
259
|
+
this.mount(newRemote, el, name);
|
|
260
|
+
return [2 /*return*/];
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
};
|
|
265
|
+
return MFEHost;
|
|
266
|
+
}());
|
|
267
|
+
|
|
268
|
+
function createSharedStore(initial) {
|
|
269
|
+
var state = initial;
|
|
270
|
+
var listeners = new Set();
|
|
271
|
+
return {
|
|
272
|
+
getState: function () {
|
|
273
|
+
return state;
|
|
274
|
+
},
|
|
275
|
+
setState: function (partial) {
|
|
276
|
+
state = __assign(__assign({}, state), partial);
|
|
277
|
+
listeners.forEach(function (l) { return l(state); });
|
|
278
|
+
},
|
|
279
|
+
subscribe: function (fn) {
|
|
280
|
+
listeners.add(fn);
|
|
281
|
+
return function () { return listeners.delete(fn); };
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function createSharedRouter(ctx) {
|
|
287
|
+
var eventBus = ctx.eventBus, name = ctx.name;
|
|
288
|
+
return {
|
|
289
|
+
/** Remote → Host */
|
|
290
|
+
go: function (path) {
|
|
291
|
+
eventBus.emit(ROUTER_EVENTS.REQUEST_NAVIGATE, {
|
|
292
|
+
from: name,
|
|
293
|
+
path: path,
|
|
294
|
+
});
|
|
295
|
+
},
|
|
296
|
+
/** Host → Remote */
|
|
297
|
+
onChange: function (cb) {
|
|
298
|
+
return eventBus.on(ROUTER_EVENTS.NAVIGATE, function (payload) {
|
|
299
|
+
cb(payload.path);
|
|
300
|
+
});
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export { EventBus, MFEHost, createSharedRouter, createSharedStore, enableRouterSync, loadScript };
|
package/build/index.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MFERuntimeZ = {}));
|
|
5
|
+
})(this, (function (exports) { 'use strict';
|
|
6
|
+
|
|
7
|
+
/******************************************************************************
|
|
8
|
+
Copyright (c) Microsoft Corporation.
|
|
9
|
+
|
|
10
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
11
|
+
purpose with or without fee is hereby granted.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
14
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
15
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
16
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
17
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
18
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
19
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
20
|
+
***************************************************************************** */
|
|
21
|
+
|
|
22
|
+
var __assign = function() {
|
|
23
|
+
__assign = Object.assign || function __assign(t) {
|
|
24
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
25
|
+
s = arguments[i];
|
|
26
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
|
27
|
+
}
|
|
28
|
+
return t;
|
|
29
|
+
};
|
|
30
|
+
return __assign.apply(this, arguments);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
34
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
35
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
36
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
37
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
38
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
39
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function __generator(thisArg, body) {
|
|
44
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
45
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
46
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
47
|
+
function step(op) {
|
|
48
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
49
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
50
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
51
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
52
|
+
switch (op[0]) {
|
|
53
|
+
case 0: case 1: t = op; break;
|
|
54
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
55
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
56
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
57
|
+
default:
|
|
58
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
59
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
60
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
61
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
62
|
+
if (t[2]) _.ops.pop();
|
|
63
|
+
_.trys.pop(); continue;
|
|
64
|
+
}
|
|
65
|
+
op = body.call(thisArg, _);
|
|
66
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
67
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
72
|
+
var e = new Error(message);
|
|
73
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// loader/loadScript.ts
|
|
77
|
+
var loaded = new Set();
|
|
78
|
+
function loadScript(url, options) {
|
|
79
|
+
if (options === void 0) { options = {}; }
|
|
80
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
81
|
+
var _a, timeout, _b, retries, attempt, err_1;
|
|
82
|
+
return __generator(this, function (_c) {
|
|
83
|
+
switch (_c.label) {
|
|
84
|
+
case 0:
|
|
85
|
+
_a = options.timeout, timeout = _a === void 0 ? 15000 : _a, _b = options.retries, retries = _b === void 0 ? 1 : _b;
|
|
86
|
+
if (loaded.has(url))
|
|
87
|
+
return [2 /*return*/];
|
|
88
|
+
attempt = 0;
|
|
89
|
+
_c.label = 1;
|
|
90
|
+
case 1:
|
|
91
|
+
if (!(attempt <= retries)) return [3 /*break*/, 6];
|
|
92
|
+
_c.label = 2;
|
|
93
|
+
case 2:
|
|
94
|
+
_c.trys.push([2, 4, , 5]);
|
|
95
|
+
return [4 /*yield*/, loadOnce(url, timeout)];
|
|
96
|
+
case 3:
|
|
97
|
+
_c.sent();
|
|
98
|
+
loaded.add(url);
|
|
99
|
+
return [2 /*return*/];
|
|
100
|
+
case 4:
|
|
101
|
+
err_1 = _c.sent();
|
|
102
|
+
if (attempt === retries) {
|
|
103
|
+
throw err_1;
|
|
104
|
+
}
|
|
105
|
+
return [3 /*break*/, 5];
|
|
106
|
+
case 5:
|
|
107
|
+
attempt++;
|
|
108
|
+
return [3 /*break*/, 1];
|
|
109
|
+
case 6: return [2 /*return*/];
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function loadOnce(url, timeout) {
|
|
115
|
+
return new Promise(function (resolve, reject) {
|
|
116
|
+
var script = document.createElement("script");
|
|
117
|
+
script.src = url;
|
|
118
|
+
script.async = true;
|
|
119
|
+
var timer = setTimeout(function () {
|
|
120
|
+
cleanup();
|
|
121
|
+
reject(new Error("Timeout loading ".concat(url)));
|
|
122
|
+
}, timeout);
|
|
123
|
+
function cleanup() {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
script.remove();
|
|
126
|
+
}
|
|
127
|
+
script.onload = function () {
|
|
128
|
+
cleanup();
|
|
129
|
+
resolve();
|
|
130
|
+
};
|
|
131
|
+
script.onerror = function () {
|
|
132
|
+
cleanup();
|
|
133
|
+
reject(new Error("Failed to load ".concat(url)));
|
|
134
|
+
};
|
|
135
|
+
document.body.appendChild(script);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
var EventBus = /** @class */ (function () {
|
|
140
|
+
function EventBus() {
|
|
141
|
+
this.map = new Map();
|
|
142
|
+
}
|
|
143
|
+
EventBus.prototype.on = function (event, handler) {
|
|
144
|
+
if (!this.map.has(event)) {
|
|
145
|
+
this.map.set(event, new Set());
|
|
146
|
+
}
|
|
147
|
+
this.map.get(event).add(handler);
|
|
148
|
+
};
|
|
149
|
+
EventBus.prototype.off = function (event, handler) {
|
|
150
|
+
var _a;
|
|
151
|
+
(_a = this.map.get(event)) === null || _a === void 0 ? void 0 : _a.delete(handler);
|
|
152
|
+
};
|
|
153
|
+
EventBus.prototype.emit = function (event, payload) {
|
|
154
|
+
var _a;
|
|
155
|
+
(_a = this.map.get(event)) === null || _a === void 0 ? void 0 : _a.forEach(function (fn) { return fn(payload); });
|
|
156
|
+
};
|
|
157
|
+
return EventBus;
|
|
158
|
+
}());
|
|
159
|
+
|
|
160
|
+
var ROUTER_EVENTS = {
|
|
161
|
+
REQUEST_NAVIGATE: "ROUTER:REQUEST_NAVIGATE",
|
|
162
|
+
NAVIGATE: "ROUTER:NAVIGATE",
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
function enableRouterSync(eventBus, navigate) {
|
|
166
|
+
eventBus.on(ROUTER_EVENTS.REQUEST_NAVIGATE, function (_a) {
|
|
167
|
+
var path = _a.path;
|
|
168
|
+
navigate(path);
|
|
169
|
+
eventBus.emit(ROUTER_EVENTS.NAVIGATE, { path: path });
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
var MFEHost = /** @class */ (function () {
|
|
174
|
+
function MFEHost(options) {
|
|
175
|
+
if (options === void 0) { options = {}; }
|
|
176
|
+
var _a;
|
|
177
|
+
this.eventBus = new EventBus();
|
|
178
|
+
this.stores = (_a = options.stores) !== null && _a !== void 0 ? _a : {};
|
|
179
|
+
this.navigate = options.navigate;
|
|
180
|
+
this.onRemoteError = options.onRemoteError;
|
|
181
|
+
if (this.navigate) {
|
|
182
|
+
enableRouterSync(this.eventBus, this.navigate);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** load script only */
|
|
186
|
+
MFEHost.prototype.load = function (url, global) {
|
|
187
|
+
var _a, _b;
|
|
188
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
189
|
+
var err_1, remote, err;
|
|
190
|
+
return __generator(this, function (_c) {
|
|
191
|
+
switch (_c.label) {
|
|
192
|
+
case 0:
|
|
193
|
+
_c.trys.push([0, 2, , 3]);
|
|
194
|
+
return [4 /*yield*/, loadScript(url, { retries: 1 })];
|
|
195
|
+
case 1:
|
|
196
|
+
_c.sent();
|
|
197
|
+
return [3 /*break*/, 3];
|
|
198
|
+
case 2:
|
|
199
|
+
err_1 = _c.sent();
|
|
200
|
+
(_a = this.onRemoteError) === null || _a === void 0 ? void 0 : _a.call(this, err_1);
|
|
201
|
+
throw err_1;
|
|
202
|
+
case 3:
|
|
203
|
+
remote = window[global];
|
|
204
|
+
if (!(remote === null || remote === void 0 ? void 0 : remote.mount)) {
|
|
205
|
+
err = new Error("Remote \"".concat(global, "\" not found"));
|
|
206
|
+
(_b = this.onRemoteError) === null || _b === void 0 ? void 0 : _b.call(this, err);
|
|
207
|
+
throw err;
|
|
208
|
+
}
|
|
209
|
+
return [2 /*return*/, remote];
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
/** mount with identity */
|
|
215
|
+
MFEHost.prototype.mount = function (remote, el, name) {
|
|
216
|
+
var _a;
|
|
217
|
+
var ctx = {
|
|
218
|
+
name: name,
|
|
219
|
+
eventBus: this.eventBus,
|
|
220
|
+
stores: this.stores,
|
|
221
|
+
host: {
|
|
222
|
+
navigate: this.navigate,
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
try {
|
|
226
|
+
remote.mount(el, ctx);
|
|
227
|
+
console.log("[MFE] mounted ".concat(name));
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
(_a = this.onRemoteError) === null || _a === void 0 ? void 0 : _a.call(this, err);
|
|
231
|
+
throw err;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
MFEHost.prototype.unmount = function (remote, el, name) {
|
|
235
|
+
var _a, _b;
|
|
236
|
+
try {
|
|
237
|
+
(_a = remote.unmount) === null || _a === void 0 ? void 0 : _a.call(remote, el);
|
|
238
|
+
name && console.log("[MFE] unmounted ".concat(name));
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
(_b = this.onRemoteError) === null || _b === void 0 ? void 0 : _b.call(this, err);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
MFEHost.prototype.getEventBus = function () {
|
|
245
|
+
return this.eventBus;
|
|
246
|
+
};
|
|
247
|
+
/** 🔥 reload = unmount + reload script + mount (KEEP name) */
|
|
248
|
+
MFEHost.prototype.reloadRemote = function (options) {
|
|
249
|
+
var _a;
|
|
250
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
251
|
+
var name, url, global, el, oldRemote, newRemote;
|
|
252
|
+
return __generator(this, function (_b) {
|
|
253
|
+
switch (_b.label) {
|
|
254
|
+
case 0:
|
|
255
|
+
name = options.name, url = options.url, global = options.global, el = options.el;
|
|
256
|
+
oldRemote = window[global];
|
|
257
|
+
try {
|
|
258
|
+
(_a = oldRemote === null || oldRemote === void 0 ? void 0 : oldRemote.unmount) === null || _a === void 0 ? void 0 : _a.call(oldRemote, el);
|
|
259
|
+
}
|
|
260
|
+
catch (_c) { }
|
|
261
|
+
delete window[global];
|
|
262
|
+
return [4 /*yield*/, this.load("".concat(url, "?t=").concat(Date.now()), global)];
|
|
263
|
+
case 1:
|
|
264
|
+
newRemote = _b.sent();
|
|
265
|
+
this.mount(newRemote, el, name);
|
|
266
|
+
return [2 /*return*/];
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
return MFEHost;
|
|
272
|
+
}());
|
|
273
|
+
|
|
274
|
+
function createSharedStore(initial) {
|
|
275
|
+
var state = initial;
|
|
276
|
+
var listeners = new Set();
|
|
277
|
+
return {
|
|
278
|
+
getState: function () {
|
|
279
|
+
return state;
|
|
280
|
+
},
|
|
281
|
+
setState: function (partial) {
|
|
282
|
+
state = __assign(__assign({}, state), partial);
|
|
283
|
+
listeners.forEach(function (l) { return l(state); });
|
|
284
|
+
},
|
|
285
|
+
subscribe: function (fn) {
|
|
286
|
+
listeners.add(fn);
|
|
287
|
+
return function () { return listeners.delete(fn); };
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function createSharedRouter(ctx) {
|
|
293
|
+
var eventBus = ctx.eventBus, name = ctx.name;
|
|
294
|
+
return {
|
|
295
|
+
/** Remote → Host */
|
|
296
|
+
go: function (path) {
|
|
297
|
+
eventBus.emit(ROUTER_EVENTS.REQUEST_NAVIGATE, {
|
|
298
|
+
from: name,
|
|
299
|
+
path: path,
|
|
300
|
+
});
|
|
301
|
+
},
|
|
302
|
+
/** Host → Remote */
|
|
303
|
+
onChange: function (cb) {
|
|
304
|
+
return eventBus.on(ROUTER_EVENTS.NAVIGATE, function (payload) {
|
|
305
|
+
cb(payload.path);
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
exports.EventBus = EventBus;
|
|
312
|
+
exports.MFEHost = MFEHost;
|
|
313
|
+
exports.createSharedRouter = createSharedRouter;
|
|
314
|
+
exports.createSharedStore = createSharedStore;
|
|
315
|
+
exports.enableRouterSync = enableRouterSync;
|
|
316
|
+
exports.loadScript = loadScript;
|
|
317
|
+
|
|
318
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
319
|
+
|
|
320
|
+
}));
|
|
@@ -0,0 +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 u(t){try{c(o.next(t))}catch(t){i(t)}}function a(t){try{c(o.throw(t))}catch(t){i(t)}}function c(t){var e;t.done?r(t.value):(e=t.value,e instanceof n?e:new n(function(t){t(e)})).then(u,a)}c((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:[]},u=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return u.next=a(0),u.throw=a(1),u.return=a(2),"function"==typeof Symbol&&(u[Symbol.iterator]=function(){return this}),u;function a(a){return function(c){return function(a){if(n)throw new TypeError("Generator is already executing.");for(;u&&(u=0,a[0]&&(i=0)),i;)try{if(n=1,o&&(r=2&a[0]?o.return:a[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,a[1])).done)return r;switch(o=0,r&&(a=[2&a[0],r.value]),a[0]){case 0:case 1:r=a;break;case 4:return i.label++,{value:a[1],done:!1};case 5:i.label++,o=a[1],a=[0];continue;case 7:a=i.ops.pop(),i.trys.pop();continue;default:if(!(r=i.trys,(r=r.length>0&&r[r.length-1])||6!==a[0]&&2!==a[0])){i=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]<r[3])){i.label=a[1];break}if(6===a[0]&&i.label<r[1]){i.label=r[1],r=a;break}if(r&&i.label<r[2]){i.label=r[2],i.ops.push(a);break}r[2]&&i.ops.pop(),i.trys.pop();continue}a=e.call(t,i)}catch(t){a=[6,t],o=0}finally{n=r=0}if(5&a[0])throw a[1];return{value:a[0]?a[1]:void 0,done:!0}}([a,c])}}}"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,a,c,s,l;return o(this,function(o){switch(o.label){case 0:if(n=e.timeout,i=void 0===n?15e3:n,a=e.retries,c=void 0===a?1:a,r.has(t))return[2];s=0,o.label=1;case 1:if(!(s<=c))return[3,6];o.label=2;case 2:return o.trys.push([2,4,,5]),[4,u(t,i)];case 3:return o.sent(),r.add(t),[2];case 4:if(l=o.sent(),s===c)throw l;return[3,5];case 5:return s++,[3,1];case 6:return[2]}})})}function u(t,e){return new Promise(function(n,o){var r=document.createElement("script");r.src=t,r.async=!0;var i=setTimeout(function(){u(),o(new Error("Timeout loading ".concat(t)))},e);function u(){clearTimeout(i),r.remove()}r.onload=function(){u(),n()},r.onerror=function(){u(),o(new Error("Failed to load ".concat(t)))},document.body.appendChild(r)})}var a=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}(),c="ROUTER:REQUEST_NAVIGATE",s="ROUTER:NAVIGATE";function l(t,e){t.on(c,function(n){var o=n.path;e(o),t.emit(s,{path:o})})}var f=function(){function t(t){var e;void 0===t&&(t={}),this.eventBus=new a,this.stores=null!==(e=t.stores)&&void 0!==e?e:{},this.navigate=t.navigate,this.onRemoteError=t.onRemoteError,this.navigate&&l(this.eventBus,this.navigate)}return t.prototype.load=function(t,e){var r,u;return n(this,void 0,void 0,function(){var n,a,c;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==(a=window[e])?void 0:a.mount))throw c=new Error('Remote "'.concat(e,'" not found')),null===(u=this.onRemoteError)||void 0===u||u.call(this,c),c;return[2,a]}})})},t.prototype.mount=function(t,e,n){var o,r={name:n,eventBus:this.eventBus,stores:this.stores,host:{navigate:this.navigate}};try{t.mount(e,r),console.log("[MFE] mounted ".concat(n))}catch(t){throw null===(o=this.onRemoteError)||void 0===o||o.call(this,t),t}},t.prototype.unmount=function(t,e,n){var o,r;try{null===(o=t.unmount)||void 0===o||o.call(t,e),n&&console.log("[MFE] unmounted ".concat(n))}catch(t){null===(r=this.onRemoteError)||void 0===r||r.call(this,t)}},t.prototype.getEventBus=function(){return this.eventBus},t.prototype.reloadRemote=function(t){var e;return n(this,void 0,void 0,function(){var n,r,i,u,a,c;return o(this,function(o){switch(o.label){case 0:n=t.name,r=t.url,i=t.global,u=t.el,a=window[i];try{null===(e=null==a?void 0:a.unmount)||void 0===e||e.call(a,u)}catch(t){}return delete window[i],[4,this.load("".concat(r,"?t=").concat(Date.now()),i)];case 1:return c=o.sent(),this.mount(c,u,n),[2]}})})},t}();t.EventBus=a,t.MFEHost=f,t.createSharedRouter=function(t){var e=t.eventBus,n=t.name;return{go:function(t){e.emit(c,{from:n,path:t})},onChange:function(t){return e.on(s,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){return o.add(t),function(){return o.delete(t)}}}},t.enableRouterSync=l,t.loadScript=i,Object.defineProperty(t,"__esModule",{value:!0})});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type Handler<T = any> = (payload: T) => void;
|
|
2
|
+
export declare class EventBus<Events extends Record<string, any> = any> {
|
|
3
|
+
private map;
|
|
4
|
+
on<K extends keyof Events>(event: K, handler: Handler<Events[K]>): void;
|
|
5
|
+
off<K extends keyof Events>(event: K, handler: Handler<Events[K]>): void;
|
|
6
|
+
emit<K extends keyof Events>(event: K, payload: Events[K]): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { EventBus } from "../event-bus/EventBus";
|
|
2
|
+
import type { RemoteApp } from "../types/RemoteApp";
|
|
3
|
+
type HostOptions = {
|
|
4
|
+
stores?: Record<string, any>;
|
|
5
|
+
navigate?: (path: string) => void;
|
|
6
|
+
onRemoteError?: (error: Error) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare class MFEHost {
|
|
9
|
+
private eventBus;
|
|
10
|
+
private stores;
|
|
11
|
+
private navigate?;
|
|
12
|
+
private onRemoteError?;
|
|
13
|
+
constructor(options?: HostOptions);
|
|
14
|
+
/** load script only */
|
|
15
|
+
load(url: string, global: string): Promise<RemoteApp>;
|
|
16
|
+
/** mount with identity */
|
|
17
|
+
mount(remote: RemoteApp, el: HTMLElement, name: string): void;
|
|
18
|
+
unmount(remote: RemoteApp, el: HTMLElement, name?: string): void;
|
|
19
|
+
getEventBus(): EventBus<any>;
|
|
20
|
+
/** 🔥 reload = unmount + reload script + mount (KEEP name) */
|
|
21
|
+
reloadRemote(options: {
|
|
22
|
+
name: string;
|
|
23
|
+
url: string;
|
|
24
|
+
global: string;
|
|
25
|
+
el: HTMLElement;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type RemoteConfig = {
|
|
2
|
+
name: string;
|
|
3
|
+
url: string;
|
|
4
|
+
global: string;
|
|
5
|
+
el: HTMLElement;
|
|
6
|
+
};
|
|
7
|
+
export declare class RemoteRegistry {
|
|
8
|
+
private remotes;
|
|
9
|
+
register(config: RemoteConfig): void;
|
|
10
|
+
get(name: string): RemoteConfig;
|
|
11
|
+
getAll(): any[];
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function enableRouterSync(eventBus: any, navigate: (path: string) => void): void;
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mfe-runtime-z",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Framework-agnostic micro-frontend runtime (host, event bus, shared state, router sync)",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"module": "build/index.esm.js",
|
|
7
|
+
"browser": "build/index.js",
|
|
8
|
+
"unpkg": "build/index.min.js",
|
|
9
|
+
"types": "build/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"build"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./build/index.esm.js",
|
|
16
|
+
"require": "./build/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./dev": {
|
|
19
|
+
"import": "./build/dev.esm.js",
|
|
20
|
+
"require": "./build/dev.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"clean": "rimraf build",
|
|
25
|
+
"build": "rollup -c",
|
|
26
|
+
"cb": "npm run clean && npm run build",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/delpikye-v/mfe-runtime-z.git"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/delpikye-v/mfe-runtime-z#readme",
|
|
34
|
+
"keywords": [
|
|
35
|
+
"micro-frontend",
|
|
36
|
+
"mfe",
|
|
37
|
+
"runtime",
|
|
38
|
+
"event-bus",
|
|
39
|
+
"shared-state",
|
|
40
|
+
"router-sync",
|
|
41
|
+
"framework-agnostic",
|
|
42
|
+
"micro-frontend-runtime",
|
|
43
|
+
"mfe-runtime"
|
|
44
|
+
],
|
|
45
|
+
"author": "Delpi.Kye",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/delpikye-v/mfe-runtime-z/issues"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@babel/core": "^7.15.0",
|
|
52
|
+
"@rollup/plugin-commonjs": "^17.1.0",
|
|
53
|
+
"@rollup/plugin-node-resolve": "^11.2.1",
|
|
54
|
+
"babel-loader": "^8.2.2",
|
|
55
|
+
"rimraf": "^3.0.2",
|
|
56
|
+
"rollup": "^2.56.3",
|
|
57
|
+
"rollup-plugin-copy": "^3.4.0",
|
|
58
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
59
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
60
|
+
"rollup-plugin-typescript2": "^0.29.0",
|
|
61
|
+
"tslib": "^2.3.1",
|
|
62
|
+
"typescript": "^4.4.2",
|
|
63
|
+
"ws": "^8.18.3"
|
|
64
|
+
},
|
|
65
|
+
"optionalDependencies": {
|
|
66
|
+
"ws": "^8.18.3"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"eventbus-z": "^2.1.0"
|
|
70
|
+
}
|
|
71
|
+
}
|