diff --git a/src/core/util/options.ts b/src/core/util/options.ts index baddeb09b6e..ef6a10342a9 100644 --- a/src/core/util/options.ts +++ b/src/core/util/options.ts @@ -51,7 +51,8 @@ if (__DEV__) { */ function mergeData( to: Record, - from: Record | null + from: Record | null, + recursive = true ): Record { if (!from) return to let key, toVal, fromVal @@ -66,7 +67,7 @@ function mergeData( if (key === '__ob__') continue toVal = to[key] fromVal = from[key] - if (!hasOwn(to, key)) { + if (!recursive || !hasOwn(to, key)) { set(to, key, fromVal) } else if ( toVal !== fromVal && @@ -262,7 +263,22 @@ strats.props = if (childVal) extend(ret, childVal) return ret } -strats.provide = mergeDataOrFn + +strats.provide = function (parentVal: Object | null, childVal: Object | null) { + if (!parentVal) return childVal + return function () { + const ret = Object.create(null) + mergeData(ret, isFunction(parentVal) ? parentVal.call(this) : parentVal) + if (childVal) { + mergeData( + ret, + isFunction(childVal) ? childVal.call(this) : childVal, + false // non-recursive + ) + } + return ret + } +} /** * Default strategy. diff --git a/test/unit/features/options/inject.spec.ts b/test/unit/features/options/inject.spec.ts index 983367b6126..b4c295e3f51 100644 --- a/test/unit/features/options/inject.spec.ts +++ b/test/unit/features/options/inject.spec.ts @@ -712,4 +712,12 @@ describe('Options provide/inject', () => { await nextTick() expect(spy).toHaveBeenCalledWith(2) }) + + // #12854 + test('should not mutate original provide options', () => { + const hairMixin = { provide: { hair: 'red' } } + const eyesMixin = { provide: { eyes: 'brown' } } + new Vue({ mixins: [hairMixin, eyesMixin], render() {} }).$mount() + expect(eyesMixin.provide).toStrictEqual({ eyes: 'brown' }) + }) })