@granite-js/micro-frontend 2.1.0 → 2.2.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/CHANGELOG.md +17 -0
- package/README.md +31 -0
- package/dist/module/createMicroFrontendRuntime.js +1 -0
- package/dist/module/createMicroFrontendRuntime.js.map +1 -1
- package/dist/module/index.js.map +1 -1
- package/dist/module/runtime/createMicroFrontendRuntime.js +6 -1
- package/dist/module/runtime/createMicroFrontendRuntime.js.map +1 -1
- package/dist/module/runtime/lifecycle.js +22 -0
- package/dist/module/runtime/lifecycle.js.map +1 -0
- package/dist/module/runtime/nativeComponentRegistryCompatibility.js +53 -0
- package/dist/module/runtime/nativeComponentRegistryCompatibility.js.map +1 -0
- package/dist/module/session/useMicroFrontendSessions.js +35 -6
- package/dist/module/session/useMicroFrontendSessions.js.map +1 -1
- package/dist/typescript/createMicroFrontendRuntime.d.ts +2 -1
- package/dist/typescript/index.d.ts +1 -1
- package/dist/typescript/runtime/createMicroFrontendRuntime.d.ts +2 -1
- package/dist/typescript/runtime/lifecycle.d.ts +3 -0
- package/dist/typescript/runtime/nativeComponentRegistryCompatibility.d.ts +1 -0
- package/dist/typescript/types.d.ts +11 -0
- package/package.json +4 -4
- package/src/createMicroFrontendRuntime.ts +3 -1
- package/src/index.ts +3 -0
- package/src/runtime/createMicroFrontendRuntime.ts +10 -1
- package/src/runtime/lifecycle.ts +28 -0
- package/src/runtime/nativeComponentRegistryCompatibility.ts +60 -0
- package/src/session/useMicroFrontendSessions.ts +61 -9
- package/src/types.ts +14 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @granite-js/micro-frontend
|
|
2
2
|
|
|
3
|
+
## 2.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2513bd8: Allow hosts to observe committed micro-frontend session mount and unmount transitions through an `onLifecycleEvent` runtime option with session metadata and an active-session snapshot.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- @granite-js/utils@2.2.0
|
|
12
|
+
|
|
13
|
+
## 2.1.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 4f7aad6: Reuse host native component registrations when remote bundles evaluate matching native wrappers.
|
|
18
|
+
- @granite-js/utils@2.1.1
|
|
19
|
+
|
|
3
20
|
## 2.1.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -172,6 +172,9 @@ const runtime = createMicroFrontendRuntime({
|
|
|
172
172
|
return { filePath };
|
|
173
173
|
},
|
|
174
174
|
},
|
|
175
|
+
onLifecycleEvent(event) {
|
|
176
|
+
reportMicroFrontendLifecycle(event);
|
|
177
|
+
},
|
|
175
178
|
});
|
|
176
179
|
|
|
177
180
|
await runtime.preloadApp('cart');
|
|
@@ -193,6 +196,11 @@ adapter.loadBundle({ appName: 'cart' })
|
|
|
193
196
|
Concurrent preload/import calls for one app share an evaluation. A successful
|
|
194
197
|
evaluation remains cached for the lifetime of the JavaScript runtime. Failed
|
|
195
198
|
evaluation removes the partial container so a later request can retry.
|
|
199
|
+
Before evaluating a remote, the runtime also adapts the shared React Native
|
|
200
|
+
component registry so a native view wrapper already evaluated by the host can
|
|
201
|
+
be evaluated again by a separate remote bundle. The canonical host registration
|
|
202
|
+
is reused only for React Native's exact duplicate-view invariant; other registry
|
|
203
|
+
errors continue to fail the remote evaluation.
|
|
196
204
|
Native `preloadApp` events invoke `preloadApp()` inside the runtime and are not
|
|
197
205
|
forwarded to session listeners. `onPreloadError` is optional and lets the host
|
|
198
206
|
route best-effort native preload failures to its observability provider.
|
|
@@ -206,6 +214,29 @@ The public runtime API is:
|
|
|
206
214
|
| `evaluateScript(filePath)` | Evaluate an already-local bundle in the retained runtime. |
|
|
207
215
|
| `onEvent(listener)` | Subscribe to native open, close, and visibility events. |
|
|
208
216
|
|
|
217
|
+
The host can provide `onLifecycleEvent` when creating the runtime for logging
|
|
218
|
+
and other observability integrations. Each event contains `session.id`,
|
|
219
|
+
`session.appName`, and the `activeSessions` snapshot when the callback is
|
|
220
|
+
emitted. The callback has the same lifetime as the runtime.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
const runtime = createMicroFrontendRuntime({
|
|
224
|
+
adapter,
|
|
225
|
+
onLifecycleEvent(event) {
|
|
226
|
+
reportMicroFrontendLifecycle({
|
|
227
|
+
...event,
|
|
228
|
+
activeSessionCount: event.activeSessions.length,
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`mounted` is emitted after the session is committed to React state. `unmounted`
|
|
235
|
+
is emitted after the session is removed. When the session was the app's last
|
|
236
|
+
active session, Granite waits for the app's registered dispose callbacks before
|
|
237
|
+
emitting `unmounted`. Callback failures are reported without interrupting the
|
|
238
|
+
session lifecycle.
|
|
239
|
+
|
|
209
240
|
Remote code can register an idempotent callback for explicit app-level resource
|
|
210
241
|
cleanup:
|
|
211
242
|
|
|
@@ -9,6 +9,7 @@ export function createMicroFrontendRuntime(options) {
|
|
|
9
9
|
return createMicroFrontendRuntimeWithDependencies({
|
|
10
10
|
adapter: options.adapter,
|
|
11
11
|
nativeRuntime: NativeGraniteMicroFrontendRuntime,
|
|
12
|
+
onLifecycleEvent: options.onLifecycleEvent,
|
|
12
13
|
onPreloadError: options.onPreloadError ?? (() => undefined),
|
|
13
14
|
parseEvent: parseNativeRuntimeEvent,
|
|
14
15
|
registry: microFrontendModuleRegistry,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["removePendingHostComponentRoutes","createMicroFrontendRuntimeWithDependencies","parseNativeRuntimeEvent","microFrontendModuleRegistry","NativeGraniteMicroFrontendRuntime","createMicroFrontendRuntime","options","adapter","nativeRuntime","onPreloadError","undefined","parseEvent","registry"],"sourceRoot":"../../src","sources":["createMicroFrontendRuntime.ts"],"mappings":";;AAAA,SAASA,gCAAgC,QAAQ,qCAAkC;AACnF,SAASC,0CAA0C,QAAQ,yCAAsC;AACjG,SAASC,uBAAuB,QAAQ,sCAAmC;AAC3E,SAASC,2BAA2B,QAAQ,uBAAoB;AAChE,OAAOC,iCAAiC,MAAM,8CAA2C;
|
|
1
|
+
{"version":3,"names":["removePendingHostComponentRoutes","createMicroFrontendRuntimeWithDependencies","parseNativeRuntimeEvent","microFrontendModuleRegistry","NativeGraniteMicroFrontendRuntime","createMicroFrontendRuntime","options","adapter","nativeRuntime","onLifecycleEvent","onPreloadError","undefined","parseEvent","registry"],"sourceRoot":"../../src","sources":["createMicroFrontendRuntime.ts"],"mappings":";;AAAA,SAASA,gCAAgC,QAAQ,qCAAkC;AACnF,SAASC,0CAA0C,QAAQ,yCAAsC;AACjG,SAASC,uBAAuB,QAAQ,sCAAmC;AAC3E,SAASC,2BAA2B,QAAQ,uBAAoB;AAChE,OAAOC,iCAAiC,MAAM,8CAA2C;AASzF,OAAO,SAASC,0BAA0BA,CAACC,OAA0C,EAA2B;EAC9G,OAAOL,0CAA0C,CAAC;IAChDM,OAAO,EAAED,OAAO,CAACC,OAAO;IACxBC,aAAa,EAAEJ,iCAAiC;IAChDK,gBAAgB,EAAEH,OAAO,CAACG,gBAAgB;IAC1CC,cAAc,EAAEJ,OAAO,CAACI,cAAc,KAAK,MAAMC,SAAS,CAAC;IAC3DC,UAAU,EAAEV,uBAAuB;IACnCW,QAAQ,EAAEV,2BAA2B;IACrCH;EACF,CAAC,CAAC;AACJ","ignoreList":[]}
|
package/dist/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["createMicroFrontendRuntime","createRoute","PendingHostComponent","usePendingHostComponentController","useIsPendingHostComponentHidden","useResolvedPendingHostComponent","createPendingHostComponentRoutePrefix","normalizeRoutePath","hidePendingHostComponent","installPendingHostComponentBridge","registerPendingHostComponentRoute","resetPendingHostComponent","resolvePendingHostComponent","MicroFrontendSessionProvider","MissingMicroFrontendSessionError","useMicroFrontendSession","useMicroFrontendSessions","default","Portal","AppContainerNotFoundError","ExposedModuleNotFoundError","InvalidAppNameError","InvalidAppRequestError","InvalidNativeRuntimeEventError"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,0BAA0B,QAAQ,iCAA8B;AAEzE,SAASC,WAAW,QAAQ,kBAAe;AAG3C,SACEC,oBAAoB,EACpBC,iCAAiC,EACjCC,+BAA+B,EAC/BC,+BAA+B,QAC1B,gCAA6B;AASpC,SACEC,qCAAqC,EACrCC,kBAAkB,QACb,wBAAqB;AAC5B,SACEC,wBAAwB,EACxBC,iCAAiC,EACjCC,iCAAiC,EACjCC,yBAAyB,EACzBC,2BAA2B,QACtB,qCAAkC;AACzC,SACEC,4BAA4B,EAC5BC,gCAAgC,EAChCC,uBAAuB,QAClB,0CAAuC;AAE9C,SAASC,wBAAwB,QAAQ,uCAAoC;AAG7E,SAASC,OAAO,IAAIC,MAAM,QAAQ,mCAAmC;
|
|
1
|
+
{"version":3,"names":["createMicroFrontendRuntime","createRoute","PendingHostComponent","usePendingHostComponentController","useIsPendingHostComponentHidden","useResolvedPendingHostComponent","createPendingHostComponentRoutePrefix","normalizeRoutePath","hidePendingHostComponent","installPendingHostComponentBridge","registerPendingHostComponentRoute","resetPendingHostComponent","resolvePendingHostComponent","MicroFrontendSessionProvider","MissingMicroFrontendSessionError","useMicroFrontendSession","useMicroFrontendSessions","default","Portal","AppContainerNotFoundError","ExposedModuleNotFoundError","InvalidAppNameError","InvalidAppRequestError","InvalidNativeRuntimeEventError"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,0BAA0B,QAAQ,iCAA8B;AAEzE,SAASC,WAAW,QAAQ,kBAAe;AAG3C,SACEC,oBAAoB,EACpBC,iCAAiC,EACjCC,+BAA+B,EAC/BC,+BAA+B,QAC1B,gCAA6B;AASpC,SACEC,qCAAqC,EACrCC,kBAAkB,QACb,wBAAqB;AAC5B,SACEC,wBAAwB,EACxBC,iCAAiC,EACjCC,iCAAiC,EACjCC,yBAAyB,EACzBC,2BAA2B,QACtB,qCAAkC;AACzC,SACEC,4BAA4B,EAC5BC,gCAAgC,EAChCC,uBAAuB,QAClB,0CAAuC;AAE9C,SAASC,wBAAwB,QAAQ,uCAAoC;AAG7E,SAASC,OAAO,IAAIC,MAAM,QAAQ,mCAAmC;AAerE,SACEC,yBAAyB,EACzBC,0BAA0B,EAC1BC,mBAAmB,EACnBC,sBAAsB,EACtBC,8BAA8B,QACzB,qBAAkB","ignoreList":[]}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { AppContainerNotFoundError, InvalidAppNameError } from "./errors.js";
|
|
4
4
|
import { getMicroFrontendGlobalContext } from "./globalContext.js";
|
|
5
|
+
import { setMicroFrontendLifecycleCallback } from "./lifecycle.js";
|
|
6
|
+
import { installNativeComponentRegistryCompatibility } from "./nativeComponentRegistryCompatibility.js";
|
|
5
7
|
import { parseAppRequest } from "./parseAppRequest.js";
|
|
6
8
|
export function createMicroFrontendRuntimeWithDependencies(dependencies) {
|
|
7
9
|
// A fulfilled promise is the evaluated-state cache. Rejected evaluations remove themselves.
|
|
@@ -25,6 +27,7 @@ export function createMicroFrontendRuntimeWithDependencies(dependencies) {
|
|
|
25
27
|
}
|
|
26
28
|
function evaluateScript(filePath) {
|
|
27
29
|
getMicroFrontendGlobalContext();
|
|
30
|
+
installNativeComponentRegistryCompatibility();
|
|
28
31
|
return dependencies.nativeRuntime.evaluateScript({
|
|
29
32
|
filePath
|
|
30
33
|
});
|
|
@@ -45,7 +48,7 @@ export function createMicroFrontendRuntimeWithDependencies(dependencies) {
|
|
|
45
48
|
throw error;
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
|
-
|
|
51
|
+
const runtime = {
|
|
49
52
|
evaluateScript,
|
|
50
53
|
preloadApp,
|
|
51
54
|
async importApp(request) {
|
|
@@ -78,5 +81,7 @@ export function createMicroFrontendRuntimeWithDependencies(dependencies) {
|
|
|
78
81
|
return subscription;
|
|
79
82
|
}
|
|
80
83
|
};
|
|
84
|
+
setMicroFrontendLifecycleCallback(runtime, dependencies.onLifecycleEvent);
|
|
85
|
+
return runtime;
|
|
81
86
|
}
|
|
82
87
|
//# sourceMappingURL=createMicroFrontendRuntime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["AppContainerNotFoundError","InvalidAppNameError","getMicroFrontendGlobalContext","parseAppRequest","createMicroFrontendRuntimeWithDependencies","dependencies","appEvaluations","Map","resetFailedEvaluation","appName","delete","registry","removeContainer","removePendingHostComponentRoutes","preloadApp","length","Promise","reject","existingEvaluation","get","evaluation","evaluateApp","set","evaluateScript","filePath","nativeRuntime","adapter","loadBundle","hasContainer","error","importApp","request","importModule","onEvent","listener","subscription","event","parsedEvent","parseEvent","name","params","catch","onPreloadError","exhaustiveEvent","startEventDelivery"],"sourceRoot":"../../../src","sources":["runtime/createMicroFrontendRuntime.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"names":["AppContainerNotFoundError","InvalidAppNameError","getMicroFrontendGlobalContext","setMicroFrontendLifecycleCallback","installNativeComponentRegistryCompatibility","parseAppRequest","createMicroFrontendRuntimeWithDependencies","dependencies","appEvaluations","Map","resetFailedEvaluation","appName","delete","registry","removeContainer","removePendingHostComponentRoutes","preloadApp","length","Promise","reject","existingEvaluation","get","evaluation","evaluateApp","set","evaluateScript","filePath","nativeRuntime","adapter","loadBundle","hasContainer","error","runtime","importApp","request","importModule","onEvent","listener","subscription","event","parsedEvent","parseEvent","name","params","catch","onPreloadError","exhaustiveEvent","startEventDelivery","onLifecycleEvent"],"sourceRoot":"../../../src","sources":["runtime/createMicroFrontendRuntime.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const lifecycleCallbackByRuntime = new WeakMap();
|
|
4
|
+
export function setMicroFrontendLifecycleCallback(runtime, callback) {
|
|
5
|
+
if (callback == null) {
|
|
6
|
+
lifecycleCallbackByRuntime.delete(runtime);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
lifecycleCallbackByRuntime.set(runtime, callback);
|
|
10
|
+
}
|
|
11
|
+
export function emitMicroFrontendLifecycleEvent(runtime, event) {
|
|
12
|
+
const callback = lifecycleCallbackByRuntime.get(runtime);
|
|
13
|
+
if (callback == null) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
callback(event);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error('Failed to run a micro-frontend lifecycle callback', error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["lifecycleCallbackByRuntime","WeakMap","setMicroFrontendLifecycleCallback","runtime","callback","delete","set","emitMicroFrontendLifecycleEvent","event","get","error","console"],"sourceRoot":"../../../src","sources":["runtime/lifecycle.ts"],"mappings":";;AAEA,MAAMA,0BAA0B,GAAG,IAAIC,OAAO,CAAuD,CAAC;AAEtG,OAAO,SAASC,iCAAiCA,CAC/CC,OAAe,EACfC,QAAoE,EAC9D;EACN,IAAIA,QAAQ,IAAI,IAAI,EAAE;IACpBJ,0BAA0B,CAACK,MAAM,CAACF,OAAO,CAAC;IAC1C;EACF;EAEAH,0BAA0B,CAACM,GAAG,CAACH,OAAO,EAAEC,QAAQ,CAAC;AACnD;AAEA,OAAO,SAASG,+BAA+BA,CAACJ,OAAe,EAAEK,KAAkC,EAAQ;EACzG,MAAMJ,QAAQ,GAAGJ,0BAA0B,CAACS,GAAG,CAACN,OAAO,CAAC;EACxD,IAAIC,QAAQ,IAAI,IAAI,EAAE;IACpB;EACF;EAEA,IAAI;IACFA,QAAQ,CAACI,KAAK,CAAC;EACjB,CAAC,CAAC,OAAOE,KAAK,EAAE;IACdC,OAAO,CAACD,KAAK,CAAC,mDAAmD,EAAEA,KAAK,CAAC;EAC3E;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { getMicroFrontendGlobalContext } from "./globalContext.js";
|
|
4
|
+
const NATIVE_COMPONENT_REGISTRY_MODULE = 'react-native/Libraries/NativeComponent/NativeComponentRegistry';
|
|
5
|
+
const COMPATIBILITY_MARKER = Symbol.for('granite.microFrontend.nativeComponentRegistryCompatibility');
|
|
6
|
+
export function installNativeComponentRegistryCompatibility() {
|
|
7
|
+
const sharedModules = getMicroFrontendGlobalContext().__SHARED__;
|
|
8
|
+
const sharedEntry = sharedModules[NATIVE_COMPONENT_REGISTRY_MODULE];
|
|
9
|
+
if (sharedEntry == null || Reflect.get(sharedEntry, COMPATIBILITY_MARKER) === true) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const nativeComponentRegistry = sharedEntry.get();
|
|
13
|
+
if (!isNativeComponentRegistry(nativeComponentRegistry)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const compatibleRegistry = new Proxy(nativeComponentRegistry, {
|
|
17
|
+
get(target, property, receiver) {
|
|
18
|
+
if (property !== 'get') {
|
|
19
|
+
return Reflect.get(target, property, receiver);
|
|
20
|
+
}
|
|
21
|
+
return (...args) => {
|
|
22
|
+
try {
|
|
23
|
+
return Reflect.apply(target.get, target, args);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
const componentName = args[0];
|
|
26
|
+
if (typeof componentName === 'string' && isDuplicateNativeViewRegistration(error, componentName)) {
|
|
27
|
+
return componentName;
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
const compatibleEntry = {
|
|
35
|
+
get: () => compatibleRegistry,
|
|
36
|
+
loaded: sharedEntry.loaded
|
|
37
|
+
};
|
|
38
|
+
Reflect.defineProperty(compatibleEntry, COMPATIBILITY_MARKER, {
|
|
39
|
+
value: true
|
|
40
|
+
});
|
|
41
|
+
sharedModules[NATIVE_COMPONENT_REGISTRY_MODULE] = compatibleEntry;
|
|
42
|
+
}
|
|
43
|
+
function isNativeComponentRegistry(value) {
|
|
44
|
+
return typeof value === 'object' && value != null && typeof Reflect.get(value, 'get') === 'function';
|
|
45
|
+
}
|
|
46
|
+
function isDuplicateNativeViewRegistration(error, componentName) {
|
|
47
|
+
if (typeof error !== 'object' || error == null) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const message = Reflect.get(error, 'message');
|
|
51
|
+
return typeof message === 'string' && message.endsWith(`Tried to register two views with the same name ${componentName}`);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=nativeComponentRegistryCompatibility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["getMicroFrontendGlobalContext","NATIVE_COMPONENT_REGISTRY_MODULE","COMPATIBILITY_MARKER","Symbol","for","installNativeComponentRegistryCompatibility","sharedModules","__SHARED__","sharedEntry","Reflect","get","nativeComponentRegistry","isNativeComponentRegistry","compatibleRegistry","Proxy","target","property","receiver","args","apply","error","componentName","isDuplicateNativeViewRegistration","compatibleEntry","loaded","defineProperty","value","message","endsWith"],"sourceRoot":"../../../src","sources":["runtime/nativeComponentRegistryCompatibility.ts"],"mappings":";;AAAA,SAASA,6BAA6B,QAAQ,oBAAiB;AAE/D,MAAMC,gCAAgC,GAAG,gEAAgE;AACzG,MAAMC,oBAAoB,GAAGC,MAAM,CAACC,GAAG,CAAC,4DAA4D,CAAC;AAErG,OAAO,SAASC,2CAA2CA,CAAA,EAAS;EAClE,MAAMC,aAAa,GAAGN,6BAA6B,CAAC,CAAC,CAACO,UAAU;EAChE,MAAMC,WAAW,GAAGF,aAAa,CAACL,gCAAgC,CAAC;EACnE,IAAIO,WAAW,IAAI,IAAI,IAAIC,OAAO,CAACC,GAAG,CAACF,WAAW,EAAEN,oBAAoB,CAAC,KAAK,IAAI,EAAE;IAClF;EACF;EAEA,MAAMS,uBAAuB,GAAGH,WAAW,CAACE,GAAG,CAAC,CAAC;EACjD,IAAI,CAACE,yBAAyB,CAACD,uBAAuB,CAAC,EAAE;IACvD;EACF;EAEA,MAAME,kBAAkB,GAAG,IAAIC,KAAK,CAACH,uBAAuB,EAAE;IAC5DD,GAAGA,CAACK,MAAM,EAAEC,QAAQ,EAAEC,QAAQ,EAAE;MAC9B,IAAID,QAAQ,KAAK,KAAK,EAAE;QACtB,OAAOP,OAAO,CAACC,GAAG,CAACK,MAAM,EAAEC,QAAQ,EAAEC,QAAQ,CAAC;MAChD;MAEA,OAAO,CAAC,GAAGC,IAAwB,KAAK;QACtC,IAAI;UACF,OAAOT,OAAO,CAACU,KAAK,CAACJ,MAAM,CAACL,GAAG,EAAEK,MAAM,EAAEG,IAAI,CAAC;QAChD,CAAC,CAAC,OAAOE,KAAK,EAAE;UACd,MAAMC,aAAa,GAAGH,IAAI,CAAC,CAAC,CAAC;UAC7B,IAAI,OAAOG,aAAa,KAAK,QAAQ,IAAIC,iCAAiC,CAACF,KAAK,EAAEC,aAAa,CAAC,EAAE;YAChG,OAAOA,aAAa;UACtB;UACA,MAAMD,KAAK;QACb;MACF,CAAC;IACH;EACF,CAAC,CAAC;EACF,MAAMG,eAAe,GAAG;IACtBb,GAAG,EAAEA,CAAA,KAAMG,kBAAkB;IAC7BW,MAAM,EAAEhB,WAAW,CAACgB;EACtB,CAAC;EACDf,OAAO,CAACgB,cAAc,CAACF,eAAe,EAAErB,oBAAoB,EAAE;IAAEwB,KAAK,EAAE;EAAK,CAAC,CAAC;EAC9EpB,aAAa,CAACL,gCAAgC,CAAC,GAAGsB,eAAe;AACnE;AAEA,SAASX,yBAAyBA,CAChCc,KAAc,EACuD;EACrE,OAAO,OAAOA,KAAK,KAAK,QAAQ,IAAIA,KAAK,IAAI,IAAI,IAAI,OAAOjB,OAAO,CAACC,GAAG,CAACgB,KAAK,EAAE,KAAK,CAAC,KAAK,UAAU;AACtG;AAEA,SAASJ,iCAAiCA,CAACF,KAAc,EAAEC,aAAqB,EAAW;EACzF,IAAI,OAAOD,KAAK,KAAK,QAAQ,IAAIA,KAAK,IAAI,IAAI,EAAE;IAC9C,OAAO,KAAK;EACd;EAEA,MAAMO,OAAO,GAAGlB,OAAO,CAACC,GAAG,CAACU,KAAK,EAAE,SAAS,CAAC;EAC7C,OACE,OAAOO,OAAO,KAAK,QAAQ,IAAIA,OAAO,CAACC,QAAQ,CAAC,kDAAkDP,aAAa,EAAE,CAAC;AAEtH","ignoreList":[]}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useLayoutEffect, useReducer, useRef } from 'react';
|
|
4
4
|
import { installPendingHostComponentBridge, resetPendingHostComponent } from "../host/pendingHostComponentStore.js";
|
|
5
|
+
import { emitMicroFrontendLifecycleEvent } from "../runtime/lifecycle.js";
|
|
5
6
|
import { disposeAppResources } from "../runtime/registry.js";
|
|
6
7
|
const INITIAL_SESSIONS = [];
|
|
7
8
|
function reduceMicroFrontendSessions(sessions, event) {
|
|
@@ -43,17 +44,32 @@ export function useMicroFrontendSessions(runtime) {
|
|
|
43
44
|
const activeAppNames = new Set(sessions.map(({
|
|
44
45
|
appName
|
|
45
46
|
}) => appName));
|
|
46
|
-
const
|
|
47
|
+
const mountedSessions = sessions.filter(({
|
|
47
48
|
sessionId
|
|
48
|
-
}) => !
|
|
49
|
+
}) => !previousSessions.some(previousSession => previousSession.sessionId === sessionId));
|
|
50
|
+
const inactiveSessions = previousSessions.filter(({
|
|
51
|
+
sessionId
|
|
52
|
+
}) => !activeSessionIds.has(sessionId));
|
|
53
|
+
const inactiveAppNames = new Set(inactiveSessions.map(({
|
|
49
54
|
appName
|
|
50
55
|
}) => appName).filter(appName => !activeAppNames.has(appName)));
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
mountedSessions.forEach(session => {
|
|
57
|
+
emitMicroFrontendLifecycleEvent(runtime, createLifecycleEvent('mounted', session, sessions));
|
|
58
|
+
});
|
|
59
|
+
const appDisposals = new Map(Array.from(inactiveAppNames, appName => [appName, disposeAppResources(appName).catch(error => {
|
|
60
|
+
console.error(`Failed to dispose micro-frontend app resources for '${appName}'`, error);
|
|
61
|
+
})]));
|
|
62
|
+
inactiveSessions.forEach(session => {
|
|
63
|
+
const appDisposal = appDisposals.get(session.appName);
|
|
64
|
+
if (appDisposal == null) {
|
|
65
|
+
emitMicroFrontendLifecycleEvent(runtime, createLifecycleEvent('unmounted', session, sessions));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
void appDisposal.then(() => {
|
|
69
|
+
emitMicroFrontendLifecycleEvent(runtime, createLifecycleEvent('unmounted', session, previousSessionsRef.current));
|
|
54
70
|
});
|
|
55
71
|
});
|
|
56
|
-
}, [sessions]);
|
|
72
|
+
}, [runtime, sessions]);
|
|
57
73
|
useLayoutEffect(() => {
|
|
58
74
|
installPendingHostComponentBridge();
|
|
59
75
|
const subscription = runtime.onEvent(event => {
|
|
@@ -66,4 +82,17 @@ export function useMicroFrontendSessions(runtime) {
|
|
|
66
82
|
}, [runtime]);
|
|
67
83
|
return sessions;
|
|
68
84
|
}
|
|
85
|
+
function createLifecycleEvent(phase, session, activeSessions) {
|
|
86
|
+
return {
|
|
87
|
+
phase,
|
|
88
|
+
session: toLifecycleSession(session),
|
|
89
|
+
activeSessions: activeSessions.map(toLifecycleSession)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function toLifecycleSession(session) {
|
|
93
|
+
return {
|
|
94
|
+
appName: session.appName,
|
|
95
|
+
id: session.sessionId
|
|
96
|
+
};
|
|
97
|
+
}
|
|
69
98
|
//# sourceMappingURL=useMicroFrontendSessions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useEffect","useLayoutEffect","useReducer","useRef","installPendingHostComponentBridge","resetPendingHostComponent","disposeAppResources","INITIAL_SESSIONS","reduceMicroFrontendSessions","sessions","event","name","some","sessionId","params","appName","scheme","isVisible","filter","map","session","exhaustiveEvent","useMicroFrontendSessions","runtime","dispatch","previousSessionsRef","previousSessions","current","activeSessionIds","Set","activeAppNames","
|
|
1
|
+
{"version":3,"names":["useEffect","useLayoutEffect","useReducer","useRef","installPendingHostComponentBridge","resetPendingHostComponent","emitMicroFrontendLifecycleEvent","disposeAppResources","INITIAL_SESSIONS","reduceMicroFrontendSessions","sessions","event","name","some","sessionId","params","appName","scheme","isVisible","filter","map","session","exhaustiveEvent","useMicroFrontendSessions","runtime","dispatch","previousSessionsRef","previousSessions","current","activeSessionIds","Set","activeAppNames","mountedSessions","previousSession","inactiveSessions","has","inactiveAppNames","forEach","createLifecycleEvent","appDisposals","Map","Array","from","catch","error","console","appDisposal","get","then","subscription","onEvent","remove","phase","activeSessions","toLifecycleSession","id"],"sourceRoot":"../../../src","sources":["session/useMicroFrontendSessions.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { MicroFrontendAdapter, MicroFrontendRuntimeApi } from './types';
|
|
1
|
+
import type { MicroFrontendAdapter, MicroFrontendLifecycleCallback, MicroFrontendRuntimeApi } from './types';
|
|
2
2
|
export interface CreateMicroFrontendRuntimeOptions {
|
|
3
3
|
readonly adapter: MicroFrontendAdapter;
|
|
4
|
+
readonly onLifecycleEvent?: MicroFrontendLifecycleCallback;
|
|
4
5
|
readonly onPreloadError?: (error: unknown) => void;
|
|
5
6
|
}
|
|
6
7
|
export declare function createMicroFrontendRuntime(options: CreateMicroFrontendRuntimeOptions): MicroFrontendRuntimeApi;
|
|
@@ -13,5 +13,5 @@ export { useMicroFrontendSessions } from './session/useMicroFrontendSessions';
|
|
|
13
13
|
export type { MicroFrontendSessionState } from './session/useMicroFrontendSessions';
|
|
14
14
|
export type { AppDisposeCallback, MicroFrontendRuntimeContext } from './runtime/registry';
|
|
15
15
|
export { default as Portal } from './specs/PortalViewNativeComponent';
|
|
16
|
-
export type { AppRequest, MicroFrontendAppProps, MicroFrontendAdapter, MicroFrontendBundle, MicroFrontendBundleRequest, MicroFrontendRuntimeApi, MicroFrontendRuntimeEvent, MicroFrontendRuntimeEventSubscription, MicroFrontendSessionEvent, } from './types';
|
|
16
|
+
export type { AppRequest, MicroFrontendAppProps, MicroFrontendAdapter, MicroFrontendBundle, MicroFrontendBundleRequest, MicroFrontendLifecycleCallback, MicroFrontendLifecycleEvent, MicroFrontendLifecycleSession, MicroFrontendRuntimeApi, MicroFrontendRuntimeEvent, MicroFrontendRuntimeEventSubscription, MicroFrontendSessionEvent, } from './types';
|
|
17
17
|
export { AppContainerNotFoundError, ExposedModuleNotFoundError, InvalidAppNameError, InvalidAppRequestError, InvalidNativeRuntimeEventError, } from './runtime/errors';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppRequest, MicroFrontendAdapter, MicroFrontendRuntimeApi, MicroFrontendRuntimeEvent, MicroFrontendRuntimeEventSubscription } from '../types';
|
|
1
|
+
import type { AppRequest, MicroFrontendAdapter, MicroFrontendLifecycleEvent, MicroFrontendRuntimeApi, MicroFrontendRuntimeEvent, MicroFrontendRuntimeEventSubscription } from '../types';
|
|
2
2
|
export interface NativeMicroFrontendRuntimeEvent {
|
|
3
3
|
readonly name: string;
|
|
4
4
|
readonly params: {
|
|
@@ -24,6 +24,7 @@ export interface CreateMicroFrontendRuntimeDependencies {
|
|
|
24
24
|
readonly adapter: MicroFrontendAdapter;
|
|
25
25
|
readonly nativeRuntime: NativeMicroFrontendRuntime;
|
|
26
26
|
readonly onPreloadError: (error: unknown) => void;
|
|
27
|
+
readonly onLifecycleEvent?: (event: MicroFrontendLifecycleEvent) => void;
|
|
27
28
|
readonly registry: MicroFrontendModuleRegistry;
|
|
28
29
|
readonly removePendingHostComponentRoutes: (appName: string) => void;
|
|
29
30
|
readonly parseEvent: (event: NativeMicroFrontendRuntimeEvent) => MicroFrontendRuntimeEvent;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { MicroFrontendLifecycleEvent } from '../types';
|
|
2
|
+
export declare function setMicroFrontendLifecycleCallback(runtime: object, callback: ((event: MicroFrontendLifecycleEvent) => void) | undefined): void;
|
|
3
|
+
export declare function emitMicroFrontendLifecycleEvent(runtime: object, event: MicroFrontendLifecycleEvent): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function installNativeComponentRegistryCompatibility(): void;
|
|
@@ -41,6 +41,17 @@ export type MicroFrontendSessionEvent = Exclude<MicroFrontendRuntimeEvent, {
|
|
|
41
41
|
export interface MicroFrontendRuntimeEventSubscription {
|
|
42
42
|
readonly remove: () => void;
|
|
43
43
|
}
|
|
44
|
+
export interface MicroFrontendLifecycleSession {
|
|
45
|
+
readonly appName: string;
|
|
46
|
+
readonly id: string;
|
|
47
|
+
}
|
|
48
|
+
export interface MicroFrontendLifecycleEvent {
|
|
49
|
+
readonly phase: 'mounted' | 'unmounted';
|
|
50
|
+
readonly session: MicroFrontendLifecycleSession;
|
|
51
|
+
/** Active sessions across every app when the callback is emitted. */
|
|
52
|
+
readonly activeSessions: readonly MicroFrontendLifecycleSession[];
|
|
53
|
+
}
|
|
54
|
+
export type MicroFrontendLifecycleCallback = (event: MicroFrontendLifecycleEvent) => void;
|
|
44
55
|
export interface MicroFrontendRuntimeApi {
|
|
45
56
|
readonly evaluateScript: (filePath: string) => Promise<void>;
|
|
46
57
|
readonly preloadApp: (appName: string) => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@granite-js/micro-frontend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Micro-frontend build, runtime, session, and Portal contracts for Granite brownfield applications",
|
|
5
5
|
"main": "./dist/module/index.js",
|
|
6
6
|
"types": "./dist/typescript/index.d.ts",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@babel/core": "7.28.5",
|
|
59
|
-
"@granite-js/utils": "2.
|
|
59
|
+
"@granite-js/utils": "2.2.0",
|
|
60
60
|
"@standard-schema/spec": "^1.0.0"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@babel/types": "7.28.5",
|
|
75
|
-
"@granite-js/plugin-core": "2.
|
|
76
|
-
"@granite-js/react-native": "2.
|
|
75
|
+
"@granite-js/plugin-core": "2.2.0",
|
|
76
|
+
"@granite-js/react-native": "2.2.0",
|
|
77
77
|
"@types/babel__core": "^7.20.5",
|
|
78
78
|
"@types/node": "24.10.12",
|
|
79
79
|
"@types/react": "19.2.0",
|
|
@@ -3,10 +3,11 @@ import { createMicroFrontendRuntimeWithDependencies } from './runtime/createMicr
|
|
|
3
3
|
import { parseNativeRuntimeEvent } from './runtime/parseNativeRuntimeEvent';
|
|
4
4
|
import { microFrontendModuleRegistry } from './runtime/registry';
|
|
5
5
|
import NativeGraniteMicroFrontendRuntime from './specs/NativeGraniteMicroFrontendRuntime';
|
|
6
|
-
import type { MicroFrontendAdapter, MicroFrontendRuntimeApi } from './types';
|
|
6
|
+
import type { MicroFrontendAdapter, MicroFrontendLifecycleCallback, MicroFrontendRuntimeApi } from './types';
|
|
7
7
|
|
|
8
8
|
export interface CreateMicroFrontendRuntimeOptions {
|
|
9
9
|
readonly adapter: MicroFrontendAdapter;
|
|
10
|
+
readonly onLifecycleEvent?: MicroFrontendLifecycleCallback;
|
|
10
11
|
readonly onPreloadError?: (error: unknown) => void;
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -14,6 +15,7 @@ export function createMicroFrontendRuntime(options: CreateMicroFrontendRuntimeOp
|
|
|
14
15
|
return createMicroFrontendRuntimeWithDependencies({
|
|
15
16
|
adapter: options.adapter,
|
|
16
17
|
nativeRuntime: NativeGraniteMicroFrontendRuntime,
|
|
18
|
+
onLifecycleEvent: options.onLifecycleEvent,
|
|
17
19
|
onPreloadError: options.onPreloadError ?? (() => undefined),
|
|
18
20
|
parseEvent: parseNativeRuntimeEvent,
|
|
19
21
|
registry: microFrontendModuleRegistry,
|
package/src/index.ts
CHANGED
|
@@ -44,6 +44,9 @@ export type {
|
|
|
44
44
|
MicroFrontendAdapter,
|
|
45
45
|
MicroFrontendBundle,
|
|
46
46
|
MicroFrontendBundleRequest,
|
|
47
|
+
MicroFrontendLifecycleCallback,
|
|
48
|
+
MicroFrontendLifecycleEvent,
|
|
49
|
+
MicroFrontendLifecycleSession,
|
|
47
50
|
MicroFrontendRuntimeApi,
|
|
48
51
|
MicroFrontendRuntimeEvent,
|
|
49
52
|
MicroFrontendRuntimeEventSubscription,
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AppRequest,
|
|
3
3
|
MicroFrontendAdapter,
|
|
4
|
+
MicroFrontendLifecycleEvent,
|
|
4
5
|
MicroFrontendRuntimeApi,
|
|
5
6
|
MicroFrontendRuntimeEvent,
|
|
6
7
|
MicroFrontendRuntimeEventSubscription,
|
|
7
8
|
} from '../types';
|
|
8
9
|
import { AppContainerNotFoundError, InvalidAppNameError } from './errors';
|
|
9
10
|
import { getMicroFrontendGlobalContext } from './globalContext';
|
|
11
|
+
import { setMicroFrontendLifecycleCallback } from './lifecycle';
|
|
12
|
+
import { installNativeComponentRegistryCompatibility } from './nativeComponentRegistryCompatibility';
|
|
10
13
|
import { parseAppRequest } from './parseAppRequest';
|
|
11
14
|
|
|
12
15
|
export interface NativeMicroFrontendRuntimeEvent {
|
|
@@ -37,6 +40,7 @@ export interface CreateMicroFrontendRuntimeDependencies {
|
|
|
37
40
|
readonly adapter: MicroFrontendAdapter;
|
|
38
41
|
readonly nativeRuntime: NativeMicroFrontendRuntime;
|
|
39
42
|
readonly onPreloadError: (error: unknown) => void;
|
|
43
|
+
readonly onLifecycleEvent?: (event: MicroFrontendLifecycleEvent) => void;
|
|
40
44
|
readonly registry: MicroFrontendModuleRegistry;
|
|
41
45
|
readonly removePendingHostComponentRoutes: (appName: string) => void;
|
|
42
46
|
readonly parseEvent: (event: NativeMicroFrontendRuntimeEvent) => MicroFrontendRuntimeEvent;
|
|
@@ -71,6 +75,7 @@ export function createMicroFrontendRuntimeWithDependencies(
|
|
|
71
75
|
|
|
72
76
|
function evaluateScript(filePath: string): Promise<void> {
|
|
73
77
|
getMicroFrontendGlobalContext();
|
|
78
|
+
installNativeComponentRegistryCompatibility();
|
|
74
79
|
return dependencies.nativeRuntime.evaluateScript({ filePath });
|
|
75
80
|
}
|
|
76
81
|
|
|
@@ -88,7 +93,7 @@ export function createMicroFrontendRuntimeWithDependencies(
|
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
|
|
91
|
-
|
|
96
|
+
const runtime: MicroFrontendRuntimeApi = {
|
|
92
97
|
evaluateScript,
|
|
93
98
|
preloadApp,
|
|
94
99
|
async importApp<TModule>(request: AppRequest): Promise<TModule> {
|
|
@@ -118,4 +123,8 @@ export function createMicroFrontendRuntimeWithDependencies(
|
|
|
118
123
|
return subscription;
|
|
119
124
|
},
|
|
120
125
|
};
|
|
126
|
+
|
|
127
|
+
setMicroFrontendLifecycleCallback(runtime, dependencies.onLifecycleEvent);
|
|
128
|
+
|
|
129
|
+
return runtime;
|
|
121
130
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { MicroFrontendLifecycleEvent } from '../types';
|
|
2
|
+
|
|
3
|
+
const lifecycleCallbackByRuntime = new WeakMap<object, (event: MicroFrontendLifecycleEvent) => void>();
|
|
4
|
+
|
|
5
|
+
export function setMicroFrontendLifecycleCallback(
|
|
6
|
+
runtime: object,
|
|
7
|
+
callback: ((event: MicroFrontendLifecycleEvent) => void) | undefined
|
|
8
|
+
): void {
|
|
9
|
+
if (callback == null) {
|
|
10
|
+
lifecycleCallbackByRuntime.delete(runtime);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
lifecycleCallbackByRuntime.set(runtime, callback);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function emitMicroFrontendLifecycleEvent(runtime: object, event: MicroFrontendLifecycleEvent): void {
|
|
18
|
+
const callback = lifecycleCallbackByRuntime.get(runtime);
|
|
19
|
+
if (callback == null) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
callback(event);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error('Failed to run a micro-frontend lifecycle callback', error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { getMicroFrontendGlobalContext } from './globalContext';
|
|
2
|
+
|
|
3
|
+
const NATIVE_COMPONENT_REGISTRY_MODULE = 'react-native/Libraries/NativeComponent/NativeComponentRegistry';
|
|
4
|
+
const COMPATIBILITY_MARKER = Symbol.for('granite.microFrontend.nativeComponentRegistryCompatibility');
|
|
5
|
+
|
|
6
|
+
export function installNativeComponentRegistryCompatibility(): void {
|
|
7
|
+
const sharedModules = getMicroFrontendGlobalContext().__SHARED__;
|
|
8
|
+
const sharedEntry = sharedModules[NATIVE_COMPONENT_REGISTRY_MODULE];
|
|
9
|
+
if (sharedEntry == null || Reflect.get(sharedEntry, COMPATIBILITY_MARKER) === true) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const nativeComponentRegistry = sharedEntry.get();
|
|
14
|
+
if (!isNativeComponentRegistry(nativeComponentRegistry)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const compatibleRegistry = new Proxy(nativeComponentRegistry, {
|
|
19
|
+
get(target, property, receiver) {
|
|
20
|
+
if (property !== 'get') {
|
|
21
|
+
return Reflect.get(target, property, receiver);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (...args: readonly unknown[]) => {
|
|
25
|
+
try {
|
|
26
|
+
return Reflect.apply(target.get, target, args);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
const componentName = args[0];
|
|
29
|
+
if (typeof componentName === 'string' && isDuplicateNativeViewRegistration(error, componentName)) {
|
|
30
|
+
return componentName;
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
const compatibleEntry = {
|
|
38
|
+
get: () => compatibleRegistry,
|
|
39
|
+
loaded: sharedEntry.loaded,
|
|
40
|
+
};
|
|
41
|
+
Reflect.defineProperty(compatibleEntry, COMPATIBILITY_MARKER, { value: true });
|
|
42
|
+
sharedModules[NATIVE_COMPONENT_REGISTRY_MODULE] = compatibleEntry;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isNativeComponentRegistry(
|
|
46
|
+
value: unknown
|
|
47
|
+
): value is { readonly get: (...args: readonly unknown[]) => unknown } {
|
|
48
|
+
return typeof value === 'object' && value != null && typeof Reflect.get(value, 'get') === 'function';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isDuplicateNativeViewRegistration(error: unknown, componentName: string): boolean {
|
|
52
|
+
if (typeof error !== 'object' || error == null) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const message = Reflect.get(error, 'message');
|
|
57
|
+
return (
|
|
58
|
+
typeof message === 'string' && message.endsWith(`Tried to register two views with the same name ${componentName}`)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect, useReducer, useRef } from 'react';
|
|
2
2
|
import { installPendingHostComponentBridge, resetPendingHostComponent } from '../host/pendingHostComponentStore';
|
|
3
|
+
import { emitMicroFrontendLifecycleEvent } from '../runtime/lifecycle';
|
|
3
4
|
import { disposeAppResources } from '../runtime/registry';
|
|
4
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
MicroFrontendLifecycleEvent,
|
|
7
|
+
MicroFrontendLifecycleSession,
|
|
8
|
+
MicroFrontendRuntimeApi,
|
|
9
|
+
MicroFrontendSessionEvent,
|
|
10
|
+
} from '../types';
|
|
5
11
|
|
|
6
12
|
export interface MicroFrontendSessionState {
|
|
7
13
|
readonly appName: string;
|
|
@@ -53,19 +59,46 @@ export function useMicroFrontendSessions(
|
|
|
53
59
|
previousSessionsRef.current = sessions;
|
|
54
60
|
const activeSessionIds = new Set(sessions.map(({ sessionId }) => sessionId));
|
|
55
61
|
const activeAppNames = new Set(sessions.map(({ appName }) => appName));
|
|
62
|
+
const mountedSessions = sessions.filter(
|
|
63
|
+
({ sessionId }) => !previousSessions.some((previousSession) => previousSession.sessionId === sessionId)
|
|
64
|
+
);
|
|
65
|
+
const inactiveSessions = previousSessions.filter(({ sessionId }) => !activeSessionIds.has(sessionId));
|
|
56
66
|
const inactiveAppNames = new Set(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
inactiveSessions.map(({ appName }) => appName).filter((appName) => !activeAppNames.has(appName))
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
mountedSessions.forEach((session) => {
|
|
71
|
+
emitMicroFrontendLifecycleEvent(runtime, createLifecycleEvent('mounted', session, sessions));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const appDisposals = new Map<string, Promise<void>>(
|
|
75
|
+
Array.from(
|
|
76
|
+
inactiveAppNames,
|
|
77
|
+
(appName) =>
|
|
78
|
+
[
|
|
79
|
+
appName,
|
|
80
|
+
disposeAppResources(appName).catch((error) => {
|
|
81
|
+
console.error(`Failed to dispose micro-frontend app resources for '${appName}'`, error);
|
|
82
|
+
}),
|
|
83
|
+
] as const
|
|
84
|
+
)
|
|
61
85
|
);
|
|
62
86
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
inactiveSessions.forEach((session) => {
|
|
88
|
+
const appDisposal = appDisposals.get(session.appName);
|
|
89
|
+
if (appDisposal == null) {
|
|
90
|
+
emitMicroFrontendLifecycleEvent(runtime, createLifecycleEvent('unmounted', session, sessions));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
void appDisposal.then(() => {
|
|
95
|
+
emitMicroFrontendLifecycleEvent(
|
|
96
|
+
runtime,
|
|
97
|
+
createLifecycleEvent('unmounted', session, previousSessionsRef.current)
|
|
98
|
+
);
|
|
66
99
|
});
|
|
67
100
|
});
|
|
68
|
-
}, [sessions]);
|
|
101
|
+
}, [runtime, sessions]);
|
|
69
102
|
|
|
70
103
|
useLayoutEffect(() => {
|
|
71
104
|
installPendingHostComponentBridge();
|
|
@@ -81,3 +114,22 @@ export function useMicroFrontendSessions(
|
|
|
81
114
|
|
|
82
115
|
return sessions;
|
|
83
116
|
}
|
|
117
|
+
|
|
118
|
+
function createLifecycleEvent(
|
|
119
|
+
phase: MicroFrontendLifecycleEvent['phase'],
|
|
120
|
+
session: MicroFrontendSessionState,
|
|
121
|
+
activeSessions: readonly MicroFrontendSessionState[]
|
|
122
|
+
): MicroFrontendLifecycleEvent {
|
|
123
|
+
return {
|
|
124
|
+
phase,
|
|
125
|
+
session: toLifecycleSession(session),
|
|
126
|
+
activeSessions: activeSessions.map(toLifecycleSession),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function toLifecycleSession(session: MicroFrontendSessionState): MicroFrontendLifecycleSession {
|
|
131
|
+
return {
|
|
132
|
+
appName: session.appName,
|
|
133
|
+
id: session.sessionId,
|
|
134
|
+
};
|
|
135
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -47,6 +47,20 @@ export interface MicroFrontendRuntimeEventSubscription {
|
|
|
47
47
|
readonly remove: () => void;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export interface MicroFrontendLifecycleSession {
|
|
51
|
+
readonly appName: string;
|
|
52
|
+
readonly id: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface MicroFrontendLifecycleEvent {
|
|
56
|
+
readonly phase: 'mounted' | 'unmounted';
|
|
57
|
+
readonly session: MicroFrontendLifecycleSession;
|
|
58
|
+
/** Active sessions across every app when the callback is emitted. */
|
|
59
|
+
readonly activeSessions: readonly MicroFrontendLifecycleSession[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type MicroFrontendLifecycleCallback = (event: MicroFrontendLifecycleEvent) => void;
|
|
63
|
+
|
|
50
64
|
export interface MicroFrontendRuntimeApi {
|
|
51
65
|
readonly evaluateScript: (filePath: string) => Promise<void>;
|
|
52
66
|
readonly preloadApp: (appName: string) => Promise<void>;
|