diff --git a/flow/weex.js b/flow/weex.js index 2f32bf5c699..cef6f0ba2f7 100644 --- a/flow/weex.js +++ b/flow/weex.js @@ -1,6 +1,79 @@ // global flag to be compiled away declare var __WEEX__: boolean; +declare type Weex = { + config: Object; + document: WeexDocument; + requireModule: (name: string) => Object | void; + supports: (condition: string) => boolean | void; + isRegisteredModule: (name: string, method?: string) => boolean; + isRegisteredComponent: (name: string) => boolean; +}; + +declare interface WeexDocument { + id: string | number; + URL: string; + taskCenter: Object; + + open: () => void; + close: () => void; + createElement: (tagName: string, props?: Object) => WeexElement; + createComment: (text: string) => Object; + fireEvent: (type: string) => void; + destroy: () => void; +}; + +declare interface WeexElement { + nodeType: number; + nodeId: string | number; + type: string; + ref: string | number; + text?: string; + + parentNode: WeexElement | void; + children: Array; + previousSibling: WeexElement | void; + nextSibling: WeexElement | void; + + appendChild: (node: WeexElement) => void; + removeChild: (node: WeexElement, preserved?: boolean) => void; + insertBefore: (node: WeexElement, before: WeexElement) => void; + insertAfter: (node: WeexElement, after: WeexElement) => void; + setAttr: (key: string, value: any, silent?: boolean) => void; + setAttrs: (attrs: Object, silent?: boolean) => void; + setStyle: (key: string, value: any, silent?: boolean) => void; + setStyles: (attrs: Object, silent?: boolean) => void; + addEvent: (type: string, handler: Function, args?: Array) => void; + removeEvent: (type: string) => void; + fireEvent: (type: string) => void; + destroy: () => void; +} + +declare type WeexInstanceOption = { + instanceId: string; + config: Object; + document: WeexDocument; + Vue?: GlobalAPI; + app?: Component; + data?: Object; +}; + +declare type WeexRuntimeContext = { + weex: Weex; + service: Object; + BroadcastChannel?: Function; +}; + +declare type WeexInstanceContext = { + Vue: GlobalAPI; + + // DEPRECATED + setTimeout?: Function; + clearTimeout?: Function; + setInterval?: Function; + clearInterval?: Function; +}; + declare type WeexCompilerOptions = CompilerOptions & { // whether to compile special template for recyclable?: boolean; diff --git a/src/platforms/weex/entry-framework.js b/src/platforms/weex/entry-framework.js index 2c281970699..a23775d4db5 100644 --- a/src/platforms/weex/entry-framework.js +++ b/src/platforms/weex/entry-framework.js @@ -1,127 +1,89 @@ +/* @flow */ + // this will be preserved during build +// $flow-disable-line const VueFactory = require('./factory') -const instances = {} - -/** - * Prepare framework config. - * Nothing need to do actually, just an interface provided to weex runtime. - */ -export function init () {} - -/** - * Reset framework config and clear all registrations. - */ -export function reset () { - clear(instances) -} +const instanceOptions: { [key: string]: WeexInstanceOption } = {} /** - * Delete all keys of an object. - * @param {object} obj + * Create instance context. */ -function clear (obj) { - for (const key in obj) { - delete obj[key] +export function createInstanceContext ( + instanceId: string, + runtimeContext: WeexRuntimeContext, + data: Object = {} +): WeexInstanceContext { + const weex: Weex = runtimeContext.weex + const instance: WeexInstanceOption = instanceOptions[instanceId] = { + instanceId, + config: weex.config, + document: weex.document, + data } -} - -/** - * Create an instance with id, code, config and external data. - * @param {string} instanceId - * @param {string} appCode - * @param {object} config - * @param {object} data - * @param {object} env { info, config, services } - */ -export function createInstance ( - instanceId, - appCode = '', - config = {}, - data, - env = {} -) { - const weex = env.weex - const document = weex.document - const instance = instances[instanceId] = { - instanceId, config, data, - document - } - - const timerAPIs = getInstanceTimer(instanceId, weex.requireModule) // Each instance has a independent `Vue` module instance const Vue = instance.Vue = createVueModuleInstance(instanceId, weex) - // The function which create a closure the JS Bundle will run in. - // It will declare some instance variables like `Vue`, HTML5 Timer APIs etc. - const instanceVars = Object.assign({ - Vue, - weex - }, timerAPIs, env.services) - - appCode = `(function(global){ \n${appCode}\n })(Object.create(this))` - callFunction(instanceVars, appCode) + // DEPRECATED + const timerAPIs = getInstanceTimer(instanceId, weex.requireModule) - return instance + const instanceContext = Object.assign({ Vue }, timerAPIs) + Object.freeze(instanceContext) + return instanceContext } /** * Destroy an instance with id. It will make sure all memory of * this instance released and no more leaks. - * @param {string} instanceId */ -export function destroyInstance (instanceId) { - const instance = instances[instanceId] +export function destroyInstance (instanceId: string): void { + const instance = instanceOptions[instanceId] if (instance && instance.app instanceof instance.Vue) { - instance.document.destroy() - instance.app.$destroy() + try { + instance.app.$destroy() + instance.document.destroy() + } catch (e) {} delete instance.document delete instance.app } - delete instances[instanceId] + delete instanceOptions[instanceId] } /** * Refresh an instance with id and new top-level component data. * It will use `Vue.set` on all keys of the new data. So it's better * define all possible meaningful keys when instance created. - * @param {string} instanceId - * @param {object} data */ -export function refreshInstance (instanceId, data) { - const instance = instances[instanceId] +export function refreshInstance ( + instanceId: string, + data: Object +): Error | void { + const instance = instanceOptions[instanceId] if (!instance || !(instance.app instanceof instance.Vue)) { return new Error(`refreshInstance: instance ${instanceId} not found!`) } - for (const key in data) { - instance.Vue.set(instance.app, key, data[key]) + if (instance.Vue && instance.Vue.set) { + for (const key in data) { + instance.Vue.set(instance.app, key, data[key]) + } } // Finally `refreshFinish` signal needed. instance.document.taskCenter.send('dom', { action: 'refreshFinish' }, []) } -/** - * Get the JSON object of the root element. - * @param {string} instanceId - */ -export function getRoot (instanceId) { - const instance = instances[instanceId] - if (!instance || !(instance.app instanceof instance.Vue)) { - return new Error(`getRoot: instance ${instanceId} not found!`) - } - return instance.app.$el.toJSON() -} - /** * Create a fresh instance of Vue for each Weex instance. */ -function createVueModuleInstance (instanceId, weex) { +function createVueModuleInstance ( + instanceId: string, + weex: Weex +): GlobalAPI { const exports = {} VueFactory(exports, weex.document) const Vue = exports.Vue - const instance = instances[instanceId] + const instance = instanceOptions[instanceId] // patch reserved tag detection to account for dynamically registered // components @@ -161,7 +123,7 @@ function createVueModuleInstance (instanceId, weex) { mounted () { const options = this.$options // root component (vm) - if (options.el && weex.document) { + if (options.el && weex.document && instance.app === this) { try { // Send "createFinish" signal to native. weex.document.taskCenter.send('dom', { action: 'createFinish' }, []) @@ -185,16 +147,17 @@ function createVueModuleInstance (instanceId, weex) { } /** + * DEPRECATED * Generate HTML5 Timer APIs. An important point is that the callback * will be converted into callback id when sent to native. So the * framework can make sure no side effect of the callback happened after * an instance destroyed. - * @param {[type]} instanceId [description] - * @param {[type]} moduleGetter [description] - * @return {[type]} [description] */ -function getInstanceTimer (instanceId, moduleGetter) { - const instance = instances[instanceId] +function getInstanceTimer ( + instanceId: string, + moduleGetter: Function +): Object { + const instance = instanceOptions[instanceId] const timer = moduleGetter('timer') const timerAPIs = { setTimeout: (...args) => { @@ -222,22 +185,3 @@ function getInstanceTimer (instanceId, moduleGetter) { } return timerAPIs } - -/** - * Call a new function body with some global objects. - * @param {object} globalObjects - * @param {string} code - * @return {any} - */ -function callFunction (globalObjects, body) { - const globalKeys = [] - const globalValues = [] - for (const key in globalObjects) { - globalKeys.push(key) - globalValues.push(globalObjects[key]) - } - globalKeys.push(body) - - const result = new Function(...globalKeys) - return result(...globalValues) -} diff --git a/test/weex/helpers/index.js b/test/weex/helpers/index.js index 6ed807cb867..fca2e1eafbc 100644 --- a/test/weex/helpers/index.js +++ b/test/weex/helpers/index.js @@ -124,7 +124,7 @@ function omitUseless (object) { } export function getRoot (instance) { - return omitUseless(instance.document.body.toJSON()) + return omitUseless(instance.$getRoot()) } // Get all binding events in the instance @@ -141,7 +141,7 @@ export function getEvents (instance) { node.children.forEach(recordEvent) } } - recordEvent(instance.document.body.toJSON()) + recordEvent(instance.$getRoot()) return events } @@ -158,9 +158,14 @@ export function createInstance (id, code, ...args) { context.registerModules({ timer: ['setTimeout', 'setInterval'] }) - const instance = context.createInstance(id, `// { "framework": "Vue" }\n${code}`, ...args) + const instance = context.createInstance(id, `// { "framework": "Vue" }\n${code}`, ...args) || {} + instance.document = context.getDocument(id) + instance.$getRoot = () => context.getRoot(id) instance.$refresh = (data) => context.refreshInstance(id, data) - instance.$destroy = () => context.destroyInstance(id) + instance.$destroy = () => { + delete instance.document + context.destroyInstance(id) + } instance.$triggerHook = (id, hook, args) => { instance.document.taskCenter.triggerHook(id, 'lifecycle', hook, { args }) } diff --git a/test/weex/runtime/framework.spec.js b/test/weex/runtime/framework.spec.js index 5a8daed4615..ac74680c41b 100644 --- a/test/weex/runtime/framework.spec.js +++ b/test/weex/runtime/framework.spec.js @@ -1,4 +1,3 @@ -import * as framework from '../../../packages/weex-vue-framework' import { getRoot, createInstance } from '../helpers/index' describe('framework APIs', () => { @@ -26,17 +25,17 @@ describe('framework APIs', () => { new Vue({ render: function (createElement) { return createElement('div', {}, [ - createElement('text', { attrs: { value: JSON.stringify(this.$getConfig()) }}, []) + createElement('text', { attrs: { value: JSON.stringify(weex.config) }}, []) ]) }, el: "body" }) - `, { bundleUrl: 'http://example.com/', a: 1, b: 2 }) + `, { bundleType: 'Vue', bundleUrl: 'http://example.com/', a: 1, b: 2 }) expect(getRoot(instance)).toEqual({ type: 'div', children: [{ type: 'text', - attr: { value: '{"bundleUrl":"http://example.com/","a":1,"b":2,"env":{},"bundleType":"Vue"}' } + attr: { value: '{"bundleType":"Vue","bundleUrl":"http://example.com/","a":1,"b":2,"env":{}}' } }] }) }) @@ -122,63 +121,6 @@ describe('framework APIs', () => { }) }) - it('getRoot', () => { - const id = String(Date.now() * Math.random()) - const instance = createInstance(id, ` - new Vue({ - data: { - x: 'Hello' - }, - render: function (createElement) { - return createElement('div', {}, [ - createElement('text', { attrs: { value: this.x }}, []) - ]) - }, - el: "body" - }) - `) - - let root = framework.getRoot(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' }) - framework.destroyInstance(instance.id) - - root = framework.getRoot(instance.id) - expect(root instanceof Error).toBe(true) - expect(root).toMatch(/getRoot/) - expect(root).toMatch(/not found/) - }) - - it('vm.$getConfig', () => { - const id = String(Date.now() * Math.random()) - global.WXEnvironment = { - weexVersion: '0.10.0', - platform: 'Node.js' - } - const instance = createInstance(id, ` - new Vue({ - render: function (createElement) { - return createElement('div', {}, [ - createElement('text', { attrs: { value: JSON.stringify(this.$getConfig()) }}, []) - ]) - }, - el: "body" - }) - `, { bundleUrl: 'http://whatever.com/x.js' }) - expect(JSON.parse(getRoot(instance).children[0].attr.value)).toEqual({ - bundleUrl: 'http://whatever.com/x.js', - bundleType: 'Vue', - env: { - weexVersion: '0.10.0', - platform: 'Node.js' - } - }) - delete global.WXEnvironment - }) - it('registering global assets', () => { const id = String(Date.now() * Math.random()) const instance = createInstance(id, `