@granite-js/micro-frontend 2.0.2 → 2.1.1
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 +18 -0
- package/README.md +129 -55
- package/dist/module/host/pendingHostComponentStore.js +33 -14
- package/dist/module/host/pendingHostComponentStore.js.map +1 -1
- package/dist/module/index.js.map +1 -1
- package/dist/module/plugin/disposeBabelPlugin.js +38 -0
- package/dist/module/plugin/disposeBabelPlugin.js.map +1 -0
- package/dist/module/plugin/disposeTransform.js +37 -0
- package/dist/module/plugin/disposeTransform.js.map +1 -0
- package/dist/module/plugin/globalContextScript.js +121 -0
- package/dist/module/plugin/globalContextScript.js.map +1 -0
- package/dist/module/plugin/microFrontendPlugin.js +12 -1
- package/dist/module/plugin/microFrontendPlugin.js.map +1 -1
- package/dist/module/plugin/prelude.js +4 -2
- package/dist/module/plugin/prelude.js.map +1 -1
- package/dist/module/plugin/resolver.js +1 -1
- package/dist/module/plugin/resolver.js.map +1 -1
- package/dist/module/runtime/containerModuleExposure.js +79 -0
- package/dist/module/runtime/containerModuleExposure.js.map +1 -0
- package/dist/module/runtime/containerPairing.js +86 -0
- package/dist/module/runtime/containerPairing.js.map +1 -0
- package/dist/module/runtime/containerRegistry.js +190 -0
- package/dist/module/runtime/containerRegistry.js.map +1 -0
- package/dist/module/runtime/createMicroFrontendRuntime.js +11 -6
- package/dist/module/runtime/createMicroFrontendRuntime.js.map +1 -1
- package/dist/module/runtime/globalContext.js +89 -0
- package/dist/module/runtime/globalContext.js.map +1 -0
- package/dist/module/runtime/nativeComponentRegistryCompatibility.js +53 -0
- package/dist/module/runtime/nativeComponentRegistryCompatibility.js.map +1 -0
- package/dist/module/runtime/registry.js +103 -45
- package/dist/module/runtime/registry.js.map +1 -1
- package/dist/module/session/useMicroFrontendSessions.js +23 -1
- package/dist/module/session/useMicroFrontendSessions.js.map +1 -1
- package/dist/typescript/index.d.ts +1 -0
- package/dist/typescript/plugin/disposeBabelPlugin.d.ts +8 -0
- package/dist/typescript/plugin/disposeTransform.d.ts +3 -0
- package/dist/typescript/plugin/globalContextScript.d.ts +1 -0
- package/dist/typescript/runtime/containerModuleExposure.d.ts +2 -0
- package/dist/typescript/runtime/containerPairing.d.ts +18 -0
- package/dist/typescript/runtime/containerRegistry.d.ts +6 -0
- package/dist/typescript/runtime/globalContext.d.ts +17 -0
- package/dist/typescript/runtime/nativeComponentRegistryCompatibility.d.ts +1 -0
- package/dist/typescript/runtime/registry.d.ts +10 -8
- package/package.json +7 -4
- package/src/host/pendingHostComponentStore.ts +58 -29
- package/src/index.ts +1 -0
- package/src/plugin/disposeBabelPlugin.ts +51 -0
- package/src/plugin/disposeTransform.ts +43 -0
- package/src/plugin/globalContextScript.ts +118 -0
- package/src/plugin/microFrontendPlugin.ts +13 -1
- package/src/plugin/prelude.ts +160 -20
- package/src/plugin/resolver.ts +1 -1
- package/src/runtime/containerModuleExposure.ts +92 -0
- package/src/runtime/containerPairing.ts +120 -0
- package/src/runtime/containerRegistry.ts +239 -0
- package/src/runtime/createMicroFrontendRuntime.ts +10 -2
- package/src/runtime/globalContext.ts +164 -0
- package/src/runtime/nativeComponentRegistryCompatibility.ts +60 -0
- package/src/runtime/registry.ts +151 -58
- package/src/session/useMicroFrontendSessions.ts +26 -16
|
@@ -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
|
+
}
|
package/src/runtime/registry.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { AppRequest } from '../types';
|
|
2
|
+
import { createContainer, exposeModule, getContainer, getExposedModule, removeContainer } from './containerRegistry';
|
|
2
3
|
import type { MicroFrontendModuleRegistry } from './createMicroFrontendRuntime';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
ExposedModuleAlreadyRegisteredError,
|
|
6
|
-
ExposedModuleNotFoundError,
|
|
7
|
-
SharedModuleAlreadyRegisteredError,
|
|
8
|
-
} from './errors';
|
|
4
|
+
import { ExposedModuleNotFoundError, SharedModuleAlreadyRegisteredError } from './errors';
|
|
5
|
+
import { getMicroFrontendGlobalContext, type MicroFrontendGlobalContext } from './globalContext';
|
|
9
6
|
import { parseAppRequest } from './parseAppRequest';
|
|
7
|
+
export { createContainer, exposeModule, getContainer, removeContainer };
|
|
10
8
|
|
|
11
9
|
export interface SharedModuleConfig {
|
|
12
10
|
readonly eager?: boolean;
|
|
@@ -24,6 +22,13 @@ export interface AppContainer {
|
|
|
24
22
|
readonly exposedModules: Record<string, unknown>;
|
|
25
23
|
}
|
|
26
24
|
|
|
25
|
+
export type AppDisposeCallback = () => void | Promise<void>;
|
|
26
|
+
|
|
27
|
+
export interface RegisterAppDispose {
|
|
28
|
+
(callback: AppDisposeCallback): () => void;
|
|
29
|
+
(appName: string, callback: AppDisposeCallback): () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
interface SharedModule {
|
|
28
33
|
readonly get: () => unknown;
|
|
29
34
|
readonly loaded: boolean;
|
|
@@ -31,55 +36,22 @@ interface SharedModule {
|
|
|
31
36
|
|
|
32
37
|
export interface MicroFrontendRuntimeContext {
|
|
33
38
|
readonly containers: Record<string, AppContainer>;
|
|
39
|
+
readonly dispose: RegisterAppDispose;
|
|
40
|
+
readonly disposeCallbacksByApp: Record<string, Set<AppDisposeCallback>>;
|
|
34
41
|
readonly sharedModules: Record<string, SharedModule>;
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
declare global {
|
|
38
|
-
var _graniteMicroFrontend: MicroFrontendRuntimeContext | undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
44
|
export function getMicroFrontendRuntimeContext(): MicroFrontendRuntimeContext {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
return existingContext;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const context: MicroFrontendRuntimeContext = {
|
|
48
|
-
containers: {},
|
|
49
|
-
sharedModules: {},
|
|
50
|
-
};
|
|
51
|
-
globalThis._graniteMicroFrontend = context;
|
|
45
|
+
const context = getMicroFrontendGlobalContext();
|
|
46
|
+
ensureRuntimeContext(context);
|
|
52
47
|
return context;
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
export function createContainer(appName: string, config: AppContainerConfig = {}): AppContainer {
|
|
56
|
-
const containers = getMicroFrontendRuntimeContext().containers;
|
|
57
|
-
if (containers[appName] != null) {
|
|
58
|
-
throw new AppContainerAlreadyRegisteredError(appName);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const container: AppContainer = {
|
|
62
|
-
appName,
|
|
63
|
-
config,
|
|
64
|
-
exposedModules: {},
|
|
65
|
-
};
|
|
66
|
-
containers[appName] = container;
|
|
67
|
-
return container;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function exposeModule(container: AppContainer, exposedModule: string, module: unknown): void {
|
|
71
|
-
const normalizedModule = normalizeExposedModule(exposedModule);
|
|
72
|
-
if (container.exposedModules[normalizedModule] != null) {
|
|
73
|
-
throw new ExposedModuleAlreadyRegisteredError(container.appName, normalizedModule);
|
|
74
|
-
}
|
|
75
|
-
container.exposedModules[normalizedModule] = module;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
50
|
export function registerShared(moduleName: string, module: unknown): void {
|
|
79
|
-
const sharedModules =
|
|
51
|
+
const sharedModules = getMicroFrontendGlobalContext().__SHARED__;
|
|
80
52
|
const existingModule = sharedModules[moduleName];
|
|
81
53
|
if (existingModule != null) {
|
|
82
|
-
if (Object.is(existingModule.get(), module)) {
|
|
54
|
+
if (isSharedModule(existingModule) && Object.is(existingModule.get(), module)) {
|
|
83
55
|
return;
|
|
84
56
|
}
|
|
85
57
|
throw new SharedModuleAlreadyRegisteredError(moduleName);
|
|
@@ -90,21 +62,22 @@ export function registerShared(moduleName: string, module: unknown): void {
|
|
|
90
62
|
};
|
|
91
63
|
}
|
|
92
64
|
|
|
93
|
-
|
|
94
|
-
return
|
|
65
|
+
function isSharedModule(value: unknown): value is SharedModule {
|
|
66
|
+
return (
|
|
67
|
+
typeof value === 'object' &&
|
|
68
|
+
value != null &&
|
|
69
|
+
typeof Reflect.get(value, 'get') === 'function' &&
|
|
70
|
+
typeof Reflect.get(value, 'loaded') === 'boolean'
|
|
71
|
+
);
|
|
95
72
|
}
|
|
96
73
|
|
|
97
|
-
export function
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
if (container == null) {
|
|
74
|
+
export async function disposeAppResources(appName: string): Promise<void> {
|
|
75
|
+
const disposeCallbacks = getMicroFrontendRuntimeContext().disposeCallbacksByApp[appName];
|
|
76
|
+
if (disposeCallbacks == null) {
|
|
101
77
|
return;
|
|
102
78
|
}
|
|
103
79
|
|
|
104
|
-
|
|
105
|
-
Reflect.deleteProperty(container.exposedModules, exposedModule);
|
|
106
|
-
}
|
|
107
|
-
Reflect.deleteProperty(containers, appName);
|
|
80
|
+
await runAppDisposeCallbacks(disposeCallbacks);
|
|
108
81
|
}
|
|
109
82
|
|
|
110
83
|
export function hasContainer(appName: string): boolean {
|
|
@@ -113,15 +86,135 @@ export function hasContainer(appName: string): boolean {
|
|
|
113
86
|
|
|
114
87
|
export function importModule<TModule>(request: AppRequest): TModule {
|
|
115
88
|
const { appName, exposedModule } = parseAppRequest(request);
|
|
116
|
-
const
|
|
89
|
+
const container = getContainer(appName);
|
|
90
|
+
const module = container == null ? null : getExposedModule(container, exposedModule);
|
|
117
91
|
if (module == null) {
|
|
118
92
|
throw new ExposedModuleNotFoundError(appName, exposedModule);
|
|
119
93
|
}
|
|
120
94
|
return module as TModule;
|
|
121
95
|
}
|
|
122
96
|
|
|
123
|
-
function
|
|
124
|
-
|
|
97
|
+
function ensureRuntimeContext(
|
|
98
|
+
context: MicroFrontendGlobalContext
|
|
99
|
+
): asserts context is MicroFrontendGlobalContext & MicroFrontendRuntimeContext {
|
|
100
|
+
const containers = context.__CONTAINERS__;
|
|
101
|
+
const sharedModules = context.__SHARED__;
|
|
102
|
+
const propertyDescriptors = new Map<PropertyKey, PropertyDescriptor | undefined>();
|
|
103
|
+
const runtimeProperties = {
|
|
104
|
+
containers,
|
|
105
|
+
dispose: isRegisterAppDispose(Reflect.get(context, 'dispose'))
|
|
106
|
+
? Reflect.get(context, 'dispose')
|
|
107
|
+
: createRegisterAppDispose(),
|
|
108
|
+
disposeCallbacksByApp: isDisposeCallbackRegistry(Reflect.get(context, 'disposeCallbacksByApp'))
|
|
109
|
+
? Reflect.get(context, 'disposeCallbacksByApp')
|
|
110
|
+
: {},
|
|
111
|
+
sharedModules,
|
|
112
|
+
} satisfies MicroFrontendRuntimeContext;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
for (const property of Reflect.ownKeys(runtimeProperties)) {
|
|
116
|
+
propertyDescriptors.set(property, Reflect.getOwnPropertyDescriptor(context, property));
|
|
117
|
+
const value = Reflect.get(runtimeProperties, property);
|
|
118
|
+
if (
|
|
119
|
+
!Reflect.defineProperty(context, property, {
|
|
120
|
+
configurable: true,
|
|
121
|
+
enumerable: false,
|
|
122
|
+
value,
|
|
123
|
+
writable: true,
|
|
124
|
+
})
|
|
125
|
+
) {
|
|
126
|
+
throw new Error(`Cannot install micro-frontend runtime property '${String(property)}'`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
for (const [property, descriptor] of Array.from(propertyDescriptors).reverse()) {
|
|
131
|
+
if (descriptor == null) {
|
|
132
|
+
Reflect.deleteProperty(context, property);
|
|
133
|
+
} else {
|
|
134
|
+
Reflect.defineProperty(context, property, descriptor);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
142
|
+
return typeof value === 'object' && value != null && !Array.isArray(value);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isRegisterAppDispose(value: unknown): value is RegisterAppDispose {
|
|
146
|
+
return typeof value === 'function';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isDisposeCallbackRegistry(value: unknown): value is Record<string, Set<AppDisposeCallback>> {
|
|
150
|
+
return isObjectRecord(value) && Object.values(value).every(isDisposeCallbackSet);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function isDisposeCallbackSet(value: unknown): value is Set<AppDisposeCallback> {
|
|
154
|
+
return (
|
|
155
|
+
typeof value === 'object' &&
|
|
156
|
+
value != null &&
|
|
157
|
+
typeof Reflect.get(value, 'add') === 'function' &&
|
|
158
|
+
typeof Reflect.get(value, 'delete') === 'function' &&
|
|
159
|
+
typeof Reflect.get(value, 'has') === 'function' &&
|
|
160
|
+
typeof Reflect.get(value, Symbol.iterator) === 'function'
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function createRegisterAppDispose(): RegisterAppDispose {
|
|
165
|
+
function dispose(callback: AppDisposeCallback): () => void;
|
|
166
|
+
function dispose(appName: string, callback: AppDisposeCallback): () => void;
|
|
167
|
+
function dispose(appNameOrCallback: string | AppDisposeCallback, callback?: AppDisposeCallback): () => void {
|
|
168
|
+
if (typeof appNameOrCallback !== 'string' || callback == null) {
|
|
169
|
+
throw new Error('dispose() must be compiled with the microFrontend plugin');
|
|
170
|
+
}
|
|
171
|
+
return registerAppDisposeCallback(getMicroFrontendRuntimeContext(), appNameOrCallback, callback);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return dispose;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function registerAppDisposeCallback(
|
|
178
|
+
context: MicroFrontendRuntimeContext,
|
|
179
|
+
appName: string,
|
|
180
|
+
callback: AppDisposeCallback
|
|
181
|
+
): () => void {
|
|
182
|
+
if (typeof appName !== 'string' || typeof callback !== 'function') {
|
|
183
|
+
throw new Error('dispose() must be compiled with the microFrontend plugin');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const disposeCallbacks = (context.disposeCallbacksByApp[appName] ??= new Set());
|
|
187
|
+
disposeCallbacks.add(callback);
|
|
188
|
+
let isRegistered = true;
|
|
189
|
+
|
|
190
|
+
return () => {
|
|
191
|
+
if (!isRegistered) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
isRegistered = false;
|
|
195
|
+
disposeCallbacks.delete(callback);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function runAppDisposeCallbacks(disposeCallbacks: Set<AppDisposeCallback>): Promise<void> {
|
|
200
|
+
const callbacks = Array.from(disposeCallbacks).reverse();
|
|
201
|
+
let firstDisposeError: unknown;
|
|
202
|
+
let didDisposeFail = false;
|
|
203
|
+
|
|
204
|
+
for (const dispose of callbacks) {
|
|
205
|
+
try {
|
|
206
|
+
await dispose();
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (!didDisposeFail) {
|
|
209
|
+
firstDisposeError = error;
|
|
210
|
+
didDisposeFail = true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (didDisposeFail) {
|
|
216
|
+
throw firstDisposeError;
|
|
217
|
+
}
|
|
125
218
|
}
|
|
126
219
|
|
|
127
220
|
export const microFrontendModuleRegistry: MicroFrontendModuleRegistry = {
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import { useLayoutEffect, useReducer } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from '../host/pendingHostComponentStore';
|
|
6
|
-
import type {
|
|
7
|
-
MicroFrontendRuntimeApi,
|
|
8
|
-
MicroFrontendSessionEvent,
|
|
9
|
-
} from '../types';
|
|
1
|
+
import { useEffect, useLayoutEffect, useReducer, useRef } from 'react';
|
|
2
|
+
import { installPendingHostComponentBridge, resetPendingHostComponent } from '../host/pendingHostComponentStore';
|
|
3
|
+
import { disposeAppResources } from '../runtime/registry';
|
|
4
|
+
import type { MicroFrontendRuntimeApi, MicroFrontendSessionEvent } from '../types';
|
|
10
5
|
|
|
11
6
|
export interface MicroFrontendSessionState {
|
|
12
7
|
readonly appName: string;
|
|
@@ -38,9 +33,7 @@ function reduceMicroFrontendSessions(
|
|
|
38
33
|
return sessions.filter(({ sessionId }) => sessionId !== event.params.sessionId);
|
|
39
34
|
case 'sessionVisibilityChanged':
|
|
40
35
|
return sessions.map((session) =>
|
|
41
|
-
session.sessionId === event.params.sessionId
|
|
42
|
-
? { ...session, isVisible: event.params.isVisible }
|
|
43
|
-
: session
|
|
36
|
+
session.sessionId === event.params.sessionId ? { ...session, isVisible: event.params.isVisible } : session
|
|
44
37
|
);
|
|
45
38
|
default: {
|
|
46
39
|
const exhaustiveEvent: never = event;
|
|
@@ -52,10 +45,27 @@ function reduceMicroFrontendSessions(
|
|
|
52
45
|
export function useMicroFrontendSessions(
|
|
53
46
|
runtime: Pick<MicroFrontendRuntimeApi, 'onEvent'>
|
|
54
47
|
): readonly MicroFrontendSessionState[] {
|
|
55
|
-
const [sessions, dispatch] = useReducer(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
48
|
+
const [sessions, dispatch] = useReducer(reduceMicroFrontendSessions, INITIAL_SESSIONS);
|
|
49
|
+
const previousSessionsRef = useRef(sessions);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const previousSessions = previousSessionsRef.current;
|
|
53
|
+
previousSessionsRef.current = sessions;
|
|
54
|
+
const activeSessionIds = new Set(sessions.map(({ sessionId }) => sessionId));
|
|
55
|
+
const activeAppNames = new Set(sessions.map(({ appName }) => appName));
|
|
56
|
+
const inactiveAppNames = new Set(
|
|
57
|
+
previousSessions
|
|
58
|
+
.filter(({ sessionId }) => !activeSessionIds.has(sessionId))
|
|
59
|
+
.map(({ appName }) => appName)
|
|
60
|
+
.filter((appName) => !activeAppNames.has(appName))
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
inactiveAppNames.forEach((appName) => {
|
|
64
|
+
void disposeAppResources(appName).catch((error) => {
|
|
65
|
+
console.error(`Failed to dispose micro-frontend app resources for '${appName}'`, error);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}, [sessions]);
|
|
59
69
|
|
|
60
70
|
useLayoutEffect(() => {
|
|
61
71
|
installPendingHostComponentBridge();
|