Explorar el Código

properly handle cosntructor options modification before global mixin application (fix #4976)

Evan You hace 9 años
padre
commit
4cf49828c0

+ 1 - 0
flow/component.js

@@ -14,6 +14,7 @@ declare interface Component {
   static extend: (options: Object) => Function;
   static superOptions: Object;
   static extendOptions: Object;
+  static sealedOptions: Object;
   static super: Class<Component>;
   // assets
   static directive: (id: string, def?: Function | Object) => Function | Object | void;

+ 2 - 1
src/core/global-api/extend.js

@@ -1,7 +1,7 @@
 /* @flow */
 
 import config from '../config'
-import { warn, mergeOptions } from '../util/index'
+import { warn, extend, mergeOptions } from '../util/index'
 import { defineComputed, proxy } from '../instance/state'
 
 export function initExtend (Vue: GlobalAPI) {
@@ -78,6 +78,7 @@ export function initExtend (Vue: GlobalAPI) {
     // been updated.
     Sub.superOptions = Super.options
     Sub.extendOptions = extendOptions
+    Sub.sealedOptions = extend({}, Sub.options)
 
     // cache constructor
     cachedCtors[SuperId] = Sub

+ 23 - 7
src/core/instance/init.js

@@ -8,7 +8,7 @@ import { initRender } from './render'
 import { initEvents } from './events'
 import { initInjections } from './inject'
 import { initLifecycle, callHook } from './lifecycle'
-import { mergeOptions, formatComponentName } from '../util/index'
+import { extend, mergeOptions, formatComponentName } from '../util/index'
 
 let uid = 0
 
@@ -88,14 +88,17 @@ export function resolveConstructorOptions (Ctor: Class<Component>) {
   if (Ctor.super) {
     const superOptions = Ctor.super.options
     const cachedSuperOptions = Ctor.superOptions
-    const extendOptions = Ctor.extendOptions
     if (superOptions !== cachedSuperOptions) {
-      // super option changed
+      // super option changed,
+      // need to resolve new options.
       Ctor.superOptions = superOptions
-      extendOptions.render = options.render
-      extendOptions.staticRenderFns = options.staticRenderFns
-      extendOptions._scopeId = options._scopeId
-      options = Ctor.options = mergeOptions(superOptions, extendOptions)
+      // check if there are any late-modified/attached options (#4976)
+      const modifiedOptions = resolveModifiedOptions(Ctor)
+      // update base extend options
+      if (modifiedOptions) {
+        extend(Ctor.extendOptions, modifiedOptions)
+      }
+      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
       if (options.name) {
         options.components[options.name] = Ctor
       }
@@ -103,3 +106,16 @@ export function resolveConstructorOptions (Ctor: Class<Component>) {
   }
   return options
 }
+
+function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
+  let res
+  const options = Ctor.options
+  const sealed = Ctor.sealedOptions
+  for (const key in options) {
+    if (sealed[key] !== options[key]) {
+      if (!res) res = {}
+      res[key] = options[key]
+    }
+  }
+  return res
+}

+ 30 - 0
test/unit/features/global-api/mixin.spec.js

@@ -84,4 +84,34 @@ describe('Global API: mixin', () => {
 
     expect(vm.$el.children[0].hasAttribute('foo')).toBe(true)
   })
+
+  // #4976
+  it('should not drop late-attached custom options on existing constructors', () => {
+    const Test = Vue.extend({})
+
+    // Inject options later
+    // vue-loader and vue-hot-reload-api are doing like this
+    Test.options.computed = {
+      $style: () => 123
+    }
+
+    const spy = jasmine.createSpy('mixin')
+    Test.options.beforeCreate = [spy]
+
+    // Update super constructor's options
+    Vue.mixin({})
+
+    // mount the component
+    const vm = new Test({
+      template: '<div>{{ $style }}</div>'
+    }).$mount()
+
+    expect(spy).toHaveBeenCalled()
+    expect(vm.$el.textContent).toBe('123')
+    expect(vm.$style).toBe(123)
+
+    // Should not be dropped
+    expect(Test.options.computed.$style()).toBe(123)
+    expect(Test.options.beforeCreate).toEqual([spy])
+  })
 })