Skip to content

Commit

Permalink
feat(core): enhance module loader with module type
Browse files Browse the repository at this point in the history
Co-authored-by: Stefan Meyer <[email protected]>
Co-authored-by: Clemens Akens <[email protected]>
Co-authored-by: Niko Steinhoff <[email protected]>
  • Loading branch information
4 people committed Jun 25, 2021
1 parent 5899b9f commit ed56640
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 11 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/__tests__/create-feature-hub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
});
Expand Down
66 changes: 66 additions & 0 deletions packages/core/src/__tests__/feature-app-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown>;
let mockFeatureAppDefinitionB: FeatureAppDefinition<unknown>;
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([
Expand Down
26 changes: 17 additions & 9 deletions packages/core/src/feature-app-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ export interface FeatureAppDefinition<
create(env: FeatureAppEnvironment<TFeatureServices, TConfig>): TFeatureApp;
}

export type ModuleLoader = (url: string) => Promise<unknown>;
export type ModuleLoader = (
url: string,
moduleType?: string
) => Promise<unknown>;

export interface FeatureAppScope<TFeatureApp> {
readonly featureApp: TFeatureApp;
Expand Down Expand Up @@ -135,7 +138,6 @@ export interface FeatureAppManagerOptions {
readonly logger?: Logger;
}

type FeatureAppModuleUrl = string;
type FeatureAppId = string;

interface FeatureAppRetainer<TFeatureApp> {
Expand All @@ -149,7 +151,7 @@ interface FeatureAppRetainer<TFeatureApp> {
*/
export class FeatureAppManager {
private readonly asyncFeatureAppDefinitions = new Map<
FeatureAppModuleUrl,
string,
AsyncValue<FeatureAppDefinition<unknown>>
>();

Expand Down Expand Up @@ -187,14 +189,19 @@ export class FeatureAppManager {
* as default.
*/
public getAsyncFeatureAppDefinition(
url: string
url: string,
moduleType?: string
): AsyncValue<FeatureAppDefinition<unknown>> {
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;
Expand Down Expand Up @@ -276,7 +283,8 @@ export class FeatureAppManager {
}

private createAsyncFeatureAppDefinition(
url: string
url: string,
moduleType?: string
): AsyncValue<FeatureAppDefinition<unknown>> {
const {moduleLoader: loadModule} = this.options;

Expand All @@ -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(
Expand Down

0 comments on commit ed56640

Please sign in to comment.