UNPKG

vue

Version:

Reactive, component-oriented view layer for modern web interfaces.

423 lines (385 loc) 12.1 kB
import Vue from 'weex/runtime/index' import renderer from 'weex/runtime/config' Vue.weexVersion = '__WEEX_VERSION__' export { Vue } const { instances, modules, components } = renderer let activeId const oriIsReservedTag = (Vue && Vue.config && typeof Vue.config.isReservedTag === 'function') ? Vue.config.isReservedTag : function () {} /** * Prepare framework config, basically about the virtual-DOM and JS bridge. * @param {object} cfg */ export function init (cfg) { renderer.Document = cfg.Document renderer.Element = cfg.Element renderer.Comment = cfg.Comment renderer.sendTasks = cfg.sendTasks } /** * Reset framework config and clear all registrations. */ export function reset () { clear(instances) clear(modules) clear(components) delete renderer.Document delete renderer.Element delete renderer.Comment delete renderer.sendTasks Vue.config.isReservedTag = oriIsReservedTag } /** * Delete all keys of an object. * @param {object} obj */ function clear (obj) { for (const key in obj) { delete obj[key] } } /** * 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 = {} ) { // Set active instance id and put some information into `instances` map. activeId = instanceId // 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 instances[instanceId] = { instanceId, config, data, document, callbacks, callbackId } // Prepare native module getter and HTML5 Timer APIs. const moduleGetter = genModuleGetter(instanceId) const timerAPIs = getInstanceTimer(instanceId, moduleGetter) // Prepare `weex` instance variable. const weexInstanceVar = { config, document, requireModule: moduleGetter } Object.freeze(weexInstanceVar) // Each instance has a independent `Vue` variable and it should have // all top-level public APIs. const subVue = Vue.extend({}) // ensure plain-object components are extended from the subVue subVue.options._base = subVue // expose global utility ;['util', 'set', 'delete', 'nextTick', 'version', 'weexVersion', 'config'].forEach(name => { subVue[name] = Vue[name] }) // 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: subVue, weex: weexInstanceVar, __weex_require_module__: weexInstanceVar.requireModule // deprecated }, timerAPIs) callFunction(instanceVars, appCode) // Send `createFinish` signal to native. renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'createFinish', args: [] }], -1) } /** * 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] || {} if (instance.app instanceof Vue) { instance.app.$destroy() } delete instances[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] || {} if (!(instance.app instanceof Vue)) { return new Error(`refreshInstance: instance ${instanceId} not found!`) } for (const key in data) { Vue.set(instance.app, key, data[key]) } // Finally `refreshFinish` signal needed. renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'refreshFinish', args: [] }], -1) } /** * Get the JSON object of the root element. * @param {string} instanceId */ export function getRoot (instanceId) { const instance = instances[instanceId] || {} if (!(instance.app instanceof Vue)) { return new Error(`getRoot: instance ${instanceId} not found!`) } 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.app instanceof Vue)) { return new Error(`receiveTasks: instance ${instanceId} not found!`) } 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 } } } }) // Finally `updateFinish` signal needed. renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'updateFinish', args: [] }], -1) } /** * Register native modules information. * @param {object} newModules */ export function registerModules (newModules) { for (const name in newModules) { if (!modules[name]) { modules[name] = {} } newModules[name].forEach(method => { if (typeof method === 'string') { modules[name][method] = true } else { modules[name][method.name] = method.args } }) } } /** * Register native components information. * @param {array} newComponents */ export function registerComponents (newComponents) { const config = Vue.config const newConfig = {} if (Array.isArray(newComponents)) { newComponents.forEach(component => { if (!component) { return } if (typeof component === 'string') { components[component] = true newConfig[component] = true } else if (typeof component === 'object' && typeof component.type === 'string') { components[component.type] = component newConfig[component.type] = true } }) const oldIsReservedTag = config.isReservedTag config.isReservedTag = name => { return newConfig[name] || oldIsReservedTag(name) } } } // Hack `Vue` behavior to handle instance information and data // before root component created. Vue.mixin({ beforeCreate () { const options = this.$options const parentOptions = (options.parent && options.parent.$options) || {} // root component (vm) if (options.el) { // record instance info const instance = instances[activeId] || {} this.$instanceId = activeId options.instanceId = activeId this.$document = instance.document // set external data of instance const dataOption = options.data const internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {} options.data = Object.assign(internalData, instance.data) // record instance by id instance.app = this activeId = undefined } else { this.$instanceId = options.instanceId = parentOptions.instanceId } } }) /** * @deprecated Just instance variable `weex.config` * Get instance config. * @return {object} */ Vue.prototype.$getConfig = function () { const instance = instances[this.$instanceId] || {} if (instance.app instanceof Vue) { return instance.config } } /** * Generate native module getter. Each native module has several * methods to call. And all the hebaviors 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. * @param {string} instanceId * @return {function} */ function genModuleGetter (instanceId) { const instance = instances[instanceId] return function (name) { 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) } } return output } } /** * 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 callabck happened after * an instance destroyed. * @param {[type]} instanceId [description] * @param {[type]} moduleGetter [description] * @return {[type]} [description] */ function getInstanceTimer (instanceId, moduleGetter) { const instance = instances[instanceId] const timer = moduleGetter('timer') const timerAPIs = { setTimeout: (...args) => { const handler = function () { args[0](...args.slice(2)) } timer.setTimeout(handler, args[1]) return instance.callbackId.toString() }, setInterval: (...args) => { const handler = function () { args[0](...args.slice(2)) } timer.setInterval(handler, args[1]) return instance.callbackId.toString() }, clearTimeout: (n) => { timer.clearTimeout(n) }, clearInterval: (n) => { timer.clearInterval(n) } } 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) } /** * 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} */ 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) } } /** * 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() }