Răsfoiți Sursa

[weex] enable extended constructors to use mixins

Evan You 9 ani în urmă
părinte
comite
cb4854a50e

+ 1 - 0
flow/options.js

@@ -60,6 +60,7 @@ declare type ComponentOptions = {
   _renderChildren?: ?VNodeChildren;
   _componentTag: ?string;
   _scopeId: ?string;
+  _base: Class<Component>;
 }
 
 declare type PropOptions = {

+ 1 - 1
src/core/global-api/assets.js

@@ -26,7 +26,7 @@ export function initAssetRegisters (Vue: GlobalAPI) {
         }
         if (type === 'component' && isPlainObject(definition)) {
           definition.name = definition.name || id
-          definition = Vue.extend(definition)
+          definition = this.options._base.extend(definition)
         }
         if (type === 'directive' && typeof definition === 'function') {
           definition = { bind: definition, update: definition }

+ 8 - 7
src/core/global-api/extend.js

@@ -18,9 +18,10 @@ export function initExtend (Vue: GlobalAPI) {
   Vue.extend = function (extendOptions: Object): Function {
     extendOptions = extendOptions || {}
     const Super = this
-    const isFirstExtend = Super.cid === 0
-    if (isFirstExtend && extendOptions._Ctor) {
-      return extendOptions._Ctor
+    const SuperId = Super.cid
+    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
+    if (cachedCtors[SuperId]) {
+      return cachedCtors[SuperId]
     }
     const name = extendOptions.name || Super.options.name
     if (process.env.NODE_ENV !== 'production') {
@@ -42,8 +43,10 @@ export function initExtend (Vue: GlobalAPI) {
       extendOptions
     )
     Sub['super'] = Super
-    // allow further extension
+    // allow further extension/mixin/plugin usage
     Sub.extend = Super.extend
+    Sub.mixin = Super.mixin
+    Sub.use = Super.use
     // create asset registers, so extended classes
     // can have their private assets too.
     config._assetTypes.forEach(function (type) {
@@ -59,9 +62,7 @@ export function initExtend (Vue: GlobalAPI) {
     Sub.superOptions = Super.options
     Sub.extendOptions = extendOptions
     // cache constructor
-    if (isFirstExtend) {
-      extendOptions._Ctor = Sub
-    }
+    cachedCtors[SuperId] = Sub
     return Sub
   }
 }

+ 4 - 0
src/core/global-api/index.js

@@ -31,6 +31,10 @@ export function initGlobalAPI (Vue: GlobalAPI) {
     Vue.options[type + 's'] = Object.create(null)
   })
 
+  // this is used to identify the "base" constructor to extend all plain-object
+  // components with in Weex's multi-instance scenarios.
+  Vue.options._base = Vue
+
   util.extend(Vue.options.components, builtInComponents)
 
   initUse(Vue)

+ 1 - 1
src/core/global-api/mixin.js

@@ -4,6 +4,6 @@ import { mergeOptions } from '../util/index'
 
 export function initMixin (Vue: GlobalAPI) {
   Vue.mixin = function (mixin: Object) {
-    Vue.options = mergeOptions(Vue.options, mixin)
+    this.options = mergeOptions(this.options, mixin)
   }
 }

+ 5 - 4
src/core/vdom/create-component.js

@@ -1,6 +1,5 @@
 /* @flow */
 
-import Vue from '../instance/index'
 import VNode from './vnode'
 import { normalizeChildren } from './helpers/index'
 import { resolveConstructorOptions } from '../instance/init'
@@ -23,8 +22,9 @@ export function createComponent (
     return
   }
 
+  const baseCtor = context.$options._base
   if (isObject(Ctor)) {
-    Ctor = Vue.extend(Ctor)
+    Ctor = baseCtor.extend(Ctor)
   }
 
   if (typeof Ctor !== 'function') {
@@ -39,7 +39,7 @@ export function createComponent (
     if (Ctor.resolved) {
       Ctor = Ctor.resolved
     } else {
-      Ctor = resolveAsyncComponent(Ctor, () => {
+      Ctor = resolveAsyncComponent(Ctor, baseCtor, () => {
         // it's ok to queue this on every render because
         // $forceUpdate is buffered by the scheduler.
         context.$forceUpdate()
@@ -195,6 +195,7 @@ function destroy (vnode: MountedComponentVNode) {
 
 function resolveAsyncComponent (
   factory: Function,
+  baseCtor: Class<Component>,
   cb: Function
 ): Class<Component> | void {
   if (factory.requested) {
@@ -207,7 +208,7 @@ function resolveAsyncComponent (
 
     const resolve = (res: Object | Class<Component>) => {
       if (isObject(res)) {
-        res = Vue.extend(res)
+        res = baseCtor.extend(res)
       }
       // cache resolved
       factory.resolved = res

+ 1 - 1
src/core/vdom/create-element.js

@@ -21,7 +21,7 @@ export function createElement (
   return _createElement(this._self, tag, data, children)
 }
 
-function _createElement (
+export function _createElement (
   context: Component,
   tag?: string | Class<Component> | Function | Object,
   data?: VNodeData,

+ 6 - 2
src/entries/weex-framework.js

@@ -2,6 +2,7 @@ import Vue from 'weex/runtime/index'
 import renderer from 'weex/runtime/config'
 
 Vue.weexVersion = '2.0.5-weex.1'
+export { Vue }
 
 const {
   instances,
@@ -85,8 +86,11 @@ export function createInstance (
 
   // Each instance has a independent `Vue` object and it should have
   // all top-level public APIs.
-  const subVue = Vue.extend({});
-  ['util', 'set', 'delete', 'nextTick', 'use'].forEach(name => {
+  const subVue = Vue.extend({})
+  // ensure plain-object components are extended from the subVue
+  subVue.options._base = subVue
+  // expose global utility
+  ;['util', 'set', 'delete', 'nextTick'].forEach(name => {
     subVue[name] = Vue[name]
   })
 

+ 118 - 39
test/weex/runtime/framework.spec.js

@@ -1,4 +1,4 @@
-import * as Vue from '../../../packages/weex-vue-framework'
+import * as framework from '../../../packages/weex-vue-framework'
 import { DEFAULT_ENV, Runtime, Instance } from 'weex-vdom-tester'
 import { config } from 'weex-js-runtime'
 
@@ -18,8 +18,8 @@ describe('framework APIs', () => {
 
   beforeEach(() => {
     Document.handler = sendTasks
-    Vue.init({ Document, Element, Comment, sendTasks })
-    runtime = new Runtime(Vue)
+    framework.init({ Document, Element, Comment, sendTasks })
+    runtime = new Runtime(framework)
     sendTasksHandler = function () {
       runtime.target.callNative(...arguments)
     }
@@ -27,12 +27,12 @@ describe('framework APIs', () => {
 
   afterEach(() => {
     delete Document.handler
-    Vue.reset()
+    framework.reset()
   })
 
   it('createInstance', () => {
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       new Vue({
         render: function (createElement) {
           return createElement('div', {}, [
@@ -50,7 +50,7 @@ describe('framework APIs', () => {
 
   it('createInstance with config', () => {
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       new Vue({
         render: function (createElement) {
           return createElement('div', {}, [
@@ -68,7 +68,7 @@ describe('framework APIs', () => {
 
   it('createInstance with external data', () => {
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       new Vue({
         data: {
           a: 1,
@@ -90,7 +90,7 @@ describe('framework APIs', () => {
 
   it('destroyInstance', (done) => {
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       new Vue({
         data: {
           x: 'Hello'
@@ -114,7 +114,7 @@ describe('framework APIs', () => {
           type: 'div',
           children: [{ type: 'text', attr: { value: 'World' }}]
         })
-        Vue.destroyInstance(instance.id)
+        framework.destroyInstance(instance.id)
       }),
       checkRefresh(instance, { x: 'Weex' }, result => {
         expect(result).toEqual({
@@ -128,7 +128,7 @@ describe('framework APIs', () => {
 
   it('refreshInstance', (done) => {
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       new Vue({
         data: {
           x: 'Hello'
@@ -146,15 +146,15 @@ describe('framework APIs', () => {
       children: [{ type: 'text', attr: { value: 'Hello' }}]
     })
 
-    Vue.refreshInstance(instance.id, { x: 'World' })
+    framework.refreshInstance(instance.id, { x: 'World' })
     setTimeout(() => {
       expect(instance.getRealRoot()).toEqual({
         type: 'div',
         children: [{ type: 'text', attr: { value: 'World' }}]
       })
 
-      Vue.destroyInstance(instance.id)
-      const result = Vue.refreshInstance(instance.id, { x: 'Weex' })
+      framework.destroyInstance(instance.id)
+      const result = framework.refreshInstance(instance.id, { x: 'Weex' })
       expect(result instanceof Error).toBe(true)
       expect(result).toMatch(/refreshInstance/)
       expect(result).toMatch(/not found/)
@@ -171,7 +171,7 @@ describe('framework APIs', () => {
 
   it('getRoot', () => {
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       new Vue({
         data: {
           x: 'Hello'
@@ -185,15 +185,15 @@ describe('framework APIs', () => {
       })
     `)
 
-    let root = Vue.getRoot(instance.id)
+    let root = framework.getRoot(instance.id)
     expect(root.ref).toEqual('_root')
     expect(root.type).toEqual('div')
     expect(root.children.length).toEqual(1)
     expect(root.children[0].type).toEqual('text')
     expect(root.children[0].attr).toEqual({ value: 'Hello' })
-    Vue.destroyInstance(instance.id)
+    framework.destroyInstance(instance.id)
 
-    root = Vue.getRoot(instance.id)
+    root = framework.getRoot(instance.id)
     expect(root instanceof Error).toBe(true)
     expect(root).toMatch(/getRoot/)
     expect(root).toMatch(/not found/)
@@ -201,7 +201,7 @@ describe('framework APIs', () => {
 
   it('reveiveTasks: fireEvent', (done) => {
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       new Vue({
         data: {
           x: 'Hello'
@@ -228,8 +228,8 @@ describe('framework APIs', () => {
       }]
     })
 
-    const textRef = Vue.getRoot(instance.id).children[0].ref
-    Vue.receiveTasks(instance.id, [
+    const textRef = framework.getRoot(instance.id).children[0].ref
+    framework.receiveTasks(instance.id, [
       { method: 'fireEvent', args: [textRef, 'click'] }
     ])
 
@@ -243,8 +243,8 @@ describe('framework APIs', () => {
         }]
       })
 
-      Vue.destroyInstance(instance.id)
-      const result = Vue.receiveTasks(instance.id, [
+      framework.destroyInstance(instance.id)
+      const result = framework.receiveTasks(instance.id, [
         { method: 'fireEvent', args: [textRef, 'click'] }
       ])
       expect(result instanceof Error).toBe(true)
@@ -255,12 +255,12 @@ describe('framework APIs', () => {
   })
 
   it('reveiveTasks: callback', (done) => {
-    Vue.registerModules({
+    framework.registerModules({
       foo: ['a', 'b', 'c']
     })
 
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       const moduleFoo = __weex_require_module__('foo')
       new Vue({
         data: {
@@ -299,7 +299,7 @@ describe('framework APIs', () => {
         return true
       }
     })
-    Vue.receiveTasks(instance.id, [
+    framework.receiveTasks(instance.id, [
       { method: 'callback', args: [callbackId, undefined, true] }
     ])
 
@@ -312,7 +312,7 @@ describe('framework APIs', () => {
         }]
       })
 
-      Vue.receiveTasks(instance.id, [
+      framework.receiveTasks(instance.id, [
         { method: 'callback', args: [callbackId, { value: 'Weex' }, true] }
       ])
       setTimeout(() => {
@@ -324,7 +324,7 @@ describe('framework APIs', () => {
           }]
         })
 
-        Vue.receiveTasks(instance.id, [
+        framework.receiveTasks(instance.id, [
           { method: 'callback', args: [callbackId] }
         ])
         setTimeout(() => {
@@ -336,8 +336,8 @@ describe('framework APIs', () => {
             }]
           })
 
-          Vue.destroyInstance(instance.id)
-          const result = Vue.receiveTasks(instance.id, [
+          framework.destroyInstance(instance.id)
+          const result = framework.receiveTasks(instance.id, [
             { method: 'callback', args: [callbackId] }
           ])
           expect(result instanceof Error).toBe(true)
@@ -350,7 +350,7 @@ describe('framework APIs', () => {
   })
 
   it('registerModules', () => {
-    Vue.registerModules({
+    framework.registerModules({
       foo: ['a', 'b', 'c'],
       bar: [
         { name: 'a', args: ['string'] },
@@ -360,7 +360,7 @@ describe('framework APIs', () => {
     })
 
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       const moduleFoo = __weex_require_module__('foo')
       const moduleBar = __weex_require_module__('bar')
       const moduleBaz = __weex_require_module__('baz')
@@ -410,9 +410,9 @@ describe('framework APIs', () => {
   })
 
   it('registerComponents', () => {
-    Vue.registerComponents(['foo', { type: 'bar' }, 'text'])
+    framework.registerComponents(['foo', { type: 'bar' }, 'text'])
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       new Vue({
         render: function (createElement) {
           return createElement('div', {}, [
@@ -431,7 +431,7 @@ describe('framework APIs', () => {
     })
   })
 
-  it('Vue.$getConfig', () => {
+  it('vm.$getConfig', () => {
     const instance = new Instance(runtime)
     instance.$create(`
       new Vue({
@@ -517,7 +517,7 @@ describe('framework APIs', () => {
       expect(instance.getRealRoot().children[0].attr.value).toEqual('1-4')
     }, 3250)
     setTimeout(() => {
-      Vue.destroyInstance(instance.id)
+      framework.destroyInstance(instance.id)
     }, 3500)
     setTimeout(() => {
       expect(instance.getRealRoot().children[0].attr.value).toEqual('1-4')
@@ -526,12 +526,12 @@ describe('framework APIs', () => {
   })
 
   it('send function param', () => {
-    Vue.registerModules({
+    framework.registerModules({
       foo: ['a']
     })
 
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       const moduleFoo = __weex_require_module__('foo')
       new Vue({
         mounted: function () {
@@ -558,12 +558,12 @@ describe('framework APIs', () => {
   })
 
   it('send Element param', () => {
-    Vue.registerModules({
+    framework.registerModules({
       foo: ['a']
     })
 
     const instance = new Instance(runtime)
-    Vue.createInstance(instance.id, `
+    framework.createInstance(instance.id, `
       const moduleFoo = __weex_require_module__('foo')
       new Vue({
         mounted: function () {
@@ -588,4 +588,83 @@ describe('framework APIs', () => {
 
     expect(typeof callbackId).toEqual('string')
   })
+
+  it('registering global assets', () => {
+    const instance = new Instance(runtime)
+    framework.createInstance(instance.id, `
+      Vue.component('test', {
+        render (h) {
+          return h('div', 'Hello')
+        }
+      })
+      new Vue({
+        render (h) {
+          return h('test')
+        },
+        el: 'body'
+      })
+    `)
+    expect(instance.getRealRoot()).toEqual({
+      type: 'div',
+      children: [{ type: 'text', attr: { value: 'Hello' }}]
+    })
+    // ensure base Vue is unaffected
+    expect(framework.Vue.options.components.test).toBeUndefined()
+  })
+
+  it('adding prototype methods', () => {
+    const instance = new Instance(runtime)
+    framework.createInstance(instance.id, `
+      Vue.prototype.$test = () => 'Hello'
+      const Test = {
+        render (h) {
+          return h('div', this.$test())
+        }
+      }
+      new Vue({
+        render (h) {
+          return h(Test)
+        },
+        el: 'body'
+      })
+    `)
+    expect(instance.getRealRoot()).toEqual({
+      type: 'div',
+      children: [{ type: 'text', attr: { value: 'Hello' }}]
+    })
+    // ensure base Vue is unaffected
+    expect(framework.Vue.prototype.$test).toBeUndefined()
+  })
+
+  it('using global mixins', () => {
+    const instance = new Instance(runtime)
+    framework.createInstance(instance.id, `
+      Vue.mixin({
+        created () {
+          this.test = true
+        }
+      })
+      const Test = {
+        data: () => ({ test: false }),
+        render (h) {
+          return h('div', this.test ? 'Hello' : 'nope')
+        }
+      }
+      new Vue({
+        data: { test: false },
+        render (h) {
+          return this.test ? h(Test) : h('p')
+        },
+        el: 'body'
+      })
+    `)
+    expect(instance.getRealRoot()).toEqual({
+      type: 'div',
+      children: [{ type: 'text', attr: { value: 'Hello' }}]
+    })
+    const vm = new framework.Vue({
+      data: { test: false }
+    })
+    expect(vm.test).toBe(false)
+  })
 })