Skip to content

Commit bb3298d

Browse files
Merge pull request #478 from nakedgun/470-clean-model-state-on-SSR-reinitialization
Fix service plugin in SSR setups (do not preserve model/module state if reinitialized)
2 parents 0ca8dbf + e4883b8 commit bb3298d

File tree

2 files changed

+155
-2
lines changed

2 files changed

+155
-2
lines changed

src/service-module/make-service-plugin.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ export default function prepareMakeServicePlugin(
8989
// (1^) Create and register the Vuex module
9090
options.namespace = makeNamespace(namespace, servicePath, nameStyle)
9191
const module = makeServiceModule(service, options)
92-
store.registerModule(options.namespace, module)
92+
// Don't preserve state if reinitialized (prevents state pollution in SSR)
93+
store.registerModule(options.namespace, module, { preserveState: false })
9394

9495
// (2a^) Monkey patch the BaseModel in globalModels
9596
const BaseModel = _get(globalModels, `[${options.serverAlias}].BaseModel`)
@@ -100,13 +101,16 @@ export default function prepareMakeServicePlugin(
100101
}
101102
// (2b^) Monkey patch the Model(s) and add to globalModels
102103
assignIfNotPresent(Model, {
103-
store,
104104
namespace: options.namespace,
105105
servicePath,
106106
instanceDefaults,
107107
setupInstance,
108108
preferUpdate
109109
})
110+
// As per 1^, don't preserve state on the model either (prevents state pollution in SSR)
111+
Object.assign(Model, {
112+
store
113+
})
110114
if (!Model.modelName || Model.modelName === 'BaseModel') {
111115
throw new Error(
112116
'The modelName property is required for Feathers-Vuex Models'
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { assert } from 'chai'
2+
import Vuex from 'vuex'
3+
import { feathersRestClient as feathersClient } from '../fixtures/feathers-client'
4+
import feathersVuex from '../../src/index'
5+
6+
interface RootState {
7+
todos: any
8+
}
9+
10+
function makeContext() {
11+
const todoService = feathersClient.service('todos')
12+
const serverAlias = 'reinitialization'
13+
const { makeServicePlugin, BaseModel, models } = feathersVuex(
14+
feathersClient,
15+
{
16+
serverAlias
17+
}
18+
)
19+
class Todo extends BaseModel {
20+
public static modelName = 'Todo'
21+
}
22+
return {
23+
makeServicePlugin,
24+
BaseModel,
25+
todoService,
26+
Todo,
27+
models,
28+
serverAlias
29+
}
30+
}
31+
32+
describe('Service Module - Reinitialization', function() {
33+
/**
34+
* Tests that when the make service plugin is reinitialized state
35+
* is reset in the vuex module/model.
36+
* This prevents state pollution in SSR setups.
37+
*/
38+
it('does not preserve module/model state when reinitialized', function() {
39+
const {
40+
makeServicePlugin,
41+
todoService,
42+
Todo,
43+
models,
44+
serverAlias
45+
} = makeContext()
46+
const todosPlugin = makeServicePlugin({
47+
servicePath: 'todos',
48+
Model: Todo,
49+
service: todoService
50+
})
51+
let store = new Vuex.Store<RootState>({
52+
plugins: [todosPlugin]
53+
})
54+
let todoState = store.state['todos']
55+
const virginState = {
56+
addOnUpsert: false,
57+
autoRemove: false,
58+
debug: false,
59+
copiesById: {},
60+
enableEvents: true,
61+
errorOnCreate: null,
62+
errorOnFind: null,
63+
errorOnGet: null,
64+
errorOnPatch: null,
65+
errorOnRemove: null,
66+
errorOnUpdate: null,
67+
idField: 'id',
68+
tempIdField: '__id',
69+
ids: [],
70+
isCreatePending: false,
71+
isFindPending: false,
72+
isGetPending: false,
73+
isPatchPending: false,
74+
isRemovePending: false,
75+
isUpdatePending: false,
76+
keepCopiesInStore: false,
77+
keyedById: {},
78+
modelName: 'Todo',
79+
nameStyle: 'short',
80+
namespace: 'todos',
81+
pagination: {
82+
defaultLimit: null,
83+
defaultSkip: null
84+
},
85+
paramsForServer: [],
86+
preferUpdate: false,
87+
replaceItems: false,
88+
serverAlias,
89+
servicePath: 'todos',
90+
skipRequestIfExists: false,
91+
tempsById: {},
92+
whitelist: []
93+
}
94+
95+
assert.deepEqual(
96+
todoState,
97+
virginState,
98+
'vuex module state is correct on first initialization'
99+
)
100+
assert.deepEqual(
101+
models[serverAlias][Todo.name].store.state[Todo.namespace],
102+
todoState,
103+
'model state is the same as vuex module state on first initialization'
104+
)
105+
106+
// Simulate some mutations on the store.
107+
const todo = {
108+
id: 1,
109+
testProp: true
110+
}
111+
112+
store.commit('todos/addItem', todo)
113+
const serviceTodo = store.state['todos'].keyedById[1]
114+
115+
assert.equal(
116+
todo.testProp,
117+
serviceTodo.testProp,
118+
'todo is added to the store'
119+
)
120+
121+
assert.deepEqual(
122+
models[serverAlias][Todo.name].store.state[Todo.namespace],
123+
todoState,
124+
'model state is the same as vuex module state when store is mutated'
125+
)
126+
127+
// Here we are going to simulate the make service plugin being reinitialized.
128+
// This is the default behaviour in SSR setups, e.g. nuxt universal mode,
129+
// although unlikely in SPAs.
130+
store = new Vuex.Store<RootState>({
131+
plugins: [todosPlugin]
132+
})
133+
134+
todoState = store.state['todos']
135+
136+
// We expect vuex module state for this service to be reset.
137+
assert.deepEqual(
138+
todoState,
139+
virginState,
140+
'store state in vuex module is not preserved on reinitialization'
141+
)
142+
// We also expect model store state for this service to be reset.
143+
assert.deepEqual(
144+
models[serverAlias][Todo.name].store.state[Todo.namespace],
145+
virginState,
146+
'store state in service model is not preserved on reinitialization'
147+
)
148+
})
149+
})

0 commit comments

Comments
 (0)