core-api: switch component data to use a global store

This commit is contained in:
Patrik Oldsberg
2021-02-23 00:51:31 +01:00
parent 4fe07fac7a
commit 2a271d89e8
4 changed files with 129 additions and 9 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/core-api': patch
'@backstage/core': patch
---
Internal refactor of how component data is access to avoid polluting components and make it possible to bridge across versions.
@@ -59,4 +59,63 @@ describe('elementData', () => {
'Attempted to attach duplicate data "my-data" to component "MyComponent"',
);
});
describe('works across versions', () => {
function getDataSymbol() {
const Component = () => null;
attachComponentData(Component, 'my-data', {});
const [symbol] = Object.getOwnPropertySymbols(Component);
return symbol;
}
it('should should be able to get data from older versions', () => {
const symbol = getDataSymbol();
const data = { foo: 'bar' };
const Component = () => null;
attachComponentData(Component, 'my-data', data);
const element = <Component />;
expect((element as any).type[symbol].map.get('my-data')).toBe(data);
});
it('should should be able to attach data for older versions', () => {
const symbol = getDataSymbol();
const data = { foo: 'bar' };
const Component = () => null;
(Component as any)[symbol] = {
map: new Map([['my-data', data]]),
};
const element = <Component />;
expect(getComponentData(element, 'my-data')).toBe(data);
});
it('should be able to get data from newer 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__'
].store.get(element.type);
expect(container.map.get('my-data')).toBe(data);
});
it('should should be able to attach data for newer versions', () => {
const data = { foo: 'bar' };
const Component = () => null;
(global as any)['__@backstage/component-data-store__'].store.set(
Component,
{
map: new Map([['my-data', data]]),
},
);
const element = <Component />;
expect(getComponentData(element, 'my-data')).toBe(data);
});
});
});
@@ -15,21 +15,40 @@
*/
import { ComponentType, ReactNode } from 'react';
import { globalObject } from '../lib/globalObject';
// TODO(Rugvip): Access via symbol is deprecated, remove once on 0.3.x
const DATA_KEY = Symbol('backstage-component-data');
type DataContainer = {
map: Map<string, unknown>;
};
type ComponentWithData<P> = ComponentType<P> & {
[DATA_KEY]?: DataContainer;
};
type ReactNodeWithData = ReactNode & {
type?: { [DATA_KEY]?: DataContainer };
type DataContainer = {
map: Map<string, unknown>;
};
type MaybeComponentNode = ReactNode & {
type?: ComponentType<any> & { [DATA_KEY]?: DataContainer };
};
const GLOBAL_KEY = '__@backstage/component-data-store__';
// The store is bridged across versions using the global object
function getStore() {
let storeObj = globalObject[GLOBAL_KEY] as
| { store: WeakMap<ComponentType<any>, DataContainer> }
| undefined;
if (!storeObj) {
const store = new WeakMap<ComponentType<any>, DataContainer>();
storeObj = { store };
globalObject[GLOBAL_KEY] = storeObj;
}
return storeObj.store;
}
const store = getStore();
export function attachComponentData<P>(
component: ComponentType<P>,
type: string,
@@ -37,9 +56,11 @@ export function attachComponentData<P>(
) {
const dataComponent = component as ComponentWithData<P>;
let container = dataComponent[DATA_KEY];
let container = store.get(component) || dataComponent[DATA_KEY];
if (!container) {
container = dataComponent[DATA_KEY] = { map: new Map() };
container = { map: new Map() };
store.set(component, container);
dataComponent[DATA_KEY] = container;
}
if (container.map.has(type)) {
@@ -60,7 +81,12 @@ export function getComponentData<T>(
return undefined;
}
const container = (node as ReactNodeWithData).type?.[DATA_KEY];
const component = (node as MaybeComponentNode).type;
if (!component) {
return undefined;
}
const container = store.get(component) || component[DATA_KEY];
if (!container) {
return undefined;
}
+29
View File
@@ -0,0 +1,29 @@
/*
* Copyright 2021 Spotify AB
*
* 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.
*/
// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028
function getGlobal() {
if (typeof window !== 'undefined' && window.Math === Math) {
return window;
}
if (typeof self !== 'undefined' && self.Math === Math) {
return self;
}
// eslint-disable-next-line no-new-func
return Function('return this')();
}
export const globalObject = getGlobal();