提交 32f99d02 编写于 作者: fxy060608's avatar fxy060608

feat(v3): mp runtime

上级 a0fe152b
......@@ -1918,7 +1918,10 @@ function flushCallbacks () {
// where microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690, which have workarounds)
// or even between bubbling of the same event (#6566).
var timerFunc;
// fixed by xxxxxx app-plus 平台 Promise 执行顺序不一致,导致各种乱七八糟的 Bug,统一使用 setTimeout
var timerFunc = function () {
setTimeout(flushCallbacks, 0);
};
// The nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
......@@ -1927,48 +1930,50 @@ var timerFunc;
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise) && !isIOS) { // fixed by xxxxxx
var p = Promise.resolve();
timerFunc = function () {
p.then(flushCallbacks);
// In problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) { setTimeout(noop); }
};
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
var counter = 1;
var observer = new MutationObserver(flushCallbacks);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = function () {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Technically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = function () {
setImmediate(flushCallbacks);
};
} else {
// Fallback to setTimeout.
timerFunc = function () {
setTimeout(flushCallbacks, 0);
};
}
// if (typeof Promise !== 'undefined' && isNative(Promise)) {
// const p = Promise.resolve()
// timerFunc = () => {
// p.then(flushCallbacks)
// // In problematic UIWebViews, Promise.then doesn't completely break, but
// // it can get stuck in a weird state where callbacks are pushed into the
// // microtask queue but the queue isn't being flushed, until the browser
// // needs to do some other work, e.g. handle a timer. Therefore we can
// // "force" the microtask queue to be flushed by adding an empty timer.
// if (isIOS) setTimeout(noop)
// }
// isUsingMicroTask = true
// } else if (!isIE && typeof MutationObserver !== 'undefined' && (
// isNative(MutationObserver) ||
// // PhantomJS and iOS 7.x
// MutationObserver.toString() === '[object MutationObserverConstructor]'
// )) {
// // Use MutationObserver where native Promise is not available,
// // e.g. PhantomJS, iOS7, Android 4.4
// // (#6466 MutationObserver is unreliable in IE11)
// let counter = 1
// const observer = new MutationObserver(flushCallbacks)
// const textNode = document.createTextNode(String(counter))
// observer.observe(textNode, {
// characterData: true
// })
// timerFunc = () => {
// counter = (counter + 1) % 2
// textNode.data = String(counter)
// }
// isUsingMicroTask = true
// } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// // Fallback to setImmediate.
// // Technically it leverages the (macro) task queue,
// // but it is still a better choice than setTimeout.
// timerFunc = () => {
// setImmediate(flushCallbacks)
// }
// } else {
// // Fallback to setTimeout.
// timerFunc = () => {
// setTimeout(flushCallbacks, 0)
// }
// }
function nextTick (cb, ctx) {
var _resolve;
......@@ -2258,18 +2263,29 @@ function mergeVNodeHook (def, hookKey, hook) {
/* */
// fixed by xxxxxx (mp properties)
function extractPropertiesFromVNodeData(data, Ctor, res) {
function extractPropertiesFromVNodeData(data, Ctor, res, context) {
var propOptions = Ctor.options.mpOptions && Ctor.options.mpOptions.properties;
if (isUndef(propOptions)) {
return res
}
}
var externalClasses = Ctor.options.mpOptions.externalClasses || [];
var attrs = data.attrs;
var props = data.props;
if (isDef(attrs) || isDef(props)) {
for (var key in propOptions) {
var altKey = hyphenate(key);
checkProp(res, props, key, altKey, true) ||
checkProp(res, attrs, key, altKey, false);
var altKey = hyphenate(key);
var result = checkProp(res, props, key, altKey, true) ||
checkProp(res, attrs, key, altKey, false);
// externalClass
if (
result &&
res[key] &&
externalClasses.indexOf(altKey) !== -1 &&
context[camelize(res[key])]
) {
// 赋值 externalClass 真正的值(模板里 externalClass 的值可能是字符串)
res[key] = context[camelize(res[key])];
}
}
}
return res
......@@ -2278,7 +2294,8 @@ function extractPropertiesFromVNodeData(data, Ctor, res) {
function extractPropsFromVNodeData (
data,
Ctor,
tag
tag,
context
) {
// we are only extracting raw values here.
// validation and default values are handled in the child
......@@ -2286,7 +2303,7 @@ function extractPropsFromVNodeData (
var propOptions = Ctor.options.props;
if (isUndef(propOptions)) {
// fixed by xxxxxx
return extractPropertiesFromVNodeData(data, Ctor, {})
return extractPropertiesFromVNodeData(data, Ctor, {}, context)
}
var res = {};
var attrs = data.attrs;
......@@ -2315,7 +2332,7 @@ function extractPropsFromVNodeData (
}
}
// fixed by xxxxxx
return extractPropertiesFromVNodeData(data, Ctor, res)
return extractPropertiesFromVNodeData(data, Ctor, res, context)
}
function checkProp (
......@@ -3158,6 +3175,8 @@ var componentVNodeHooks = {
// fixed by xxxxxx
componentInstance._isMounted = true;
if (componentInstance._$vd) {// 延迟 mounted
callHook(componentInstance, 'onServiceCreated');
callHook(componentInstance, 'onServiceAttached');
componentInstance._$vd.addMountedVm(componentInstance);
} else {
callHook(componentInstance, 'mounted');
......@@ -3249,7 +3268,7 @@ function createComponent (
}
// extract props
var propsData = extractPropsFromVNodeData(data, Ctor, tag);
var propsData = extractPropsFromVNodeData(data, Ctor, tag, context);
// functional component
if (isTrue(Ctor.options.functional)) {
......@@ -4110,6 +4129,8 @@ function mountComponent (
// fixed by xxxxxx
vm._isMounted = true;
if (vm._$vd) {// 延迟 mounted 事件
callHook(vm, 'onServiceCreated');
callHook(vm, 'onServiceAttached');
vm._$vd.addMountedVm(vm);
} else {
callHook(vm, 'mounted');
......
......@@ -26,7 +26,7 @@ function hasOwn (obj, key) {
return hasOwnProperty.call(obj, key)
}
function noop () {}
function noop () { }
/**
* Create a cached version of a pure function.
......@@ -49,9 +49,9 @@ const camelize = cached((str) => {
const SOURCE_KEY = '__data__';
const COMPONENT_LIFECYCLE = {
'created': 'created',
'attached': 'created',
const COMPONENT_LIFECYCLE = {
'created': 'onServiceCreated',
'attached': 'onServiceAttached',
'ready': 'mounted',
'moved': 'moved',
'detached': 'destroyed'
......@@ -147,7 +147,7 @@ function parseLifecycle (mpComponentOptions, vueComponentOptions) {
(vueComponentOptions[COMPONENT_LIFECYCLE[name]] || (vueComponentOptions[COMPONENT_LIFECYCLE[name]] = []))
.push(mpComponentOptions[name]);
}
});
});
}
const mpBehaviors = {
......@@ -382,6 +382,102 @@ function parseComponent (mpComponentOptions) {
return vueComponentOptions
}
function initRelationHandlers (type, handler, target, ctx) {
if (!handler) {
return
}
const name = `_$${type}Handlers`;
(ctx[name] || (ctx[name] = [])).push(function () {
handler.call(ctx, target);
});
}
function initLinkedHandlers (relation, target, ctx) {
const type = 'linked';
const name = relation.name;
const relationNodes = ctx._$relationNodes || (ctx._$relationNodes = Object.create(null));
(relationNodes[name] || (relationNodes[name] = [])).push(target);
initRelationHandlers(type, relation[type], target, ctx);
}
function initUnlinkedHandlers (relation, target, ctx) {
const type = 'unlinked';
initRelationHandlers(type, relation[type], target, ctx);
}
function findParentRelation (parentVm, target, type) {
const relations = parentVm &&
parentVm.$options.mpOptions &&
parentVm.$options.mpOptions.relations;
if (!relations) {
return []
}
const name = Object.keys(relations).find(name => {
const relation = relations[name];
return relation.target === target && relation.type === type
});
if (!name) {
return []
}
return [relations[name], parentVm]
}
function initParentRelation (vm, childRelation, match) {
const [parentRelation, parentVm] = match(vm, vm.$options.mpOptions.path);
if (!parentRelation) {
return
}
initLinkedHandlers(parentRelation, vm, parentVm);
initLinkedHandlers(childRelation, parentVm, vm);
initUnlinkedHandlers(parentRelation, vm, parentVm);
initUnlinkedHandlers(childRelation, parentVm, vm);
}
function initRelation (relation, vm) {
const type = relation.type;
if (type === 'parent') {
initParentRelation(vm, relation, function matchParent (vm, target) {
return findParentRelation(vm.$parent, target, 'child')
});
} else if (type === 'ancestor') {
initParentRelation(vm, relation, function matchAncestor (vm, target) {
let $parent = vm.$parent;
while ($parent) {
const ret = findParentRelation($parent, target, 'descendant');
if (ret.length) {
return ret
}
$parent = $parent.$parent;
}
return []
});
}
}
function initRelations (vm) {
const {
relations
} = vm.$options.mpOptions || {};
if (!relations) {
return
}
Object.keys(relations).forEach(name => {
initRelation(relations[name], vm);
});
}
function handleRelations (vm, type) {
// TODO 需要移除 relationNodes
const handlers = vm[`_$${type}Handlers`];
if (!handlers) {
return
}
handlers.forEach(handler => handler());
}
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
......@@ -467,7 +563,12 @@ function validateProp (key, propsOptions, propsData, vm) {
value = !!value;
}
const observer = propOptions && propOptions.observer;
observer && observe(observer, vm, value);
if (observer) {
// 初始化时,异步触发 observer,否则 observer 中无法访问 methods 或其他
setTimeout(function () {
observe(observer, vm, value);
}, 4);
}
return value
}
return getPropertyVal(propsOptions[key])
......@@ -508,7 +609,7 @@ function initProperties (vm, instanceData) {
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
}
value = newVal;
if (observer) {
observe(observer, vm, newVal, oldVal);
......@@ -589,102 +690,6 @@ function initMethods (vm) {
vm._$updateProperties = updateProperties;
}
function initRelationHandlers (type, handler, target, ctx, handlerCtx) {
if (!handler) {
return
}
const name = `_$${type}Handlers`;
(handlerCtx[name] || (handlerCtx[name] = [])).push(function () {
handler.call(ctx, target);
});
}
function initLinkedHandlers (relation, target, ctx, handlerCtx) {
const type = 'linked';
const name = relation.name;
const relationNodes = ctx._$relationNodes || (ctx._$relationNodes = Object.create(null));
(relationNodes[name] || (relationNodes[name] = [])).push(target);
initRelationHandlers(type, relation[type], target, ctx, handlerCtx);
}
function initUnlinkedHandlers (relation, target, ctx, handlerCtx) {
const type = 'unlinked';
initRelationHandlers(type, relation[type], target, ctx, handlerCtx);
}
function findParentRelation (parentVm, target, type) {
const relations = parentVm &&
parentVm.$options.mpOptions &&
parentVm.$options.mpOptions.relations;
if (!relations) {
return []
}
const name = Object.keys(relations).find(name => {
const relation = relations[name];
return relation.target === target && relation.type === type
});
if (!name) {
return []
}
return [relations[name], parentVm]
}
function initParentRelation (vm, childRelation, match) {
const [parentRelation, parentVm] = match(vm, vm.$options.mpOptions.path);
if (!parentRelation) {
return
}
// 先父后子
initLinkedHandlers(parentRelation, vm, parentVm, vm);
initLinkedHandlers(childRelation, parentVm, vm, vm);
initUnlinkedHandlers(parentRelation, vm, parentVm, vm);
initUnlinkedHandlers(childRelation, parentVm, vm, vm);
}
function initRelation (relation, vm) {
const type = relation.type;
if (type === 'parent') {
initParentRelation(vm, relation, function matchParent (vm, target) {
return findParentRelation(vm.$parent, target, 'child')
});
} else if (type === 'ancestor') {
initParentRelation(vm, relation, function matchAncestor (vm, target) {
let $parent = vm.$parent;
while ($parent) {
const ret = findParentRelation($parent, target, 'descendant');
if (ret.length) {
return ret
}
$parent = $parent.$parent;
}
return []
});
}
}
function initRelations (vm) {
const {
relations
} = vm.$options.mpOptions || {};
if (!relations) {
return
}
Object.keys(relations).forEach(name => {
initRelation(relations[name], vm);
});
}
function handleRelations (vm, type) {
// TODO 需要移除 relationNodes
const handlers = vm[`_$${type}Handlers`];
if (!handlers) {
return
}
handlers.forEach(handler => handler());
}
function handleObservers (vm) {
const watch = vm.$options.watch;
if (!watch) {
......@@ -705,13 +710,14 @@ function handleObservers (vm) {
var polyfill = {
beforeCreate () {
// 取消 development 时的 Proxy,避免小程序组件模板中使用尚未定义的属性告警
this._renderProxy = this;
},
created () { // properties 中可能会访问 methods,故需要在 created 中初始化
initState(this);
initMethods(this);
initRelations(this);
},
created () {
handleRelations(this, 'linked');
},
mounted () {
handleObservers(this);
},
......@@ -749,10 +755,21 @@ function Page (options) {
global['__wxComponents'][global['__wxRoute']] = pageOptions;
}
function initRelationsHandler (vueComponentOptions) {
// linked 需要在当前组件 attached 之后再执行
if (!vueComponentOptions['onServiceAttached']) {
vueComponentOptions['onServiceAttached'] = [];
}
vueComponentOptions['onServiceAttached'].push(function onServiceAttached () {
handleRelations(this, 'linked');
});
}
function Component (options) {
const componentOptions = parseComponent(options);
componentOptions.mixins.unshift(polyfill);
componentOptions.mpOptions.path = global['__wxRoute'];
initRelationsHandler(componentOptions);
global['__wxComponents'][global['__wxRoute']] = componentOptions;
}
......
......@@ -5,112 +5,119 @@ function assertCodegen (template, generatedCode, ...args) {
mp: {
platform: 'app-plus'
},
service: true,
service: true,
filterModules: ['swipe']
})
expect(compiled.render).toBe(generatedCode)
}
/* eslint-disable quotes */
describe('codegen', () => {
it('generate block', () => {
assertCodegen(
'<block v-if="show"></block>',
`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((_$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((_$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)}`
)
assertCodegen(
'<div><block v-for="(item,index) in list" :key="index"><block><text>{{item}}</text></block></block></div>',
`with(this){return _c('div',[_l((_$s(1,'f',{forItems:list})),function(item,index,$20,$30){return [[_c('text',{key:_$s(("3-"+$30),'a-key',index+'_0'+'_0')},[_v((_$s(("3-"+$30),'t0',_s(item))))])]]})],2)}`
)
describe('codegen', () => {
it('generate block', () => {
assertCodegen(
'<block v-if="show"></block>',
`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((_$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((_$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)}`
)
assertCodegen(
'<div><block v-for="(item,index) in list" :key="index"><block><text>{{item}}</text></block></block></div>',
`with(this){return _c('div',[_l((_$s(1,'f',{forItems:list})),function(item,index,$20,$30){return [[_c('text',{key:_$s(("3-"+$30),'a-key',index+'_0'+'_0')},[_v((_$s(("3-"+$30),'t0',_s(item))))])]]})],2)}`
)
})
it('generate directive', () => {
assertCodegen(
'<p v-custom1:[arg1].modifier="value1" v-custom2></p>',
`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((_$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((_$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((_$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((_$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((_$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((_$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 v-for directive', () => {
assertCodegen(
'<div><template v-for="item in items"><div></div><div></div></template></div>',
`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((_$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((_$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((_$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((_$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((_$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 :id="'a'+b">A{{ d | e | f }}B{{text}}C</div>`,
`with(this){return _c('div',{attrs:{"id":_$s(0,'a-id','a'+b),"_i":0}},[_v((_$s(0,'t0',_s(_f("f")(_f("e")(d)))))+(_$s(0,'t1',_s(text))))])}`
)
})
it('generate v-slot', () => {
assertCodegen(
'<current-user v-slot="{ user }">{{ user.firstName }}</current-user>',
`with(this){return _c('current-user',{attrs:{"_i":0},scopedSlots:_u([{key:"default",fn:function({ user }, _svm, _si){return [_v((_svm._$s(("0-"+_si),'t0',_s(user.firstName))))]}}])})}`
)
assertCodegen(
'<current-user>ABCD</current-user>',
`with(this){return _c('current-user',{attrs:{"_i":0}},[_v("")])}`
)
assertCodegen(
`<current-user>
<template v-slot:default="{result}">
<view v-for="(item,index) in result.list">{{item.name}}</view>
</template>
</current-user>`,
`with(this){return _c('current-user',{attrs:{"_i":0},scopedSlots:_u([{key:"default",fn:function({result}, _svm, _si){return _l((_svm._$s(("2-"+_si),'f',{forItems:result.list})),function(item,index,$20,$30){return _c('view',{key:_svm._$s(("2-"+_si),'f',{forIndex:$20,key:("2-"+_si)+'-'+$30}),attrs:{"_i":(("2-"+_si)+$30)}},[_v((_svm._$s((("2-"+_si)+$30),'t0',_s(item.name))))])})}}])})}`
)
})
it('generate keep-alive', () => {
assertCodegen(
`<keep-alive exclude="componentWithStatus1"><component is="componentWithStatus"/></keep-alive>`,
`with(this){return _c('keep-alive',{attrs:{"exclude":"componentWithStatus1","_i":0}},[_c("componentWithStatus",{tag:"component",attrs:{"_i":1}})],1)}`
)
assertCodegen(
`<keep-alive :exclude="componentWithStatus1"><component :is="'componentWithStatus'+index"/></keep-alive>`,
`with(this){return _c('keep-alive',{attrs:{"exclude":_$s(0,'a-exclude',componentWithStatus1),"_i":0}},[_c(_$s(1,'is','componentWithStatus'+index),{tag:"component",attrs:{"_i":1}})],1)}`
)
})
it('generate wxs props', () => {
assertCodegen(
'<p :change:prop="swipe.sizeReady" :prop="pos" @touchstart="swipe.touchstart" @touchmove="swipe.touchmove" @touchend="swipe.touchend" @change="change"></p>',
`with(this){return _c('p',{wxsProps:{"change:prop":"pos"},attrs:{"prop":_$s(0,'change:pos',pos),"_i":0},on:{"change":change}})}`
)
})
it('generate staticClass and id', () => {
assertCodegen(
'<view id="aaa" class="bbbb"></view>',
`with(this){return _c('view',{staticClass:_$s(0,'sc',"bbbb"),attrs:{"id":"aaa","_i":0}})}`
)
})
it('generate v-slot', () => {
assertCodegen(
'<current-user v-slot="{ user }">{{ user.firstName }}</current-user>',
`with(this){return _c('current-user',{attrs:{"_i":0},scopedSlots:_u([{key:"default",fn:function({ user }, _svm, _si){return [_v((_svm._$s(("0-"+_si),'t0',_s(user.firstName))))]}}])})}`
)
assertCodegen(
'<current-user>ABCD</current-user>',
`with(this){return _c('current-user',{attrs:{"_i":0}},[_v("")])}`
)
assertCodegen(
`<current-user>
<template v-slot:default="{result}">
<view v-for="(item,index) in result.list">{{item.name}}</view>
</template>
</current-user>`,
`with(this){return _c('current-user',{attrs:{"_i":0},scopedSlots:_u([{key:"default",fn:function({result}, _svm, _si){return _l((_svm._$s(("2-"+_si),'f',{forItems:result.list})),function(item,index,$20,$30){return _c('view',{key:_svm._$s(("2-"+_si),'f',{forIndex:$20,key:("2-"+_si)+'-'+$30}),attrs:{"_i":(("2-"+_si)+$30)}},[_v((_svm._$s((("2-"+_si)+$30),'t0',_s(item.name))))])})}}])})}`
)
})
it('generate keep-alive', () => {
assertCodegen(
`<keep-alive exclude="componentWithStatus1"><component is="componentWithStatus"/></keep-alive>`,
`with(this){return _c('keep-alive',{attrs:{"exclude":"componentWithStatus1","_i":0}},[_c("componentWithStatus",{tag:"component",attrs:{"_i":1}})],1)}`
)
assertCodegen(
`<keep-alive :exclude="componentWithStatus1"><component :is="'componentWithStatus'+index"/></keep-alive>`,
`with(this){return _c('keep-alive',{attrs:{"exclude":_$s(0,'a-exclude',componentWithStatus1),"_i":0}},[_c(_$s(1,'is','componentWithStatus'+index),{tag:"component",attrs:{"_i":1}})],1)}`
)
})
it('generate wxs props', () => {
assertCodegen(
'<p :change:prop="swipe.sizeReady" :prop="pos" @touchstart="swipe.touchstart" @touchmove="swipe.touchmove" @touchend="swipe.touchend" @change="change"></p>',
`with(this){return _c('p',{wxsProps:{"change:prop":"pos"},attrs:{"prop":_$s(0,'change:pos',pos),"_i":0},on:{"change":change}})}`
)
})
it('generate staticClass and id', () => {
assertCodegen(
'<view id="aaa" class="bbbb"></view>',
`with(this){return _c('view',{staticClass:_$s(0,'sc',"bbbb"),attrs:{"id":"aaa","_i":0}})}`
)
})
// TODO 后续优化 dataset
// it('generate dataset', () => {
// assertCodegen(
// '<view data-a="1" :data-b="b"></view>',
// `with(this){return _c('view',{attrs:{"data-a":"1","data-b":b,"_i":0}})}`
// )
// })
})
/* eslint-enable quotes */
......@@ -63,5 +63,12 @@ describe('codegen', () => {
`with(this){return _c('v-uni-view',{wxsProps:{"change:prop":"pos"},attrs:{"change:prop":swipe.sizeReady,"prop":_$gc(0,'change:pos'),"_i":0},on:{"touchstart":function($event){$event = $handleWxsEvent($event);swipe.touchstart($event, $getComponentDescriptor())},"touchmove":function($event){$event = $handleWxsEvent($event);swipe.touchmove($event, $getComponentDescriptor())},"touchend":function($event){$event = $handleWxsEvent($event);swipe.touchend($event, $getComponentDescriptor())},"change":function($event){return $handleViewEvent($event)}}})}`
)
})
// TODO 后续优化dataset
// it('generate dataset', () => {
// assertCodegen(
// '<view data-a="1" :data-b="b"></view>',
// `with(this){return _c('v-uni-view',{attrs:{"_i":0}})}`
// )
// })
})
/* eslint-enable quotes */
const compiler = require('../lib')
const res = compiler.compile(
`
<view><van-grid-item v-for="(item,index) in (4)" :key="item"></van-grid-item></view>
<view data-a="1" :data-b="b"></view>
`, {
miniprogram: true,
resourcePath: '/User/fxy/Documents/test.wxml',
......
......@@ -63,7 +63,16 @@ function markStatic (node) {
const isCustomComponent = isComponent(node.tag)
if (node.attrs && !isCustomComponent && node.tag !== 'keep-alive') { // 移除静态属性
// 保留 id 属性, selectComponent 需要使用
node.attrs = node.attrs.filter(attr => attr.name === 'id' || attr.name === ID || isVar(attr.value))
node.attrs = node.attrs.filter(attr => {
const {
name,
value
} = attr
return name === 'id' ||
name === ID ||
// name.indexOf('data-') === 0 || // TODO dataset
isVar(value)
})
}
node.children = node.children.filter(child => { // 移除静态文本
......
......@@ -57,13 +57,18 @@ function parseDirs (el, genVar, ignoreDirs = []) {
function parseAttrs (el, genVar) {
el.attrs && el.attrs.forEach(attr => {
const {
name,
value
} = attr
if (
attr.name !== ID &&
attr.name.indexOf('change:') !== 0 && // wxs change:prop
isVar(attr.value) &&
attr.value.indexOf('_$') !== 0 // 已被提前处理过了,如 wxs prop:_$gc(2,'change:prop')
name !== ID &&
// name.indexOf('data-') !== 0 && // TODO dataset 保留
name.indexOf('change:') !== 0 && // wxs change:prop
isVar(value) &&
value.indexOf('_$') !== 0 // 已被提前处理过了,如 wxs prop:_$gc(2,'change:prop')
) {
attr.value = genVar('a-' + attr.name, attr.value)
attr.value = genVar('a-' + name, value)
}
})
}
......
......@@ -139,6 +139,11 @@ function transformNode (el, parent, state, isScopedSlot) {
parseWxsProps(el, {
isAppView: true
})
// if (el.attrs) { // TODO 过滤 dataset
// el.attrs = el.attrs.filter(attr => attr.name.indexOf('data-') !== 0)
// }
parseAttrs(el, genVar)
parseProps(el, genVar)
......
export const SOURCE_KEY = '__data__'
export const COMPONENT_LIFECYCLE = {
'created': 'created',
'attached': 'created',
export const COMPONENT_LIFECYCLE = {
'created': 'onServiceCreated',
'attached': 'onServiceAttached',
'ready': 'mounted',
'moved': 'moved',
'detached': 'destroyed'
......
......@@ -8,6 +8,10 @@ import {
parseComponent
} from './parser/component-parser'
import {
handleRelations
} from './polyfill/relations'
import polyfill from './polyfill/index'
export * from './wxs'
......@@ -23,10 +27,21 @@ export function Page (options) {
global['__wxComponents'][global['__wxRoute']] = pageOptions
}
function initRelationsHandler (vueComponentOptions) {
// linked 需要在当前组件 attached 之后再执行
if (!vueComponentOptions['onServiceAttached']) {
vueComponentOptions['onServiceAttached'] = []
}
vueComponentOptions['onServiceAttached'].push(function onServiceAttached () {
handleRelations(this, 'linked')
})
}
export function Component (options) {
const componentOptions = parseComponent(options)
componentOptions.mixins.unshift(polyfill)
componentOptions.mpOptions.path = global['__wxRoute']
initRelationsHandler(componentOptions)
global['__wxComponents'][global['__wxRoute']] = componentOptions
}
......
......@@ -13,5 +13,5 @@ export function parseLifecycle (mpComponentOptions, vueComponentOptions) {
(vueComponentOptions[COMPONENT_LIFECYCLE[name]] || (vueComponentOptions[COMPONENT_LIFECYCLE[name]] = []))
.push(mpComponentOptions[name])
}
})
})
}
......@@ -13,17 +13,18 @@ import {
import {
handleObservers
} from './observers'
} from './observers'
export default {
beforeCreate () {
// 取消 development 时的 Proxy,避免小程序组件模板中使用尚未定义的属性告警
this._renderProxy = this
},
created () { // properties 中可能会访问 methods,故需要在 created 中初始化
initState(this)
initMethods(this)
initRelations(this)
},
created () {
handleRelations(this, 'linked')
},
mounted () {
handleObservers(this)
},
......
function initRelationHandlers (type, handler, target, ctx, handlerCtx) {
function initRelationHandlers (type, handler, target, ctx) {
if (!handler) {
return
}
const name = `_$${type}Handlers`;
(handlerCtx[name] || (handlerCtx[name] = [])).push(function () {
(ctx[name] || (ctx[name] = [])).push(function () {
handler.call(ctx, target)
})
}
function initLinkedHandlers (relation, target, ctx, handlerCtx) {
function initLinkedHandlers (relation, target, ctx) {
const type = 'linked'
const name = relation.name
const relationNodes = ctx._$relationNodes || (ctx._$relationNodes = Object.create(null));
(relationNodes[name] || (relationNodes[name] = [])).push(target)
initRelationHandlers(type, relation[type], target, ctx, handlerCtx)
initRelationHandlers(type, relation[type], target, ctx)
}
function initUnlinkedHandlers (relation, target, ctx, handlerCtx) {
function initUnlinkedHandlers (relation, target, ctx) {
const type = 'unlinked'
initRelationHandlers(type, relation[type], target, ctx, handlerCtx)
initRelationHandlers(type, relation[type], target, ctx)
}
function findParentRelation (parentVm, target, type) {
......@@ -44,12 +44,12 @@ function initParentRelation (vm, childRelation, match) {
if (!parentRelation) {
return
}
// 先父后子
initLinkedHandlers(parentRelation, vm, parentVm, vm)
initLinkedHandlers(childRelation, parentVm, vm, vm)
initUnlinkedHandlers(parentRelation, vm, parentVm, vm)
initUnlinkedHandlers(childRelation, parentVm, vm, vm)
initLinkedHandlers(parentRelation, vm, parentVm)
initLinkedHandlers(childRelation, parentVm, vm)
initUnlinkedHandlers(parentRelation, vm, parentVm)
initUnlinkedHandlers(childRelation, parentVm, vm)
}
function initRelation (relation, vm) {
......
......@@ -43,7 +43,12 @@ function validateProp (key, propsOptions, propsData, vm) {
value = !!value
}
const observer = propOptions && propOptions.observer
observer && observe(observer, vm, value)
if (observer) {
// 初始化时,异步触发 observer,否则 observer 中无法访问 methods 或其他
setTimeout(function () {
observe(observer, vm, value)
}, 4)
}
return value
}
return getPropertyVal(propsOptions[key])
......@@ -84,7 +89,7 @@ export function initProperties (vm, instanceData) {
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
}
value = newVal
if (observer) {
observe(observer, vm, newVal, oldVal)
......
......@@ -28,7 +28,10 @@ const LIFECYCLE_HOOKS = [
// 'onReady', // 兼容旧版本,应该移除该事件
'onPageShow',
'onPageHide',
'onPageResize'
'onPageResize',
// 小程序的 created,attached 生命周期(需要在 service 层的 Vue 内核 mounted 时触发,因小程序 created 可以使用 selectComponent)
'onServiceCreated',
'onServiceAttached'
]
export function lifecycleMixin (Vue) {
// fixed vue-class-component
......
......@@ -12,7 +12,15 @@ function parseSelector (selector) {
if (selector.indexOf('#') === 0) {
const id = selector.substr(1)
return function match (vnode) {
return vnode.data && vnode.data.attrs && vnode.data.attrs.id === id
// props
if (vnode.componentInstance && vnode.componentInstance.id === id) {
return true
}
// attrs
if (vnode.data && vnode.data.attrs && vnode.data.attrs.id === id) {
return true
}
return false
}
} else if (selector.indexOf('.') === 0) {
const clazz = selector.substr(1)
......@@ -52,17 +60,21 @@ function querySelectorAll (vm, matchSelector, ret) {
}
const $children = vm.$children
for (let i = 0; i < $children.length; i++) {
const childVm = querySelectorAll($children[i], matchSelector, ret)
childVm && ret.push(childVm)
querySelectorAll($children[i], matchSelector, ret)
}
return ret
}
export function initPolyfill (Vue) {
Vue.prototype.createIntersectionObserver = function createIntersectionObserver (options) {
return uni.createIntersectionObserver(this, options)
}
Vue.prototype.selectComponent = function selectComponent (selector) {
return querySelector(this, parseSelector(selector))
}
Vue.prototype.selectAllComponent = function selectAllComponent (selector) {
Vue.prototype.selectAllComponents = function selectAllComponents (selector) {
return querySelectorAll(this, parseSelector(selector), [])
}
}
......@@ -12,7 +12,10 @@ function findVmById (id, vm) {
}
}
export function findElm (component, pageVm) {
export function findElm (component, pageVm) {
if (!pageVm) {
return console.error(`page is not ready`)
}
if (!component) {
return pageVm.$el
}
......
......@@ -95,8 +95,8 @@ export function registerPage ({
selectComponent (selector) {
return this.$vm.selectComponent(selector)
},
selectAllComponent (selector) {
return this.$vm.selectAllComponent(selector)
selectAllComponents (selector) {
return this.$vm.selectAllComponents(selector)
}
}
......
......@@ -144,6 +144,12 @@ function setData (id, name, value) {
case V_FOR:
return setForData.call(this, id, value)
}
// TODO 暂时先传递 dataset 至 view 层(理论上不需要)
if (name.indexOf('a-data-') === 0) {
try {
value = JSON.stringify(value)
} catch (e) {}
}
return ((this._$newData[id] || (this._$newData[id] = {}))[name] = value)
}
......
......@@ -3,7 +3,8 @@ import {
} from 'uni-shared'
import {
V_FOR
V_FOR,
B_STYLE
} from '../../constants'
function setResult (data, k, v) {
......@@ -52,11 +53,14 @@ function diffElmData (newObj, oldObj) {
old = oldObj[key]
if (old !== cur) {
// 全量同步 style (因为 style 可能会动态删除部分样式)
// if (key === B_STYLE && isPlainObject(cur) && isPlainObject(old)) {
// const style = diffObject(cur, old)
// style && setResult(result || (result = Object.create(null)), B_STYLE, style)
// } else
if (key === V_FOR && Array.isArray(cur) && Array.isArray(old)) {
if (key === B_STYLE && isPlainObject(cur) && isPlainObject(old)) {
if (Object.keys(cur).length !== Object.keys(old).length) { // 长度不等
setResult(result || (result = Object.create(null)), B_STYLE, cur)
} else {
const style = diffObject(cur, old, false)
style && setResult(result || (result = Object.create(null)), B_STYLE, style)
}
} else if (key === V_FOR && Array.isArray(cur) && Array.isArray(old)) {
const vFor = diffArray(cur, old)
vFor && setResult(result || (result = Object.create(null)), V_FOR, vFor)
} else {
......
......@@ -152,20 +152,20 @@ export class VDomSync {
this.initialized = true
this.batchData.push([PAGE_CREATED, [this.pageId, this.pagePath]])
}
this.batchData = this.batchData.filter(data => {
const batchData = this.batchData.filter(data => {
if (data[0] === UPDATED_DATA && !Object.keys(data[1][1]).length) {
return false
}
return true
})
if (this.batchData.length) {
this.batchData.length = 0
if (batchData.length) {
UniServiceJSBridge.publishHandler(VD_SYNC, {
data: this.batchData,
data: batchData,
options: {
timestamp: Date.now()
}
}, [this.pageId])
this.batchData.length = 0
}
}
......
......@@ -34,8 +34,8 @@ const handleData = {
const [pageId, pagePath, pageOptions] = data
document.title = `${pagePath}[${pageId}]`
// 设置当前页面伪对象,方便其他地方使用 getCurrentPages 获取当前页面 id,route
setCurrentPage(pageId, pagePath)
// 通知页面创建,根据当前页面配置信息,初始化部分事件
setCurrentPage(pageId, pagePath)
// 通知页面创建,根据当前页面配置信息,初始化部分事件
UniViewJSBridge.subscribeHandler(ON_PAGE_CREATE, pageOptions, pageId)
// 初始化当前页面 VueComponent(生成页面样式代码)
PageVueComponent = getPageVueComponent(pagePath)
......@@ -103,6 +103,9 @@ function vdSync ({
function getData (id, name) {
try {
if (name.indexOf('a-data-') === 0) { // TODO 临时方案序列化,反序列化dataset,后续应该将dataset保留在service层
return JSON.parse(this.$r[id][name])
}
return this.$r[id][name]
} catch (e) {
console.error(this.$options.__file + `:[${this._$id}]$r[${id}][${name}] is undefined`)
......@@ -124,7 +127,7 @@ function getChangeData (id, name) {
}
export function initData (Vue) {
Vue.prototype._$g = getData
Vue.prototype._$g = getData
Vue.prototype._$gc = getChangeData
UniViewJSBridge.subscribe(VD_SYNC, vdSync)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册