feat: big refactor of componentRefs again to move away from makeComponentRefs

Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
benjdlambert
2025-08-05 14:27:04 +02:00
parent e3c320a514
commit 71510fb812
28 changed files with 310 additions and 499 deletions
@@ -14,9 +14,8 @@
* limitations under the License.
*/
import { ComponentType } from 'react';
import { createApiRef, useApi } from '@backstage/core-plugin-api';
import { ComponentRef } from '../../components';
import { createApiRef } from '../system/ApiRef';
/**
* API for looking up components based on component refs.
@@ -24,8 +23,15 @@ import { ComponentRef } from '../../components';
* @public
*/
export interface ComponentsApi {
// TODO: Should component refs also provide the default implementation so that we're guaranteed to get a component?
getComponent<T extends {}>(ref: ComponentRef<T>): ComponentType<T>;
getComponent<
TInnerComponentProps extends {},
TExternalComponentProps extends {} = TInnerComponentProps,
>(
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>,
):
| (() => (props: TInnerComponentProps) => JSX.Element | null)
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>)
| undefined;
}
/**
@@ -36,14 +42,3 @@ export interface ComponentsApi {
export const componentsApiRef = createApiRef<ComponentsApi>({
id: 'core.components',
});
/**
* @public
* Returns the component associated with the given ref.
*/
export function useComponentRef<T extends {}>(
ref: ComponentRef<T>,
): ComponentType<T> {
const componentsApi = useApi(componentsApiRef);
return componentsApi.getComponent<T>(ref);
}
@@ -14,23 +14,22 @@
* limitations under the License.
*/
import { renderInTestApp } from '@backstage/frontend-test-utils';
import { createComponentRef } from '../components';
import { makeComponentFromRef } from '../components/makeComponentFromRef';
import { ComponentImplementationBlueprint } from './ComponentImplementationBlueprint';
import { createAdaptableComponent } from '../components';
import { AdaptableComponentBlueprint } from './AdaptableComponentBlueprint';
import { PageBlueprint } from './PageBlueprint';
import { waitFor, screen } from '@testing-library/react';
describe('ComponentImplementationBlueprint', () => {
describe('AdaptableComponentBlueprint', () => {
it('should allow defining a component override for a component ref', () => {
const componentRef = createComponentRef({
const Component = createAdaptableComponent({
id: 'test.component',
loader: () => (props: { hello: string }) => <div>{props.hello}</div>,
});
const extension = ComponentImplementationBlueprint.make({
const extension = AdaptableComponentBlueprint.make({
params: define =>
define({
ref: componentRef,
component: Component,
loader: () => props => {
// @ts-expect-error
const t: number = props.hello;
@@ -44,13 +43,11 @@ describe('ComponentImplementationBlueprint', () => {
});
it('should render default component refs in the app', async () => {
const testComponentRef = createComponentRef({
const TestComponent = createAdaptableComponent({
id: 'test.component',
loader: () => (props: { hello: string }) => <div>{props.hello}</div>,
});
const TestComponent = makeComponentFromRef({ ref: testComponentRef });
renderInTestApp(<div />, {
extensions: [
PageBlueprint.make({
@@ -69,12 +66,10 @@ describe('ComponentImplementationBlueprint', () => {
});
it('should render a component ref without a default implementation', async () => {
const testComponentRef = createComponentRef({
const TestComponent = createAdaptableComponent({
id: 'test.component',
});
const TestComponent = makeComponentFromRef({ ref: testComponentRef });
renderInTestApp(<div />, {
extensions: [
PageBlueprint.make({
@@ -94,14 +89,12 @@ describe('ComponentImplementationBlueprint', () => {
});
it('should render a component ref with an async loader implementation', async () => {
const testComponentRef = createComponentRef({
const TestComponent = createAdaptableComponent({
id: 'test.component',
loader: async () => (props: { hello: string }) =>
<div>{props.hello}</div>,
});
const TestComponent = makeComponentFromRef({ ref: testComponentRef });
renderInTestApp(<div />, {
extensions: [
PageBlueprint.make({
@@ -120,17 +113,15 @@ describe('ComponentImplementationBlueprint', () => {
});
it('should allow overriding a component ref with the blueprint', async () => {
const testComponentRef = createComponentRef({
const TestComponent = createAdaptableComponent({
id: 'test.component',
loader: () => (props: { hello: string }) => <div>{props.hello}</div>,
});
const TestComponent = makeComponentFromRef({ ref: testComponentRef });
const extension = ComponentImplementationBlueprint.make({
const extension = AdaptableComponentBlueprint.make({
params: define =>
define({
ref: testComponentRef,
component: TestComponent,
loader: () => props => <div>Override {props.hello}</div>,
}),
});
@@ -27,7 +27,7 @@ export const componentDataRef = createExtensionDataRef<{
| (() => Promise<(props: {}) => JSX.Element | null>);
}>().with({ id: 'core.component.component' });
export const ComponentImplementationBlueprint = createExtensionBlueprint({
export const AdaptableComponentBlueprint = createExtensionBlueprint({
kind: 'component',
attachTo: { id: 'api:app/components', input: 'components' },
output: [componentDataRef],
@@ -35,7 +35,9 @@ export const ComponentImplementationBlueprint = createExtensionBlueprint({
component: componentDataRef,
},
defineParams<Ref extends ComponentRef<any>>(params: {
ref: Ref;
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
? { ref: Ref } & ((props: IExternalComponentProps) => JSX.Element)
: never;
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
?
| (() => (props: IInnerComponentProps) => JSX.Element | null)
@@ -46,7 +48,7 @@ export const ComponentImplementationBlueprint = createExtensionBlueprint({
},
factory: params => [
componentDataRef({
ref: params.ref,
ref: params.component.ref,
loader: params.loader,
}),
],
@@ -33,3 +33,4 @@ export { RouterBlueprint } from './RouterBlueprint';
export { SignInPageBlueprint } from './SignInPageBlueprint';
export { ThemeBlueprint } from './ThemeBlueprint';
export { TranslationBlueprint } from './TranslationBlueprint';
export { AdaptableComponentBlueprint } from './AdaptableComponentBlueprint';
@@ -25,8 +25,8 @@ import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api';
import { ErrorBoundary } from './ErrorBoundary';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker';
import { AppNode, useComponentRef } from '../apis';
import { coreComponentRefs } from './coreComponentRefs';
import { AppNode } from '../apis';
import { Progress } from '@backstage/core-components';
import { coreExtensionData } from '../wiring';
import { AppNodeProvider } from './AppNodeProvider';
@@ -66,8 +66,9 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) {
);
const plugin = node.spec.plugin;
const Progress = useComponentRef(coreComponentRefs.progress);
const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback);
// todo: fallback
const fallback = () => <div>Fallback</div>;
// Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight
const attributes = {
@@ -1,43 +0,0 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
CoreErrorBoundaryFallbackProps,
CoreNotFoundErrorPageProps,
CoreProgressProps,
} from '../types';
import { createComponentRef } from './createComponentRef';
const coreProgressComponentRef = createComponentRef<CoreProgressProps>({
id: 'core.components.progress',
});
const coreNotFoundErrorPageComponentRef =
createComponentRef<CoreNotFoundErrorPageProps>({
id: 'core.components.notFoundErrorPage',
});
const coreErrorBoundaryFallbackComponentRef =
createComponentRef<CoreErrorBoundaryFallbackProps>({
id: 'core.components.errorBoundaryFallback',
});
/** @public */
export const coreComponentRefs = {
progress: coreProgressComponentRef,
notFoundErrorPage: coreNotFoundErrorPageComponentRef,
errorBoundaryFallback: coreErrorBoundaryFallbackComponentRef,
};
@@ -1,5 +1,5 @@
/*
* Copyright 2025 The Backstage Authors
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,14 +13,61 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { render, screen } from '@testing-library/react';
import { createComponentRef } from './createComponentRef';
import { makeComponentFromRef } from './makeComponentFromRef';
describe('makeComponentFromRef', () => {
import { render, screen } from '@testing-library/react';
import { createAdaptableComponent } from './createAdaptableComponent';
describe('createAdaptableComponent', () => {
it('can be created and read', () => {
const { ref } = createAdaptableComponent({ id: 'foo' });
expect(ref.id).toBe('foo');
expect(String(ref)).toBe('ComponentRef{id=foo}');
});
it('should allow defining a default component implementation', () => {
const Test = () => <div>test</div>;
createAdaptableComponent<{ foo: string }, { bar: string }>({
id: 'foo',
loader:
() =>
({ foo }) =>
<Test key={foo} />,
});
createAdaptableComponent<{ foo: string }, { bar: string }>({
id: 'foo',
loader:
async () =>
({ foo }) =>
<Test key={foo} />,
});
createAdaptableComponent<{ foo: string }, { bar: string }>({
id: 'foo',
});
expect(Test).toBeDefined();
});
it('should allow transformings props', () => {
createAdaptableComponent<{ foo: string }, { bar: string }>({
id: 'foo',
transformProps: props => ({ foo: props.bar }),
});
createAdaptableComponent<{ foo: string }, { bar: string }>({
id: 'foo',
// @ts-expect-error - this should be an error as foo is not a string
transformProps: props => ({ foo: 1 }),
});
expect(true).toBe(true);
});
describe('sync', () => {
it('should create a component from a ref for sync component', () => {
const ref = createComponentRef({
const Component = createAdaptableComponent({
id: 'random',
loader: () => (props: { name: string }) => {
return <div data-testid="test">{props.name}</div>;
@@ -30,26 +77,23 @@ describe('makeComponentFromRef', () => {
}),
});
const Component = makeComponentFromRef({ ref });
render(<Component id="test" />);
expect(screen.getByTestId('test')).toHaveTextContent('test');
});
it('should render a fallback when theres no default implementation provided', () => {
const ref = createComponentRef({
const Component = createAdaptableComponent({
id: 'random',
});
const Component = makeComponentFromRef({ ref });
render(<Component />);
expect(screen.getByTestId('random')).toBeInTheDocument();
});
it('should map props from external to internal', () => {
const ref = createComponentRef({
const Component = createAdaptableComponent({
id: 'random',
transformProps: (props: { name: string }) => ({
uppercase: props.name.toUpperCase(),
@@ -62,8 +106,6 @@ describe('makeComponentFromRef', () => {
},
});
const Component = makeComponentFromRef({ ref });
render(<Component name="test" />);
expect(screen.getByTestId('test')).toHaveTextContent('TEST');
@@ -72,34 +114,30 @@ describe('makeComponentFromRef', () => {
describe('async', () => {
it('should create a component from a ref for async component', async () => {
const ref = createComponentRef({
const Component = createAdaptableComponent({
id: 'random',
loader: async () => (props: { name: string }) => {
return <div data-testid="test">{props.name}</div>;
},
});
const Component = makeComponentFromRef({ ref });
render(<Component name="test" />);
await expect(screen.findByTestId('test')).resolves.toBeInTheDocument();
});
it('should render a fallback when theres no default implementation provided', async () => {
const ref = createComponentRef({
const Component = createAdaptableComponent({
id: 'random',
});
const Component = makeComponentFromRef({ ref });
render(<Component />);
await expect(screen.findByTestId('random')).resolves.toBeInTheDocument();
});
it('should map props from external to internal', async () => {
const ref = createComponentRef({
const Component = createAdaptableComponent({
id: 'random',
transformProps: (props: { name: string }) => ({
uppercase: props.name.toUpperCase(),
@@ -112,8 +150,6 @@ describe('makeComponentFromRef', () => {
},
});
const Component = makeComponentFromRef({ ref });
render(<Component name="test" />);
await expect(screen.findByTestId('test')).resolves.toHaveTextContent(
@@ -0,0 +1,119 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { OpaqueComponentRef } from '@internal/frontend';
import { componentsApiRef, useApi } from '../apis';
import { lazy, Suspense } from 'react';
/** @public */
export type ComponentRef<
TInnerComponentProps extends {} = {},
TExternalComponentProps extends {} = TInnerComponentProps,
> = {
id: string;
TProps: TInnerComponentProps;
TExternalProps: TExternalComponentProps;
$$type: '@backstage/ComponentRef';
};
export type ComponentRefOptions<
TInnerComponentProps extends {},
TExternalComponentProps extends {} = TInnerComponentProps,
> = {
id: string;
loader?:
| (() => (props: TInnerComponentProps) => JSX.Element | null)
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>);
transformProps?: (props: TExternalComponentProps) => TInnerComponentProps;
};
const useComponentRefApi = () => {
try {
return useApi(componentsApiRef);
} catch (e) {
return undefined;
}
};
function makeComponentFromRef<
InternalComponentProps extends {},
ExternalComponentProps extends {},
>({
ref,
}: {
ref: ComponentRef<InternalComponentProps, ExternalComponentProps>;
}): (props: ExternalComponentProps) => JSX.Element {
const internalRef = OpaqueComponentRef.toInternal(ref);
const FallbackComponent = (p: JSX.IntrinsicAttributes) => (
<div data-testid={ref.id} {...p} />
);
const ComponentRefImpl = (props: ExternalComponentProps) => {
const api = useComponentRefApi();
const ComponentOrPromise =
api?.getComponent<any>(ref)?.() ??
internalRef.loader?.() ??
FallbackComponent;
const innerProps = internalRef.transformProps?.(props) ?? props;
if ('then' in ComponentOrPromise) {
const DefaultImplementation = lazy(() =>
ComponentOrPromise.then(c => {
return { default: c };
}),
);
return (
<Suspense>
<DefaultImplementation {...innerProps} />
</Suspense>
);
}
return <ComponentOrPromise {...innerProps} />;
};
return ComponentRefImpl;
}
export function createAdaptableComponent<
TInnerComponentProps extends {},
TExternalComponentProps extends {} = TInnerComponentProps,
>(
options: ComponentRefOptions<TInnerComponentProps, TExternalComponentProps>,
): ((props: TExternalComponentProps) => JSX.Element) & {
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>;
} {
const ref = OpaqueComponentRef.createInstance('v1', {
id: options.id,
TProps: null as unknown as TInnerComponentProps,
TExternalProps: null as unknown as TExternalComponentProps,
toString() {
return `ComponentRef{id=${options.id}}`;
},
loader: options.loader as (typeof OpaqueComponentRef.TInternal)['loader'],
transformProps:
options.transformProps as (typeof OpaqueComponentRef.TInternal)['transformProps'],
});
const component = makeComponentFromRef({ ref });
Object.assign(component, { ref });
return component as {
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>;
} & ((props: object) => JSX.Element);
}
@@ -1,66 +0,0 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createComponentRef } from './createComponentRef';
describe('createComponentRef', () => {
it('can be created and read', () => {
const ref = createComponentRef({ id: 'foo' });
expect(ref.id).toBe('foo');
expect(String(ref)).toBe('ComponentRef{id=foo}');
});
it('should allow defining a default component implementation', () => {
const Test = () => <div>test</div>;
createComponentRef<{ foo: string }, { bar: string }>({
id: 'foo',
loader:
() =>
({ foo }) =>
<Test key={foo} />,
});
createComponentRef<{ foo: string }, { bar: string }>({
id: 'foo',
loader:
async () =>
({ foo }) =>
<Test key={foo} />,
});
createComponentRef<{ foo: string }, { bar: string }>({
id: 'foo',
});
expect(Test).toBeDefined();
});
it('should allow transformings props', () => {
createComponentRef<{ foo: string }, { bar: string }>({
id: 'foo',
transformProps: props => ({ foo: props.bar }),
});
createComponentRef<{ foo: string }, { bar: string }>({
id: 'foo',
// @ts-expect-error - this should be an error as foo is not a string
transformProps: props => ({ foo: 1 }),
});
expect(true).toBe(true);
});
});
@@ -1,59 +0,0 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { OpaqueComponentRef } from '@internal/frontend';
/** @public */
export type ComponentRef<
TInnerComponentProps extends {} = {},
TExternalComponentProps extends {} = TInnerComponentProps,
> = {
id: string;
TProps: TInnerComponentProps;
TExternalProps: TExternalComponentProps;
$$type: '@backstage/ComponentRef';
};
export type ComponentRefOptions<
TInnerComponentProps extends {},
TExternalComponentProps extends {} = TInnerComponentProps,
> = {
id: string;
loader?:
| (() => (props: TInnerComponentProps) => JSX.Element | null)
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>);
transformProps?: (props: TExternalComponentProps) => TInnerComponentProps;
};
export function createComponentRef<
TInnerComponentProps extends {},
TExternalComponentProps extends {} = TInnerComponentProps,
>(
options: ComponentRefOptions<TInnerComponentProps, TExternalComponentProps>,
): ComponentRef<TInnerComponentProps, TExternalComponentProps> {
return OpaqueComponentRef.createInstance('v1', {
id: options.id,
TProps: null as unknown as TInnerComponentProps,
TExternalProps: null as unknown as TExternalComponentProps,
toString() {
return `ComponentRef{id=${options.id}}`;
},
options: {
loader: options.loader,
transformProps: options.transformProps,
} as (typeof OpaqueComponentRef.TInternal)['options'],
});
}
@@ -18,6 +18,8 @@ export {
ExtensionBoundary,
type ExtensionBoundaryProps,
} from './ExtensionBoundary';
export { coreComponentRefs } from './coreComponentRefs';
export { createComponentRef, type ComponentRef } from './createComponentRef';
export {
createAdaptableComponent,
type ComponentRef,
} from './createAdaptableComponent';
export { useAppNode } from './AppNodeProvider';
@@ -1,61 +0,0 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { lazy, Suspense } from 'react';
import { ComponentRef } from './createComponentRef';
import { OpaqueComponentRef } from '@internal/frontend';
import { componentsApiRef, useApi } from '../apis';
export function makeComponentFromRef<
InternalComponentProps extends {},
ExternalComponentProps extends {},
>({
ref,
}: {
ref: ComponentRef<InternalComponentProps, ExternalComponentProps>;
}): (props: ExternalComponentProps) => JSX.Element {
const { options } = OpaqueComponentRef.toInternal(ref);
const FallbackComponent = (p: JSX.IntrinsicAttributes) => (
<div data-testid={ref.id} {...p} />
);
const ComponentRefImpl = (props: ExternalComponentProps) => {
// todo(blam): use the component that's in the API ref instead if it's defined.
// otherwise use the fallback..
const api = useApi(componentsApiRef);
const innerProps = options.transformProps?.(props) ?? props;
const ComponentOrPromise = options.loader?.() ?? FallbackComponent;
if ('then' in ComponentOrPromise) {
const DefaultImplementation = lazy(() =>
ComponentOrPromise.then(c => {
return { default: c };
}),
);
return (
// todo: is this necessary? can we remove this?
<Suspense>
<DefaultImplementation {...innerProps} />
</Suspense>
);
}
return <ComponentOrPromise {...innerProps} />;
};
return ComponentRefImpl;
}
@@ -1,72 +0,0 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { lazy, ComponentType } from 'react';
import { createExtension, createExtensionDataRef } from '../wiring';
import { ComponentRef } from '../components';
/** @public */
export function createComponentExtension<TProps extends {}>(options: {
ref: ComponentRef<TProps>;
name?: string;
disabled?: boolean;
loader:
| {
lazy: () => Promise<ComponentType<TProps>>;
}
| {
sync: () => ComponentType<TProps>;
};
}) {
return createExtension({
kind: 'component',
name: options.name ?? options.ref.id,
attachTo: { id: 'api:app/components', input: 'components' },
disabled: options.disabled,
output: [createComponentExtension.componentDataRef],
factory() {
if ('sync' in options.loader) {
return [
createComponentExtension.componentDataRef({
ref: options.ref,
impl: options.loader.sync() as ComponentType,
}),
];
}
const lazyLoader = options.loader.lazy;
const ExtensionComponent = lazy(() =>
lazyLoader().then(Component => ({
default: Component,
})),
) as unknown as ComponentType;
return [
createComponentExtension.componentDataRef({
ref: options.ref,
impl: ExtensionComponent,
}),
];
},
});
}
/** @public */
export namespace createComponentExtension {
export const componentDataRef = createExtensionDataRef<{
ref: ComponentRef;
impl: ComponentType;
}>().with({ id: 'core.component.component' });
}
@@ -1,17 +0,0 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { createComponentExtension } from './createComponentExtension';
@@ -24,7 +24,6 @@ export * from './analytics';
export * from './apis';
export * from './blueprints';
export * from './components';
export * from './extensions';
export * from './icons';
export * from './routing';
export * from './schema';