From ed5664083c02a2e2f849e1ab914b7253074a7ea2 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 25 Jun 2021 13:04:24 +0200 Subject: [PATCH] feat(core): enhance module loader with module type Co-authored-by: Stefan Meyer Co-authored-by: Clemens Akens Co-authored-by: Niko Steinhoff --- .../src/__tests__/create-feature-hub.test.ts | 4 +- .../src/__tests__/feature-app-manager.test.ts | 66 +++++++++++++++++++ packages/core/src/feature-app-manager.ts | 26 +++++--- 3 files changed, 85 insertions(+), 11 deletions(-) diff --git a/packages/core/src/__tests__/create-feature-hub.test.ts b/packages/core/src/__tests__/create-feature-hub.test.ts index eff4ccb56..6d47cc975 100644 --- a/packages/core/src/__tests__/create-feature-hub.test.ts +++ b/packages/core/src/__tests__/create-feature-hub.test.ts @@ -69,9 +69,9 @@ describe('createFeatureHub()', () => { const url = 'http://example.com/test.js'; - featureAppManager.getAsyncFeatureAppDefinition(url); + featureAppManager.getAsyncFeatureAppDefinition(url, 'a'); - expect(mockModuleLoader).toHaveBeenCalledWith(url); + expect(mockModuleLoader).toHaveBeenCalledWith(url, 'a'); }); }); }); diff --git a/packages/core/src/__tests__/feature-app-manager.test.ts b/packages/core/src/__tests__/feature-app-manager.test.ts index ad15bd073..335c9a322 100644 --- a/packages/core/src/__tests__/feature-app-manager.test.ts +++ b/packages/core/src/__tests__/feature-app-manager.test.ts @@ -89,6 +89,72 @@ describe('FeatureAppManager', () => { expect(asyncFeatureAppDefinition.value).toBe(featureAppDefinition); expect(asyncFeatureAppDefinition.error).toBeUndefined(); + expect(featureAppDefinition).toBe(mockFeatureAppDefinition); + }); + + describe('with a custom module loader that handles multiple module types', () => { + let mockFeatureAppDefinitionA: FeatureAppDefinition; + let mockFeatureAppDefinitionB: FeatureAppDefinition; + let mockFeatureAppModuleA: FeatureAppModule | undefined; + let mockFeatureAppModuleB: FeatureAppModule | undefined; + + beforeEach(() => { + mockFeatureAppDefinitionA = {create: mockFeatureAppCreate}; + mockFeatureAppDefinitionB = {create: mockFeatureAppCreate}; + mockFeatureAppModuleA = {default: mockFeatureAppDefinitionA}; + mockFeatureAppModuleB = {default: mockFeatureAppDefinitionB}; + + featureAppManager = new FeatureAppManager(mockFeatureServiceRegistry, { + logger, + moduleLoader: async (_url, moduleType) => + moduleType === 'a' + ? mockFeatureAppModuleA + : moduleType === 'b' + ? mockFeatureAppModuleB + : undefined, + }); + }); + + it('returns an async value containing the Feature App definition for the correct module type', async () => { + // Intentionally used for both module types to show that this is handled + // correctly by the Feature App Manager. + const url = '/example.js'; + + const asyncFeatureAppDefinitionA = featureAppManager.getAsyncFeatureAppDefinition( + url, + 'a' + ); + + const featureAppDefinitionA = await asyncFeatureAppDefinitionA.promise; + + expect(featureAppDefinitionA).toBe(mockFeatureAppDefinitionA); + + const asyncFeatureAppDefinitionB = featureAppManager.getAsyncFeatureAppDefinition( + url, + 'b' + ); + + const featureAppDefinitionB = await asyncFeatureAppDefinitionB.promise; + + expect(featureAppDefinitionB).toBe(mockFeatureAppDefinitionB); + }); + + it('returns an async value containing an error for an unknown module type', async () => { + const expectedError = new Error( + 'The Feature App module at the url "/example.js" is invalid. A Feature App module must have a Feature App definition as default export. A Feature App definition is an object with at least a `create` method.' + ); + + await expect( + featureAppManager.getAsyncFeatureAppDefinition('/example.js').promise + ).rejects.toEqual(expectedError); + + await expect( + featureAppManager.getAsyncFeatureAppDefinition( + '/example.js', + 'unknown' + ).promise + ).rejects.toEqual(expectedError); + }); }); describe.each([ diff --git a/packages/core/src/feature-app-manager.ts b/packages/core/src/feature-app-manager.ts index c81de93f0..915dbac73 100644 --- a/packages/core/src/feature-app-manager.ts +++ b/packages/core/src/feature-app-manager.ts @@ -60,7 +60,10 @@ export interface FeatureAppDefinition< create(env: FeatureAppEnvironment): TFeatureApp; } -export type ModuleLoader = (url: string) => Promise; +export type ModuleLoader = ( + url: string, + moduleType?: string +) => Promise; export interface FeatureAppScope { readonly featureApp: TFeatureApp; @@ -135,7 +138,6 @@ export interface FeatureAppManagerOptions { readonly logger?: Logger; } -type FeatureAppModuleUrl = string; type FeatureAppId = string; interface FeatureAppRetainer { @@ -149,7 +151,7 @@ interface FeatureAppRetainer { */ export class FeatureAppManager { private readonly asyncFeatureAppDefinitions = new Map< - FeatureAppModuleUrl, + string, AsyncValue> >(); @@ -187,14 +189,19 @@ export class FeatureAppManager { * as default. */ public getAsyncFeatureAppDefinition( - url: string + url: string, + moduleType?: string ): AsyncValue> { - let asyncFeatureAppDefinition = this.asyncFeatureAppDefinitions.get(url); + const key = `${url}${moduleType}`; + let asyncFeatureAppDefinition = this.asyncFeatureAppDefinitions.get(key); if (!asyncFeatureAppDefinition) { - asyncFeatureAppDefinition = this.createAsyncFeatureAppDefinition(url); + asyncFeatureAppDefinition = this.createAsyncFeatureAppDefinition( + url, + moduleType + ); - this.asyncFeatureAppDefinitions.set(url, asyncFeatureAppDefinition); + this.asyncFeatureAppDefinitions.set(key, asyncFeatureAppDefinition); } return asyncFeatureAppDefinition; @@ -276,7 +283,8 @@ export class FeatureAppManager { } private createAsyncFeatureAppDefinition( - url: string + url: string, + moduleType?: string ): AsyncValue> { const {moduleLoader: loadModule} = this.options; @@ -285,7 +293,7 @@ export class FeatureAppManager { } return new AsyncValue( - loadModule(url).then((featureAppModule) => { + loadModule(url, moduleType).then((featureAppModule) => { if (!isFeatureAppModule(featureAppModule)) { throw new Error( `The Feature App module at the url ${JSON.stringify(