Skip to content

Commit f42b4ac

Browse files
feat(overmind): server Side Rendering
1 parent 4828591 commit f42b4ac

File tree

6 files changed

+243
-66
lines changed

6 files changed

+243
-66
lines changed

packages/node_modules/overmind/src/index.ts

Lines changed: 118 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import {
1818
NestedPartial,
1919
Options,
2020
ResolveActions,
21-
ResolveMockActions,
21+
DefaultMode,
22+
TestMode,
23+
SSRMode,
2224
ResolveState,
2325
} from './internalTypes'
2426
import { proxifyEffects } from './proxyfyEffects'
@@ -31,6 +33,11 @@ import {
3133
IState,
3234
IOnInitialize,
3335
} from './types'
36+
import {
37+
deepCopy,
38+
MockedEventEmitter,
39+
makeStringifySafeMutations,
40+
} from './utils'
3441

3542
export * from './types'
3643

@@ -52,6 +59,11 @@ const IS_PRODUCTION = process.env.NODE_ENV === 'production'
5259
const IS_DEVELOPMENT =
5360
!process.env.NODE_ENV || process.env.NODE_ENV === 'development'
5461
const IS_OPERATOR = Symbol('operator')
62+
63+
const MODE_DEFAULT = Symbol('MODE_DEFAULT')
64+
const MODE_TEST = Symbol('MODE_TEST')
65+
const MODE_SSR = Symbol('MODE_SSR')
66+
5567
let hasWarnedDeprecatedValue = false
5668

5769
function warnValueDeprecation() {
@@ -63,31 +75,33 @@ function warnValueDeprecation() {
6375
}
6476
}
6577

66-
export const makeStringifySafeMutations = (mutations: IMutation[]) => {
67-
return mutations.map((mutation) => ({
68-
...mutation,
69-
args: safeValues(mutation.args),
70-
}))
78+
export interface OvermindSSR<Config extends IConfiguration>
79+
extends Overmind<Config> {
80+
hydrate(): IMutation[]
7181
}
7282

73-
function deepCopy(obj) {
74-
if (isPlainObject(obj)) {
75-
return Object.keys(obj).reduce((aggr, key) => {
76-
aggr[key] = deepCopy(obj[key])
83+
export function createOvermindSSR<Config extends IConfiguration>(
84+
config: Config
85+
): OvermindSSR<Config> {
86+
const ssr = new Overmind(
87+
config,
88+
{
89+
devtools: false,
90+
},
91+
{
92+
mode: MODE_SSR,
93+
} as SSRMode
94+
) as OvermindSSR<Config>
7795

78-
return aggr
79-
}, {})
80-
} else if (Array.isArray(obj)) {
81-
return obj.map((item) => deepCopy(item))
96+
ssr.state = (ssr as any).proxyStateTree.getMutationTree().state
97+
ssr.hydrate = () => {
98+
return (ssr as any).proxyStateTree.mutationTree.flush().mutations
8299
}
83-
84-
return obj
100+
return ssr
85101
}
86102

87-
export interface OvermindMock<Config extends IConfiguration> {
88-
actions: ResolveMockActions<Config['actions']>
89-
effects: Config['effects']
90-
state: ResolveState<Config['state']>
103+
export interface OvermindMock<Config extends IConfiguration>
104+
extends Overmind<Config> {
91105
onInitialize: () => Promise<IMutation[]>
92106
}
93107

@@ -101,7 +115,10 @@ export function createOvermindMock<Config extends IConfiguration>(
101115
}),
102116
{
103117
devtools: false,
104-
testMode: {
118+
},
119+
{
120+
mode: MODE_TEST,
121+
options: {
105122
effectsCallback: (effect) => {
106123
const mockedEffect = (effect.name
107124
? effect.name.split('.')
@@ -124,33 +141,50 @@ export function createOvermindMock<Config extends IConfiguration>(
124141
return execution.flush().mutations
125142
},
126143
},
127-
}
128-
)
144+
} as TestMode
145+
) as OvermindMock<Config>
146+
147+
const action = (mock as any).createAction('onInitialize', config.onInitialize)
148+
149+
mock.onInitialize = () => action(mock)
129150

130151
return mock as any
131152
}
132153

154+
export function createOvermind<Config extends IConfiguration>(
155+
config: Config,
156+
options?: Options
157+
): Overmind<Config> {
158+
return new Overmind(config, options, { mode: MODE_DEFAULT })
159+
}
160+
133161
const hotReloadingCache = {}
134162

135163
// We do not use IConfig<Config> directly to type the class in order to avoid
136164
// the 'import(...)' function to be used in exported types.
137-
138165
export class Overmind<ThisConfig extends IConfiguration>
139166
implements IConfiguration {
140167
private proxyStateTree: ProxyStateTree<object>
141168
private actionReferences: Function[] = []
142169
private nextExecutionId: number = 0
143170
private options: Options
171+
private mode: DefaultMode | TestMode | SSRMode
144172
initialized: Promise<any>
145173
eventHub: EventEmitter<Events>
146174
devtools: Devtools
147175
actions: ResolveActions<ThisConfig['actions']>
148176
state: ResolveState<ThisConfig['state']>
149177
effects: ThisConfig['effects'] & {}
150-
constructor(configuration: ThisConfig, options: Options = {}) {
151-
const name = options.name || 'MyConfig'
152-
153-
if (IS_DEVELOPMENT && !options.testMode) {
178+
constructor(
179+
configuration: ThisConfig,
180+
options: Options = {},
181+
mode: DefaultMode | TestMode | SSRMode = {
182+
mode: MODE_DEFAULT,
183+
} as DefaultMode
184+
) {
185+
const name = options.name || 'OvermindApp'
186+
187+
if (IS_DEVELOPMENT && mode.mode === MODE_DEFAULT) {
154188
if (hotReloadingCache[name]) {
155189
return hotReloadingCache[name]
156190
} else {
@@ -161,7 +195,10 @@ export class Overmind<ThisConfig extends IConfiguration>
161195
/*
162196
Set up an eventHub to trigger information from derived, computed and reactions
163197
*/
164-
const eventHub = new EventEmitter<Events>()
198+
const eventHub =
199+
mode.mode === MODE_SSR
200+
? new MockedEventEmitter()
201+
: new EventEmitter<Events>()
165202

166203
/*
167204
Create the proxy state tree instance with the state and a wrapper to expose
@@ -170,27 +207,34 @@ export class Overmind<ThisConfig extends IConfiguration>
170207
const proxyStateTree = new ProxyStateTree(
171208
this.getState(configuration) as any,
172209
{
173-
devmode: !IS_PRODUCTION,
210+
devmode: IS_DEVELOPMENT,
174211
dynamicWrapper: (_, path, func) => func(eventHub, proxyStateTree, path),
175-
onGetter: (path, value) => {
176-
// We need to let any initial values be set first
177-
setTimeout(() => {
178-
this.eventHub.emit(EventType.GETTER, {
179-
path,
180-
value: safeValue(value),
181-
})
182-
})
183-
},
212+
onGetter: IS_DEVELOPMENT
213+
? (path, value) => {
214+
// We need to let any initial values be set first
215+
setTimeout(() => {
216+
this.eventHub.emit(EventType.GETTER, {
217+
path,
218+
value: safeValue(value),
219+
})
220+
})
221+
}
222+
: undefined,
184223
}
185224
)
186225

187226
this.state = proxyStateTree.state
188227
this.effects = configuration.effects || {}
189228
this.proxyStateTree = proxyStateTree
190-
this.eventHub = eventHub
229+
this.eventHub = eventHub as EventEmitter<Event>
191230
this.options = options
231+
this.mode = mode
192232

193-
if (!IS_PRODUCTION && typeof window !== 'undefined') {
233+
if (
234+
IS_DEVELOPMENT &&
235+
mode.mode === MODE_DEFAULT &&
236+
typeof window !== 'undefined'
237+
) {
194238
let warning = 'OVERMIND: You are running in DEVELOPMENT mode.'
195239
if (options.logProxies !== true) {
196240
const originalConsoleLog = console.log
@@ -246,7 +290,7 @@ export class Overmind<ThisConfig extends IConfiguration>
246290
nextTick && clearTimeout(nextTick)
247291
nextTick = setTimeout(flushTree, 0)
248292
})
249-
} else if (!options.testMode) {
293+
} else if (mode.mode === MODE_DEFAULT) {
250294
eventHub.on(EventType.OPERATOR_ASYNC, (execution) => {
251295
const flushData = execution.flush()
252296
if (this.devtools && flushData.mutations.length) {
@@ -279,15 +323,7 @@ export class Overmind<ThisConfig extends IConfiguration>
279323
*/
280324
this.actions = this.getActions(configuration)
281325

282-
if (options.testMode) {
283-
const action = this.createAction(
284-
'onInitialize',
285-
configuration.onInitialize
286-
)
287-
288-
const overmind = this as any
289-
overmind.onInitialize = () => action(this)
290-
} else if (configuration.onInitialize) {
326+
if (mode.mode === MODE_DEFAULT && configuration.onInitialize) {
291327
const onInitialize = this.createAction(
292328
'onInitialize',
293329
configuration.onInitialize
@@ -390,7 +426,12 @@ export class Overmind<ThisConfig extends IConfiguration>
390426
operatorId: finalContext.execution.operatorId - 1,
391427
})
392428
if (err) reject(err)
393-
else resolve(this.options.testMode && finalContext.execution)
429+
else
430+
resolve(
431+
this.mode.mode === MODE_TEST
432+
? finalContext.execution
433+
: undefined
434+
)
394435
}
395436
)
396437
: resolve(
@@ -454,12 +495,13 @@ export class Overmind<ThisConfig extends IConfiguration>
454495
await result
455496
}
456497

457-
return this.options.testMode && execution
498+
return this.mode.mode === MODE_TEST ? execution : undefined
458499
}
459500
}
460501

461-
if (this.options.testMode) {
462-
const actionCallback = this.options.testMode.actionCallback
502+
if (this.mode.mode === MODE_TEST) {
503+
const mode = this.mode as TestMode
504+
const actionCallback = mode.options.actionCallback
463505

464506
return async (value?) => {
465507
const result = await actionFunc(value)
@@ -478,9 +520,12 @@ export class Overmind<ThisConfig extends IConfiguration>
478520
return proxifyEffects(this.effects, (effect) => {
479521
let result
480522
try {
481-
result = this.options.testMode
482-
? this.options.testMode.effectsCallback(effect)
483-
: effect.func.apply(this, effect.args)
523+
if (this.mode.mode === MODE_TEST) {
524+
const mode = this.mode as TestMode
525+
result = mode.options.effectsCallback(effect)
526+
} else {
527+
result = effect.func.apply(this, effect.args)
528+
}
484529
} catch (error) {
485530
// eslint-disable-next-line standard/no-callback-literal
486531
this.eventHub.emit(EventType.EFFECT, {
@@ -657,6 +702,21 @@ export class Overmind<ThisConfig extends IConfiguration>
657702
addFlushListener = (cb: IFlushCallback) => {
658703
return this.proxyStateTree.onFlush(cb)
659704
}
705+
rehydrate(state: object, mutations: IMutation[]) {
706+
mutations.forEach((mutation) => {
707+
const pathArray = mutation.path.split('.')
708+
const key = pathArray.pop()
709+
const target = pathArray.reduce((aggr, key) => aggr[key], state)
710+
711+
if (mutation.method === 'set') {
712+
target[key] = mutation.args[0]
713+
} else if (mutation.method === 'unset') {
714+
delete target[key]
715+
} else {
716+
target[key][mutation.method](...mutation.args)
717+
}
718+
})
719+
}
660720
}
661721

662722
/*

packages/node_modules/overmind/src/internalTypes.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,15 @@ export type Options = {
1414
name?: string
1515
devtools?: string | boolean
1616
logProxies?: boolean
17-
testMode?: {
17+
}
18+
19+
export type DefaultMode = {
20+
mode: Symbol
21+
}
22+
23+
export type TestMode = {
24+
mode: Symbol
25+
options: {
1826
effectsCallback: (
1927
effect: {
2028
effectId: number
@@ -27,6 +35,10 @@ export type Options = {
2735
}
2836
}
2937

38+
export type SSRMode = {
39+
mode: Symbol
40+
}
41+
3042
export enum EventType {
3143
ACTION_START = 'action:start',
3244
ACTION_END = 'action:end',

packages/node_modules/overmind/src/mock.test.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import { createOvermindMock, IAction, IOnInitialize } from './'
22

3-
type State = {
4-
foo: string
5-
upperFoo: string
6-
}
7-
83
describe('Mock', () => {
94
test('should run action tests', async () => {
105
type State = {

0 commit comments

Comments
 (0)