diff --git a/src/platforms/weex/entry-framework.js b/src/platforms/weex/entry-framework.js index f0309f1c1..cc5181d34 100644 --- a/src/platforms/weex/entry-framework.js +++ b/src/platforms/weex/entry-framework.js @@ -22,7 +22,7 @@ export function init (cfg) { renderer.Document = cfg.Document renderer.Element = cfg.Element renderer.Comment = cfg.Comment - renderer.sendTasks = cfg.sendTasks + renderer.compileBundle = cfg.compileBundle } /** @@ -35,7 +35,7 @@ export function reset () { delete renderer.Document delete renderer.Element delete renderer.Comment - delete renderer.sendTasks + delete renderer.compileBundle } /** @@ -66,18 +66,9 @@ export function createInstance ( // Virtual-DOM object. const document = new renderer.Document(instanceId, config.bundleUrl) - // All function/callback of parameters before sent to native - // will be converted as an id. So `callbacks` is used to store - // these real functions. When a callback invoked and won't be - // called again, it should be removed from here automatically. - const callbacks = [] - - // The latest callback id, incremental. - const callbackId = 1 - const instance = instances[instanceId] = { instanceId, config, data, - document, callbacks, callbackId + document } // Prepare native module getter and HTML5 Timer APIs. @@ -102,11 +93,16 @@ export function createInstance ( weex: weexInstanceVar, // deprecated __weex_require_module__: weexInstanceVar.requireModule // eslint-disable-line - }, timerAPIs) - callFunction(instanceVars, appCode) + }, timerAPIs, env.services) + + if (!callFunctionNative(instanceVars, appCode)) { + // If failed to compile functionBody on native side, + // fallback to 'callFunction()'. + callFunction(instanceVars, appCode) + } // Send `createFinish` signal to native. - renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'createFinish', args: [] }], -1) + instance.document.taskCenter.send('dom', { action: 'createFinish' }, []) } /** @@ -117,6 +113,7 @@ export function createInstance ( export function destroyInstance (instanceId) { const instance = instances[instanceId] if (instance && instance.app instanceof instance.Vue) { + instance.document.destroy() instance.app.$destroy() } delete instances[instanceId] @@ -138,7 +135,7 @@ export function refreshInstance (instanceId, data) { instance.Vue.set(instance.app, key, data[key]) } // Finally `refreshFinish` signal needed. - renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'refreshFinish', args: [] }], -1) + instance.document.taskCenter.send('dom', { action: 'refreshFinish' }, []) } /** @@ -153,41 +150,51 @@ export function getRoot (instanceId) { return instance.app.$el.toJSON() } -/** - * Receive tasks from native. Generally there are two types of tasks: - * 1. `fireEvent`: an device actions or user actions from native. - * 2. `callback`: invoke function which sent to native as a parameter before. - * @param {string} instanceId - * @param {array} tasks - */ -export function receiveTasks (instanceId, tasks) { - const instance = instances[instanceId] - if (!instance || !(instance.app instanceof instance.Vue)) { - return new Error(`receiveTasks: instance ${instanceId} not found!`) +const jsHandlers = { + fireEvent: (id, ...args) => { + return fireEvent(instances[id], ...args) + }, + callback: (id, ...args) => { + return callback(instances[id], ...args) } - const { callbacks, document } = instance - tasks.forEach(task => { - // `fireEvent` case: find the event target and fire. - if (task.method === 'fireEvent') { - const [nodeId, type, e, domChanges] = task.args - const el = document.getRef(nodeId) - document.fireEvent(el, type, e, domChanges) - } - // `callback` case: find the callback by id and call it. - if (task.method === 'callback') { - const [callbackId, data, ifKeepAlive] = task.args - const callback = callbacks[callbackId] - if (typeof callback === 'function') { - callback(data) - // Remove the callback from `callbacks` if it won't called again. - if (typeof ifKeepAlive === 'undefined' || ifKeepAlive === false) { - callbacks[callbackId] = undefined - } +} + +function fireEvent (instance, nodeId, type, e, domChanges) { + const el = instance.document.getRef(nodeId) + if (el) { + return instance.document.fireEvent(el, type, e, domChanges) + } + return new Error(`invalid element reference "${nodeId}"`) +} + +function callback (instance, callbackId, data, ifKeepAlive) { + const result = instance.document.taskCenter.callback(callbackId, data, ifKeepAlive) + instance.document.taskCenter.send('dom', { action: 'updateFinish' }, []) + return result +} + +/** + * Accept calls from native (event or callback). + * + * @param {string} id + * @param {array} tasks list with `method` and `args` + */ +export function receiveTasks (id, tasks) { + const instance = instances[id] + if (instance && Array.isArray(tasks)) { + const results = [] + tasks.forEach((task) => { + const handler = jsHandlers[task.method] + const args = [...task.args] + /* istanbul ignore else */ + if (typeof handler === 'function') { + args.unshift(id) + results.push(handler(...args)) } - } - }) - // Finally `updateFinish` signal needed. - renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'updateFinish', args: [] }], -1) + }) + return results + } + return new Error(`invalid instance id "${id}" or tasks`) } /** @@ -288,9 +295,7 @@ function createVueModuleInstance (instanceId, moduleGetter) { * Generate native module getter. Each native module has several * methods to call. And all the behaviors is instance-related. So * this getter will return a set of methods which additionally - * send current instance id to native when called. Also the args - * will be normalized into "safe" value. For example function arg - * will be converted into a callback id. + * send current instance id to native when called. * @param {string} instanceId * @return {function} */ @@ -300,12 +305,20 @@ function genModuleGetter (instanceId) { const nativeModule = modules[name] || [] const output = {} for (const methodName in nativeModule) { - output[methodName] = (...args) => { - const finalArgs = args.map(value => { - return normalize(value, instance) - }) - renderer.sendTasks(instanceId + '', [{ module: name, method: methodName, args: finalArgs }], -1) - } + Object.defineProperty(output, methodName, { + enumerable: true, + configurable: true, + get: function proxyGetter () { + return (...args) => { + return instance.document.taskCenter.send('module', { module: name, method: methodName }, args) + } + }, + set: function proxySetter (val) { + if (typeof val === 'function') { + return instance.document.taskCenter.send('module', { module: name, method: methodName }, [val]) + } + } + }) } return output } @@ -328,15 +341,17 @@ function getInstanceTimer (instanceId, moduleGetter) { const handler = function () { args[0](...args.slice(2)) } + timer.setTimeout(handler, args[1]) - return instance.callbackId.toString() + return instance.document.taskCenter.callbackManager.lastCallbackId.toString() }, setInterval: (...args) => { const handler = function () { args[0](...args.slice(2)) } + timer.setInterval(handler, args[1]) - return instance.callbackId.toString() + return instance.document.taskCenter.callbackManager.lastCallbackId.toString() }, clearTimeout: (n) => { timer.clearTimeout(n) @@ -368,50 +383,53 @@ function callFunction (globalObjects, body) { } /** - * Convert all type of values into "safe" format to send to native. - * 1. A `function` will be converted into callback id. - * 2. An `Element` object will be converted into `ref`. - * The `instance` param is used to generate callback id and store - * function if necessary. - * @param {any} v - * @param {object} instance - * @return {any} + * Call a new function generated on the V8 native side. + * + * This function helps speed up bundle compiling. Normally, the V8 + * engine needs to download, parse, and compile a bundle on every + * visit. If 'compileBundle()' is available on native side, + * the downloding, parsing, and compiling steps would be skipped. + * @param {object} globalObjects + * @param {string} body + * @return {boolean} */ -function normalize (v, instance) { - const type = typof(v) - - switch (type) { - case 'undefined': - case 'null': - return '' - case 'regexp': - return v.toString() - case 'date': - return v.toISOString() - case 'number': - case 'string': - case 'boolean': - case 'array': - case 'object': - if (v instanceof renderer.Element) { - return v.ref - } - return v - case 'function': - instance.callbacks[++instance.callbackId] = v - return instance.callbackId.toString() - default: - return JSON.stringify(v) +function callFunctionNative (globalObjects, body) { + if (typeof renderer.compileBundle !== 'function') { + return false } -} -/** - * Get the exact type of an object by `toString()`. For example call - * `toString()` on an array will be returned `[object Array]`. - * @param {any} v - * @return {string} - */ -function typof (v) { - const s = Object.prototype.toString.call(v) - return s.substring(8, s.length - 1).toLowerCase() + let fn = void 0 + let isNativeCompileOk = false + let script = '(function (' + const globalKeys = [] + const globalValues = [] + for (const key in globalObjects) { + globalKeys.push(key) + globalValues.push(globalObjects[key]) + } + for (let i = 0; i < globalKeys.length - 1; ++i) { + script += globalKeys[i] + script += ',' + } + script += globalKeys[globalKeys.length - 1] + script += ') {' + script += body + script += '} )' + + try { + const weex = globalObjects.weex || {} + const config = weex.config || {} + fn = renderer.compileBundle(script, + config.bundleUrl, + config.bundleDigest, + config.codeCachePath) + if (fn && typeof fn === 'function') { + fn(...globalValues) + isNativeCompileOk = true + } + } catch (e) { + console.error(e) + } + + return isNativeCompileOk } diff --git a/test/weex/runtime/framework.spec.js b/test/weex/runtime/framework.spec.js index 50e707cc6..8b8242664 100644 --- a/test/weex/runtime/framework.spec.js +++ b/test/weex/runtime/framework.spec.js @@ -248,8 +248,8 @@ describe('framework APIs', () => { { method: 'fireEvent', args: [textRef, 'click'] } ]) expect(result instanceof Error).toBe(true) - expect(result).toMatch(/receiveTasks/) - expect(result).toMatch(/not found/) + expect(result).toMatch(/invalid\sinstance\sid/) + expect(result).toMatch(instance.id) done() }) }) @@ -341,8 +341,8 @@ describe('framework APIs', () => { { method: 'callback', args: [callbackId] } ]) expect(result instanceof Error).toBe(true) - expect(result).toMatch(/receiveTasks/) - expect(result).toMatch(/not found/) + expect(result).toMatch(/invalid\sinstance\sid/) + expect(result).toMatch(instance.id) done() }) })