提交 6b4164fe 编写于 作者: fxy060608's avatar fxy060608

refactor(v3)

上级 04d653a1
......@@ -252,6 +252,8 @@ var serviceContext = (function () {
return hasOwnProperty.call(obj, key)
}
function noop () {}
function toRawType (val) {
return _toString.call(val).slice(8, -1)
}
......@@ -3591,7 +3593,10 @@ var serviceContext = (function () {
const V_FOR = 'f';
const V_IF = 'i';
const V_ELSE_IF = 'e';
const V_SHOW = 'v-show';
const V_SHOW = 'v-show';
const B_CLASS = 'c';
const B_STYLE = 's';
const callbacks = {};
const WEB_INVOKE_APPSERVICE = 'WEB_INVOKE_APPSERVICE';
......@@ -6350,8 +6355,11 @@ var serviceContext = (function () {
const UPDATED_DATA = 6;
const PAGE_CREATED = 10;
const UI_EVENT = 20;
const VD_SYNC = 'vdSync';
const WEBVIEW_READY = 'webviewReady';
const WEBVIEW_UI_EVENT = 'webviewUIEvent';
const VD_SYNC_CALLBACK = 'vdSyncCallback';
const pageFactory = Object.create(null);
......@@ -6474,7 +6482,7 @@ var serviceContext = (function () {
const pageId = webview.id;
// 通知页面已开始创建
UniServiceJSBridge.publishHandler('vdSync', {
UniServiceJSBridge.publishHandler(VD_SYNC, {
data: [
[PAGE_CREATE, [pageId, route]]
],
......@@ -6484,7 +6492,9 @@ var serviceContext = (function () {
}, [pageId]);
try {
createPage(route, pageId, query, pageInstance).$mount();
} catch (e) {}
} catch (e) {
console.error(e);
}
}
}
......@@ -8740,31 +8750,37 @@ var serviceContext = (function () {
}
}
const webviewUIEvents = Object.create(null);
const vdSyncHandlers = Object.create(null);
function registerWebviewUIEvent (pageId, callback) {
(webviewUIEvents[pageId] || (webviewUIEvents[pageId] = [])).push(callback);
function registerVdSync (pageId, callback) {
(vdSyncHandlers[pageId] || (vdSyncHandlers[pageId] = [])).push(callback);
}
function removeWebviewUIEvent (pageId) {
delete webviewUIEvents[pageId];
function removeVdSync (pageId) {
delete vdSyncHandlers[pageId];
}
function onWebviewUIEvent ({
function onVdSync ({
data,
options
}, pageId) {
const {
cid,
nid
} = options;
const handlers = webviewUIEvents[pageId];
const handlers = vdSyncHandlers[pageId];
if (Array.isArray(handlers)) {
handlers.forEach(handler => {
handler(cid, nid, data);
handler(data);
});
} else {
console.error(`events[${pageId}] not found`);
console.error(`vdSync[${pageId}] not found`);
}
}
const vdSyncCallbacks = []; // 数据同步 callback
function onVdSyncCallback () {
const copies = vdSyncCallbacks.slice(0);
vdSyncCallbacks.length = 0;
for (let i = 0; i < copies.length; i++) {
copies[i]();
}
}
......@@ -8779,7 +8795,9 @@ var serviceContext = (function () {
});
// TODO 检测目标 preloadWebview 是否已准备好,因为 preloadWebview 准备好时,此处代码还没执行
subscribe(WEBVIEW_READY, onWebviewReady);
subscribe(WEBVIEW_UI_EVENT, onWebviewUIEvent);
subscribe(VD_SYNC, onVdSync);
subscribe(VD_SYNC_CALLBACK, onVdSyncCallback);
}
let appCtx;
......@@ -8975,109 +8993,99 @@ var serviceContext = (function () {
};
}
function noop () {}
const handleVdData = {
[UI_EVENT]: function onUIEvent(vdBatchEvent, vd) {
vdBatchEvent.forEach(([cid, nid, event]) => {
console.log(`[EVENT]`, cid, nid, event);
event.preventDefault = noop;
event.stopPropagation = noop;
const target = vd.elements.find(target => target.cid === cid && target.nid === nid);
if (!target) {
return console.error(`event handler[${cid}][${nid}] not found`)
}
target.dispatchEvent(event.type, event);
});
}
};
const callbacks$a = []; // 数据同步 callback
function onVdSync$1(vdBatchData, vd) {
vdBatchData.forEach(([type, vdData]) => {
handleVdData[type](vdData, vd);
});
}
class VDomSync {
constructor (pageId, pagePath) {
constructor(pageId, pagePath) {
this.pageId = pageId;
this.pagePath = pagePath;
this.batchData = [];
this.vms = Object.create(null);
this.initialized = false;
// 事件
this.handlers = Object.create(null);
this.elements = []; // 目前仅存储事件 element
this._init();
}
_init () {
UniServiceJSBridge.subscribe(VD_SYNC_CALLBACK, () => {
const copies = callbacks$a.slice(0);
callbacks$a.length = 0;
for (let i = 0; i < copies.length; i++) {
copies[i]();
}
});
registerWebviewUIEvent(this.pageId, (cid, nid, event) => {
console.log(`[EVENT]`, cid, nid, event);
event.preventDefault = noop;
event.stopPropagation = noop;
if (
this.handlers[cid] &&
this.handlers[cid][nid] &&
this.handlers[cid][nid][event.type]
) {
this.handlers[cid][nid][event.type].forEach(handler => {
handler(event);
});
} else {
console.error(`event handler[${cid}][${nid}][${event.type}] not found`);
}
_init() {
registerVdSync(this.pageId, (vdBatchData) => {
onVdSync$1(vdBatchData, this);
});
}
addMountedVm (vm) {
addMountedVm(vm) {
vm._$mounted(); // 触发vd数据同步
this.addCallback(function mounted () {
this.addVdSyncCallback(function mounted() {
vm.__call_hook('mounted');
});
}
addUpdatedVm (vm) {
addUpdatedVm(vm) {
vm._$updated(); // 触发vd数据同步
this.addCallback(function mounted () {
this.addVdSyncCallback(function mounted() {
vm.__call_hook('updated');
});
}
addCallback (callback) {
isFn(callback) && callbacks$a.push(callback);
addVdSyncCallback(callback) {
isFn(callback) && vdSyncCallbacks.push(callback);
}
getVm (id) {
getVm(id) {
return this.vms[id]
}
addVm (vm) {
addVm(vm) {
this.vms[vm._$id] = vm;
}
removeVm (vm) {
removeVm(vm) {
delete this.vms[vm._$id];
}
addEvent (cid, nid, name, handler) {
const cHandlers = this.handlers[cid] || (this.handlers[cid] = Object.create(null));
const nHandlers = cHandlers[nid] || (cHandlers[nid] = Object.create(null));
(nHandlers[name] || (nHandlers[name] = [])).push(handler);
addElement(elm) {
this.elements.indexOf(elm) === -1 && this.elements.push(elm);
}
removeEvent (cid, nid, name, handler) {
const cHandlers = this.handlers[cid] || (this.handlers[cid] = Object.create(null));
const nHandlers = cHandlers[nid] || (cHandlers[nid] = Object.create(null));
const eHandlers = nHandlers[name];
if (Array.isArray(eHandlers)) {
const index = eHandlers.indexOf(handler);
if (index !== -1) {
eHandlers.splice(index, 1);
}
removeElement(elm) {
const elmIndex = this.elements.indexOf(elm);
if (elmIndex === -1) {
return console.error(`removeElement[${elm.cid}][${elm.nid}] not found`)
}
this.elements.splice(elmIndex, 1);
}
push (type, nodeId, data) {
this.batchData.push([type, [nodeId, data]]);
push(type, cid, data) {
this.batchData.push([type, [cid, data]]);
}
flush () {
flush() {
if (!this.initialized) {
this.initialized = true;
this.batchData.push([PAGE_CREATED, [this.pageId, this.pagePath]]);
}
if (this.batchData.length) {
UniServiceJSBridge.publishHandler('vdSync', {
UniServiceJSBridge.publishHandler(VD_SYNC, {
data: this.batchData,
options: {
timestamp: Date.now()
......@@ -9087,12 +9095,12 @@ var serviceContext = (function () {
}
}
destroy () {
destroy() {
this.batchData.length = 0;
this.vms = Object.create(null);
this.initialized = false;
this.handlers = Object.create(null);
removeWebviewUIEvent(this.pageId);
this.elements.length = 0;
removeVdSync(this.pageId);
}
}
......@@ -9159,10 +9167,17 @@ var serviceContext = (function () {
if (!this._$vd) {
return
}
// TODO 自定义组件中的 slot 数据采集是在组件内部,导致所在 context 中无法获取到差量数据
// 如何保证每个 vm 数据有变动,就加入 diff 中呢?
// 每次变化,可能触发多次 beforeUpdate,updated
// 子组件 updated 时,可能会增加父组件的 diffData,如 slot 等情况
diff(this._$newData, this._$data, this._$vdUpdatedData);
this._$data = JSON.parse(JSON.stringify(this._$newData));
console.log(`[${this._$id}] updated ` + Date.now());
this._$vd.initialized && this.$nextTick(this._$vd.flush.bind(this._$vd));
// setTimeout 一下再 nextTick( 直接 nextTick 的话,会紧接着该 updated 做 flush,导致父组件 updated 数据被丢弃)
this._$vd.initialized && setTimeout(() => {
this.$nextTick(this._$vd.flush.bind(this._$vd));
}, 0);
};
Object.defineProperty(Vue.prototype, '_$vd', {
......@@ -9188,7 +9203,6 @@ var serviceContext = (function () {
this._$vdMountedData = Object.create(null);
this._$setData(MOUNTED_DATA, this._$vdMountedData);
console.log(`[${this._$id}] beforeCreate ` + Date.now());
// 目前全量采集做 diff(iOS 需要保留全量状态做 restore),理论上可以差量采集
this._$data = Object.create(null);
this._$newData = Object.create(null);
}
......@@ -9212,20 +9226,23 @@ var serviceContext = (function () {
});
}
function setData (id, name, value) {
const diffData = this._$newData[id] || (this._$newData[id] = {});
if (typeof name !== 'string') {
for (let key in name) {
diffData[key] = name[key];
}
return name
function setData (id, name, value) {
switch (name) {
case B_CLASS:
value = this._$stringifyClass(value);
break
case B_STYLE:
value = this._$normalizeStyleBinding(value);
break
case V_IF:
case V_ELSE_IF:
value = !!value;
break
case V_FOR:
return setForData.call(this, id, value)
}
if (name === 'a-_i') {
return value
}
return (diffData[name] = value)
return ((this._$newData[id] || (this._$newData[id] = {}))[name] = value)
}
function setForData (id, value) {
......@@ -9250,11 +9267,11 @@ var serviceContext = (function () {
}
function setIfData (id, value) {
return ((this._$newData[id] || (this._$newData[id] = {}))[V_IF] = value)
return ((this._$newData[id] || (this._$newData[id] = {}))[V_IF] = !!value)
}
function setElseIfData (id, value) {
return ((this._$newData[id] || (this._$newData[id] = {}))[V_ELSE_IF] = value)
return ((this._$newData[id] || (this._$newData[id] = {}))[V_ELSE_IF] = !!value)
}
/* @flow */
......
......@@ -5514,28 +5514,58 @@ var noop$1 = {};
var UniElement = function UniElement(tagName) {
this.tagName = tagName;
this.attrs = Object.create(null);
this.cid = '';
this.nid = '';
this.events = Object.create(null);
};
UniElement.prototype.setAttribute = function setAttribute (key, value) {
this.attrs[key] = value;
if (key === 'cid') {
this.cid = value;
} else if (key === 'nid') {
this.nid = value;
}
};
UniElement.prototype.dispatchEvent = function dispatchEvent (name, target) {
var handlers = this.events[name];
if (!handlers) {
console.error(("cid=" + (this.cid) + ",nid=" + (this.nid) + " dispatchEvent(" + name + ") not found"));
}
handlers.forEach(function (handler) {
handler(target);
});
};
UniElement.prototype.addEventListener = function addEventListener (name, handler) {
var ref = this.attrs;
var cid = ref.cid;
var nid = ref.nid;
if (!cid || !nid) {
return console.error(("cid=" + cid + ",nid=" + nid + " addEventListener(" + name + ") not found"))
if (this.cid === '' || this.nid === '') {
return console.error(("cid=" + (this.cid) + ",nid=" + (this.nid) + " addEventListener(" + name + ") not found"))
}
this._$vd.addEvent(cid, nid, name, handler);
(this.events[name] || (this.events[name] = [])).push(handler);
this._$vd.addElement(this);
};
UniElement.prototype.removeEventListener = function removeEventListener (name, handler) {
var ref = this.attrs;
var cid = ref.cid;
var nid = ref.nid;
if (!cid || !nid) {
return console.error(("cid=" + cid + ",nid=" + nid + " removeEventListener(" + name + ") not found"))
UniElement.prototype.removeEventListener = function removeEventListener (name, handler) {
var this$1 = this;
if (this.cid === '' || this.nid === '') {
return console.error(("cid=" + (this.cid) + ",nid=" + (this.nid) + " removeEventListener(" + name + ") not found"))
}
this._$vd.removeEvent(cid, nid, name, handler);
var isRemoved = false;
if (this.events[name]) {
var handlerIndex = this.events[name].indexOf(handler);
if (handlerIndex !== -1) {
this.events[name].splice(handlerIndex, 1);
isRemoved = true;
}
}
if (!isRemoved) {
console.error(("cid=" + (this.cid) + ",nid=" + (this.nid) + " removeEventListener(" + name + ") handler not found"));
}
Object.keys(this.events).every(function (eventType) { return this$1.events[eventType].length === 0; }) &&
this._$vd.removeElement(this);
};
function createElement$1(tagName) {
......@@ -6631,7 +6661,14 @@ function updateDOMListeners (oldVnode, vnode) {
var on = vnode.data.on || {};
var oldOn = oldVnode.data.on || {};
target$1 = vnode.elm;
target$1._$vd = vnode.context._$vd; // fixed by xxxxxx 存储 vd
// fixed by xxxxxx 存储 vd
target$1._$vd = vnode.context._$vd;
var context = vnode.context;
// 存储事件标记
target$1.setAttribute('nid', String(vnode.data.attrs['_i']));
target$1.setAttribute('cid', context._$id);
normalizeEvents(on);
updateListeners(on, oldOn, add$1, remove$2, createOnceHandler$1, vnode.context);
target$1 = undefined;
......@@ -6642,6 +6679,31 @@ var events = {
update: updateDOMListeners
};
var platformModules = [
events
];
/* */
// the directive module should be applied last, after all
// built-in modules have been applied.
var modules = platformModules.concat(baseModules);
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
var show = {
bind: function bind() {},
update: function update() {},
unbind: function unbind() {}
};
var platformDirectives = {
show: show
};
var platformComponents = {
};
/* */
function stringifyClass (value) {
......@@ -6707,82 +6769,6 @@ function normalizeStyleBinding (bindingStyle) {
return bindingStyle
}
function updateExtras(oldVnode, vnode) {
var attrs = vnode.data.attrs;
var extras = vnode.data.extras;
var isExtrasUndef = isUndef(extras);
if (isExtrasUndef && isUndef(attrs)) {
return
}
var elm = vnode.elm;
var context = vnode.context;
var id = attrs['_i'];
// 存储事件标记
elm.setAttribute('nid', String(id));
elm.setAttribute('cid', context._$id);
if (
(
isExtrasUndef ||
Object.keys(extras).length === 0
) &&
Object.keys(attrs).length === 1
) {
return
}
var $s = vnode.context._$s.bind(vnode.context);
if (extras) {
if (extras['c']) {
extras['c'] = stringifyClass(extras['c']);
}
if (extras['s']) {
extras['s'] = normalizeStyleBinding(extras['s']);
}
for (var key in extras) {
$s(id, key, extras[key]);
}
}
if (attrs) {
for (var key$1 in attrs) {
key$1 !== '_i' && $s(id, 'a-' + key$1, attrs[key$1]);
}
}
}
var extras = {
create: updateExtras,
update: updateExtras
};
var platformModules = [
extras,// 在前,设置 cid,nid
events
];
/* */
// the directive module should be applied last, after all
// built-in modules have been applied.
var modules = platformModules.concat(baseModules);
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
var platformDirectives = {
};
var platformComponents = {
};
function callHook$2(hook, args) {
var vm = this;
// #7573 disable dep collection when invoking lifecycle hooks
......@@ -6810,6 +6796,9 @@ var plugin = {
};
Vue.prototype.__call_hook = callHook$2;
// 运行时需要格式化 class,style
Vue.prototype._$stringifyClass = stringifyClass;
Vue.prototype._$normalizeStyleBinding = normalizeStyleBinding;
}
};
......
......@@ -15,53 +15,53 @@ describe('codegen', () => {
it('generate block', () => {
assertCodegen(
'<block v-if="show"></block>',
`with(this){return (_$i(0,show))?void 0:_e()}`
`with(this){return (_$s(0,'i',show))?void 0:_e()}`
)
assertCodegen(
'<div><block v-for="item in items"><div></div><div></div></block></div>',
`with(this){return _c('div',[_l((_$f(1,{forItems:items})),function(item,$10,$20,$30){return [_c('div',{key:_$f(1,{forIndex:$20,keyIndex:0,key:1+'-0'+$30})}),_c('div',{key:_$f(1,{forIndex:$20,keyIndex:1,key:1+'-1'+$30})})]})],2)}`
`with(this){return _c('div',[_l((_$s(1,'f',{forItems:items})),function(item,$10,$20,$30){return [_c('div',{key:_$s(1,'f',{forIndex:$20,keyIndex:0,key:1+'-0'+$30})}),_c('div',{key:_$s(1,'f',{forIndex:$20,keyIndex:1,key:1+'-1'+$30})})]})],2)}`
)
assertCodegen(
'<div><block v-for="item in items" :key="item.id"><div></div><div></div></block></div>',
`with(this){return _c('div',[_l((_$f(1,{forItems:items})),function(item,$10,$20,$30){return [_c('div',{key:_$f(1,{forIndex:$20,keyIndex:0,key:item.id+'_0'})}),_c('div',{key:_$f(1,{forIndex:$20,keyIndex:1,key:item.id+'_1'})})]})],2)}`
`with(this){return _c('div',[_l((_$s(1,'f',{forItems:items})),function(item,$10,$20,$30){return [_c('div',{key:_$s(1,'f',{forIndex:$20,keyIndex:0,key:item.id+'_0'})}),_c('div',{key:_$s(1,'f',{forIndex:$20,keyIndex:1,key:item.id+'_1'})})]})],2)}`
)
})
it('generate directive', () => {
assertCodegen(
'<p v-custom1:[arg1].modifier="value1" v-custom2></p>',
`with(this){return _c('p',{extras:{"v-custom1":value1,"v-custom1-arg":arg1},attrs:{"_i":0}})}`
`with(this){return _c('p',{directives:[{name:"custom1",rawName:"v-custom1:[arg1].modifier",value:(_$s(0,'v-custom1',value1)),expression:"_$s(0,'v-custom1',value1)",arg:_$s(0,'v-custom1-arg',arg1),modifiers:{"modifier":true}},{name:"custom2",rawName:"v-custom2"}],attrs:{"_i":0}})}`
)
})
it('generate v-for directive', () => {
assertCodegen(
'<div><template v-for="item in items"><div></div><div></div></template></div>',
`with(this){return _c('div',[_l((_$f(1,{forItems:items})),function(item,$10,$20,$30){return [_c('div',{key:_$f(1,{forIndex:$20,keyIndex:0,key:1+'-0'+$30})}),_c('div',{key:_$f(1,{forIndex:$20,keyIndex:1,key:1+'-1'+$30})})]})],2)}`
`with(this){return _c('div',[_l((_$s(1,'f',{forItems:items})),function(item,$10,$20,$30){return [_c('div',{key:_$s(1,'f',{forIndex:$20,keyIndex:0,key:1+'-0'+$30})}),_c('div',{key:_$s(1,'f',{forIndex:$20,keyIndex:1,key:1+'-1'+$30})})]})],2)}`
)
assertCodegen(
'<div><template v-for="item in items">text</template></div>',
`with(this){return _c('div',[_l((_$f(1,{forItems:items})),function(item,$10,$20,$30){return [_c('text',{key:_$f(1,{forIndex:$20,keyIndex:0,key:1+'-0'+$30}),attrs:{"_i":("1-"+$30)}})]})],2)}`
`with(this){return _c('div',[_l((_$s(1,'f',{forItems:items})),function(item,$10,$20,$30){return void 0})],2)}`
)
assertCodegen(
'<div><template v-for="item in items">{{text}}</template></div>',
`with(this){return _c('div',[_l((_$f(1,{forItems:items})),function(item,$10,$20,$30){return [_c('text',{key:_$f(1,{forIndex:$20,keyIndex:0,key:1+'-0'+$30}),extras:{t0:_s(text)},attrs:{"_i":("1-"+$30)}})]})],2)}`
`with(this){return _c('div',[_l((_$s(1,'f',{forItems:items})),function(item,$10,$20,$30){return [_v((_$s(("1-"+$30),'t0',_s(text))))]})],2)}`
)
assertCodegen(
'<div><template v-for="item in items"><span></span>{{text}}</template></div>',
`with(this){return _c('div',[_l((_$f(1,{forItems:items})),function(item,$10,$20,$30){return [_c('span',{key:_$f(1,{forIndex:$20,keyIndex:0,key:1+'-0'+$30})}),_c('text',{extras:{t0:_s(text)},attrs:{"_i":("1-"+$30)}})]})],2)}`
`with(this){return _c('div',[_l((_$s(1,'f',{forItems:items})),function(item,$10,$20,$30){return [_c('span',{key:_$s(1,'f',{forIndex:$20,keyIndex:0,key:1+'-0'+$30})}),_v((_$s(("1-"+$30),'t0',_s(text))))]})],2)}`
)
assertCodegen(
'<div><template v-for="item in items">a {{text1}} b {{text2}}</template></div>',
`with(this){return _c('div',[_l((_$f(1,{forItems:items})),function(item,$10,$20,$30){return [_c('text',{key:_$f(1,{forIndex:$20,keyIndex:0,key:1+'-0'+$30}),extras:{t0:_s(text1),t1:_s(text2)},attrs:{"_i":("1-"+$30)}})]})],2)}`
`with(this){return _c('div',[_l((_$s(1,'f',{forItems:items})),function(item,$10,$20,$30){return [_v((_$s(("1-"+$30),'t0',_s(text1)))+(_$s(("1-"+$30),'t1',_s(text2))))]})],2)}`
)
assertCodegen(
'<div><template v-for="item in items"><span v-if="item.sub"></span></template></div>',
`with(this){return _c('div',[_l((_$f(1,{forItems:items})),function(item,$10,$20,$30){return [(_$i(("2-"+$30),item.sub))?_c('span',{key:_$f(1,{forIndex:$20,keyIndex:0,key:1+'-0'+$30})}):_e()]})],2)}`
`with(this){return _c('div',[_l((_$s(1,'f',{forItems:items})),function(item,$10,$20,$30){return [(_$s(("2-"+$30),'i',item.sub))?_c('span',{key:_$s(1,'f',{forIndex:$20,keyIndex:0,key:1+'-0'+$30})}):_e()]})],2)}`
)
})
it('generate text with multiple statements', () => {
assertCodegen(
'<div>A{{ d | e | f }}B{{text}}C</div>',
`with(this){return _c('div',[_c('text',{extras:{t0:_s(_f("f")(_f("e")(d))),t1:_s(text)},attrs:{"_i":0}})])}`
`with(this){return _c('div',[_v((_$s(0,'t0',_s(_f("f")(_f("e")(d)))))+(_$s(0,'t1',_s(text))))])}`
)
})
})
......
......@@ -37,7 +37,7 @@ describe('codegen', () => {
it('generate slot fallback content', () => {
assertCodegen(
'<div><slot><div>{{hi}}</div></slot></div>',
`with(this){return _c('v-uni-view',{attrs:{"_i":0}},[_t("default",[_c('v-uni-view',{attrs:{"_i":2}},[_v((_$g(2,'t0',1)))])],{"_i":1})],2)}`
`with(this){return _c('v-uni-view',{attrs:{"_i":0}},[_t("default",[_c('v-uni-view',{attrs:{"_i":2}},[_v((_$g(2,'t0')))])],{"_i":1})],2)}`
)
})
})
......
const compiler = require('../lib')
const res = compiler.compile(
`
<p slot="one">hello world</p>
<my-component v-model="\n test \n" />
`, {
resourcePath: '/User/fxy/Documents/test.wxml',
isReservedTag: function (tag) {
......
const {
ID,
isVar
} = require('./util')
const {
isComponent
} = require('../util')
let isPlatformReservedTag
function no (a, b, c) {
......@@ -17,7 +26,7 @@ function isStatic (node) {
if (node.type === 3) {
return true
}
if (node.dynamicClass || node.dynamicStyle) {
if (node.classBinding || node.styleBinding) {
return false
}
return !!(node.pre || (
......@@ -39,7 +48,21 @@ function markStatic (node) {
}
delete node.attrs
}
if (node.type === 1) {
if (node.type === 1) {
delete node.staticClass
delete node.staticStyle
if (node.attrs && !isComponent(node.tag)) { // 移除静态属性
node.attrs = node.attrs.filter(attr => attr.name === ID || isVar(attr.value))
}
node.children = node.children.filter(child => { // 移除静态文本
if (child.type === 3) { // ASTText
return false
}
return true
})
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
......
const {
ID,
V_IF,
V_FOR,
V_ELSE_IF,
isVar
} = require('../util')
const parseTextExpr = require('./text-parser')
function parseIf (el, createGenVar) {
if (!el.if) {
return
}
el.ifConditions.forEach(con => {
if (isVar(con.exp)) {
con.exp = createGenVar(con.block.attrsMap[ID])(con.block.elseif ? V_ELSE_IF : V_IF, con.exp)
}
})
el.if = createGenVar(el.attrsMap[ID])(V_IF, el.if)
}
function parseFor (el, createGenVar) {
if (el.for && isVar(el.for)) {
el.for = createGenVar(el.forId)(V_FOR, `{forItems:${el.for}}`)
return true
}
}
function parseBinding (el, genVar) {
el.classBinding && (el.classBinding = genVar('c', el.classBinding))
el.styleBinding && (el.styleBinding = genVar('s', el.styleBinding))
}
function parseDirs (el, genVar) {
el.directives && el.directives.forEach(dir => {
dir.value && (dir.value = genVar('v-' + dir.name, dir.value))
dir.isDynamicArg && (dir.arg = genVar('v-' + dir.name + '-arg', dir.arg))
})
}
function parseAttrs (el, genVar) {
el.attrs && el.attrs.forEach(attr => {
attr.name !== ID && isVar(attr.value) && (attr.value = genVar('a-' + attr.name, attr.value))
})
}
function parseProps (el, genVar) {
el.props && el.props.forEach(prop => {
isVar(prop.value) && (prop.value = genVar('a-' + prop.name, prop.value))
})
}
function parseText (el, parent, state) {
// fixed by xxxxxx 注意:保持平台一致性,trim 一下
el.parent && (el.parent = parent)
el.expression = parseTextExpr(el.text.trim(), false, state).expression
}
module.exports = {
parseIf,
parseFor,
parseText,
parseDirs,
parseAttrs,
parseProps,
parseBinding
}
......@@ -28,7 +28,6 @@ module.exports = function parseText (
if (!tagRE.test(text)) {
return
}
const dynamicTexts = [] // fixed by xxxxxx
const tokens = []
const rawTokens = []
let lastIndex = tagRE.lastIndex = 0
......@@ -44,14 +43,7 @@ module.exports = function parseText (
}
// tag token
const exp = parseFilters(match[1].trim())
if (state.service) {
dynamicTexts.push({
name: `t${state.index++}`,
value: `_s(${exp})`
})
} else {
tokens.push(`(${state.genVar('t' + (state.index++))})`)
}
tokens.push(`(${state.genVar('t' + (state.index++), '_s(' + exp + ')')})`)
rawTokens.push({
'@binding': exp
})
......@@ -65,7 +57,6 @@ module.exports = function parseText (
}
return {
expression: tokens.join('+'),
tokens: rawTokens,
dynamicTexts
tokens: rawTokens
}
}
const {
ID,
ITERATOR,
V_FOR,
SET_DATA,
isVar,
getForEl,
processForKey,
......@@ -12,290 +13,87 @@ const {
isComponent
} = require('../util')
const parseText = require('./parser/text-parser')
const {
parseIf,
parseFor,
parseText,
parseDirs,
parseAttrs,
parseProps,
parseBinding
} = require('./parser/base-parser')
const parseEvent = require('./parser/event-parser')
const parseBlock = require('./parser/block-parser')
const preTransformNode = require('./pre-transform-node')
const basePreTransformNode = require('./pre-transform-node')
const optimize = require('./optimizer')
function genData (el) {
delete el.$parentIterator3
const {
events,
dynamicClass,
dynamicStyle,
dynamicAttrs,
dynamicTexts,
directivesBinding
} = el
let extras = ''
if (directivesBinding) {
const dirs = genDirectives(directivesBinding)
dirs && (extras += dirs + ',')
}
if (dynamicClass) {
extras += genDynamicClass(dynamicClass) + ','
}
if (dynamicStyle) {
extras += genDynamicStyle(dynamicStyle) + ','
}
if (dynamicTexts) {
extras += genDynamicTexts(dynamicTexts) + ','
}
if (events) {
const dynamicHandlers = genDynamicHandlers(events)
dynamicHandlers && (extras += dynamicHandlers + ',')
}
if (Array.isArray(dynamicAttrs)) {
const dynamicProps = genDynamicProps(dynamicAttrs)
dynamicProps && (extras += dynamicProps + ',')
}
extras = extras.replace(/,$/, '')
if (extras) {
return `extras:{${extras}},`
}
return ''
}
function genDirectives (dirs) {
let directives = []
dirs.forEach(dir => {
isVar(dir.value) && directives.push(`"v-${dir.name}":${dir.value}`)
dir.isDynamicArg && (directives.push(`"v-${dir.name}-arg":${dir.arg}`))
})
return directives.join(',')
}
function genDynamicClass (classBinding) {
return `c:${classBinding}`
}
function genDynamicStyle (styleBinding) {
return `s:${styleBinding}`
}
function genDynamicTexts (dynamicTexts) {
return dynamicTexts.map(({
name,
value
}) => {
return `${name}:${value}`
}).join(',')
}
function genDynamicHandlers (events) {
let dynamicHandlers = []
let index = 0
for (let name in events) {
const eventHandler = events[name]
if (eventHandler.dynamic) {
eventHandler.index = index++
dynamicHandlers.push('"de-' + (eventHandler.index) + '":' + name)
}
}
return dynamicHandlers.join(',')
}
function genDynamicProps (props, prefix = 'da') {
let dynamicProps = []
let index = 0
for (let i = 0; i < props.length; i++) {
const {
name,
value,
dynamic
} = props[i]
if (dynamic) {
props[i].index = index++
dynamicProps.push(`"${prefix}-` + (props[i].index) + '-n":' + name)
dynamicProps.push(`"${prefix}-` + (props[i].index) + '-v":' + value)
}
}
return dynamicProps.join(',')
}
// if 使用该方案是因为 template 节点之类无法挂靠 extras
function processIfConditions (el) {
if (el.if) {
el.ifConditions.forEach(con => {
if (isVar(con.exp)) {
const method = con.block.elseif ? '_$e' : '_$i'
con.exp = `${method}(${con.block.attrsMap[ID]},${con.exp})`
}
})
el.if = `_$i(${el.attrsMap[ID]},${el.if})`
}
}
function removeStatic (el) {
delete el.staticClass
delete el.staticStyle
}
function renameBinding (el) {
// 重命名 classBinding,styleBinding,避免生成 class,style 属性
if (el.classBinding) {
el.dynamicClass = el.classBinding
delete el.classBinding
}
if (el.styleBinding) {
el.dynamicStyle = el.styleBinding
delete el.styleBinding
function createGenVar (id) {
return function genVar (name, value) {
return `${SET_DATA}(${id},'${name}',${value})`
}
}
function processKey (el) {
function parseKey (el) {
// add default key
if (processForKey(el)) {
el = el.children[0] // 当 template 下仅文本时,处理第一个动态文本
}
if (el.key && (
el.key.indexOf('_$f') !== 0 &&
el.key.indexOf('_$s') !== 0
)) { // renderList key
const forEl = getForEl(el)
if (forEl) {
if (!isVar(forEl.for)) {
return
}
const forId = forEl.forId
const it = forEl.iterator2 || forEl.iterator1 || ITERATOR
if (forEl === el) { // <view v-for="item in items" :key="item.id"></view>
el.key = `_$f(${forId},{forIndex:${it},key:${el.key}})`
} else { // <template v-for="item in items"><view :key="item.id+'1'"></view><view :key="item.id+'2'"></view></template>
const keyIndex = forEl.children.indexOf(el)
el.key = `_$f(${forId},{forIndex:${it},keyIndex:${keyIndex},key:${el.key}})`
}
} else {
isVar(el.key) && (el.key = `_$s(${el.attrsMap[ID]},'a-key',${el.key})`)
}
if (!el.key || el.key.indexOf(SET_DATA) === 0) {
return
}
}
function processIf (el) {
processIfConditions(el)
}
function processDirs (el) {
if (el.directives) {
const directivesBinding = []
el.directives = el.directives.filter(dir => {
directivesBinding.push(dir)
if (dir.name === 'model') {
return true
}
})
if (directivesBinding.length) {
el.directivesBinding = directivesBinding
}
if (!el.directives.length) {
delete el.directives
}
const forEl = getForEl(el)
if (!forEl) {
isVar(el.key) && (el.key = createGenVar(el.attrsMap[ID])('a-key', el.key))
}
}
function processDynamicText (el, state) {
el.type = 1
el.tag = 'text'
el.attrsList = [{
name: ID,
value: state.id
}]
el.attrsMap = {
[ID]: state.id
if (!isVar(forEl.for)) {
return
}
el.rawAttrsMap = {}
el.children = []
el.plain = false
el.attrs = [{
name: ID,
value: String(state.id)
}]
el.hasBindings = true
if (el.text) {
// fixed by xxxxxx 注意:保持平台一致性,trim 一下
const ret = parseText(el.text.trim(), false, state)
if (ret && ret.dynamicTexts.length) {
el.dynamicTexts = ret.dynamicTexts
}
const forId = forEl.forId
const it = forEl.iterator2
const genVar = createGenVar(forId)
if (forEl === el) { // <view v-for="item in items" :key="item.id"></view>
el.key = genVar(V_FOR, `{forIndex:${it},key:${el.key}}`)
} else { // <template v-for="item in items"><view :key="item.id+'1'"></view><view :key="item.id+'2'"></view></template>
const keyIndex = forEl.children.indexOf(el)
el.key = genVar(V_FOR, `{forIndex:${it},keyIndex:${keyIndex},key:${el.key}}`)
}
delete el.expression
delete el.tokens
delete el.text
return el
}
function processText (el) {
if (!el.children.length) {
function transformNode (el, parent, state) {
if (el.type === 3) {
return
}
parseBlock(el)
parseEvent(el)
const state = {
id: el.attrsMap[ID],
index: 0,
service: true
}
el.children = el.children.filter(child => {
if (child.type === 3) { // ASTText
return false
} else if (child.type === 2 && child.text) { // ASTExpression
processDynamicText(child, state)
child.parent = el
}
return true
})
// <div><template v-for="item in items">item</template></div>
if (!el.children.length && el.tag === 'template' && el.for) {
el.children.push(processDynamicText({
parent: el
}, state))
}
}
function processAttrs (el) {
if (!isComponent(el.tag)) { // 自定义组件,不能移除静态 attr
el.attrs = el.attrs.filter(attr => attr.name === ID || isVar(attr.value))
}
}
updateForEleId(el, state)
function processFor (el) {
if (el.for && isVar(el.for)) {
el.for = `_$f(${el.forId},{forItems:${el.for}})`
if (el.type === 2) {
return parseText(el, parent, {
index: 0,
service: true,
// <uni-popup>{{content}}</uni-popup>
genVar: createGenVar(parent.attrsMap[ID])
})
}
}
function transformNode (el, parent, state) {
parseBlock(el)
parseEvent(el)
const genVar = createGenVar(el.attrsMap[ID])
removeStatic(el)
renameBinding(el)
parseFor(el, createGenVar)
parseKey(el)
processAttrs(el)
processText(el)
parseIf(el, createGenVar)
parseBinding(el, genVar)
parseDirs(el, genVar)
updateForEleId(el, state)
if (!isComponent(el.tag)) {
parseAttrs(el, genVar)
}
processFor(el)
processKey(el)
processIf(el)
processDirs(el)
parseProps(el, genVar)
}
function postTransformNode (el, options) {
......@@ -307,8 +105,28 @@ function postTransformNode (el, options) {
optimize(el, options)
}
}
function parseTag (el) {
if (el.tag === 'input' || el.tag === 'textarea') {
el.tag = `c-${el.tag.substr(0, 1)}` // 返回一个自定义组件标签,保证 v-model
}
}
function genData (el) {
delete el.$parentIterator3
if (el.model) {
el.model.callback = `function ($$v) {}`
}
return ''
}
module.exports = {
preTransformNode,
preTransformNode: function (el, options) {
parseTag(el)
return basePreTransformNode(el, options)
},
postTransformNode,
genData
}
}
......@@ -4,8 +4,9 @@ const NUMBER_RE = /^-?\d*(\.\d+)?$/
const ID = '_i'
const ITERATOR1 = '$1'
const ITERATOR2 = '$2'
const ITERATOR3 = '$3'
const DATA_ROOT = '_$g'
const ITERATOR3 = '$3'
const SET_DATA = '_$s'
const GET_DATA = '_$g'
const V_FOR = 'f'
const V_IF = 'i'
......@@ -110,7 +111,7 @@ function getForEl (el) {
function processForKey (el) {
const forEl = getForEl(el)
if (forEl && !el.key && !el.dynamicTexts) { // 占位的 text 标签也无需添加 key
if (forEl && !el.key) { // 占位的 text 标签也无需添加 key
if (!isVar(forEl.for)) { // <view v-for="10"></view>
return
}
......@@ -120,7 +121,8 @@ function processForKey (el) {
const keyIndex = forEl.children.indexOf(el)
el.key = `${forEl.forId}+'-${keyIndex}'+${it}`
} else { // 当 template 下只有文本节点
if (el.children && el.children.length && !el.children.find(child => child.key)) {
if (el.children && el.children.length && !el.children.find(child => child.key)) {
el.children[0].parent = el
el.children[0].key = `${forEl.forId}+'-0'+${it}`
return true
}
......@@ -149,7 +151,8 @@ module.exports = {
V_IF,
V_ELSE_IF,
ID,
DATA_ROOT,
SET_DATA,
GET_DATA,
isVar,
hasOwn,
addAttr,
......
const {
ID,
DATA_ROOT,
V_FOR,
V_IF,
V_ELSE_IF,
GET_DATA,
isVar,
getForEl,
updateForEleId,
......@@ -11,69 +8,30 @@ const {
traverseNode
} = require('./util')
const {
parseIf,
parseFor,
parseText,
parseDirs,
parseAttrs,
parseProps,
parseBinding
} = require('./parser/base-parser')
const parseTag = require('./parser/tag-parser')
const parseText = require('./parser/text-parser')
const parseEvent = require('./parser/event-parser')
const parseBlock = require('./parser/block-parser')
const parseComponent = require('./parser/component-parser')
const basePreTransformNode = require('./pre-transform-node')
function createGenVar (id, isInSlot = false) {
return function genVar (name, extra = '') {
const isFallbackContent = isInSlot ? ',1' : ''
extra = extra ? (',' + extra) : ''
return `${DATA_ROOT}(${id},'${name}'${isFallbackContent}${extra})`
}
}
function isInSlot (el) {
let parent = el.parent
while (parent) {
if (parent.tag === 'slot') {
return true
}
parent = parent.parent
}
return false
}
// if 使用该方案是因为 template 节点之类无法挂靠 extras
function processIfConditions (el) {
if (el.if) {
el.ifConditions.forEach(con => {
if (isVar(con.exp)) {
con.exp = createGenVar(con.block.attrsMap[ID], isInSlot(el))(con.block.elseif ? V_ELSE_IF : V_IF)
}
})
el.if = createGenVar(el.attrsMap[ID], isInSlot(el))(V_IF)
}
}
function processBinding (el, genVar) {
if (el.classBinding) {
el.classBinding = genVar('c')
}
if (el.styleBinding) {
el.styleBinding = genVar('s')
}
}
function processFor (el, genVal) {
if (el.for && isVar(el.for)) {
el.for = createGenVar(el.forId, isInSlot(el))(V_FOR)
// <div><li v-for=" { a, b } in items"></li></div>
// =>
// <div><li v-for="$item in items"></li></div>
if (el.alias[0] === '{') {
el.alias = '$item'
}
function createGenVar (id) {
return function genVar (name) {
return `${GET_DATA}(${id},'${name}')`
}
}
function processKey (el) {
function parseKey (el) {
// add default key
processForKey(el)
......@@ -90,67 +48,44 @@ function processKey (el) {
el.key = `${forEl.alias}['k${keyIndex}']`
}
} else {
isVar(el.key) && (el.key = createGenVar(el.attrsMap[ID], isInSlot(el))('a-key'))
isVar(el.key) && (el.key = createGenVar(el.attrsMap[ID])('a-key'))
}
}
}
function processIf (el) {
processIfConditions(el)
}
function processDirs (el, genVar) {
if (el.directives) {
el.directives.forEach(dir => {
dir.value && (dir.value = genVar('v-' + dir.name))
dir.isDynamicArg && (dir.arg = genVar('v-' + dir.name + '-arg'))
})
}
}
function processAttrs (el, genVar) {
el.attrs.forEach(attr => {
attr.name !== ID && isVar(attr.value) && (attr.value = genVar('a-' + attr.name))
})
}
function processProps (el, genVar) {
el.props && el.props.forEach(prop => {
isVar(prop.value) && (prop.value = genVar('a-' + prop.name))
})
}
function processText (el, parent) {
const state = {
index: 0,
view: true,
genVar: createGenVar(parent.attrsMap[ID], isInSlot(parent))
}
// fixed by xxxxxx 注意:保持平台一致性,trim 一下
el.expression = parseText(el.text.trim(), false, state).expression
}
function transformNode (el, parent, state) {
if (el.type === 3) {
return
}
parseBlock(el)
parseComponent(el)
parseEvent(el)
// 更新 id
updateForEleId(el, state)
if (el.type !== 1) {
return (el.type === 2 && processText(el, parent))
if (el.type === 2) {
return parseText(el, parent, {
index: 0,
view: true,
// <uni-popup>{{content}}</uni-popup>
genVar: createGenVar(parent.attrsMap[ID])
})
}
const id = el.attrsMap[ID]
const genVar = createGenVar(id, isInSlot(el))
const genVar = createGenVar(el.attrsMap[ID])
if (parseFor(el, createGenVar)) {
if (el.alias[0] === '{') { // <div><li v-for=" { a, b } in items"></li></div>
el.alias = '$item'
}
}
parseKey(el)
processFor(el, genVar)
processKey(el)
processIf(el)
processBinding(el, genVar)
processDirs(el, genVar)
processAttrs(el, genVar)
processProps(el, genVar)
parseIf(el, createGenVar)
parseBinding(el, genVar)
parseDirs(el, genVar)
parseAttrs(el, genVar)
parseProps(el, genVar)
}
function postTransformNode (el) {
......@@ -162,7 +97,7 @@ function postTransformNode (el) {
}
}
function processEvents (events) {
function handleViewEvents (events) {
Object.keys(events).forEach(name => {
const modifiers = Object.create(null)
......@@ -208,9 +143,8 @@ function genData (el) {
}
// 放在 postTransformNode 中处理的时机太靠前,v-model 等指令会新增 event
// 理想情况,应该移除自定义组件的 events 配置,但目前不太好准确确定是自定义组件
el.events && processEvents(el.events)
el.nativeEvents && processEvents(el.nativeEvents)
el.events && handleViewEvents(el.events)
el.nativeEvents && handleViewEvents(el.nativeEvents)
return ''
}
......
......@@ -183,10 +183,15 @@ const {
} = require('./h5')
function isComponent (tagName) {
if (tagName === 'block' || tagName === 'template') {
if (
tagName === 'block' ||
tagName === 'template' ||
tagName === 'c-i' || // v3 service input => c-i
tagName === 'c-t' // v3 service textarea => c-t
) {
return false
}
return !hasOwn(tags, getTagName(tagName))
return !hasOwn(tags, getTagName(tagName.replace('v-uni-', '')))
}
module.exports = {
......
......@@ -2,8 +2,13 @@ export const PAGE_CREATE = 2
export const MOUNTED_DATA = 4
export const UPDATED_DATA = 6
export const PAGE_CREATED = 10
export const UI_EVENT = 20
export const LAYOUT_READY = 30
export const VD_SYNC = 'vdSync'
export const APP_SERVICE_ID = '__uniapp__service'
export const WEBVIEW_READY = 'webviewReady'
......
......@@ -12,4 +12,7 @@ export const VIEW_WEBVIEW_PATH = '_www/__uniappview.html'
export const V_FOR = 'f'
export const V_IF = 'i'
export const V_ELSE_IF = 'e'
export const V_SHOW = 'v-show'
export const V_SHOW = 'v-show'
export const B_CLASS = 'c'
export const B_STYLE = 's'
......@@ -8,6 +8,7 @@ import {
} from './navigator'
import {
VD_SYNC,
PAGE_CREATE
} from '../../constants'
......@@ -109,7 +110,7 @@ export function registerPage ({
const pageId = webview.id
// 通知页面已开始创建
UniServiceJSBridge.publishHandler('vdSync', {
UniServiceJSBridge.publishHandler(VD_SYNC, {
data: [
[PAGE_CREATE, [pageId, route]]
],
......@@ -119,7 +120,9 @@ export function registerPage ({
}, [pageId])
try {
createPage(route, pageId, query, pageInstance).$mount()
} catch (e) {}
} catch (e) {
console.error(e)
}
}
}
......
......@@ -6,17 +6,19 @@ import {
import {
MOUNTED_DATA,
UPDATED_DATA
} from '../../../constants'
} from '../../../constants'
import {
VDomSync
} from './vdom-sync'
import {
V_IF,
V_FOR,
V_ELSE_IF
} from '../../constants'
V_IF,
V_FOR,
V_ELSE_IF,
B_CLASS,
B_STYLE
} from '../../constants'
import {
diff
......@@ -49,10 +51,17 @@ export function initData (Vue) {
if (!this._$vd) {
return
}
// TODO 自定义组件中的 slot 数据采集是在组件内部,导致所在 context 中无法获取到差量数据
// 如何保证每个 vm 数据有变动,就加入 diff 中呢?
// 每次变化,可能触发多次 beforeUpdate,updated
// 子组件 updated 时,可能会增加父组件的 diffData,如 slot 等情况
diff(this._$newData, this._$data, this._$vdUpdatedData)
this._$data = JSON.parse(JSON.stringify(this._$newData))
console.log(`[${this._$id}] updated ` + Date.now())
this._$vd.initialized && this.$nextTick(this._$vd.flush.bind(this._$vd))
// setTimeout 一下再 nextTick( 直接 nextTick 的话,会紧接着该 updated 做 flush,导致父组件 updated 数据被丢弃)
this._$vd.initialized && setTimeout(() => {
this.$nextTick(this._$vd.flush.bind(this._$vd))
}, 0)
}
Object.defineProperty(Vue.prototype, '_$vd', {
......@@ -78,7 +87,6 @@ export function initData (Vue) {
this._$vdMountedData = Object.create(null)
this._$setData(MOUNTED_DATA, this._$vdMountedData)
console.log(`[${this._$id}] beforeCreate ` + Date.now())
// 目前全量采集做 diff(iOS 需要保留全量状态做 restore),理论上可以差量采集
this._$data = Object.create(null)
this._$newData = Object.create(null)
}
......@@ -102,20 +110,23 @@ export function initData (Vue) {
})
}
function setData (id, name, value) {
const diffData = this._$newData[id] || (this._$newData[id] = {})
if (typeof name !== 'string') {
for (let key in name) {
diffData[key] = name[key]
}
return name
function setData (id, name, value) {
switch (name) {
case B_CLASS:
value = this._$stringifyClass(value)
break
case B_STYLE:
value = this._$normalizeStyleBinding(value)
break
case V_IF:
case V_ELSE_IF:
value = !!value
break
case V_FOR:
return setForData.call(this, id, value)
}
if (name === 'a-_i') {
return value
}
return (diffData[name] = value)
return ((this._$newData[id] || (this._$newData[id] = {}))[name] = value)
}
function setForData (id, value) {
......@@ -140,9 +151,9 @@ function setForData (id, value) {
}
function setIfData (id, value) {
return ((this._$newData[id] || (this._$newData[id] = {}))[V_IF] = value)
return ((this._$newData[id] || (this._$newData[id] = {}))[V_IF] = !!value)
}
function setElseIfData (id, value) {
return ((this._$newData[id] || (this._$newData[id] = {}))[V_ELSE_IF] = value)
return ((this._$newData[id] || (this._$newData[id] = {}))[V_ELSE_IF] = !!value)
}
import {
isFn
isFn,
noop
} from 'uni-shared'
import {
PAGE_CREATED,
VD_SYNC_CALLBACK
VD_SYNC,
UI_EVENT,
PAGE_CREATED
} from '../../../constants'
import {
removeWebviewUIEvent,
registerWebviewUIEvent
vdSyncCallbacks,
removeVdSync,
registerVdSync
} from '../subscribe-handlers'
function noop () {}
const handleVdData = {
[UI_EVENT]: function onUIEvent (vdBatchEvent, vd) {
vdBatchEvent.forEach(([cid, nid, event]) => {
console.log(`[EVENT]`, cid, nid, event)
event.preventDefault = noop
event.stopPropagation = noop
const target = vd.elements.find(target => target.cid === cid && target.nid === nid)
if (!target) {
return console.error(`event handler[${cid}][${nid}] not found`)
}
target.dispatchEvent(event.type, event)
})
}
}
const callbacks = [] // 数据同步 callback
function onVdSync (vdBatchData, vd) {
vdBatchData.forEach(([type, vdData]) => {
handleVdData[type](vdData, vd)
})
}
export class VDomSync {
constructor (pageId, pagePath) {
......@@ -23,55 +43,34 @@ export class VDomSync {
this.batchData = []
this.vms = Object.create(null)
this.initialized = false
// 事件
this.handlers = Object.create(null)
this.elements = [] // 目前仅存储事件 element
this._init()
}
_init () {
UniServiceJSBridge.subscribe(VD_SYNC_CALLBACK, () => {
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
})
registerWebviewUIEvent(this.pageId, (cid, nid, event) => {
console.log(`[EVENT]`, cid, nid, event)
event.preventDefault = noop
event.stopPropagation = noop
if (
this.handlers[cid] &&
this.handlers[cid][nid] &&
this.handlers[cid][nid][event.type]
) {
this.handlers[cid][nid][event.type].forEach(handler => {
handler(event)
})
} else {
console.error(`event handler[${cid}][${nid}][${event.type}] not found`)
}
registerVdSync(this.pageId, (vdBatchData) => {
onVdSync(vdBatchData, this)
})
}
addMountedVm (vm) {
vm._$mounted() // 触发vd数据同步
this.addCallback(function mounted () {
this.addVdSyncCallback(function mounted () {
vm.__call_hook('mounted')
})
}
addUpdatedVm (vm) {
vm._$updated() // 触发vd数据同步
this.addCallback(function mounted () {
this.addVdSyncCallback(function mounted () {
vm.__call_hook('updated')
})
}
addCallback (callback) {
isFn(callback) && callbacks.push(callback)
addVdSyncCallback (callback) {
isFn(callback) && vdSyncCallbacks.push(callback)
}
getVm (id) {
......@@ -86,26 +85,20 @@ export class VDomSync {
delete this.vms[vm._$id]
}
addEvent (cid, nid, name, handler) {
const cHandlers = this.handlers[cid] || (this.handlers[cid] = Object.create(null))
const nHandlers = cHandlers[nid] || (cHandlers[nid] = Object.create(null));
(nHandlers[name] || (nHandlers[name] = [])).push(handler)
addElement (elm) {
this.elements.indexOf(elm) === -1 && this.elements.push(elm)
}
removeEvent (cid, nid, name, handler) {
const cHandlers = this.handlers[cid] || (this.handlers[cid] = Object.create(null))
const nHandlers = cHandlers[nid] || (cHandlers[nid] = Object.create(null))
const eHandlers = nHandlers[name]
if (Array.isArray(eHandlers)) {
const index = eHandlers.indexOf(handler)
if (index !== -1) {
eHandlers.splice(index, 1)
}
removeElement (elm) {
const elmIndex = this.elements.indexOf(elm)
if (elmIndex === -1) {
return console.error(`removeElement[${elm.cid}][${elm.nid}] not found`)
}
this.elements.splice(elmIndex, 1)
}
push (type, nodeId, data) {
this.batchData.push([type, [nodeId, data]])
push (type, cid, data) {
this.batchData.push([type, [cid, data]])
}
flush () {
......@@ -114,7 +107,7 @@ export class VDomSync {
this.batchData.push([PAGE_CREATED, [this.pageId, this.pagePath]])
}
if (this.batchData.length) {
UniServiceJSBridge.publishHandler('vdSync', {
UniServiceJSBridge.publishHandler(VD_SYNC, {
data: this.batchData,
options: {
timestamp: Date.now()
......@@ -128,7 +121,7 @@ export class VDomSync {
this.batchData.length = 0
this.vms = Object.create(null)
this.initialized = false
this.handlers = Object.create(null)
removeWebviewUIEvent(this.pageId)
this.elements.length = 0
removeVdSync(this.pageId)
}
}
import {
WEBVIEW_READY,
WEBVIEW_UI_EVENT
VD_SYNC,
VD_SYNC_CALLBACK,
WEBVIEW_READY
} from '../../constants'
import {
......@@ -44,31 +45,37 @@ function onWebviewReady (data, pageId) {
}
}
const webviewUIEvents = Object.create(null)
const vdSyncHandlers = Object.create(null)
export function registerWebviewUIEvent (pageId, callback) {
(webviewUIEvents[pageId] || (webviewUIEvents[pageId] = [])).push(callback)
export function registerVdSync (pageId, callback) {
(vdSyncHandlers[pageId] || (vdSyncHandlers[pageId] = [])).push(callback)
}
export function removeWebviewUIEvent (pageId) {
delete webviewUIEvents[pageId]
export function removeVdSync (pageId) {
delete vdSyncHandlers[pageId]
}
function onWebviewUIEvent ({
function onVdSync ({
data,
options
}, pageId) {
const {
cid,
nid
} = options
const handlers = webviewUIEvents[pageId]
const handlers = vdSyncHandlers[pageId]
if (Array.isArray(handlers)) {
handlers.forEach(handler => {
handler(cid, nid, data)
handler(data)
})
} else {
console.error(`events[${pageId}] not found`)
console.error(`vdSync[${pageId}] not found`)
}
}
export const vdSyncCallbacks = [] // 数据同步 callback
function onVdSyncCallback () {
const copies = vdSyncCallbacks.slice(0)
vdSyncCallbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
......@@ -83,5 +90,7 @@ export function initSubscribeHandlers () {
})
// TODO 检测目标 preloadWebview 是否已准备好,因为 preloadWebview 准备好时,此处代码还没执行
subscribe(WEBVIEW_READY, onWebviewReady)
subscribe(WEBVIEW_UI_EVENT, onWebviewUIEvent)
subscribe(VD_SYNC, onVdSync)
subscribe(VD_SYNC_CALLBACK, onVdSyncCallback)
}
import Vue from 'vue'
import {
VD_SYNC,
PAGE_CREATE,
MOUNTED_DATA,
UPDATED_DATA,
......@@ -20,7 +21,7 @@ import {
getPageVueComponent
} from '../../../page-factory'
let vd
export let vd
let PageVueComponent
......@@ -68,18 +69,18 @@ function vdSync ({
})
}
function getData (id, name, isFallbackContent = false) {
function getData (id, name) {
try {
return this.$r[id][name]
} catch (e) {
!isFallbackContent && console.error(this.$options.__file + `:[${this._$id}]$r[${id}][${name}] is undefined`)
console.error(this.$options.__file + `:[${this._$id}]$r[${id}][${name}] is undefined`)
}
}
export function initData (Vue) {
Vue.prototype._$g = getData
UniViewJSBridge.subscribe('vdSync', vdSync)
UniViewJSBridge.subscribe(VD_SYNC, vdSync)
Object.defineProperty(Vue.prototype, '_$vd', {
get () {
......
import {
WEBVIEW_UI_EVENT
} from '../../../constants'
vd
} from './data'
export function initEvent (Vue) {
Vue.prototype.$handleViewEvent = function ($vueEvent, options) {
......@@ -19,12 +19,10 @@ export function initEvent (Vue) {
delete $event.stopPropagation
delete $event.options
UniViewJSBridge.publishHandler(WEBVIEW_UI_EVENT, {
data: $event,
options: {
cid,
nid
}
})
vd.addUIEvent(cid, nid, $event)
// 使用 setTimeout 做批量同步
setTimeout(() => {
vd.sendUIEvent()
}, 0)
}
}
......@@ -2,47 +2,72 @@ import {
guid
} from 'uni-shared'
import {
VD_SYNC,
UI_EVENT
} from '../../../constants'
export class VDomSync {
constructor (pageId) {
this.pageId = pageId
this.addVDatas = []
this.updateVDatas = []
this.addBatchVData = []
this.updateBatchVData = []
this.vms = Object.create(null)
this.uiEventBatchData = []
}
addVData (nodeId, data = {}) {
this.addVDatas.push([nodeId, data])
addVData (cid, data = {}) {
this.addBatchVData.push([cid, data])
}
updateVData (nodeId, data = {}) {
this.updateVDatas.push([nodeId, data])
updateVData (cid, data = {}) {
this.updateBatchVData.push([cid, data])
}
initVm (vm) {
const [nodeId, data] = this.addVDatas.shift()
if (!nodeId) {
const [cid, data] = this.addBatchVData.shift()
if (!cid) {
vm._$id = guid()
console.error('nodeId unmatched', vm)
console.error('cid unmatched', vm)
} else {
vm._$id = nodeId
vm._$id = cid
}
vm.$r = data || Object.create(null)
this.vms[vm._$id] = vm
}
addUIEvent (cid, nid, event) {
this.uiEventBatchData.push([cid, nid, event])
}
sendUIEvent () {
if (this.uiEventBatchData.length) {
UniViewJSBridge.publishHandler(VD_SYNC, {
data: [
[UI_EVENT, this.uiEventBatchData]
],
options: {
timestamp: Date.now()
}
})
this.uiEventBatchData.length = 0
}
}
flush () {
console.log('update....', this.addVDatas, this.updateVDatas)
this.updateVDatas.forEach(([nodeId, data]) => {
const vm = this.vms[nodeId]
console.log('update....', this.addBatchVData, this.updateBatchVData)
this.updateBatchVData.forEach(([cid, data]) => {
const vm = this.vms[cid]
if (!vm) {
return console.error(`Not found ${nodeId}`)
return console.error(`Not found ${cid}`)
}
Object.keys(data).forEach(nodeId => {
Object.assign((vm.$r[nodeId] || (vm.$r[nodeId] = Object.create(null))), data[nodeId])
Object.keys(data).forEach(cid => {
Object.assign((vm.$r[cid] || (vm.$r[cid] = Object.create(null))), data[cid])
})
vm.$forceUpdate()
})
this.updateVDatas.length = 0
this.updateBatchVData.length = 0
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册