rename flags to advanced
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
---
|
||||
'@backstage/frontend-defaults': minor
|
||||
'@backstage/frontend-app-api': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Restructured some of option fields of `createApp` and `createSpecializedApp`.
|
||||
|
||||
- For `createApp`, all option fields _except_ `features` and `bindRoutes` have been moved into a new `advanced` object field.
|
||||
- For `createSpecializedApp`, all option fields _except_ `features`, `config`, and `bindRoutes` have been moved into a new `advanced` object field.
|
||||
|
||||
This helps highlight that some options are meant to rarely be needed or used, and simplifies the usage of those options that are almost always required.
|
||||
|
||||
As an example, if you used to supply a custom config loader, you would update your code as follows:
|
||||
|
||||
```diff
|
||||
createApp({
|
||||
features: [...],
|
||||
- configLoader: new MyCustomLoader(),
|
||||
+ advanced: {
|
||||
+ configLoader: new MyCustomLoader(),
|
||||
+ },
|
||||
})
|
||||
```
|
||||
@@ -135,7 +135,9 @@ const app = createApp({
|
||||
customHomePageModule,
|
||||
...collectedLegacyPlugins,
|
||||
],
|
||||
pluginInfoResolver,
|
||||
advanced: {
|
||||
pluginInfoResolver,
|
||||
},
|
||||
/* Handled through config instead */
|
||||
// bindRoutes({ bind }) {
|
||||
// bind(pagesPlugin.externalRoutes, { pageX: pagesPlugin.routes.pageX });
|
||||
|
||||
@@ -38,14 +38,14 @@ export type CreateSpecializedAppOptions = {
|
||||
features?: FrontendFeature[];
|
||||
config?: ConfigApi;
|
||||
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
|
||||
apis?: ApiHolder;
|
||||
extensionFactoryMiddleware?:
|
||||
| ExtensionFactoryMiddleware
|
||||
| ExtensionFactoryMiddleware[];
|
||||
flags?: {
|
||||
advanced?: {
|
||||
apis?: ApiHolder;
|
||||
allowUnknownExtensionConfig?: boolean;
|
||||
extensionFactoryMiddleware?:
|
||||
| ExtensionFactoryMiddleware
|
||||
| ExtensionFactoryMiddleware[];
|
||||
pluginInfoResolver?: FrontendPluginInfoResolver;
|
||||
};
|
||||
pluginInfoResolver?: FrontendPluginInfoResolver;
|
||||
};
|
||||
|
||||
// @public
|
||||
|
||||
@@ -313,10 +313,12 @@ describe('createSpecializedApp', () => {
|
||||
|
||||
it('should use provided apis', async () => {
|
||||
const app = createSpecializedApp({
|
||||
apis: TestApiRegistry.from([
|
||||
configApiRef,
|
||||
new ConfigReader({ anything: 'config' }),
|
||||
]),
|
||||
advanced: {
|
||||
apis: TestApiRegistry.from([
|
||||
configApiRef,
|
||||
new ConfigReader({ anything: 'config' }),
|
||||
]),
|
||||
},
|
||||
features: [
|
||||
createFrontendPlugin({
|
||||
pluginId: 'test',
|
||||
@@ -639,28 +641,30 @@ describe('createSpecializedApp', () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
extensionFactoryMiddleware: [
|
||||
function* middleware(originalFactory, { config }) {
|
||||
const result = originalFactory({
|
||||
config: config && { text: `1-${config.text}` },
|
||||
});
|
||||
yield* result;
|
||||
const el = result.get(textDataRef);
|
||||
if (el) {
|
||||
yield textDataRef(`${el}-1`);
|
||||
}
|
||||
},
|
||||
function* middleware(originalFactory, { config }) {
|
||||
const result = originalFactory({
|
||||
config: config && { text: `2-${config.text}` },
|
||||
});
|
||||
yield* result;
|
||||
const el = result.get(textDataRef);
|
||||
if (el) {
|
||||
yield textDataRef(`${el}-2`);
|
||||
}
|
||||
},
|
||||
],
|
||||
advanced: {
|
||||
extensionFactoryMiddleware: [
|
||||
function* middleware(originalFactory, { config }) {
|
||||
const result = originalFactory({
|
||||
config: config && { text: `1-${config.text}` },
|
||||
});
|
||||
yield* result;
|
||||
const el = result.get(textDataRef);
|
||||
if (el) {
|
||||
yield textDataRef(`${el}-1`);
|
||||
}
|
||||
},
|
||||
function* middleware(originalFactory, { config }) {
|
||||
const result = originalFactory({
|
||||
config: config && { text: `2-${config.text}` },
|
||||
});
|
||||
yield* result;
|
||||
const el = result.get(textDataRef);
|
||||
if (el) {
|
||||
yield textDataRef(`${el}-2`);
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const root = app.tree.root.instance!.getData(
|
||||
@@ -774,12 +778,14 @@ describe('createSpecializedApp', () => {
|
||||
|
||||
const app = createSpecializedApp({
|
||||
features: [plugin],
|
||||
async pluginInfoResolver(ctx) {
|
||||
const { info } = await ctx.defaultResolver({
|
||||
packageJson: await ctx.packageJson(),
|
||||
manifest: await ctx.manifest(),
|
||||
});
|
||||
return { info: { packageName: `decorated:${info.packageName}` } };
|
||||
advanced: {
|
||||
pluginInfoResolver: async ctx => {
|
||||
const { info } = await ctx.defaultResolver({
|
||||
packageJson: await ctx.packageJson(),
|
||||
manifest: await ctx.manifest(),
|
||||
});
|
||||
return { info: { packageName: `decorated:${info.packageName}` } };
|
||||
},
|
||||
},
|
||||
});
|
||||
const info = await app.tree.nodes.get('test')?.spec.plugin?.info();
|
||||
|
||||
@@ -200,20 +200,72 @@ class RouteResolutionApiProxy implements RouteResolutionApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for `createSpecializedApp`.
|
||||
* Options for {@link createSpecializedApp}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CreateSpecializedAppOptions = {
|
||||
/**
|
||||
* The list of features to load.
|
||||
*/
|
||||
features?: FrontendFeature[];
|
||||
|
||||
/**
|
||||
* The config API implementation to use. For most normal apps, this should be
|
||||
* specified.
|
||||
*
|
||||
* If none is given, a new _empty_ config will be used during startup. In
|
||||
* later stages of the app lifecycle, the config API in the API holder will be
|
||||
* used.
|
||||
*/
|
||||
config?: ConfigApi;
|
||||
|
||||
/**
|
||||
* Allows for the binding of plugins' external route refs within the app.
|
||||
*/
|
||||
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
|
||||
apis?: ApiHolder;
|
||||
extensionFactoryMiddleware?:
|
||||
| ExtensionFactoryMiddleware
|
||||
| ExtensionFactoryMiddleware[];
|
||||
flags?: { allowUnknownExtensionConfig?: boolean };
|
||||
pluginInfoResolver?: FrontendPluginInfoResolver;
|
||||
|
||||
/**
|
||||
* Advanced, more rarely used options.
|
||||
*/
|
||||
advanced?: {
|
||||
/**
|
||||
* A replacement API holder implementation to use.
|
||||
*
|
||||
* By default, a new API holder will be constructed automatically based on
|
||||
* the other inputs. If you pass in a custom one here, none of that
|
||||
* automation will take place - so you will have to take care to supply all
|
||||
* those APIs yourself.
|
||||
*/
|
||||
apis?: ApiHolder;
|
||||
|
||||
/**
|
||||
* If set to true, the system will silently accept and move on if
|
||||
* encountering config for extensions that do not exist. The default is to
|
||||
* reject such config to help catch simple mistakes.
|
||||
*
|
||||
* This flag can be useful in some scenarios where you have a dynamic set of
|
||||
* extensions enabled at different times, but also increases the risk of
|
||||
* accidentally missing e.g. simple typos in your config.
|
||||
*/
|
||||
allowUnknownExtensionConfig?: boolean;
|
||||
|
||||
/**
|
||||
* Applies one or more middleware on every extension, as they are added to
|
||||
* the application.
|
||||
*
|
||||
* This is an advanced use case for modifying extension data on the fly as
|
||||
* it gets emitted by extensions being instantiated.
|
||||
*/
|
||||
extensionFactoryMiddleware?:
|
||||
| ExtensionFactoryMiddleware
|
||||
| ExtensionFactoryMiddleware[];
|
||||
|
||||
/**
|
||||
* Allows for customizing how plugin info is retrieved.
|
||||
*/
|
||||
pluginInfoResolver?: FrontendPluginInfoResolver;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -229,7 +281,7 @@ export function createSpecializedApp(options?: CreateSpecializedAppOptions): {
|
||||
} {
|
||||
const config = options?.config ?? new ConfigReader({}, 'empty-config');
|
||||
const features = deduplicateFeatures(options?.features ?? []).map(
|
||||
createPluginInfoAttacher(config, options?.pluginInfoResolver),
|
||||
createPluginInfoAttacher(config, options?.advanced?.pluginInfoResolver),
|
||||
);
|
||||
|
||||
const tree = resolveAppTree(
|
||||
@@ -241,7 +293,8 @@ export function createSpecializedApp(options?: CreateSpecializedAppOptions): {
|
||||
],
|
||||
parameters: readAppExtensionsConfig(config),
|
||||
forbidden: new Set(['root']),
|
||||
allowUnknownExtensionConfig: options?.flags?.allowUnknownExtensionConfig,
|
||||
allowUnknownExtensionConfig:
|
||||
options?.advanced?.allowUnknownExtensionConfig,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -257,7 +310,7 @@ export function createSpecializedApp(options?: CreateSpecializedAppOptions): {
|
||||
|
||||
const appIdentityProxy = new AppIdentityProxy();
|
||||
const apis =
|
||||
options?.apis ??
|
||||
options?.advanced?.apis ??
|
||||
createApiHolder({
|
||||
factories,
|
||||
staticFactories: [
|
||||
@@ -294,7 +347,9 @@ export function createSpecializedApp(options?: CreateSpecializedAppOptions): {
|
||||
instantiateAppNodeTree(
|
||||
tree.root,
|
||||
apis,
|
||||
mergeExtensionFactoryMiddleware(options?.extensionFactoryMiddleware),
|
||||
mergeExtensionFactoryMiddleware(
|
||||
options?.advanced?.extensionFactoryMiddleware,
|
||||
),
|
||||
);
|
||||
|
||||
const routeInfo = extractRouteInfoFromAppNode(
|
||||
|
||||
@@ -20,25 +20,19 @@ export function createApp(options?: CreateAppOptions): {
|
||||
|
||||
// @public
|
||||
export interface CreateAppOptions {
|
||||
// (undocumented)
|
||||
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
|
||||
// (undocumented)
|
||||
configLoader?: () => Promise<{
|
||||
config: ConfigApi;
|
||||
}>;
|
||||
// (undocumented)
|
||||
extensionFactoryMiddleware?:
|
||||
| ExtensionFactoryMiddleware
|
||||
| ExtensionFactoryMiddleware[];
|
||||
// (undocumented)
|
||||
features?: (FrontendFeature | FrontendFeatureLoader)[];
|
||||
// (undocumented)
|
||||
flags?: {
|
||||
advanced?: {
|
||||
allowUnknownExtensionConfig?: boolean;
|
||||
configLoader?: () => Promise<{
|
||||
config: ConfigApi;
|
||||
}>;
|
||||
extensionFactoryMiddleware?:
|
||||
| ExtensionFactoryMiddleware
|
||||
| ExtensionFactoryMiddleware[];
|
||||
loadingComponent?: ReactNode;
|
||||
pluginInfoResolver?: FrontendPluginInfoResolver;
|
||||
};
|
||||
loadingComponent?: ReactNode;
|
||||
// (undocumented)
|
||||
pluginInfoResolver?: FrontendPluginInfoResolver;
|
||||
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
|
||||
features?: (FrontendFeature | FrontendFeatureLoader)[];
|
||||
}
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
|
||||
@@ -45,18 +45,20 @@ describe('createApp', () => {
|
||||
|
||||
it('should allow themes to be installed', async () => {
|
||||
const app = createApp({
|
||||
configLoader: async () => ({
|
||||
config: mockApis.config({
|
||||
data: {
|
||||
app: {
|
||||
extensions: [
|
||||
{ 'theme:app/light': false },
|
||||
{ 'theme:app/dark': false },
|
||||
],
|
||||
advanced: {
|
||||
configLoader: async () => ({
|
||||
config: mockApis.config({
|
||||
data: {
|
||||
app: {
|
||||
extensions: [
|
||||
{ 'theme:app/light': false },
|
||||
{ 'theme:app/dark': false },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
features: [
|
||||
createFrontendPlugin({
|
||||
pluginId: 'test',
|
||||
@@ -85,7 +87,9 @@ describe('createApp', () => {
|
||||
it('should deduplicate features keeping the last received one', async () => {
|
||||
const duplicatedFeatureId = 'test';
|
||||
const app = createApp({
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
advanced: {
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
},
|
||||
features: [
|
||||
createFrontendPlugin({
|
||||
pluginId: duplicatedFeatureId,
|
||||
@@ -138,7 +142,6 @@ describe('createApp', () => {
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
features: [
|
||||
appPlugin,
|
||||
createFrontendPlugin({
|
||||
@@ -153,12 +156,15 @@ describe('createApp', () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
pluginInfoResolver: async () => {
|
||||
return {
|
||||
info: {
|
||||
packageName: '@test/test',
|
||||
},
|
||||
};
|
||||
advanced: {
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
pluginInfoResolver: async () => {
|
||||
return {
|
||||
info: {
|
||||
packageName: '@test/test',
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -187,9 +193,11 @@ describe('createApp', () => {
|
||||
});
|
||||
|
||||
const app = createApp({
|
||||
configLoader: async () => ({
|
||||
config: mockApis.config({ data: { key: 'config-value' } }),
|
||||
}),
|
||||
advanced: {
|
||||
configLoader: async () => ({
|
||||
config: mockApis.config({ data: { key: 'config-value' } }),
|
||||
}),
|
||||
},
|
||||
features: [appPlugin, loader],
|
||||
});
|
||||
|
||||
@@ -208,9 +216,11 @@ describe('createApp', () => {
|
||||
});
|
||||
|
||||
const app = createApp({
|
||||
configLoader: async () => ({
|
||||
config: mockApis.config(),
|
||||
}),
|
||||
advanced: {
|
||||
configLoader: async () => ({
|
||||
config: mockApis.config(),
|
||||
}),
|
||||
},
|
||||
features: [loader],
|
||||
});
|
||||
|
||||
@@ -221,7 +231,9 @@ describe('createApp', () => {
|
||||
|
||||
it('should register feature flags', async () => {
|
||||
const app = createApp({
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
advanced: {
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
},
|
||||
features: [
|
||||
appPlugin.withOverrides({
|
||||
extensions: [
|
||||
@@ -273,15 +285,6 @@ describe('createApp', () => {
|
||||
|
||||
it('should allow unknown extension config if the flag is set', async () => {
|
||||
const app = createApp({
|
||||
configLoader: async () => ({
|
||||
config: mockApis.config({
|
||||
data: {
|
||||
app: {
|
||||
extensions: [{ 'unknown:lols/wut': false }],
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
features: [
|
||||
appPlugin,
|
||||
createFrontendPlugin({
|
||||
@@ -296,7 +299,18 @@ describe('createApp', () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
flags: { allowUnknownExtensionConfig: true },
|
||||
advanced: {
|
||||
allowUnknownExtensionConfig: true,
|
||||
configLoader: async () => ({
|
||||
config: mockApis.config({
|
||||
data: {
|
||||
app: {
|
||||
extensions: [{ 'unknown:lols/wut': false }],
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
await renderWithEffects(app.createRoot());
|
||||
@@ -307,7 +321,9 @@ describe('createApp', () => {
|
||||
let appTreeApi: AppTreeApi | undefined = undefined;
|
||||
|
||||
const app = createApp({
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
advanced: {
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
},
|
||||
features: [
|
||||
appPlugin,
|
||||
createFrontendPlugin({
|
||||
@@ -413,7 +429,9 @@ describe('createApp', () => {
|
||||
|
||||
it('should use "Loading..." as the default suspense fallback', async () => {
|
||||
const app = createApp({
|
||||
configLoader: () => new Promise(() => {}),
|
||||
advanced: {
|
||||
configLoader: () => new Promise(() => {}),
|
||||
},
|
||||
});
|
||||
|
||||
await renderWithEffects(app.createRoot());
|
||||
@@ -423,8 +441,10 @@ describe('createApp', () => {
|
||||
|
||||
it('should use no suspense fallback if the "loadingComponent" is null', async () => {
|
||||
const app = createApp({
|
||||
configLoader: () => new Promise(() => {}),
|
||||
loadingComponent: null,
|
||||
advanced: {
|
||||
configLoader: () => new Promise(() => {}),
|
||||
loadingComponent: null,
|
||||
},
|
||||
});
|
||||
|
||||
await renderWithEffects(app.createRoot());
|
||||
@@ -434,8 +454,10 @@ describe('createApp', () => {
|
||||
|
||||
it('should use a custom "loadingComponent"', async () => {
|
||||
const app = createApp({
|
||||
configLoader: () => new Promise(() => {}),
|
||||
loadingComponent: <span>"Custom loading message"</span>,
|
||||
advanced: {
|
||||
configLoader: () => new Promise(() => {}),
|
||||
loadingComponent: <span>"Custom loading message"</span>,
|
||||
},
|
||||
});
|
||||
|
||||
await renderWithEffects(app.createRoot());
|
||||
@@ -445,7 +467,9 @@ describe('createApp', () => {
|
||||
|
||||
it('should allow overriding the app plugin', async () => {
|
||||
const app = createApp({
|
||||
configLoader: () => new Promise(() => {}),
|
||||
advanced: {
|
||||
configLoader: () => new Promise(() => {}),
|
||||
},
|
||||
features: [
|
||||
appPlugin.withOverrides({
|
||||
extensions: [
|
||||
@@ -468,7 +492,6 @@ describe('createApp', () => {
|
||||
|
||||
it('should use a custom extensionFactoryMiddleware', async () => {
|
||||
const app = createApp({
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
features: [
|
||||
appPlugin,
|
||||
createFrontendPlugin({
|
||||
@@ -484,18 +507,21 @@ describe('createApp', () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
*extensionFactoryMiddleware(originalFactory, context) {
|
||||
const output = originalFactory();
|
||||
yield* output;
|
||||
const element = output.get(coreExtensionData.reactElement);
|
||||
advanced: {
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
*extensionFactoryMiddleware(originalFactory, context) {
|
||||
const output = originalFactory();
|
||||
yield* output;
|
||||
const element = output.get(coreExtensionData.reactElement);
|
||||
|
||||
if (element) {
|
||||
yield coreExtensionData.reactElement(
|
||||
<div data-testid={`wrapped(${context.node.spec.id})`}>
|
||||
{element}
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
if (element) {
|
||||
yield coreExtensionData.reactElement(
|
||||
<div data-testid={`wrapped(${context.node.spec.id})`}>
|
||||
{element}
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -522,7 +548,9 @@ describe('createApp', () => {
|
||||
});
|
||||
|
||||
const app = createApp({
|
||||
configLoader: () => new Promise(() => {}),
|
||||
advanced: {
|
||||
configLoader: () => new Promise(() => {}),
|
||||
},
|
||||
features: [mod],
|
||||
});
|
||||
|
||||
@@ -549,7 +577,9 @@ describe('createApp', () => {
|
||||
});
|
||||
|
||||
const app = createApp({
|
||||
configLoader: () => new Promise(() => {}),
|
||||
advanced: {
|
||||
configLoader: () => new Promise(() => {}),
|
||||
},
|
||||
features: [mod],
|
||||
});
|
||||
|
||||
|
||||
@@ -42,21 +42,64 @@ import { resolveAsyncFeatures } from './resolution';
|
||||
* @public
|
||||
*/
|
||||
export interface CreateAppOptions {
|
||||
features?: (FrontendFeature | FrontendFeatureLoader)[];
|
||||
configLoader?: () => Promise<{ config: ConfigApi }>;
|
||||
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
|
||||
/**
|
||||
* The component to render while loading the app (waiting for config, features, etc)
|
||||
*
|
||||
* Is the text "Loading..." by default.
|
||||
* If set to "null" then no loading fallback component is rendered. *
|
||||
* The list of features to load.
|
||||
*/
|
||||
loadingComponent?: ReactNode;
|
||||
extensionFactoryMiddleware?:
|
||||
| ExtensionFactoryMiddleware
|
||||
| ExtensionFactoryMiddleware[];
|
||||
pluginInfoResolver?: FrontendPluginInfoResolver;
|
||||
flags?: { allowUnknownExtensionConfig?: boolean };
|
||||
features?: (FrontendFeature | FrontendFeatureLoader)[];
|
||||
|
||||
/**
|
||||
* Allows for the binding of plugins' external route refs within the app.
|
||||
*/
|
||||
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
|
||||
|
||||
/**
|
||||
* Advanced, more rarely used options.
|
||||
*/
|
||||
advanced?: {
|
||||
/**
|
||||
* If set to true, the system will silently accept and move on if
|
||||
* encountering config for extensions that do not exist. The default is to
|
||||
* reject such config to help catch simple mistakes.
|
||||
*
|
||||
* This flag can be useful in some scenarios where you have a dynamic set of
|
||||
* extensions enabled at different times, but also increases the risk of
|
||||
* accidentally missing e.g. simple typos in your config.
|
||||
*/
|
||||
allowUnknownExtensionConfig?: boolean;
|
||||
|
||||
/**
|
||||
* Sets a custom config loader, replacing the builtin one.
|
||||
*
|
||||
* This can be used e.g. if you have the need to source config out of custom
|
||||
* storages.
|
||||
*/
|
||||
configLoader?: () => Promise<{ config: ConfigApi }>;
|
||||
|
||||
/**
|
||||
* Applies one or more middleware on every extension, as they are added to
|
||||
* the application.
|
||||
*
|
||||
* This is an advanced use case for modifying extension data on the fly as
|
||||
* it gets emitted by extensions being instantiated.
|
||||
*/
|
||||
extensionFactoryMiddleware?:
|
||||
| ExtensionFactoryMiddleware
|
||||
| ExtensionFactoryMiddleware[];
|
||||
|
||||
/**
|
||||
* The component to render while loading the app (waiting for config,
|
||||
* features, etc).
|
||||
*
|
||||
* This is the text "Loading..." by default. If set to "null" then no loading
|
||||
* fallback component is rendered at all.
|
||||
*/
|
||||
loadingComponent?: ReactNode;
|
||||
|
||||
/**
|
||||
* Allows for customizing how plugin info is retrieved.
|
||||
*/
|
||||
pluginInfoResolver?: FrontendPluginInfoResolver;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,14 +110,14 @@ export interface CreateAppOptions {
|
||||
export function createApp(options?: CreateAppOptions): {
|
||||
createRoot(): JSX.Element;
|
||||
} {
|
||||
let suspenseFallback = options?.loadingComponent;
|
||||
let suspenseFallback = options?.advanced?.loadingComponent;
|
||||
if (suspenseFallback === undefined) {
|
||||
suspenseFallback = 'Loading...';
|
||||
}
|
||||
|
||||
async function appLoader() {
|
||||
const config =
|
||||
(await options?.configLoader?.().then(c => c.config)) ??
|
||||
(await options?.advanced?.configLoader?.().then(c => c.config)) ??
|
||||
ConfigReader.fromConfigs(
|
||||
overrideBaseUrlConfigs(defaultConfigLoaderSync()),
|
||||
);
|
||||
@@ -87,12 +130,10 @@ export function createApp(options?: CreateAppOptions): {
|
||||
});
|
||||
|
||||
const app = createSpecializedApp({
|
||||
config,
|
||||
features: [appPlugin, ...loadedFeatures],
|
||||
config,
|
||||
bindRoutes: options?.bindRoutes,
|
||||
extensionFactoryMiddleware: options?.extensionFactoryMiddleware,
|
||||
pluginInfoResolver: options?.pluginInfoResolver,
|
||||
flags: options?.flags,
|
||||
advanced: options?.advanced,
|
||||
});
|
||||
|
||||
const rootEl = app.tree.root.instance!.getData(
|
||||
|
||||
@@ -30,7 +30,9 @@ describe('createPublicSignInApp', () => {
|
||||
|
||||
it('should render a sign-in page', async () => {
|
||||
const app = createPublicSignInApp({
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
advanced: {
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
},
|
||||
features: [
|
||||
createFrontendModule({
|
||||
pluginId: 'app',
|
||||
@@ -58,7 +60,9 @@ describe('createPublicSignInApp', () => {
|
||||
.mockReturnValue();
|
||||
|
||||
const app = createPublicSignInApp({
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
advanced: {
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
},
|
||||
features: [
|
||||
createFrontendModule({
|
||||
pluginId: 'app',
|
||||
|
||||
@@ -46,7 +46,9 @@ function createTestAppRoot({
|
||||
}) {
|
||||
return createApp({
|
||||
features: [...features],
|
||||
configLoader: async () => ({ config: mockApis.config({ data: config }) }),
|
||||
advanced: {
|
||||
configLoader: async () => ({ config: mockApis.config({ data: config }) }),
|
||||
},
|
||||
}).createRoot();
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,9 @@ function createTestAppRoot({
|
||||
}) {
|
||||
return createApp({
|
||||
features: [...features],
|
||||
configLoader: async () => ({ config: mockApis.config({ data: config }) }),
|
||||
advanced: {
|
||||
configLoader: async () => ({ config: mockApis.config({ data: config }) }),
|
||||
},
|
||||
}).createRoot();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ describe('appModulePublicSignIn', () => {
|
||||
|
||||
it('should render a sign-in page', async () => {
|
||||
const app = createApp({
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
advanced: {
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
},
|
||||
features: [
|
||||
appModulePublicSignIn,
|
||||
createFrontendModule({
|
||||
@@ -60,7 +62,9 @@ describe('appModulePublicSignIn', () => {
|
||||
.mockReturnValue();
|
||||
|
||||
const app = createApp({
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
advanced: {
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
},
|
||||
features: [
|
||||
appModulePublicSignIn,
|
||||
createFrontendModule({
|
||||
|
||||
Reference in New Issue
Block a user