diff --git a/src/API.ts b/src/API.ts index 8be0989..7001ec0 100644 --- a/src/API.ts +++ b/src/API.ts @@ -342,6 +342,10 @@ export interface ObservableState { export type AnyFunction = (...args: any[]) => any export type FunctionWithSameArgs = (...args: Parameters) => any +export interface Lazy { + get(): T +} + /** * An scoped communication terminal provided for an {EntryPoint} * in order to contribute its application content to the {AppHost} @@ -474,6 +478,15 @@ export interface Shell extends Pick & Partial)} memoizedFunction */ clearCache(memoizedFunction: Partial<_.MemoizedFunction> & Partial): void + /** + * Creates a lazy-evaluated value. The function `func` is only executed once when `get` is called for the first time, + * and the result is cached for subsequent calls. + * + * @template T + * @param {F} func - The function that will be lazily evaluated. It should not take any arguments. + * @returns {Lazy} An object with a `get` method that returns the lazily evaluated value of type `T`. + */ + lazyEvaluator>(func: F): Lazy } export interface PrivateShell extends Shell { diff --git a/src/appHost.tsx b/src/appHost.tsx index 4d82d27..703144e 100644 --- a/src/appHost.tsx +++ b/src/appHost.tsx @@ -9,6 +9,7 @@ import { EntryPointsInfo, ExtensionItem, ExtensionSlot, + Lazy, LazyEntryPointDescriptor, LazyEntryPointFactory, PrivateShell, @@ -237,6 +238,22 @@ export function createAppHost(initialEntryPointsOrPackages: EntryPointOrPackage[ return enrichedMemoization } + function lazyEvaluator>(fn: F): Lazy { + let _value: T + let _resolved: boolean = false + + return { + get: () => { + if (!_resolved) { + _value = fn() + _resolved = true + } + + return _value + } + } + } + // we know that addShells completes synchronously addShells(initialEntryPointsOrPackages) @@ -1092,7 +1109,8 @@ miss: ${memoizedWithMissHit.miss} wrapWithShellRenderer(component): JSX.Element { return - } + }, + lazyEvaluator } return shell diff --git a/test/appHost.spec.ts b/test/appHost.spec.ts index 82a8a6a..68dba53 100644 --- a/test/appHost.spec.ts +++ b/test/appHost.spec.ts @@ -933,6 +933,18 @@ describe('App Host', () => { } }) }) + + describe('lazyEvaluator', () => { + it('should return a getter that is evaluated only once', () => { + const { helperShell } = createHostWithDependantPackages(MockAPI) + const func = jest.fn(() => 42) + const lazyEval = helperShell.lazyEvaluator(func) + + expect(lazyEval.get()).toBe(42) + expect(lazyEval.get()).toBe(42) + expect(func).toHaveBeenCalledTimes(1) + }) + }) }) describe('Entry Point Shell Scoping', () => { diff --git a/testKit/index.tsx b/testKit/index.tsx index e896bfd..9a4d944 100644 --- a/testKit/index.tsx +++ b/testKit/index.tsx @@ -258,7 +258,8 @@ function createShell(host: AppHost): PrivateShell { clearCache: _.noop, getHostOptions: () => host.options, log: createShellLogger(host, entryPoint), - wrapWithShellRenderer: (component: JSX.Element) => component + wrapWithShellRenderer: (component: JSX.Element) => component, + lazyEvaluator: func => ({ get: func }) } }