Skip to content

Commit 6f947cb

Browse files
author
Guillaume Chau
committed
fix(vuex): clone mutation payload instead of stringify, closes #953
1 parent 6fdfd39 commit 6f947cb

File tree

3 files changed

+203
-9
lines changed

3 files changed

+203
-9
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,4 @@
8686
"engines": {
8787
"node": ">=8.10"
8888
}
89-
}
89+
}

src/backend/clone.js

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Clone deep utility for cloning initial state of the store
2+
// REFERENCE: https://github.com/pvorb/clone
3+
4+
let NativeMap
5+
try {
6+
NativeMap = Map
7+
} catch (_) {
8+
// maybe a reference error because no `Map`. Give it a dummy value that no
9+
// value will ever be an instanceof.
10+
NativeMap = function () {}
11+
}
12+
13+
let NativeSet
14+
try {
15+
NativeSet = Set
16+
} catch (_) {
17+
NativeSet = function () {}
18+
}
19+
20+
let NativePromise
21+
try {
22+
NativePromise = Promise
23+
} catch (_) {
24+
NativePromise = function () {}
25+
}
26+
27+
export default function clone (parent, { circular = true, depth = Infinity, prototype, includeNonEnumerable } = {}) {
28+
// maintain two arrays for circular references, where corresponding parents
29+
// and children have the same index
30+
var allParents = []
31+
var allChildren = []
32+
33+
var useBuffer = typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function'
34+
35+
// recurse this function so we don't reset allParents and allChildren
36+
function _clone (parent, depth) {
37+
// cloning null always returns null
38+
if (parent === null) { return null }
39+
40+
if (depth === 0) { return parent }
41+
42+
var child
43+
var proto
44+
if (typeof parent !== 'object') {
45+
return parent
46+
}
47+
48+
if (_instanceof(parent, NativeMap)) {
49+
child = new NativeMap()
50+
} else if (_instanceof(parent, NativeSet)) {
51+
child = new NativeSet()
52+
} else if (_instanceof(parent, NativePromise)) {
53+
child = new NativePromise(function (resolve, reject) {
54+
parent.then(function (value) {
55+
resolve(_clone(value, depth - 1))
56+
}, function (err) {
57+
reject(_clone(err, depth - 1))
58+
})
59+
})
60+
} else if (clone.__isArray(parent)) {
61+
child = []
62+
} else if (clone.__isRegExp(parent)) {
63+
child = new RegExp(parent.source, __getRegExpFlags(parent))
64+
if (parent.lastIndex) child.lastIndex = parent.lastIndex
65+
} else if (clone.__isDate(parent)) {
66+
child = new Date(parent.getTime())
67+
} else if (useBuffer && Buffer.isBuffer(parent)) {
68+
if (Buffer.from) {
69+
// Node.js >= 5.10.0
70+
child = Buffer.from(parent)
71+
} else {
72+
// Older Node.js versions
73+
// eslint-disable-next-line node/no-deprecated-api
74+
child = new Buffer(parent.length)
75+
parent.copy(child)
76+
}
77+
return child
78+
} else if (_instanceof(parent, Error)) {
79+
child = Object.create(parent)
80+
} else {
81+
if (typeof prototype === 'undefined') {
82+
proto = Object.getPrototypeOf(parent)
83+
child = Object.create(proto)
84+
} else {
85+
child = Object.create(prototype)
86+
proto = prototype
87+
}
88+
}
89+
90+
if (circular) {
91+
var index = allParents.indexOf(parent)
92+
93+
if (index !== -1) {
94+
return allChildren[index]
95+
}
96+
allParents.push(parent)
97+
allChildren.push(child)
98+
}
99+
100+
if (_instanceof(parent, NativeMap)) {
101+
parent.forEach(function (value, key) {
102+
var keyChild = _clone(key, depth - 1)
103+
var valueChild = _clone(value, depth - 1)
104+
child.set(keyChild, valueChild)
105+
})
106+
}
107+
if (_instanceof(parent, NativeSet)) {
108+
parent.forEach(function (value) {
109+
var entryChild = _clone(value, depth - 1)
110+
child.add(entryChild)
111+
})
112+
}
113+
114+
for (var i in parent) {
115+
var attrs = Object.getOwnPropertyDescriptor(parent, i)
116+
if (attrs) {
117+
if (attrs.hasOwnProperty('get') && attrs.get.name === 'computedGetter') {
118+
Object.defineProperty(child, i, attrs)
119+
continue
120+
}
121+
122+
child[i] = _clone(parent[i], depth - 1)
123+
}
124+
}
125+
126+
if (Object.getOwnPropertySymbols) {
127+
var symbols = Object.getOwnPropertySymbols(parent)
128+
for (let i = 0; i < symbols.length; i++) {
129+
// Don't need to worry about cloning a symbol because it is a primitive,
130+
// like a number or string.
131+
var symbol = symbols[i]
132+
var descriptor = Object.getOwnPropertyDescriptor(parent, symbol)
133+
if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
134+
continue
135+
}
136+
child[symbol] = _clone(parent[symbol], depth - 1)
137+
Object.defineProperty(child, symbol, descriptor)
138+
}
139+
}
140+
141+
if (includeNonEnumerable) {
142+
var allPropertyNames = Object.getOwnPropertyNames(parent)
143+
for (let i = 0; i < allPropertyNames.length; i++) {
144+
const propertyName = allPropertyNames[i]
145+
let descriptor = Object.getOwnPropertyDescriptor(parent, propertyName)
146+
if (descriptor && descriptor.enumerable) {
147+
continue
148+
}
149+
child[propertyName] = _clone(parent[propertyName], depth - 1)
150+
Object.defineProperty(child, propertyName, descriptor)
151+
}
152+
}
153+
154+
return child
155+
}
156+
157+
return _clone(parent, depth)
158+
}
159+
160+
// private utility functions
161+
162+
function __objToStr (o) {
163+
return Object.prototype.toString.call(o)
164+
}
165+
clone.__objToStr = __objToStr
166+
167+
function __isDate (o) {
168+
return typeof o === 'object' && __objToStr(o) === '[object Date]'
169+
}
170+
clone.__isDate = __isDate
171+
172+
function __isArray (o) {
173+
return typeof o === 'object' && __objToStr(o) === '[object Array]'
174+
}
175+
clone.__isArray = __isArray
176+
177+
function __isRegExp (o) {
178+
return typeof o === 'object' && __objToStr(o) === '[object RegExp]'
179+
}
180+
clone.__isRegExp = __isRegExp
181+
182+
function __getRegExpFlags (re) {
183+
var flags = ''
184+
if (re.global) flags += 'g'
185+
if (re.ignoreCase) flags += 'i'
186+
if (re.multiline) flags += 'm'
187+
return flags
188+
}
189+
clone.__getRegExpFlags = __getRegExpFlags
190+
191+
function _instanceof (obj, type) {
192+
return type != null && obj instanceof type
193+
}

src/backend/vuex.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { stringify, parse } from 'src/util'
22
import SharedData from 'src/shared-data'
33
import { set, get } from '../util'
44
import Vue from 'vue'
5+
import clone from './clone'
56

67
const isProd = process.env.NODE_ENV === 'production'
78

@@ -83,7 +84,7 @@ export function initVuexBackend (hook, bridge, isLegacy) {
8384
options
8485
}, {
8586
registerModule: true
86-
}, false)
87+
})
8788
}
8889

8990
return moduleInfo
@@ -110,7 +111,7 @@ export function initVuexBackend (hook, bridge, isLegacy) {
110111
path
111112
}, {
112113
unregisterModule: true
113-
}, false)
114+
})
114115
}
115116

116117
origUnregisterModule(path)
@@ -137,14 +138,14 @@ export function initVuexBackend (hook, bridge, isLegacy) {
137138
addMutation(type, payload)
138139
})
139140

140-
function addMutation (type, payload, options = {}, stringifyPayload = true) {
141+
function addMutation (type, payload, options = {}) {
141142
const index = mutations.length
142143

143-
const payloadData = stringify(payload)
144-
145144
mutations.push({
146145
type,
147-
payload: stringifyPayload ? payloadData : payload,
146+
payload: clone(payload, {
147+
includeNonEnumerable: true
148+
}),
148149
index,
149150
handlers: store._mutations[type],
150151
registeredModules: Object.keys(registeredModules),
@@ -154,7 +155,7 @@ export function initVuexBackend (hook, bridge, isLegacy) {
154155
bridge.send('vuex:mutation', {
155156
mutation: {
156157
type: type,
157-
payload: payloadData,
158+
payload: stringify(payload),
158159
index
159160
},
160161
timestamp: Date.now()
@@ -297,7 +298,7 @@ export function initVuexBackend (hook, bridge, isLegacy) {
297298
} else if (mutation.handlers) {
298299
store._committing = true
299300
try {
300-
const payload = parse(mutation.payload, true)
301+
const payload = mutation.payload
301302
if (Array.isArray(mutation.handlers)) {
302303
mutation.handlers.forEach(handler => handler(payload))
303304
} else {

0 commit comments

Comments
 (0)