diff --git a/build/config.js b/build/config.js index 20fefa8506c..5749409daac 100644 --- a/build/config.js +++ b/build/config.js @@ -17,7 +17,7 @@ const banner = const weexFactoryPlugin = { intro () { - return 'module.exports = function weexFactory (exports, renderer) {' + return 'module.exports = function weexFactory (exports, document) {' }, outro () { return '}' diff --git a/package.json b/package.json index bd1247a848f..04546c1e0ba 100644 --- a/package.json +++ b/package.json @@ -122,8 +122,7 @@ "typescript": "^2.3.4", "uglify-js": "^3.0.15", "webpack": "^2.6.1", - "weex-js-runtime": "^0.20.5", - "weex-vdom-tester": "^0.2.0" + "weex-js-runtime": "^0.22.0" }, "config": { "commitizen": { diff --git a/src/platforms/weex/entry-framework.js b/src/platforms/weex/entry-framework.js index 90f5f0d81be..9b7c920cfbd 100644 --- a/src/platforms/weex/entry-framework.js +++ b/src/platforms/weex/entry-framework.js @@ -1,41 +1,19 @@ -import TextNode from 'weex/runtime/text-node' - // this will be preserved during build const VueFactory = require('./factory') const instances = {} -const modules = {} -const components = {} - -const renderer = { - TextNode, - instances, - modules, - components -} /** - * Prepare framework config, basically about the virtual-DOM and JS bridge. - * @param {object} cfg + * Prepare framework config. + * Nothing need to do actually, just an interface provided to weex runtime. */ -export function init (cfg) { - renderer.Document = cfg.Document - renderer.Element = cfg.Element - renderer.Comment = cfg.Comment - renderer.compileBundle = cfg.compileBundle -} +export function init () {} /** * 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.compileBundle } /** @@ -63,47 +41,31 @@ export function createInstance ( data, env = {} ) { - // Virtual-DOM object. - const document = new renderer.Document(instanceId, config.bundleUrl) - + const weex = env.weex + const document = weex.document const instance = instances[instanceId] = { instanceId, config, data, document } - // 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, - supports, - requireModule: moduleGetter - } - Object.freeze(weexInstanceVar) + const timerAPIs = getInstanceTimer(instanceId, weex.requireModule) // Each instance has a independent `Vue` module instance - const Vue = instance.Vue = createVueModuleInstance(instanceId, moduleGetter) + 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: weexInstanceVar + weex }, timerAPIs, env.services) appCode = `(function(global){ \n${appCode}\n })(Object.create(this))` - if (!callFunctionNative(instanceVars, appCode)) { - // If failed to compile functionBody on native side, - // fallback to 'callFunction()'. - callFunction(instanceVars, appCode) - } + callFunction(instanceVars, appCode) // Send `createFinish` signal to native. - instance.document.taskCenter.send('dom', { action: 'createFinish' }, []) + document.taskCenter.send('dom', { action: 'createFinish' }, []) return instance } @@ -118,6 +80,8 @@ export function destroyInstance (instanceId) { if (instance && instance.app instanceof instance.Vue) { instance.document.destroy() instance.app.$destroy() + delete instance.document + delete instance.app } delete instances[instanceId] } @@ -200,91 +164,12 @@ export function receiveTasks (id, tasks) { return new Error(`invalid instance id "${id}" or tasks`) } -/** - * 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 - } - }) - } -} - -/** - * Check whether the module or the method has been registered. - * @param {String} module name - * @param {String} method name (optional) - */ -export function isRegisteredModule (name, method) { - if (typeof method === 'string') { - return !!(modules[name] && modules[name][method]) - } - return !!modules[name] -} - -/** - * Register native components information. - * @param {array} newComponents - */ -export function registerComponents (newComponents) { - if (Array.isArray(newComponents)) { - newComponents.forEach(component => { - if (!component) { - return - } - if (typeof component === 'string') { - components[component] = true - } else if (typeof component === 'object' && typeof component.type === 'string') { - components[component.type] = component - } - }) - } -} - -/** - * Check whether the component has been registered. - * @param {String} component name - */ -export function isRegisteredComponent (name) { - return !!components[name] -} - -/** - * Detects whether Weex supports specific features. - * @param {String} condition - */ -export function supports (condition) { - if (typeof condition !== 'string') return null - - const res = condition.match(/^@(\w+)\/(\w+)(\.(\w+))?$/i) - if (res) { - const type = res[1] - const name = res[2] - const method = res[4] - switch (type) { - case 'module': return isRegisteredModule(name, method) - case 'component': return isRegisteredComponent(name) - } - } - - return null -} - /** * Create a fresh instance of Vue for each Weex instance. */ -function createVueModuleInstance (instanceId, moduleGetter) { +function createVueModuleInstance (instanceId, weex) { const exports = {} - VueFactory(exports, renderer) + VueFactory(exports, weex.document) const Vue = exports.Vue const instance = instances[instanceId] @@ -295,7 +180,7 @@ function createVueModuleInstance (instanceId, moduleGetter) { const isReservedTag = Vue.config.isReservedTag || (() => false) const isRuntimeComponent = Vue.config.isRuntimeComponent || (() => false) Vue.config.isReservedTag = name => { - return (!isRuntimeComponent(name) && components[name]) || + return (!isRuntimeComponent(name) && weex.supports(`@component/${name}`)) || isReservedTag(name) || weexRegex.test(name) } @@ -307,7 +192,7 @@ function createVueModuleInstance (instanceId, moduleGetter) { // expose weex native module getter on subVue prototype so that // vdom runtime modules can access native modules via vnode.context - Vue.prototype.$requireWeexModule = moduleGetter + Vue.prototype.$requireWeexModule = weex.requireModule // Hack `Vue` behavior to handle instance information and data // before root component created. @@ -340,39 +225,6 @@ function createVueModuleInstance (instanceId, moduleGetter) { return Vue } -/** - * 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. - * @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) { - 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 - } -} - /** * Generate HTML5 Timer APIs. An important point is that the callback * will be converted into callback id when sent to native. So the @@ -430,55 +282,3 @@ function callFunction (globalObjects, body) { const result = new Function(...globalKeys) return result(...globalValues) } - -/** - * 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 downloading, parsing, and compiling steps would be skipped. - * @param {object} globalObjects - * @param {string} body - * @return {boolean} - */ -function callFunctionNative (globalObjects, body) { - if (typeof renderer.compileBundle !== 'function') { - return false - } - - 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/src/platforms/weex/runtime/node-ops.js b/src/platforms/weex/runtime/node-ops.js index e71e49f62e7..303c81945ee 100755 --- a/src/platforms/weex/runtime/node-ops.js +++ b/src/platforms/weex/runtime/node-ops.js @@ -1,22 +1,24 @@ -/* globals renderer */ -// renderer is injected by weex factory wrapper +/* globals document */ +// document is injected by weex factory wrapper + +import TextNode from 'weex/runtime/text-node' export const namespaceMap = {} export function createElement (tagName) { - return new renderer.Element(tagName) + return document.createElement(tagName) } export function createElementNS (namespace, tagName) { - return new renderer.Element(namespace + ':' + tagName) + return document.createElement(namespace + ':' + tagName) } export function createTextNode (text) { - return new renderer.TextNode(text) + return new TextNode(text) } export function createComment (text) { - return new renderer.Comment(text) + return document.createComment(text) } export function insertBefore (node, target, before) { diff --git a/src/platforms/weex/util/index.js b/src/platforms/weex/util/index.js index b74949cdd01..05da30e985e 100755 --- a/src/platforms/weex/util/index.js +++ b/src/platforms/weex/util/index.js @@ -1,4 +1,4 @@ -/* globals renderer */ +/* globals document */ import { makeMap } from 'shared/util' @@ -34,8 +34,8 @@ export function getTagNamespace () { /* console.log('getTagNamespace') */ } export function isUnknownElement () { /* console.log('isUnknownElement') */ } export function query (el, document) { - // renderer is injected by weex factory wrapper - const placeholder = new renderer.Comment('root') + // document is injected by weex factory wrapper + const placeholder = document.createComment('root') placeholder.hasAttribute = placeholder.removeAttribute = function () {} // hack for patch document.documentElement.appendChild(placeholder) return placeholder diff --git a/test/weex/helpers/index.js b/test/weex/helpers/index.js index 2375cbc61ae..ca4b3436b60 100644 --- a/test/weex/helpers/index.js +++ b/test/weex/helpers/index.js @@ -1,13 +1,8 @@ -// suppress logs from vdom-tester -const domModule = require('weex-vdom-tester/lib/modules/dom') -domModule.updateFinish = domModule.createFinish = domModule.refreshFinish = () => {} - import * as Vue from '../../../packages/weex-vue-framework' import { compile } from '../../../packages/weex-template-compiler' -import { Runtime, Instance } from 'weex-vdom-tester' -import { init, config } from 'weex-js-runtime' +import WeexRuntime from 'weex-js-runtime' -init() +console.debug = () => {} // http://stackoverflow.com/a/35478115 const matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g @@ -27,32 +22,66 @@ function parseStatic (fns) { return '[' + fns.map(fn => `function () { ${fn} }`).join(',') + ']' } -export function prepareRuntime () { - let sendTasksHandler = function () {} - config.sendTasks = config.Document.handler = function () { - sendTasksHandler.apply(null, arguments) - } - Vue.init(config) - const runtime = new Runtime(Vue) - sendTasksHandler = function () { - runtime.target.callNative.apply(runtime.target, arguments) +function isObject (object) { + return object !== null && typeof object === 'object' +} + +function isEmptyObject (object) { + return isObject(object) && Object.keys(object).length < 1 +} + +function omitUseless (object) { + if (isObject(object)) { + delete object.ref + for (const key in object) { + if (isEmptyObject(object[key]) || object[key] === undefined) { + delete object[key] + } + omitUseless(object[key]) + } } - return runtime + return object } -export function resetRuntime () { - delete config.Document.handler - Vue.reset() +export function getRoot (instance) { + return omitUseless(instance.document.body.toJSON()) } -export function createInstance (runtime, code) { - const instance = new Instance(runtime) - if (code) { - instance.$create(code) +export function fireEvent (instance, ref, type, event = {}) { + const el = instance.document.getRef(ref) + if (el) { + instance.document.fireEvent(el, type, event = {}) } +} + +export function createInstance (id, code, ...args) { + WeexRuntime.config.frameworks = { Vue } + const context = WeexRuntime.init(WeexRuntime.config) + context.registerModules({ + timer: ['setTimeout', 'setInterval'] + }) + const instance = context.createInstance(id, `// { "framework": "Vue" }\n${code}`, ...args) + instance.$refresh = (data) => context.refreshInstance(id, data) + instance.$destroy = () => context.destroyInstance(id) return instance } +export function compileAndExecute (template, additional = '') { + return new Promise(resolve => { + const id = String(Date.now() * Math.random()) + const { render, staticRenderFns } = compile(template) + const instance = createInstance(id, ` + new Vue({ + el: '#whatever', + render: function () { ${render} }, + staticRenderFns: ${parseStatic(staticRenderFns)}, + ${additional} + }) + `) + setTimeout(() => resolve(instance), 10) + }) +} + export function syncPromise (arr) { let p = Promise.resolve() arr.forEach(item => { @@ -65,7 +94,7 @@ export function checkRefresh (instance, data, checker) { return () => new Promise(res => { instance.$refresh(data) setTimeout(() => { - checker(instance.getRealRoot()) + checker(getRoot(instance)) res() }) }) diff --git a/test/weex/runtime/attrs.spec.js b/test/weex/runtime/attrs.spec.js index f87537ca435..037067ac1a2 100644 --- a/test/weex/runtime/attrs.spec.js +++ b/test/weex/runtime/attrs.spec.js @@ -1,75 +1,48 @@ -import { - compileAndStringify, - prepareRuntime, - resetRuntime, - createInstance -} from '../helpers/index' +import { getRoot, fireEvent, compileAndExecute } from '../helpers/index' describe('generate attribute', () => { - let runtime - - beforeAll(() => { - runtime = prepareRuntime() - }) - - afterAll(() => { - resetRuntime() - runtime = null - }) - - it('should be generated', () => { - const { render, staticRenderFns } = compileAndStringify(` + it('should be generated', (done) => { + compileAndExecute(`