@granite-js/micro-frontend 2.1.1 → 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 CHANGED
@@ -1,5 +1,15 @@
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
+
3
13
  ## 2.1.1
4
14
 
5
15
  ### Patch 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');
@@ -211,6 +214,29 @@ The public runtime API is:
211
214
  | `evaluateScript(filePath)` | Evaluate an already-local bundle in the retained runtime. |
212
215
  | `onEvent(listener)` | Subscribe to native open, close, and visibility events. |
213
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
+
214
240
  Remote code can register an idempotent callback for explicit app-level resource
215
241
  cleanup:
216
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;AAQzF,OAAO,SAASC,0BAA0BA,CAACC,OAA0C,EAA2B;EAC9G,OAAOL,0CAA0C,CAAC;IAChDM,OAAO,EAAED,OAAO,CAACC,OAAO;IACxBC,aAAa,EAAEJ,iCAAiC;IAChDK,cAAc,EAAEH,OAAO,CAACG,cAAc,KAAK,MAAMC,SAAS,CAAC;IAC3DC,UAAU,EAAET,uBAAuB;IACnCU,QAAQ,EAAET,2BAA2B;IACrCH;EACF,CAAC,CAAC;AACJ","ignoreList":[]}
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":[]}
@@ -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;AAYrE,SACEC,yBAAyB,EACzBC,0BAA0B,EAC1BC,mBAAmB,EACnBC,sBAAsB,EACtBC,8BAA8B,QACzB,qBAAkB","ignoreList":[]}
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,7 @@
2
2
 
3
3
  import { AppContainerNotFoundError, InvalidAppNameError } from "./errors.js";
4
4
  import { getMicroFrontendGlobalContext } from "./globalContext.js";
5
+ import { setMicroFrontendLifecycleCallback } from "./lifecycle.js";
5
6
  import { installNativeComponentRegistryCompatibility } from "./nativeComponentRegistryCompatibility.js";
6
7
  import { parseAppRequest } from "./parseAppRequest.js";
7
8
  export function createMicroFrontendRuntimeWithDependencies(dependencies) {
@@ -47,7 +48,7 @@ export function createMicroFrontendRuntimeWithDependencies(dependencies) {
47
48
  throw error;
48
49
  }
49
50
  }
50
- return {
51
+ const runtime = {
51
52
  evaluateScript,
52
53
  preloadApp,
53
54
  async importApp(request) {
@@ -80,5 +81,7 @@ export function createMicroFrontendRuntimeWithDependencies(dependencies) {
80
81
  return subscription;
81
82
  }
82
83
  };
84
+ setMicroFrontendLifecycleCallback(runtime, dependencies.onLifecycleEvent);
85
+ return runtime;
83
86
  }
84
87
  //# sourceMappingURL=createMicroFrontendRuntime.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["AppContainerNotFoundError","InvalidAppNameError","getMicroFrontendGlobalContext","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","importApp","request","importModule","onEvent","listener","subscription","event","parsedEvent","parseEvent","name","params","catch","onPreloadError","exhaustiveEvent","startEventDelivery"],"sourceRoot":"../../../src","sources":["runtime/createMicroFrontendRuntime.ts"],"mappings":";;AAOA,SAASA,yBAAyB,EAAEC,mBAAmB,QAAQ,aAAU;AACzE,SAASC,6BAA6B,QAAQ,oBAAiB;AAC/D,SAASC,2CAA2C,QAAQ,2CAAwC;AACpG,SAASC,eAAe,QAAQ,sBAAmB;AAmCnD,OAAO,SAASC,0CAA0CA,CACxDC,YAAoD,EAC3B;EACzB;EACA,MAAMC,cAAc,GAAG,IAAIC,GAAG,CAAwB,CAAC;EAEvD,SAASC,qBAAqBA,CAACC,OAAe,EAAE;IAC9CH,cAAc,CAACI,MAAM,CAACD,OAAO,CAAC;IAC9BJ,YAAY,CAACM,QAAQ,CAACC,eAAe,CAACH,OAAO,CAAC;IAC9CJ,YAAY,CAACQ,gCAAgC,CAACJ,OAAO,CAAC;EACxD;EAEA,SAASK,UAAUA,CAACL,OAAe,EAAiB;IAClD,IAAIA,OAAO,CAACM,MAAM,KAAK,CAAC,EAAE;MACxB,OAAOC,OAAO,CAACC,MAAM,CAAC,IAAIjB,mBAAmB,CAACS,OAAO,CAAC,CAAC;IACzD;IAEA,MAAMS,kBAAkB,GAAGZ,cAAc,CAACa,GAAG,CAACV,OAAO,CAAC;IACtD,IAAIS,kBAAkB,IAAI,IAAI,EAAE;MAC9B,OAAOA,kBAAkB;IAC3B;IAEA,MAAME,UAAU,GAAGC,WAAW,CAACZ,OAAO,CAAC;IACvCH,cAAc,CAACgB,GAAG,CAACb,OAAO,EAAEW,UAAU,CAAC;IACvC,OAAOA,UAAU;EACnB;EAEA,SAASG,cAAcA,CAACC,QAAgB,EAAiB;IACvDvB,6BAA6B,CAAC,CAAC;IAC/BC,2CAA2C,CAAC,CAAC;IAC7C,OAAOG,YAAY,CAACoB,aAAa,CAACF,cAAc,CAAC;MAAEC;IAAS,CAAC,CAAC;EAChE;EAEA,eAAeH,WAAWA,CAACZ,OAAe,EAAiB;IACzD,IAAI;MACF,MAAM;QAAEe;MAAS,CAAC,GAAG,MAAMnB,YAAY,CAACqB,OAAO,CAACC,UAAU,CAAC;QAAElB;MAAQ,CAAC,CAAC;MACvE,MAAMc,cAAc,CAACC,QAAQ,CAAC;MAE9B,IAAI,CAACnB,YAAY,CAACM,QAAQ,CAACiB,YAAY,CAACnB,OAAO,CAAC,EAAE;QAChD,MAAM,IAAIV,yBAAyB,CAACU,OAAO,CAAC;MAC9C;IACF,CAAC,CAAC,OAAOoB,KAAK,EAAE;MACdrB,qBAAqB,CAACC,OAAO,CAAC;MAC9B,MAAMoB,KAAK;IACb;EACF;EAEA,OAAO;IACLN,cAAc;IACdT,UAAU;IACV,MAAMgB,SAASA,CAAUC,OAAmB,EAAoB;MAC9D,MAAM;QAAEtB;MAAQ,CAAC,GAAGN,eAAe,CAAC4B,OAAO,CAAC;MAC5C,MAAMjB,UAAU,CAACL,OAAO,CAAC;MACzB,OAAOJ,YAAY,CAACM,QAAQ,CAACqB,YAAY,CAAUD,OAAO,CAAC;IAC7D,CAAC;IACDE,OAAOA,CAACC,QAAQ,EAAE;MAChB,MAAMC,YAAY,GAAG9B,YAAY,CAACoB,aAAa,CAACQ,OAAO,CAAEG,KAAK,IAAK;QACjE,MAAMC,WAAW,GAAGhC,YAAY,CAACiC,UAAU,CAACF,KAAK,CAAC;QAClD,QAAQC,WAAW,CAACE,IAAI;UACtB,KAAK,YAAY;YACf,KAAKzB,UAAU,CAACuB,WAAW,CAACG,MAAM,CAAC/B,OAAO,CAAC,CAACgC,KAAK,CAACpC,YAAY,CAACqC,cAAc,CAAC;YAC9E;UACF,KAAK,SAAS;UACd,KAAK,UAAU;UACf,KAAK,0BAA0B;YAC7BR,QAAQ,CAACG,WAAW,CAAC;YACrB;UACF;YAAS;cACP,MAAMM,eAAsB,GAAGN,WAAW;cAC1C,OAAOM,eAAe;YACxB;QACF;MACF,CAAC,CAAC;MACFtC,YAAY,CAACoB,aAAa,CAACmB,kBAAkB,CAAC,CAAC;MAC/C,OAAOT,YAAY;IACrB;EACF,CAAC;AACH","ignoreList":[]}
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":[]}
@@ -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 inactiveAppNames = new Set(previousSessions.filter(({
47
+ const mountedSessions = sessions.filter(({
47
48
  sessionId
48
- }) => !activeSessionIds.has(sessionId)).map(({
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
- inactiveAppNames.forEach(appName => {
52
- void disposeAppResources(appName).catch(error => {
53
- console.error(`Failed to dispose micro-frontend app resources for '${appName}'`, error);
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","inactiveAppNames","has","forEach","catch","error","console","subscription","onEvent","remove"],"sourceRoot":"../../../src","sources":["session/useMicroFrontendSessions.ts"],"mappings":"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","ignoreList":[]}
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":";;AAAA,SAASA,SAAS,EAAEC,eAAe,EAAEC,UAAU,EAAEC,MAAM,QAAQ,OAAO;AACtE,SAASC,iCAAiC,EAAEC,yBAAyB,QAAQ,sCAAmC;AAChH,SAASC,+BAA+B,QAAQ,yBAAsB;AACtE,SAASC,mBAAmB,QAAQ,wBAAqB;AAezD,MAAMC,gBAAsD,GAAG,EAAE;AAEjE,SAASC,2BAA2BA,CAClCC,QAA8C,EAC9CC,KAAgC,EACM;EACtC,QAAQA,KAAK,CAACC,IAAI;IAChB,KAAK,SAAS;MACZ,OAAOF,QAAQ,CAACG,IAAI,CAAC,CAAC;QAAEC;MAAU,CAAC,KAAKA,SAAS,KAAKH,KAAK,CAACI,MAAM,CAACD,SAAS,CAAC,GACzEJ,QAAQ,GACR,CACE,GAAGA,QAAQ,EACX;QACEM,OAAO,EAAEL,KAAK,CAACI,MAAM,CAACC,OAAO;QAC7BF,SAAS,EAAEH,KAAK,CAACI,MAAM,CAACD,SAAS;QACjCG,MAAM,EAAEN,KAAK,CAACI,MAAM,CAACE,MAAM;QAC3BC,SAAS,EAAE;MACb,CAAC,CACF;IACP,KAAK,UAAU;MACb,OAAOR,QAAQ,CAACS,MAAM,CAAC,CAAC;QAAEL;MAAU,CAAC,KAAKA,SAAS,KAAKH,KAAK,CAACI,MAAM,CAACD,SAAS,CAAC;IACjF,KAAK,0BAA0B;MAC7B,OAAOJ,QAAQ,CAACU,GAAG,CAAEC,OAAO,IAC1BA,OAAO,CAACP,SAAS,KAAKH,KAAK,CAACI,MAAM,CAACD,SAAS,GAAG;QAAE,GAAGO,OAAO;QAAEH,SAAS,EAAEP,KAAK,CAACI,MAAM,CAACG;MAAU,CAAC,GAAGG,OACrG,CAAC;IACH;MAAS;QACP,MAAMC,eAAsB,GAAGX,KAAK;QACpC,OAAOW,eAAe;MACxB;EACF;AACF;AAEA,OAAO,SAASC,wBAAwBA,CACtCC,OAAiD,EACX;EACtC,MAAM,CAACd,QAAQ,EAAEe,QAAQ,CAAC,GAAGvB,UAAU,CAACO,2BAA2B,EAAED,gBAAgB,CAAC;EACtF,MAAMkB,mBAAmB,GAAGvB,MAAM,CAACO,QAAQ,CAAC;EAE5CV,SAAS,CAAC,MAAM;IACd,MAAM2B,gBAAgB,GAAGD,mBAAmB,CAACE,OAAO;IACpDF,mBAAmB,CAACE,OAAO,GAAGlB,QAAQ;IACtC,MAAMmB,gBAAgB,GAAG,IAAIC,GAAG,CAACpB,QAAQ,CAACU,GAAG,CAAC,CAAC;MAAEN;IAAU,CAAC,KAAKA,SAAS,CAAC,CAAC;IAC5E,MAAMiB,cAAc,GAAG,IAAID,GAAG,CAACpB,QAAQ,CAACU,GAAG,CAAC,CAAC;MAAEJ;IAAQ,CAAC,KAAKA,OAAO,CAAC,CAAC;IACtE,MAAMgB,eAAe,GAAGtB,QAAQ,CAACS,MAAM,CACrC,CAAC;MAAEL;IAAU,CAAC,KAAK,CAACa,gBAAgB,CAACd,IAAI,CAAEoB,eAAe,IAAKA,eAAe,CAACnB,SAAS,KAAKA,SAAS,CACxG,CAAC;IACD,MAAMoB,gBAAgB,GAAGP,gBAAgB,CAACR,MAAM,CAAC,CAAC;MAAEL;IAAU,CAAC,KAAK,CAACe,gBAAgB,CAACM,GAAG,CAACrB,SAAS,CAAC,CAAC;IACrG,MAAMsB,gBAAgB,GAAG,IAAIN,GAAG,CAC9BI,gBAAgB,CAACd,GAAG,CAAC,CAAC;MAAEJ;IAAQ,CAAC,KAAKA,OAAO,CAAC,CAACG,MAAM,CAAEH,OAAO,IAAK,CAACe,cAAc,CAACI,GAAG,CAACnB,OAAO,CAAC,CACjG,CAAC;IAEDgB,eAAe,CAACK,OAAO,CAAEhB,OAAO,IAAK;MACnCf,+BAA+B,CAACkB,OAAO,EAAEc,oBAAoB,CAAC,SAAS,EAAEjB,OAAO,EAAEX,QAAQ,CAAC,CAAC;IAC9F,CAAC,CAAC;IAEF,MAAM6B,YAAY,GAAG,IAAIC,GAAG,CAC1BC,KAAK,CAACC,IAAI,CACRN,gBAAgB,EACfpB,OAAO,IACN,CACEA,OAAO,EACPT,mBAAmB,CAACS,OAAO,CAAC,CAAC2B,KAAK,CAAEC,KAAK,IAAK;MAC5CC,OAAO,CAACD,KAAK,CAAC,uDAAuD5B,OAAO,GAAG,EAAE4B,KAAK,CAAC;IACzF,CAAC,CAAC,CAER,CACF,CAAC;IAEDV,gBAAgB,CAACG,OAAO,CAAEhB,OAAO,IAAK;MACpC,MAAMyB,WAAW,GAAGP,YAAY,CAACQ,GAAG,CAAC1B,OAAO,CAACL,OAAO,CAAC;MACrD,IAAI8B,WAAW,IAAI,IAAI,EAAE;QACvBxC,+BAA+B,CAACkB,OAAO,EAAEc,oBAAoB,CAAC,WAAW,EAAEjB,OAAO,EAAEX,QAAQ,CAAC,CAAC;QAC9F;MACF;MAEA,KAAKoC,WAAW,CAACE,IAAI,CAAC,MAAM;QAC1B1C,+BAA+B,CAC7BkB,OAAO,EACPc,oBAAoB,CAAC,WAAW,EAAEjB,OAAO,EAAEK,mBAAmB,CAACE,OAAO,CACxE,CAAC;MACH,CAAC,CAAC;IACJ,CAAC,CAAC;EACJ,CAAC,EAAE,CAACJ,OAAO,EAAEd,QAAQ,CAAC,CAAC;EAEvBT,eAAe,CAAC,MAAM;IACpBG,iCAAiC,CAAC,CAAC;IACnC,MAAM6C,YAAY,GAAGzB,OAAO,CAAC0B,OAAO,CAAEvC,KAAK,IAAK;MAC9C,IAAIA,KAAK,CAACC,IAAI,KAAK,SAAS,EAAE;QAC5BP,yBAAyB,CAAC,CAAC;MAC7B;MACAoB,QAAQ,CAACd,KAAK,CAAC;IACjB,CAAC,CAAC;IAEF,OAAO,MAAMsC,YAAY,CAACE,MAAM,CAAC,CAAC;EACpC,CAAC,EAAE,CAAC3B,OAAO,CAAC,CAAC;EAEb,OAAOd,QAAQ;AACjB;AAEA,SAAS4B,oBAAoBA,CAC3Bc,KAA2C,EAC3C/B,OAAkC,EAClCgC,cAAoD,EACvB;EAC7B,OAAO;IACLD,KAAK;IACL/B,OAAO,EAAEiC,kBAAkB,CAACjC,OAAO,CAAC;IACpCgC,cAAc,EAAEA,cAAc,CAACjC,GAAG,CAACkC,kBAAkB;EACvD,CAAC;AACH;AAEA,SAASA,kBAAkBA,CAACjC,OAAkC,EAAiC;EAC7F,OAAO;IACLL,OAAO,EAAEK,OAAO,CAACL,OAAO;IACxBuC,EAAE,EAAElC,OAAO,CAACP;EACd,CAAC;AACH","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;
@@ -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.1.1",
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.1.1",
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.1.1",
76
- "@granite-js/react-native": "2.1.1",
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,14 @@
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';
10
12
  import { installNativeComponentRegistryCompatibility } from './nativeComponentRegistryCompatibility';
11
13
  import { parseAppRequest } from './parseAppRequest';
12
14
 
@@ -38,6 +40,7 @@ export interface CreateMicroFrontendRuntimeDependencies {
38
40
  readonly adapter: MicroFrontendAdapter;
39
41
  readonly nativeRuntime: NativeMicroFrontendRuntime;
40
42
  readonly onPreloadError: (error: unknown) => void;
43
+ readonly onLifecycleEvent?: (event: MicroFrontendLifecycleEvent) => void;
41
44
  readonly registry: MicroFrontendModuleRegistry;
42
45
  readonly removePendingHostComponentRoutes: (appName: string) => void;
43
46
  readonly parseEvent: (event: NativeMicroFrontendRuntimeEvent) => MicroFrontendRuntimeEvent;
@@ -90,7 +93,7 @@ export function createMicroFrontendRuntimeWithDependencies(
90
93
  }
91
94
  }
92
95
 
93
- return {
96
+ const runtime: MicroFrontendRuntimeApi = {
94
97
  evaluateScript,
95
98
  preloadApp,
96
99
  async importApp<TModule>(request: AppRequest): Promise<TModule> {
@@ -120,4 +123,8 @@ export function createMicroFrontendRuntimeWithDependencies(
120
123
  return subscription;
121
124
  },
122
125
  };
126
+
127
+ setMicroFrontendLifecycleCallback(runtime, dependencies.onLifecycleEvent);
128
+
129
+ return runtime;
123
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
+ }
@@ -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 { MicroFrontendRuntimeApi, MicroFrontendSessionEvent } from '../types';
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
- previousSessions
58
- .filter(({ sessionId }) => !activeSessionIds.has(sessionId))
59
- .map(({ appName }) => appName)
60
- .filter((appName) => !activeAppNames.has(appName))
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
- inactiveAppNames.forEach((appName) => {
64
- void disposeAppResources(appName).catch((error) => {
65
- console.error(`Failed to dispose micro-frontend app resources for '${appName}'`, error);
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>;