core-api: switch component data to use a global store
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user