core-plugin-api: switch to using a plain string key for attaching component data

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-09-07 19:41:43 +02:00
parent 96f239228a
commit 3d238b0283
3 changed files with 78 additions and 8 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-plugin-api': patch
---
Migrated component data attachment method to have better compatibility with component proxies such as `react-hot-loader`.
@@ -17,7 +17,7 @@
import React from 'react';
import { attachComponentData, getComponentData } from './componentData';
describe('elementData', () => {
describe('componentData', () => {
it('should attach a single piece of data', () => {
const data = { foo: 'bar' };
const Component = () => null;
@@ -59,4 +59,51 @@ describe('elementData', () => {
'Attempted to attach duplicate data "my-data" to component "MyComponent"',
);
});
describe('works across versions', () => {
it('should should be able to get data from newer versions', () => {
const data = { foo: 'bar' };
const Component = () => null;
attachComponentData(Component, 'my-data', data);
const element = <Component />;
expect((element as any).type.__backstage_data.map.get('my-data')).toBe(
data,
);
});
it('should should be able to attach data for newer versions', () => {
const data = { foo: 'bar' };
const Component = () => null;
(Component as any).__backstage_data = {
map: new Map([['my-data', data]]),
};
const element = <Component />;
expect(getComponentData(element, 'my-data')).toBe(data);
});
it('should be able to get data from older versions', () => {
const data = { foo: 'bar' };
const Component = () => null;
attachComponentData(Component, 'my-data', data);
const element = <Component />;
const container = (global as any)[
'__@backstage/component-data-store__'
].get(element.type);
expect(container.map.get('my-data')).toBe(data);
});
it('should should be able to attach data for older versions', () => {
const data = { foo: 'bar' };
const Component = () => null;
(global as any)['__@backstage/component-data-store__'].set(Component, {
map: new Map([['my-data', data]]),
});
const element = <Component />;
expect(getComponentData(element, 'my-data')).toBe(data);
});
});
});
@@ -21,24 +21,42 @@ type DataContainer = {
map: Map<string, unknown>;
};
type MaybeComponentNode = ReactNode & {
type?: ComponentType<any>;
};
// The store is bridged across versions using the global object
// This method of storing the component data was deprecated in September 2021, it
// will be removed in the future for the reasons described below.
const globalStore = getOrCreateGlobalSingleton(
'component-data-store',
() => new WeakMap<ComponentType<any>, DataContainer>(),
);
// This key is used to attach component data to the component type (function or class)
// itself. This method is used because it has better compatibility component wrappers
// like react-hot-loader, as opposed to the WeakMap method or using a symbol.
const componentDataKey = '__backstage_data';
type ComponentWithData = ComponentType<any> & {
[componentDataKey]?: DataContainer;
};
type MaybeComponentNode = ReactNode & {
type?: ComponentWithData;
};
export function attachComponentData<P>(
component: ComponentType<P>,
type: string,
data: unknown,
) {
let container = globalStore.get(component);
const dataComponent = component as ComponentWithData;
let container = dataComponent[componentDataKey] ?? globalStore.get(component);
if (!container) {
container = { map: new Map() };
Object.defineProperty(dataComponent, componentDataKey, {
enumerable: false,
configurable: true,
writable: false,
value: container,
});
globalStore.set(component, container);
}
@@ -65,7 +83,7 @@ export function getComponentData<T>(
return undefined;
}
const container = globalStore.get(component);
const container = component[componentDataKey] ?? globalStore.get(component);
if (!container) {
return undefined;
}