From dcd4bedeed50f9e17c45fbf46eccd4e270de0771 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Thu, 20 Mar 2025 11:03:48 +0100 Subject: [PATCH] fix(core): do not process manifest for intialized scopes --- .../src/e2e/test-app/sdk-plugin-loading.cy.ts | 7 ++++ examples/test-app/src/routes/SDKModules.tsx | 32 +++++++++++++-- .../src/modules/delayedModule.tsx | 41 +++++++++++++++++++ federation-cdn-mock/webpack.config.js | 1 + packages/core/src/index.test.ts | 1 + packages/core/src/index.ts | 26 ++++++++++-- 6 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 federation-cdn-mock/src/modules/delayedModule.tsx diff --git a/examples/test-app-e2e/src/e2e/test-app/sdk-plugin-loading.cy.ts b/examples/test-app-e2e/src/e2e/test-app/sdk-plugin-loading.cy.ts index b27d97f..5d5d815 100644 --- a/examples/test-app-e2e/src/e2e/test-app/sdk-plugin-loading.cy.ts +++ b/examples/test-app-e2e/src/e2e/test-app/sdk-plugin-loading.cy.ts @@ -18,4 +18,11 @@ describe('SDK module loading', () => { cy.get(`[aria-label="Checked"]`).should('exist'); cy.get('#plugin-manifest').should('exist'); }); + + it('should render delayed module without processing entire manifest', () => { + cy.visit('http://localhost:4200/sdk'); + // Delayed module is fetched after 5 seconds + cy.wait(5001); + cy.get('#delayed-module').should('exist'); + }); }); diff --git a/examples/test-app/src/routes/SDKModules.tsx b/examples/test-app/src/routes/SDKModules.tsx index 65165cc..bfb2542 100644 --- a/examples/test-app/src/routes/SDKModules.tsx +++ b/examples/test-app/src/routes/SDKModules.tsx @@ -1,15 +1,33 @@ -import React from 'react'; +import { useEffect, useState } from 'react'; import { ScalprumComponent } from '@scalprum/react-core'; -import { Grid } from '@mui/material'; +import { Grid, Typography } from '@mui/material'; const SDKModules = () => { + const [delayed, setDelayed] = useState(false); + const [seconds, setSeconds] = useState(0); + useEffect(() => { + const timeout = setTimeout(() => { + setDelayed(true); + }, 5000); + const interval = setInterval(() => { + if (seconds >= 6) { + clearInterval(interval); + return; + } + setSeconds((prevSeconds) => prevSeconds + 1); + }, 1000); + return () => { + clearTimeout(timeout); + clearInterval(interval); + }; + }, []); const props = { name: 'plugin-manifest', }; return ( - ; + @@ -17,6 +35,14 @@ const SDKModules = () => { + + + {delayed ? ( + + ) : ( + Loading delayed module in {5 - seconds} seconds + )} + ); }; diff --git a/federation-cdn-mock/src/modules/delayedModule.tsx b/federation-cdn-mock/src/modules/delayedModule.tsx new file mode 100644 index 0000000..44c14ba --- /dev/null +++ b/federation-cdn-mock/src/modules/delayedModule.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import FormatAlignLeftIcon from '@mui/icons-material/FormatAlignLeft'; +import FormatAlignCenterIcon from '@mui/icons-material/FormatAlignCenter'; +import FormatAlignRightIcon from '@mui/icons-material/FormatAlignRight'; +import FormatAlignJustifyIcon from '@mui/icons-material/FormatAlignJustify'; +import ToggleButton from '@mui/material/ToggleButton'; +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; + +export default function ToggleButtons() { + const [alignment, setAlignment] = React.useState('left'); + + const handleAlignment = ( + event: React.MouseEvent, + newAlignment: string | null, + ) => { + setAlignment(newAlignment); + }; + + return ( + + + + + + + + + + + + + + + ); +} diff --git a/federation-cdn-mock/webpack.config.js b/federation-cdn-mock/webpack.config.js index 08b99f4..cd89d3d 100644 --- a/federation-cdn-mock/webpack.config.js +++ b/federation-cdn-mock/webpack.config.js @@ -50,6 +50,7 @@ const TestSDKPlugin = new DynamicRemotePlugin({ './ModuleFour': resolve(__dirname, './src/modules/moduleFour.tsx'), './SDKComponent': resolve(__dirname, './src/modules/SDKComponent.tsx'), './ApiModule': resolve(__dirname, './src/modules/apiModule.tsx'), + './DelayedModule': resolve(__dirname, './src/modules/delayedModule.tsx'), }, }, }); diff --git a/packages/core/src/index.test.ts b/packages/core/src/index.test.ts index 8c4d836..051986f 100644 --- a/packages/core/src/index.test.ts +++ b/packages/core/src/index.test.ts @@ -113,6 +113,7 @@ describe('scalprum', () => { pendingInjections: {}, pendingLoading: {}, pendingPrefetch: {}, + existingScopes: new Set(), api: {}, scalprumOptions: { cacheTimeout: 120, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 243f6b6..9a5a388 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -45,6 +45,7 @@ export type Scalprum = Record> = { pendingPrefetch: { [key: string]: Promise; }; + existingScopes: Set; exposedModules: { [moduleId: string]: ExposedScalprumModule; }; @@ -248,6 +249,7 @@ export const initialize = = Record>( pendingInjections: {}, pendingLoading: {}, pendingPrefetch: {}, + existingScopes: new Set(), exposedModules: {}, scalprumOptions: defaultOptions, api: api || {}, @@ -263,7 +265,11 @@ export const removeScalprum = () => { export const getAppData = (name: string): AppMetadata => getScalprum().appsConfig[name]; -const setExposedModule = (moduleId: string, exposedModule: ExposedScalprumModule) => { +const setExposedModule = (scope: string, module: string, exposedModule: ExposedScalprumModule) => { + if (!getScalprum().existingScopes.has(scope)) { + getScalprum().existingScopes.add(scope); + } + const moduleId = getModuleIdentifier(scope, module); getScalprum().exposedModules[moduleId] = exposedModule; }; @@ -302,11 +308,23 @@ export async function processManifest( processor?: (manifest: any) => string[], ): Promise { let pendingInjection = getPendingInjection(scope); - const { pluginStore } = getScalprum(); + const { pluginStore, existingScopes } = getScalprum(); + + if (existingScopes.has(scope)) { + try { + const exposedModule = await pluginStore.getExposedModule(scope, module); + setExposedModule(scope, module, exposedModule); + return; + } catch (error) { + console.warn('Unable to load module from existing container', error); + console.warn('Scalprum will try to process manifest from scratch.'); + } + } + if (pendingInjection) { await pendingInjection; const exposedModule = await pluginStore.getExposedModule(scope, module); - setExposedModule(getModuleIdentifier(scope, module), exposedModule); + setExposedModule(scope, module, exposedModule); return; } @@ -361,7 +379,7 @@ export async function processManifest( await pluginStore.loadPlugin(sdkManifest); try { const exposedModule = await pluginStore.getExposedModule(scope, module); - setExposedModule(getModuleIdentifier(scope, module), exposedModule); + setExposedModule(scope, module, exposedModule); return; } catch (error) { clearPendingInjection(scope);