Skip to content

Commit 729a07e

Browse files
authored
add-custom-data-strucutre-extension-slot (#332)
1 parent 5669241 commit 729a07e

File tree

14 files changed

+412
-150
lines changed

14 files changed

+412
-150
lines changed

packages/repluggable-core/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"build-es": "tsc --outDir dist/es --module esNext",
2121
"build-debug": "tsc --project tsconfig.debug.json",
2222
"test": "jest --coverage",
23-
"posttest": "tslint --project ."
23+
"posttest": "tslint --project .",
24+
"lintfix": "tslint --fix --project ."
2425
},
2526
"dependencies": {
2627
"lodash": "^4.17.20",

packages/repluggable-core/src/API.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as Redux from 'redux'
22
import { ThrottledStore } from './throttledStore'
33
import { INTERNAL_DONT_USE_SHELL_GET_APP_HOST } from './__internal'
4-
4+
import { CustomCreateExtensionSlot } from './extensionSlot'
55

66
export interface AnySlotKey {
77
readonly name: string
@@ -333,6 +333,12 @@ export interface APILayer {
333333
name: string
334334
}
335335

336+
interface AppHostPlugins {
337+
extensionSlot?: {
338+
customCreateExtensionSlot: CustomCreateExtensionSlot
339+
}
340+
}
341+
336342
export interface AppHostOptions {
337343
readonly logger?: HostLogger
338344
readonly monitoring: MonitoringOptions
@@ -343,6 +349,7 @@ export interface AppHostOptions {
343349
readonly enableReduxDevtoolsExtension?: boolean
344350
readonly experimentalCyclicMode?: boolean
345351
readonly shouldScopeReducers?: boolean
352+
readonly plugins?: AppHostPlugins
346353
}
347354

348355
export interface MemoizeMissHit {
@@ -526,9 +533,8 @@ export interface PrivateShell extends Shell {
526533
setDependencyAPIs(APIs: AnySlotKey[]): void
527534
setLifecycleState(enableStore: boolean, enableAPIs: boolean, initCompleted: boolean): void
528535
getBoundaryAspects(): ShellBoundaryAspect[]
529-
getHostOptions(): AppHostOptions,
530-
readonly [INTERNAL_DONT_USE_SHELL_GET_APP_HOST]: () => AppHost
531-
536+
getHostOptions(): AppHostOptions
537+
[INTERNAL_DONT_USE_SHELL_GET_APP_HOST](): AppHost
532538
}
533539

534540
export interface EntryPointsInfo {

packages/repluggable-core/src/IterableWeakMap.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export class IterableWeakMap<K extends object = object, V = any> implements Map<
8181

8282
forEach(callbackfn: (value: V, key: K, map: this) => void, thisArg?: any): void {
8383
for (const [key, value] of this) {
84-
callbackfn(value, key, this )
84+
callbackfn(value, key, this)
8585
}
8686
}
8787

@@ -107,17 +107,15 @@ export class IterableWeakMap<K extends object = object, V = any> implements Map<
107107
return this[Symbol.iterator]() as unknown as ReturnType<Map<K, V>['entries']>
108108
}
109109

110-
*keys(): ReturnType<Map<K, V>['keys']> {
110+
*keys(): ReturnType<Map<K, V>['keys']> {
111111
for (const [key] of this) {
112112
yield key
113113
}
114114
}
115115

116-
*values(): ReturnType<Map<K, V>['values']> {
116+
*values(): ReturnType<Map<K, V>['values']> {
117117
for (const [_, value] of this) {
118118
yield value
119119
}
120120
}
121121
}
122-
123-
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/**
22
* This symbol is only for internal use of repluggable mono-repo.
33
*/
4-
export const INTERNAL_DONT_USE_SHELL_GET_APP_HOST: unique symbol = Symbol.for('SHELL_GET_APP_HOST')
4+
export const INTERNAL_DONT_USE_SHELL_GET_APP_HOST: unique symbol = Symbol.for('SHELL_GET_APP_HOST')

packages/repluggable-core/src/appHost.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,10 @@ export function createAppHost(initialEntryPointsOrPackages: EntryPointOrPackage[
251251
const memoized = _.memoize(func, resolver)
252252

253253
if (options.monitoring.debugMemoization) {
254-
Object.defineProperty(memoized, 'name', { value: `${func.name}_memoized`, writable: false })
254+
Object.defineProperty(memoized, 'name', {
255+
value: `${func.name}_memoized`,
256+
writable: false
257+
})
255258
}
256259

257260
if (options.monitoring.disableMonitoring) {
@@ -560,7 +563,12 @@ miss: ${memoizedWithMissHit.miss}
560563
}
561564

562565
function declareSlot<TItem>(key: SlotKey<TItem>, declaringShell?: Shell): ExtensionSlot<TItem> {
563-
const newSlot = registerSlotOrThrow(key, () => createExtensionSlot(key, host, declaringShell))
566+
const newSlot = registerSlotOrThrow(key, () =>
567+
createExtensionSlot(key, host, {
568+
declaringShell,
569+
customCreateExtensionSlot: options.plugins?.extensionSlot?.customCreateExtensionSlot
570+
})
571+
)
564572
return newSlot
565573
}
566574

packages/repluggable-core/src/extensionSlot.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,44 @@ const alwaysTrue = () => true
2121

2222
type Unsubscribe = () => void
2323

24+
interface ItemsDataStructure<T> {
25+
get(): ExtensionItem<T>[]
26+
add(item: ExtensionItem<T>): void
27+
discardBy(predicate: (item: ExtensionItem<T>) => boolean): void
28+
}
29+
30+
const itemsDataStructure = <T>(): ItemsDataStructure<T> => {
31+
let items: ExtensionItem<T>[] = []
32+
return {
33+
get: () => items,
34+
add: (item: ExtensionItem<T>) => {
35+
items.push(item)
36+
},
37+
discardBy: (predicate: (item: ExtensionItem<T>) => boolean) => {
38+
items = items.filter(predicate)
39+
}
40+
}
41+
}
42+
43+
export type CustomCreateExtensionSlot = <T>() => ItemsDataStructure<T>
44+
45+
export interface CreateExtensionSlotOptions {
46+
declaringShell?: Shell
47+
customCreateExtensionSlot?: CustomCreateExtensionSlot
48+
}
49+
2450
export function createExtensionSlot<T>(
2551
key: SlotKey<T>,
2652
host: PrivateAppHost,
27-
declaringShell?: Shell
53+
options?: CreateExtensionSlotOptions
2854
): PrivateExtensionSlot<T> & AnyExtensionSlot {
29-
let items: ExtensionItem<T>[] = []
55+
const items = options?.customCreateExtensionSlot ? options.customCreateExtensionSlot<T>() : itemsDataStructure<T>()
3056
let subscribers: (() => void)[] = []
3157
const slotUniqueId = _.uniqueId()
3258

3359
return {
3460
host,
35-
declaringShell,
61+
declaringShell: options?.declaringShell,
3662
name: key.name,
3763
contribute,
3864
getItems,
@@ -43,7 +69,7 @@ export function createExtensionSlot<T>(
4369
}
4470

4571
function contribute(fromShell: Shell, item: T, condition?: ContributionPredicate): void {
46-
items.push({
72+
items.add({
4773
shell: fromShell,
4874
contribution: item,
4975
condition: condition || alwaysTrue,
@@ -53,21 +79,21 @@ export function createExtensionSlot<T>(
5379
}
5480

5581
function getItems(forceAll: boolean = false): ExtensionItem<T>[] {
56-
return forceAll ? items : items.filter(item => item.condition())
82+
return forceAll ? items.get() : items.get().filter(item => item.condition())
5783
}
5884

5985
function getSingleItem(): ExtensionItem<T> | undefined {
60-
return items.find(item => item.condition())
86+
return items.get().find(item => item.condition())
6187
}
6288

6389
function getItemByName(name: string): ExtensionItem<T> | undefined {
64-
return items.find(item => item.name === name && item.condition())
90+
return items.get().find(item => item.name === name && item.condition())
6591
}
6692

6793
function discardBy(predicate: ExtensionItemFilter<T>) {
68-
const originalContributionCount = items.length
69-
items = items.filter(v => !predicate(v))
70-
if (items.length !== originalContributionCount) {
94+
const originalContributionCount = items.get().length
95+
items.discardBy(v => !predicate(v))
96+
if (items.get().length !== originalContributionCount) {
7197
subscribers.forEach(func => func())
7298
}
7399
}

packages/repluggable-core/src/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,11 @@ export {
2626
ScopedStore
2727
} from './API'
2828

29-
30-
3129
export { ConsoleHostLogger } from './loggers'
3230

3331
export { createAppHost, makeLazyEntryPoint, mainViewSlotKey, stateSlotKey } from './appHost'
3432

35-
export {AppHostServicesProvider} from './appHostServices'
33+
export { AppHostServicesProvider } from './appHostServices'
3634

3735
export { InstalledShellsSelectors, ShellToggleSet } from './installedShellsState'
3836

@@ -42,5 +40,4 @@ export { interceptAnyObject } from './interceptAnyObject'
4240
export { monitorAPI } from './monitorAPI'
4341
export { hot } from './hot'
4442

45-
46-
export {INTERNAL_DONT_USE_SHELL_GET_APP_HOST} from './__internal'
43+
export { INTERNAL_DONT_USE_SHELL_GET_APP_HOST } from './__internal'

packages/repluggable-core/src/throttledStore.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,17 @@ interface AnyShellAction extends AnyAction {
1616
__shellName?: string
1717
}
1818

19-
20-
function createTimeOutPublisher ( notify: () => void) {
21-
let id : undefined | NodeJS.Timeout = undefined
19+
function createTimeOutPublisher(notify: () => void) {
20+
let id: undefined | NodeJS.Timeout = undefined
2221
return () => {
2322
if (id === undefined) {
2423
id = setTimeout(() => {
2524
id = undefined
2625
notify()
27-
},0)
26+
}, 0)
2827
}
2928
return () => {
30-
if(id === undefined) {
29+
if (id === undefined) {
3130
return
3231
}
3332
clearTimeout(id)
@@ -36,8 +35,8 @@ function createTimeOutPublisher ( notify: () => void) {
3635
}
3736
}
3837

39-
function createAnimationFramePublisher ( notify: () => void) {
40-
let id : undefined | number = undefined
38+
function createAnimationFramePublisher(notify: () => void) {
39+
let id: undefined | number = undefined
4140
return () => {
4241
if (id === undefined) {
4342
id = requestAnimationFrame(() => {
@@ -46,7 +45,7 @@ function createAnimationFramePublisher ( notify: () => void) {
4645
})
4746
}
4847
return () => {
49-
if(id === undefined) {
48+
if (id === undefined) {
5049
return
5150
}
5251
cancelAnimationFrame(id)
@@ -55,9 +54,6 @@ function createAnimationFramePublisher ( notify: () => void) {
5554
}
5655
}
5756

58-
59-
60-
6157
type Subscriber = () => void
6258

6359
export interface StateContribution<TState = {}, TAction extends AnyAction = AnyAction> {
@@ -252,10 +248,8 @@ export const createThrottledStore = (
252248
notifyAll()
253249
}
254250

255-
256-
257-
258-
const notifyAllOnPublish = typeof window === 'undefined' ? createTimeOutPublisher(scheduledNotifyAll) : createAnimationFramePublisher(scheduledNotifyAll)
251+
const notifyAllOnPublish =
252+
typeof window === 'undefined' ? createTimeOutPublisher(scheduledNotifyAll) : createAnimationFramePublisher(scheduledNotifyAll)
259253

260254
let cancelRender = _.noop
261255

packages/repluggable-core/test/appHost.spec.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
import { AppHostAPI, AppHostServicesEntryPointName, AppHostServicesProvider } from '../src/appHostServices'
3333
import { ConsoleHostLogger } from '../src/loggers'
3434
import { createCircularEntryPoints, createDirectCircularEntryPoints } from './appHost.mock'
35+
import { createSignalItemsDataStructure } from './createSignalItemsDataStructure'
3536

3637
const testHostOptions: AppHostOptions = {
3738
monitoring: { disableMonitoring: true }
@@ -1792,4 +1793,92 @@ If the API is intended to be public, it should be declared as "public: true" in
17921793
)
17931794
})
17941795
})
1796+
1797+
describe('Custom Items Data Structure via AppHost Options (plugins)', () => {
1798+
it('should use customCreateExtensionSlot from appHostOptions when contributing and removing items', async () => {
1799+
interface SlotItem {
1800+
value: string
1801+
}
1802+
const slotKey: SlotKey<SlotItem> = { name: 'host_options_signal_slot' }
1803+
1804+
interface SlotContributionAPI {
1805+
contributeItem(fromShell: Shell, item: SlotItem): void
1806+
getItems(): SlotItem[]
1807+
}
1808+
const SlotContributionAPIKey: SlotKey<SlotContributionAPI> = {
1809+
name: 'SLOT_CONTRIBUTION_API'
1810+
}
1811+
1812+
const itemsSpy = jest.fn()
1813+
1814+
const { createDataStructure, effect } = createSignalItemsDataStructure()
1815+
1816+
const slotOwnerEntryPoint: EntryPoint = {
1817+
name: 'SLOT_OWNER_ENTRY_POINT',
1818+
declareAPIs() {
1819+
return [SlotContributionAPIKey]
1820+
},
1821+
attach(shell: Shell) {
1822+
const slot = shell.declareSlot(slotKey)
1823+
shell.contributeAPI(SlotContributionAPIKey, () => ({
1824+
contributeItem(fromShell: Shell, item: SlotItem) {
1825+
slot.contribute(fromShell, item)
1826+
},
1827+
getItems() {
1828+
return slot.getItems().map(item => item.contribution)
1829+
}
1830+
}))
1831+
}
1832+
}
1833+
1834+
const ContributorEntryPoint: EntryPoint = {
1835+
name: 'CONTRIBUTOR_ENTRY_POINT',
1836+
getDependencyAPIs() {
1837+
return [SlotContributionAPIKey, ListenerAPI]
1838+
},
1839+
extend(shell: Shell) {
1840+
const api = shell.getAPI(SlotContributionAPIKey)
1841+
api.contributeItem(shell, { value: 'item1' })
1842+
api.contributeItem(shell, { value: 'item2' })
1843+
}
1844+
}
1845+
1846+
const ListenerAPI: SlotKey<{}> = { name: 'LISTENER_API' }
1847+
1848+
const ListenerEntryPoint: EntryPoint = {
1849+
name: 'LISTENER_ENTRY_POINT',
1850+
declareAPIs() {
1851+
return [ListenerAPI]
1852+
},
1853+
getDependencyAPIs() {
1854+
return [SlotContributionAPIKey]
1855+
},
1856+
// move to attach to make sure
1857+
attach(shell: Shell) {
1858+
const slotContributionAPI = shell.getAPI(SlotContributionAPIKey)
1859+
effect(() => {
1860+
slotContributionAPI.getItems()
1861+
itemsSpy()
1862+
})
1863+
// contribute an api to make sure that the contribution depends on it and happens after.
1864+
shell.contributeAPI(ListenerAPI, () => ({}))
1865+
}
1866+
}
1867+
1868+
const host = createAppHost([slotOwnerEntryPoint, ContributorEntryPoint, ListenerEntryPoint], {
1869+
monitoring: {},
1870+
plugins: {
1871+
extensionSlot: {
1872+
customCreateExtensionSlot: createDataStructure
1873+
}
1874+
}
1875+
})
1876+
1877+
// first was created and then two time
1878+
expect(itemsSpy).toBeCalledTimes(3)
1879+
1880+
await host.removeShells([ContributorEntryPoint.name])
1881+
expect(itemsSpy).toBeCalledTimes(4)
1882+
})
1883+
})
17951884
})

0 commit comments

Comments
 (0)