You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

464 lines
13 KiB

'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var latestNodeId = 1;
function TextNode (text) {
this.instanceId = '';
this.nodeId = latestNodeId++;
this.parentNode = null;
this.nodeType = 3;
this.text = text;
}
// this will be preserved during build
var VueFactory = require('./factory');
var instances = {};
var modules = {};
var components = {};
var renderer = {
TextNode: TextNode,
instances: instances,
modules: modules,
components: components
};
/**
* Prepare framework config, basically about the virtual-DOM and JS bridge.
* @param {object} cfg
*/
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.
*/
function reset () {
clear(instances);
clear(modules);
clear(components);
delete renderer.Document;
delete renderer.Element;
delete renderer.Comment;
delete renderer.sendTasks;
}
/**
* Delete all keys of an object.
* @param {object} obj
*/
function clear (obj) {
for (var 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 }
*/
function createInstance (
instanceId,
appCode,
config,
data,
env
) {
if ( appCode === void 0 ) appCode = '';
if ( config === void 0 ) config = {};
if ( env === void 0 ) env = {};
// Virtual-DOM object.
var 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.
var callbacks = [];
// The latest callback id, incremental.
var callbackId = 1;
var instance = instances[instanceId] = {
instanceId: instanceId, config: config, data: data,
document: document, callbacks: callbacks, callbackId: callbackId
};
// Prepare native module getter and HTML5 Timer APIs.
var moduleGetter = genModuleGetter(instanceId);
var timerAPIs = getInstanceTimer(instanceId, moduleGetter);
// Prepare `weex` instance variable.
var weexInstanceVar = {
config: config,
document: document,
requireModule: moduleGetter
};
Object.freeze(weexInstanceVar);
// Each instance has a independent `Vue` mdoule instance
var Vue = instance.Vue = createVueModuleInstance(instanceId, moduleGetter);
// The function which create a closure the JS Bundle will run in.
// It will declare some instance variables like `Vue`, HTML5 Timer APIs etc.
var instanceVars = Object.assign({
Vue: Vue,
weex: weexInstanceVar,
// deprecated
__weex_require_module__: weexInstanceVar.requireModule // eslint-disable-line
}, 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
*/
function destroyInstance (instanceId) {
var instance = instances[instanceId];
if (instance && instance.app instanceof instance.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
*/
function refreshInstance (instanceId, data) {
var instance = instances[instanceId];
if (!instance || !(instance.app instanceof instance.Vue)) {
return new Error(("refreshInstance: instance " + instanceId + " not found!"))
}
for (var key in data) {
instance.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
*/
function getRoot (instanceId) {
var instance = instances[instanceId];
if (!instance || !(instance.app instanceof instance.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
*/
function receiveTasks (instanceId, tasks) {
var instance = instances[instanceId];
if (!instance || !(instance.app instanceof instance.Vue)) {
return new Error(("receiveTasks: instance " + instanceId + " not found!"))
}
var callbacks = instance.callbacks;
var document = instance.document;
tasks.forEach(function (task) {
// `fireEvent` case: find the event target and fire.
if (task.method === 'fireEvent') {
var ref = task.args;
var nodeId = ref[0];
var type = ref[1];
var e = ref[2];
var domChanges = ref[3];
var el = document.getRef(nodeId);
document.fireEvent(el, type, e, domChanges);
}
// `callback` case: find the callback by id and call it.
if (task.method === 'callback') {
var ref$1 = task.args;
var callbackId = ref$1[0];
var data = ref$1[1];
var ifKeepAlive = ref$1[2];
var 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
*/
function registerModules (newModules) {
var loop = function ( name ) {
if (!modules[name]) {
modules[name] = {};
}
newModules[name].forEach(function (method) {
if (typeof method === 'string') {
modules[name][method] = true;
} else {
modules[name][method.name] = method.args;
}
});
};
for (var name in newModules) loop( name );
}
/**
* Register native components information.
* @param {array} newComponents
*/
function registerComponents (newComponents) {
if (Array.isArray(newComponents)) {
newComponents.forEach(function (component) {
if (!component) {
return
}
if (typeof component === 'string') {
components[component] = true;
} else if (typeof component === 'object' && typeof component.type === 'string') {
components[component.type] = component;
}
});
}
}
/**
* Create a fresh instance of Vue for each Weex instance.
*/
function createVueModuleInstance (instanceId, moduleGetter) {
var exports = {};
VueFactory(exports, renderer);
var Vue = exports.Vue;
var instance = instances[instanceId];
// patch reserved tag detection to account for dynamically registered
// components
var isReservedTag = Vue.config.isReservedTag || (function () { return false; });
Vue.config.isReservedTag = function (name) {
return components[name] || isReservedTag(name)
};
// expose weex-specific info
Vue.prototype.$instanceId = instanceId;
Vue.prototype.$document = instance.document;
// expose weex native module getter on subVue prototype so that
// vdom runtime modules can access native modules via vnode.context
Vue.prototype.$requireWeexModule = moduleGetter;
// Hack `Vue` behavior to handle instance information and data
// before root component created.
Vue.mixin({
beforeCreate: function beforeCreate () {
var options = this.$options;
// root component (vm)
if (options.el) {
// set external data of instance
var dataOption = options.data;
var internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {};
options.data = Object.assign(internalData, instance.data);
// record instance by id
instance.app = this;
}
}
});
/**
* @deprecated Just instance variable `weex.config`
* Get instance config.
* @return {object}
*/
Vue.prototype.$getConfig = function () {
if (instance.app instanceof Vue) {
return instance.config
}
};
return Vue
}
/**
* 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) {
var instance = instances[instanceId];
return function (name) {
var nativeModule = modules[name] || [];
var output = {};
var loop = function ( methodName ) {
output[methodName] = function () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
var finalArgs = args.map(function (value) {
return normalize(value, instance)
});
renderer.sendTasks(instanceId + '', [{ module: name, method: methodName, args: finalArgs }], -1);
};
};
for (var methodName in nativeModule) loop( methodName );
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) {
var instance = instances[instanceId];
var timer = moduleGetter('timer');
var timerAPIs = {
setTimeout: function () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
var handler = function () {
args[0].apply(args, args.slice(2));
};
timer.setTimeout(handler, args[1]);
return instance.callbackId.toString()
},
setInterval: function () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
var handler = function () {
args[0].apply(args, args.slice(2));
};
timer.setInterval(handler, args[1]);
return instance.callbackId.toString()
},
clearTimeout: function (n) {
timer.clearTimeout(n);
},
clearInterval: function (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) {
var globalKeys = [];
var globalValues = [];
for (var key in globalObjects) {
globalKeys.push(key);
globalValues.push(globalObjects[key]);
}
globalKeys.push(body);
var result = new (Function.prototype.bind.apply( Function, [ null ].concat( globalKeys) ));
return result.apply(void 0, 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) {
var 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) {
var s = Object.prototype.toString.call(v);
return s.substring(8, s.length - 1).toLowerCase()
}
exports.init = init;
exports.reset = reset;
exports.createInstance = createInstance;
exports.destroyInstance = destroyInstance;
exports.refreshInstance = refreshInstance;
exports.getRoot = getRoot;
exports.receiveTasks = receiveTasks;
exports.registerModules = registerModules;
exports.registerComponents = registerComponents;