Skip to content
This repository was archived by the owner on May 17, 2019. It is now read-only.

Commit e78ef24

Browse files
committed
Add compound plugins
1 parent c44c780 commit e78ef24

File tree

6 files changed

+228
-6
lines changed

6 files changed

+228
-6
lines changed

src/__tests__/compound-plugins.js

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/* @flow */
2+
import tape from 'tape-cup';
3+
import ClientAppFactory from '../client-app';
4+
import ServerAppFactory from '../server-app';
5+
import {createPlugin} from '../create-plugin';
6+
import {createToken, createArrayToken} from '../create-token';
7+
import type {FusionPlugin, Token, ArrayToken} from '../types.js';
8+
9+
const App = __BROWSER__ ? ClientAppFactory() : ServerAppFactory();
10+
type DepsType = {
11+
dep: string,
12+
};
13+
type AType = {
14+
a: string,
15+
};
16+
17+
const TokenA: ArrayToken<AType> = createArrayToken('TokenA');
18+
const TokenDep1: Token<DepsType> = createToken('TokenDep1');
19+
const TokenDep2: Token<DepsType> = createToken('TokenDep2');
20+
21+
tape('compound tokens support dependencies', t => {
22+
const app = new App('el', el => el);
23+
t.ok(app, 'creates an app');
24+
const counters = {
25+
deps: 0,
26+
a: 0,
27+
b: 0,
28+
};
29+
const PluginDeps: FusionPlugin<{}, DepsType> = createPlugin({
30+
provides: () => {
31+
counters.deps++;
32+
t.equal(counters.deps, 1, 'only instantiates once');
33+
return {
34+
dep: 'PluginDep',
35+
};
36+
},
37+
});
38+
39+
const PluginA: FusionPlugin<void, AType> = createPlugin({
40+
provides: () => {
41+
counters.a++;
42+
t.equal(counters.a, 1, 'only instantiates once');
43+
return {
44+
a: 'PluginA',
45+
};
46+
},
47+
});
48+
49+
type PluginBType = FusionPlugin<{dep: Token<DepsType>}, AType>;
50+
const PluginB: PluginBType = createPlugin({
51+
deps: {dep: TokenDep1},
52+
provides: deps => {
53+
counters.b++;
54+
t.equal(deps.dep.dep, 'PluginDep');
55+
t.equal(counters.b, 1, 'only instantiates once');
56+
return {
57+
a: 'PluginB',
58+
};
59+
},
60+
});
61+
62+
app.register(TokenA, PluginA);
63+
app.register(TokenA, PluginB);
64+
// $FlowFixMe
65+
app.register(TokenA, 'value');
66+
app.register(TokenDep1, PluginDeps);
67+
app.register(
68+
createPlugin({
69+
deps: {a: TokenA},
70+
provides: deps => {
71+
t.equal(deps.a[0].a, 'PluginA');
72+
t.equal(deps.a[1].a, 'PluginB');
73+
t.equal(deps.a[2], 'value');
74+
},
75+
})
76+
);
77+
t.equal(counters.a, 0, 'does not instantiate until resolve is called');
78+
t.equal(counters.b, 0, 'does not instantiate until resolve is called');
79+
t.equal(counters.deps, 0, 'does not instantiate until resolve is called');
80+
app.resolve();
81+
t.equal(counters.a, 1, 'only instantiates once');
82+
t.equal(counters.b, 1, 'only instantiates once');
83+
t.equal(counters.deps, 1, 'only instantiates once');
84+
t.end();
85+
});
86+
87+
tape('dependency registration with aliases', t => {
88+
const app = new App('el', el => el);
89+
t.ok(app, 'creates an app');
90+
const counters = {
91+
dep1: 0,
92+
dep2: 0,
93+
a: 0,
94+
b: 0,
95+
};
96+
const PluginDep1: FusionPlugin<{}, DepsType> = createPlugin({
97+
provides: () => {
98+
counters.dep1++;
99+
t.equal(counters.dep1, 1, 'only instantiates once');
100+
return {
101+
dep: 'PluginDep1',
102+
};
103+
},
104+
});
105+
const PluginDep2: FusionPlugin<{}, DepsType> = createPlugin({
106+
provides: () => {
107+
counters.dep2++;
108+
t.equal(counters.dep2, 1, 'only instantiates once');
109+
return {
110+
dep: 'PluginDep2',
111+
};
112+
},
113+
});
114+
115+
const PluginA: FusionPlugin<{dep: Token<DepsType>}, AType> = createPlugin({
116+
deps: {dep: TokenDep1},
117+
provides: deps => {
118+
counters.a++;
119+
t.equal(deps.dep.dep, 'PluginDep2');
120+
t.equal(counters.a, 1, 'only instantiates once');
121+
return {
122+
a: 'PluginA',
123+
};
124+
},
125+
});
126+
127+
type PluginBType = FusionPlugin<{dep: Token<DepsType>}, AType>;
128+
const PluginB: PluginBType = createPlugin({
129+
deps: {dep: TokenDep1},
130+
provides: deps => {
131+
counters.b++;
132+
t.equal(deps.dep.dep, 'PluginDep2');
133+
t.equal(counters.b, 1, 'only instantiates once');
134+
return {
135+
a: 'PluginB',
136+
};
137+
},
138+
});
139+
140+
app.register(TokenA, PluginA);
141+
app.register(TokenA, PluginB).alias(TokenDep1, TokenDep2);
142+
app.register(TokenDep1, PluginDep1);
143+
app.register(TokenDep2, PluginDep2);
144+
app.register(
145+
createPlugin({
146+
deps: {a: TokenA},
147+
provides: deps => {
148+
t.equal(deps.a[0].a, 'PluginA');
149+
t.equal(deps.a[1].a, 'PluginB');
150+
},
151+
})
152+
);
153+
t.equal(counters.a, 0, 'does not instantiate until resolve is called');
154+
t.equal(counters.b, 0, 'does not instantiate until resolve is called');
155+
t.equal(counters.dep1, 0, 'does not instantiate until resolve is called');
156+
t.equal(counters.dep2, 0, 'does not instantiate until resolve is called');
157+
app.resolve();
158+
t.equal(counters.a, 1, 'only instantiates once');
159+
t.equal(counters.b, 1, 'only instantiates once');
160+
t.equal(counters.dep1, 1, 'only instantiates once');
161+
t.equal(counters.dep2, 1, 'only instantiates once');
162+
t.end();
163+
});

src/base-app.js

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,42 @@ class FusionApp {
6868
return this._register(token, value);
6969
}
7070
_register<TResolved>(token: Token<TResolved>, value: *) {
71-
this.plugins.push(token);
72-
const {aliases, enhancers} = this.registered.get(getTokenRef(token)) || {
71+
const {value: registeredValue, aliases, enhancers} = this.registered.get(
72+
getTokenRef(token)
73+
) || {
74+
value: null,
7375
aliases: new Map(),
7476
enhancers: [],
7577
};
76-
this.registered.set(getTokenRef(token), {value, aliases, enhancers, token});
78+
if (token.isCompound) {
79+
if (!registeredValue) {
80+
// Initial value is set as an empty array
81+
this.registered.set(getTokenRef(token), {
82+
// $FlowFixMe
83+
value: [],
84+
aliases,
85+
enhancers,
86+
token,
87+
});
88+
}
89+
this.enhance(token, originalValue => {
90+
return createPlugin({
91+
deps: {...value.deps},
92+
provides: (...args) => {
93+
value = value.provides ? value.provides(...args) : value;
94+
return [...originalValue, value];
95+
},
96+
});
97+
});
98+
} else {
99+
this.registered.set(getTokenRef(token), {
100+
value,
101+
aliases,
102+
enhancers,
103+
token,
104+
});
105+
}
106+
this.plugins.push(token);
77107
function alias(sourceToken: *, destToken: *) {
78108
if (aliases) {
79109
aliases.set(sourceToken, destToken);
@@ -101,7 +131,12 @@ class FusionApp {
101131
if (enhancers && Array.isArray(enhancers)) {
102132
enhancers.push(enhancer);
103133
}
104-
this.registered.set(getTokenRef(token), {value, aliases, enhancers, token});
134+
this.registered.set(getTokenRef(token), {
135+
value,
136+
aliases,
137+
enhancers,
138+
token,
139+
});
105140
}
106141
cleanup() {
107142
return Promise.all(this.cleanups.map(fn => fn()));

src/base-app.js.flow

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
FusionPlugin,
1414
Middleware,
1515
Token,
16+
ArrayToken,
1617
} from './types.js';
1718

1819
declare class FusionApp {
@@ -26,11 +27,16 @@ declare class FusionApp {
2627
register<TDeps, TProvides>(
2728
Plugin: FusionPlugin<TDeps, TProvides>
2829
): aliaser<Token<*>>;
30+
register<TVal, TDeps>(
31+
token: ArrayToken<TVal>,
32+
Plugin: FusionPlugin<TDeps, TVal>
33+
): aliaser<Token<*>>;
2934
register<TVal, TDeps>(
3035
token: Token<TVal>,
3136
Plugin: FusionPlugin<TDeps, TVal>
3237
): aliaser<Token<*>>;
3338
register<TVal>(token: Token<TVal>, val: TVal): aliaser<Token<*>>;
39+
register<TVal>(token: ArrayToken<TVal>, val: TVal): aliaser<Token<*>>;
3440
middleware<TDeps>(
3541
deps: TDeps,
3642
middleware: (Deps: $ObjMap<TDeps, ExtractReturnType>) => Middleware

src/create-token.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* @flow
77
*/
88

9-
import type {Token} from './types.js';
9+
import type {Token, ArrayToken} from './types.js';
1010

1111
export const TokenType = {
1212
Required: 0,
@@ -18,6 +18,7 @@ export class TokenImpl<TResolved> {
1818
ref: mixed;
1919
type: $Values<typeof TokenType>;
2020
optional: ?TokenImpl<TResolved>;
21+
isCompound = false;
2122

2223
constructor(name: string, ref: mixed) {
2324
this.name = name;
@@ -33,3 +34,12 @@ export function createToken<TResolvedType>(name: string): Token<TResolvedType> {
3334
// $FlowFixMe
3435
return new TokenImpl(name);
3536
}
37+
38+
export function createArrayToken<TResolvedType>(
39+
name: string
40+
): ArrayToken<TResolvedType> {
41+
const token = new TokenImpl(name);
42+
token.isCompound = true;
43+
// $FlowFixMe
44+
return token;
45+
}

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export {
4141
HttpServerToken,
4242
} from './tokens';
4343
export {createPlugin} from './create-plugin';
44-
export {createToken} from './create-token';
44+
export {createToken, createArrayToken} from './create-token';
4545
export {getEnv};
4646

4747
type FusionApp = typeof BaseApp;

src/types.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ import type {Context as KoaContext} from 'koa';
1111
export type Token<T> = {
1212
(): T,
1313
optional: () => void | T,
14+
isCompound: false,
15+
};
16+
17+
export type ArrayToken<T> = {
18+
(): T,
19+
} & {
20+
...Token<Array<T>>,
21+
isCompound: true,
1422
};
1523

1624
type ExtendedKoaContext = KoaContext & {memoized: Map<Object, mixed>};

0 commit comments

Comments
 (0)