Skip to content

Commit 4cf4982

Browse files
committed
properly handle cosntructor options modification before global mixin application (fix vuejs#4976)
1 parent 2a5fb41 commit 4cf4982

File tree

4 files changed

+56
-8
lines changed

4 files changed

+56
-8
lines changed

flow/component.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ declare interface Component {
1414
static extend: (options: Object) => Function;
1515
static superOptions: Object;
1616
static extendOptions: Object;
17+
static sealedOptions: Object;
1718
static super: Class<Component>;
1819
// assets
1920
static directive: (id: string, def?: Function | Object) => Function | Object | void;

src/core/global-api/extend.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* @flow */
22

33
import config from '../config'
4-
import { warn, mergeOptions } from '../util/index'
4+
import { warn, extend, mergeOptions } from '../util/index'
55
import { defineComputed, proxy } from '../instance/state'
66

77
export function initExtend (Vue: GlobalAPI) {
@@ -78,6 +78,7 @@ export function initExtend (Vue: GlobalAPI) {
7878
// been updated.
7979
Sub.superOptions = Super.options
8080
Sub.extendOptions = extendOptions
81+
Sub.sealedOptions = extend({}, Sub.options)
8182

8283
// cache constructor
8384
cachedCtors[SuperId] = Sub

src/core/instance/init.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { initRender } from './render'
88
import { initEvents } from './events'
99
import { initInjections } from './inject'
1010
import { initLifecycle, callHook } from './lifecycle'
11-
import { mergeOptions, formatComponentName } from '../util/index'
11+
import { extend, mergeOptions, formatComponentName } from '../util/index'
1212

1313
let uid = 0
1414

@@ -88,18 +88,34 @@ export function resolveConstructorOptions (Ctor: Class<Component>) {
8888
if (Ctor.super) {
8989
const superOptions = Ctor.super.options
9090
const cachedSuperOptions = Ctor.superOptions
91-
const extendOptions = Ctor.extendOptions
9291
if (superOptions !== cachedSuperOptions) {
93-
// super option changed
92+
// super option changed,
93+
// need to resolve new options.
9494
Ctor.superOptions = superOptions
95-
extendOptions.render = options.render
96-
extendOptions.staticRenderFns = options.staticRenderFns
97-
extendOptions._scopeId = options._scopeId
98-
options = Ctor.options = mergeOptions(superOptions, extendOptions)
95+
// check if there are any late-modified/attached options (#4976)
96+
const modifiedOptions = resolveModifiedOptions(Ctor)
97+
// update base extend options
98+
if (modifiedOptions) {
99+
extend(Ctor.extendOptions, modifiedOptions)
100+
}
101+
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
99102
if (options.name) {
100103
options.components[options.name] = Ctor
101104
}
102105
}
103106
}
104107
return options
105108
}
109+
110+
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
111+
let res
112+
const options = Ctor.options
113+
const sealed = Ctor.sealedOptions
114+
for (const key in options) {
115+
if (sealed[key] !== options[key]) {
116+
if (!res) res = {}
117+
res[key] = options[key]
118+
}
119+
}
120+
return res
121+
}

test/unit/features/global-api/mixin.spec.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,34 @@ describe('Global API: mixin', () => {
8484

8585
expect(vm.$el.children[0].hasAttribute('foo')).toBe(true)
8686
})
87+
88+
// #4976
89+
it('should not drop late-attached custom options on existing constructors', () => {
90+
const Test = Vue.extend({})
91+
92+
// Inject options later
93+
// vue-loader and vue-hot-reload-api are doing like this
94+
Test.options.computed = {
95+
$style: () => 123
96+
}
97+
98+
const spy = jasmine.createSpy('mixin')
99+
Test.options.beforeCreate = [spy]
100+
101+
// Update super constructor's options
102+
Vue.mixin({})
103+
104+
// mount the component
105+
const vm = new Test({
106+
template: '<div>{{ $style }}</div>'
107+
}).$mount()
108+
109+
expect(spy).toHaveBeenCalled()
110+
expect(vm.$el.textContent).toBe('123')
111+
expect(vm.$style).toBe(123)
112+
113+
// Should not be dropped
114+
expect(Test.options.computed.$style()).toBe(123)
115+
expect(Test.options.beforeCreate).toEqual([spy])
116+
})
87117
})

0 commit comments

Comments
 (0)