index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var latestNodeId = 1;
  4. function TextNode (text) {
  5. this.instanceId = '';
  6. this.nodeId = latestNodeId++;
  7. this.parentNode = null;
  8. this.nodeType = 3;
  9. this.text = text;
  10. }
  11. // this will be preserved during build
  12. var VueFactory = require('./factory');
  13. var instances = {};
  14. var modules = {};
  15. var components = {};
  16. var renderer = {
  17. TextNode: TextNode,
  18. instances: instances,
  19. modules: modules,
  20. components: components
  21. };
  22. /**
  23. * Prepare framework config, basically about the virtual-DOM and JS bridge.
  24. * @param {object} cfg
  25. */
  26. function init (cfg) {
  27. renderer.Document = cfg.Document;
  28. renderer.Element = cfg.Element;
  29. renderer.Comment = cfg.Comment;
  30. renderer.compileBundle = cfg.compileBundle;
  31. }
  32. /**
  33. * Reset framework config and clear all registrations.
  34. */
  35. function reset () {
  36. clear(instances);
  37. clear(modules);
  38. clear(components);
  39. delete renderer.Document;
  40. delete renderer.Element;
  41. delete renderer.Comment;
  42. delete renderer.compileBundle;
  43. }
  44. /**
  45. * Delete all keys of an object.
  46. * @param {object} obj
  47. */
  48. function clear (obj) {
  49. for (var key in obj) {
  50. delete obj[key];
  51. }
  52. }
  53. /**
  54. * Create an instance with id, code, config and external data.
  55. * @param {string} instanceId
  56. * @param {string} appCode
  57. * @param {object} config
  58. * @param {object} data
  59. * @param {object} env { info, config, services }
  60. */
  61. function createInstance (
  62. instanceId,
  63. appCode,
  64. config,
  65. data,
  66. env
  67. ) {
  68. if ( appCode === void 0 ) appCode = '';
  69. if ( config === void 0 ) config = {};
  70. if ( env === void 0 ) env = {};
  71. // Virtual-DOM object.
  72. var document = new renderer.Document(instanceId, config.bundleUrl);
  73. var instance = instances[instanceId] = {
  74. instanceId: instanceId, config: config, data: data,
  75. document: document
  76. };
  77. // Prepare native module getter and HTML5 Timer APIs.
  78. var moduleGetter = genModuleGetter(instanceId);
  79. var timerAPIs = getInstanceTimer(instanceId, moduleGetter);
  80. // Prepare `weex` instance variable.
  81. var weexInstanceVar = {
  82. config: config,
  83. document: document,
  84. supports: supports,
  85. requireModule: moduleGetter
  86. };
  87. Object.freeze(weexInstanceVar);
  88. // Each instance has a independent `Vue` module instance
  89. var Vue = instance.Vue = createVueModuleInstance(instanceId, moduleGetter);
  90. // The function which create a closure the JS Bundle will run in.
  91. // It will declare some instance variables like `Vue`, HTML5 Timer APIs etc.
  92. var instanceVars = Object.assign({
  93. Vue: Vue,
  94. weex: weexInstanceVar,
  95. // deprecated
  96. __weex_require_module__: weexInstanceVar.requireModule // eslint-disable-line
  97. }, timerAPIs, env.services);
  98. if (!callFunctionNative(instanceVars, appCode)) {
  99. // If failed to compile functionBody on native side,
  100. // fallback to 'callFunction()'.
  101. callFunction(instanceVars, appCode);
  102. }
  103. // Send `createFinish` signal to native.
  104. instance.document.taskCenter.send('dom', { action: 'createFinish' }, []);
  105. }
  106. /**
  107. * Destroy an instance with id. It will make sure all memory of
  108. * this instance released and no more leaks.
  109. * @param {string} instanceId
  110. */
  111. function destroyInstance (instanceId) {
  112. var instance = instances[instanceId];
  113. if (instance && instance.app instanceof instance.Vue) {
  114. instance.document.destroy();
  115. instance.app.$destroy();
  116. }
  117. delete instances[instanceId];
  118. }
  119. /**
  120. * Refresh an instance with id and new top-level component data.
  121. * It will use `Vue.set` on all keys of the new data. So it's better
  122. * define all possible meaningful keys when instance created.
  123. * @param {string} instanceId
  124. * @param {object} data
  125. */
  126. function refreshInstance (instanceId, data) {
  127. var instance = instances[instanceId];
  128. if (!instance || !(instance.app instanceof instance.Vue)) {
  129. return new Error(("refreshInstance: instance " + instanceId + " not found!"))
  130. }
  131. for (var key in data) {
  132. instance.Vue.set(instance.app, key, data[key]);
  133. }
  134. // Finally `refreshFinish` signal needed.
  135. instance.document.taskCenter.send('dom', { action: 'refreshFinish' }, []);
  136. }
  137. /**
  138. * Get the JSON object of the root element.
  139. * @param {string} instanceId
  140. */
  141. function getRoot (instanceId) {
  142. var instance = instances[instanceId];
  143. if (!instance || !(instance.app instanceof instance.Vue)) {
  144. return new Error(("getRoot: instance " + instanceId + " not found!"))
  145. }
  146. return instance.app.$el.toJSON()
  147. }
  148. var jsHandlers = {
  149. fireEvent: function (id) {
  150. var args = [], len = arguments.length - 1;
  151. while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
  152. return fireEvent.apply(void 0, [ instances[id] ].concat( args ))
  153. },
  154. callback: function (id) {
  155. var args = [], len = arguments.length - 1;
  156. while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
  157. return callback.apply(void 0, [ instances[id] ].concat( args ))
  158. }
  159. };
  160. function fireEvent (instance, nodeId, type, e, domChanges) {
  161. var el = instance.document.getRef(nodeId);
  162. if (el) {
  163. return instance.document.fireEvent(el, type, e, domChanges)
  164. }
  165. return new Error(("invalid element reference \"" + nodeId + "\""))
  166. }
  167. function callback (instance, callbackId, data, ifKeepAlive) {
  168. var result = instance.document.taskCenter.callback(callbackId, data, ifKeepAlive);
  169. instance.document.taskCenter.send('dom', { action: 'updateFinish' }, []);
  170. return result
  171. }
  172. /**
  173. * Accept calls from native (event or callback).
  174. *
  175. * @param {string} id
  176. * @param {array} tasks list with `method` and `args`
  177. */
  178. function receiveTasks (id, tasks) {
  179. var instance = instances[id];
  180. if (instance && Array.isArray(tasks)) {
  181. var results = [];
  182. tasks.forEach(function (task) {
  183. var handler = jsHandlers[task.method];
  184. var args = [].concat( task.args );
  185. /* istanbul ignore else */
  186. if (typeof handler === 'function') {
  187. args.unshift(id);
  188. results.push(handler.apply(void 0, args));
  189. }
  190. });
  191. return results
  192. }
  193. return new Error(("invalid instance id \"" + id + "\" or tasks"))
  194. }
  195. /**
  196. * Register native modules information.
  197. * @param {object} newModules
  198. */
  199. function registerModules (newModules) {
  200. var loop = function ( name ) {
  201. if (!modules[name]) {
  202. modules[name] = {};
  203. }
  204. newModules[name].forEach(function (method) {
  205. if (typeof method === 'string') {
  206. modules[name][method] = true;
  207. } else {
  208. modules[name][method.name] = method.args;
  209. }
  210. });
  211. };
  212. for (var name in newModules) loop( name );
  213. }
  214. /**
  215. * Check whether the module or the method has been registered.
  216. * @param {String} module name
  217. * @param {String} method name (optional)
  218. */
  219. function isRegisteredModule (name, method) {
  220. if (typeof method === 'string') {
  221. return !!(modules[name] && modules[name][method])
  222. }
  223. return !!modules[name]
  224. }
  225. /**
  226. * Register native components information.
  227. * @param {array} newComponents
  228. */
  229. function registerComponents (newComponents) {
  230. if (Array.isArray(newComponents)) {
  231. newComponents.forEach(function (component) {
  232. if (!component) {
  233. return
  234. }
  235. if (typeof component === 'string') {
  236. components[component] = true;
  237. } else if (typeof component === 'object' && typeof component.type === 'string') {
  238. components[component.type] = component;
  239. }
  240. });
  241. }
  242. }
  243. /**
  244. * Check whether the component has been registered.
  245. * @param {String} component name
  246. */
  247. function isRegisteredComponent (name) {
  248. return !!components[name]
  249. }
  250. /**
  251. * Detects whether Weex supports specific features.
  252. * @param {String} condition
  253. */
  254. function supports (condition) {
  255. if (typeof condition !== 'string') { return null }
  256. var res = condition.match(/^@(\w+)\/(\w+)(\.(\w+))?$/i);
  257. if (res) {
  258. var type = res[1];
  259. var name = res[2];
  260. var method = res[4];
  261. switch (type) {
  262. case 'module': return isRegisteredModule(name, method)
  263. case 'component': return isRegisteredComponent(name)
  264. }
  265. }
  266. return null
  267. }
  268. /**
  269. * Create a fresh instance of Vue for each Weex instance.
  270. */
  271. function createVueModuleInstance (instanceId, moduleGetter) {
  272. var exports = {};
  273. VueFactory(exports, renderer);
  274. var Vue = exports.Vue;
  275. var instance = instances[instanceId];
  276. // patch reserved tag detection to account for dynamically registered
  277. // components
  278. var isReservedTag = Vue.config.isReservedTag || (function () { return false; });
  279. Vue.config.isReservedTag = function (name) {
  280. return components[name] || isReservedTag(name)
  281. };
  282. // expose weex-specific info
  283. Vue.prototype.$instanceId = instanceId;
  284. Vue.prototype.$document = instance.document;
  285. // expose weex native module getter on subVue prototype so that
  286. // vdom runtime modules can access native modules via vnode.context
  287. Vue.prototype.$requireWeexModule = moduleGetter;
  288. // Hack `Vue` behavior to handle instance information and data
  289. // before root component created.
  290. Vue.mixin({
  291. beforeCreate: function beforeCreate () {
  292. var options = this.$options;
  293. // root component (vm)
  294. if (options.el) {
  295. // set external data of instance
  296. var dataOption = options.data;
  297. var internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {};
  298. options.data = Object.assign(internalData, instance.data);
  299. // record instance by id
  300. instance.app = this;
  301. }
  302. }
  303. });
  304. /**
  305. * @deprecated Just instance variable `weex.config`
  306. * Get instance config.
  307. * @return {object}
  308. */
  309. Vue.prototype.$getConfig = function () {
  310. if (instance.app instanceof Vue) {
  311. return instance.config
  312. }
  313. };
  314. return Vue
  315. }
  316. /**
  317. * Generate native module getter. Each native module has several
  318. * methods to call. And all the behaviors is instance-related. So
  319. * this getter will return a set of methods which additionally
  320. * send current instance id to native when called.
  321. * @param {string} instanceId
  322. * @return {function}
  323. */
  324. function genModuleGetter (instanceId) {
  325. var instance = instances[instanceId];
  326. return function (name) {
  327. var nativeModule = modules[name] || [];
  328. var output = {};
  329. var loop = function ( methodName ) {
  330. Object.defineProperty(output, methodName, {
  331. enumerable: true,
  332. configurable: true,
  333. get: function proxyGetter () {
  334. return function () {
  335. var args = [], len = arguments.length;
  336. while ( len-- ) args[ len ] = arguments[ len ];
  337. return instance.document.taskCenter.send('module', { module: name, method: methodName }, args)
  338. }
  339. },
  340. set: function proxySetter (val) {
  341. if (typeof val === 'function') {
  342. return instance.document.taskCenter.send('module', { module: name, method: methodName }, [val])
  343. }
  344. }
  345. });
  346. };
  347. for (var methodName in nativeModule) loop( methodName );
  348. return output
  349. }
  350. }
  351. /**
  352. * Generate HTML5 Timer APIs. An important point is that the callback
  353. * will be converted into callback id when sent to native. So the
  354. * framework can make sure no side effect of the callback happened after
  355. * an instance destroyed.
  356. * @param {[type]} instanceId [description]
  357. * @param {[type]} moduleGetter [description]
  358. * @return {[type]} [description]
  359. */
  360. function getInstanceTimer (instanceId, moduleGetter) {
  361. var instance = instances[instanceId];
  362. var timer = moduleGetter('timer');
  363. var timerAPIs = {
  364. setTimeout: function () {
  365. var args = [], len = arguments.length;
  366. while ( len-- ) args[ len ] = arguments[ len ];
  367. var handler = function () {
  368. args[0].apply(args, args.slice(2));
  369. };
  370. timer.setTimeout(handler, args[1]);
  371. return instance.document.taskCenter.callbackManager.lastCallbackId.toString()
  372. },
  373. setInterval: function () {
  374. var args = [], len = arguments.length;
  375. while ( len-- ) args[ len ] = arguments[ len ];
  376. var handler = function () {
  377. args[0].apply(args, args.slice(2));
  378. };
  379. timer.setInterval(handler, args[1]);
  380. return instance.document.taskCenter.callbackManager.lastCallbackId.toString()
  381. },
  382. clearTimeout: function (n) {
  383. timer.clearTimeout(n);
  384. },
  385. clearInterval: function (n) {
  386. timer.clearInterval(n);
  387. }
  388. };
  389. return timerAPIs
  390. }
  391. /**
  392. * Call a new function body with some global objects.
  393. * @param {object} globalObjects
  394. * @param {string} code
  395. * @return {any}
  396. */
  397. function callFunction (globalObjects, body) {
  398. var globalKeys = [];
  399. var globalValues = [];
  400. for (var key in globalObjects) {
  401. globalKeys.push(key);
  402. globalValues.push(globalObjects[key]);
  403. }
  404. globalKeys.push(body);
  405. var result = new (Function.prototype.bind.apply( Function, [ null ].concat( globalKeys) ));
  406. return result.apply(void 0, globalValues)
  407. }
  408. /**
  409. * Call a new function generated on the V8 native side.
  410. *
  411. * This function helps speed up bundle compiling. Normally, the V8
  412. * engine needs to download, parse, and compile a bundle on every
  413. * visit. If 'compileBundle()' is available on native side,
  414. * the downloding, parsing, and compiling steps would be skipped.
  415. * @param {object} globalObjects
  416. * @param {string} body
  417. * @return {boolean}
  418. */
  419. function callFunctionNative (globalObjects, body) {
  420. if (typeof renderer.compileBundle !== 'function') {
  421. return false
  422. }
  423. var fn = void 0;
  424. var isNativeCompileOk = false;
  425. var script = '(function (';
  426. var globalKeys = [];
  427. var globalValues = [];
  428. for (var key in globalObjects) {
  429. globalKeys.push(key);
  430. globalValues.push(globalObjects[key]);
  431. }
  432. for (var i = 0; i < globalKeys.length - 1; ++i) {
  433. script += globalKeys[i];
  434. script += ',';
  435. }
  436. script += globalKeys[globalKeys.length - 1];
  437. script += ') {';
  438. script += body;
  439. script += '} )';
  440. try {
  441. var weex = globalObjects.weex || {};
  442. var config = weex.config || {};
  443. fn = renderer.compileBundle(script,
  444. config.bundleUrl,
  445. config.bundleDigest,
  446. config.codeCachePath);
  447. if (fn && typeof fn === 'function') {
  448. fn.apply(void 0, globalValues);
  449. isNativeCompileOk = true;
  450. }
  451. } catch (e) {
  452. console.error(e);
  453. }
  454. return isNativeCompileOk
  455. }
  456. exports.init = init;
  457. exports.reset = reset;
  458. exports.createInstance = createInstance;
  459. exports.destroyInstance = destroyInstance;
  460. exports.refreshInstance = refreshInstance;
  461. exports.getRoot = getRoot;
  462. exports.receiveTasks = receiveTasks;
  463. exports.registerModules = registerModules;
  464. exports.isRegisteredModule = isRegisteredModule;
  465. exports.registerComponents = registerComponents;
  466. exports.isRegisteredComponent = isRegisteredComponent;
  467. exports.supports = supports;