提交 5bef921d 编写于 作者: fxy060608's avatar fxy060608

feat(mp): refactor

上级 63fa0d5e
...@@ -36,7 +36,8 @@ module.exports = { ...@@ -36,7 +36,8 @@ module.exports = {
plugins: [ plugins: [
alias({ alias({
'uni-shared': path.resolve(__dirname, '../src/shared/util.js'), 'uni-shared': path.resolve(__dirname, '../src/shared/util.js'),
'uni-platform': path.resolve(__dirname, '../src/platforms/' + process.env.UNI_PLATFORM) 'uni-platform': path.resolve(__dirname, '../src/platforms/' + process.env.UNI_PLATFORM),
'uni-wrapper': path.resolve(__dirname, '../src/core/runtime/wrapper')
}), }),
replace({ replace({
__GLOBAL__: platform.prefix, __GLOBAL__: platform.prefix,
......
...@@ -40,7 +40,7 @@ const camelize = cached((str) => { ...@@ -40,7 +40,7 @@ const camelize = cached((str) => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
}); });
const SYNC_API_RE = /subNVue|requireNativePlugin|upx2px|hideKeyboard|canIUse|^create|Sync$|Manager$/; const SYNC_API_RE = /subNVue|requireNativePlugin|upx2px|hideKeyboard|canIUse|^create|Sync$|Manager$|base64ToArrayBuffer|arrayBufferToBase64/;
const CONTEXT_API_RE = /^create|Manager$/; const CONTEXT_API_RE = /^create|Manager$/;
...@@ -89,15 +89,17 @@ function promisify (name, api) { ...@@ -89,15 +89,17 @@ function promisify (name, api) {
fail: reject fail: reject
}), ...params); }), ...params);
/* eslint-disable no-extend-native */ /* eslint-disable no-extend-native */
Promise.prototype.finally = function (callback) { if (!Promise.prototype.finally) {
const promise = this.constructor; Promise.prototype.finally = function (callback) {
return this.then( const promise = this.constructor;
value => promise.resolve(callback()).then(() => value), return this.then(
reason => promise.resolve(callback()).then(() => { value => promise.resolve(callback()).then(() => value),
throw reason reason => promise.resolve(callback()).then(() => {
}) throw reason
) })
}; )
};
}
})) }))
} }
} }
...@@ -371,69 +373,18 @@ Component = function (options = {}) { ...@@ -371,69 +373,18 @@ Component = function (options = {}) {
return MPComponent(options) return MPComponent(options)
}; };
const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__']; const PAGE_EVENT_HOOKS = [
'onPullDownRefresh',
function initBehavior (options) { 'onReachBottom',
return Behavior(options) 'onShareAppMessage',
} 'onPageScroll',
function initRefs (vm) { 'onResize',
const mpInstance = vm.$scope; 'onTabItemTap'
Object.defineProperty(vm, '$refs', { ];
get () {
const $refs = {};
const components = mpInstance.selectAllComponents('.vue-ref');
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
forComponents.forEach(component => {
const ref = component.dataset.ref;
if (!$refs[ref]) {
$refs[ref] = [];
}
$refs[ref].push(component.$vm || component);
});
return $refs
}
});
}
function triggerLink (mpInstance, vueOptions) {
mpInstance.triggerEvent('__l', mpInstance.$vm || vueOptions, {
bubbles: true,
composed: true
});
}
function handleLink (event) {
if (event.detail.$mp) { // vm
if (!event.detail.$parent) {
event.detail.$parent = this.$vm;
event.detail.$parent.$children.push(event.detail);
event.detail.$root = this.$vm.$root;
}
} else { // vueOptions
if (!event.detail.parent) {
event.detail.parent = this.$vm;
}
}
}
function initPage$1 (pageOptions) {
return initComponent$1(pageOptions)
}
function initComponent$1 (componentOptions) { function initMocks (vm, mocks) {
componentOptions.methods.$getAppWebview = function () {
return plus.webview.getWebviewById(`${this.__wxWebviewId__}`)
};
return Component(componentOptions)
}
function initMocks (vm, mocks$$1) {
const mpInstance = vm.$mp[vm.mpType]; const mpInstance = vm.$mp[vm.mpType];
mocks$$1.forEach(mock => { mocks.forEach(mock => {
if (hasOwn(mpInstance, mock)) { if (hasOwn(mpInstance, mock)) {
vm[mock] = mpInstance[mock]; vm[mock] = mpInstance[mock];
} }
...@@ -443,12 +394,46 @@ function initMocks (vm, mocks$$1) { ...@@ -443,12 +394,46 @@ function initMocks (vm, mocks$$1) {
function initHooks (mpOptions, hooks) { function initHooks (mpOptions, hooks) {
hooks.forEach(hook => { hooks.forEach(hook => {
mpOptions[hook] = function (args) { mpOptions[hook] = function (args) {
return this.$vm.__call_hook(hook, args) return this.$vm && this.$vm.__call_hook(hook, args)
}; };
}); });
} }
function getData (vueOptions, context) { function initVueComponent (Vue$$1, vueOptions) {
vueOptions = vueOptions.default || vueOptions;
let VueComponent;
if (isFn(vueOptions)) {
VueComponent = vueOptions;
vueOptions = VueComponent.extendOptions;
} else {
VueComponent = Vue$$1.extend(vueOptions);
}
return [VueComponent, vueOptions]
}
function initSlots (vm, vueSlots) {
if (Array.isArray(vueSlots) && vueSlots.length) {
const $slots = Object.create(null);
vueSlots.forEach(slotName => {
$slots[slotName] = true;
});
vm.$scopedSlots = vm.$slots = $slots;
}
}
function initVueIds (vueIds, mpInstance) {
vueIds = (vueIds || '').split(',');
const len = vueIds.length;
if (len === 1) {
mpInstance._$vueId = vueIds[0];
} else if (len === 2) {
mpInstance._$vueId = vueIds[0];
mpInstance._$vuePid = vueIds[1];
}
}
function initData (vueOptions, context) {
let data = vueOptions.data || {}; let data = vueOptions.data || {};
const methods = vueOptions.methods || {}; const methods = vueOptions.methods || {};
...@@ -490,7 +475,7 @@ function createObserver (name) { ...@@ -490,7 +475,7 @@ function createObserver (name) {
} }
} }
function getBehaviors (vueOptions) { function initBehaviors (vueOptions, initBehavior) {
const vueBehaviors = vueOptions['behaviors']; const vueBehaviors = vueOptions['behaviors'];
const vueExtends = vueOptions['extends']; const vueExtends = vueOptions['extends'];
const vueMixins = vueOptions['mixins']; const vueMixins = vueOptions['mixins'];
...@@ -519,7 +504,7 @@ function getBehaviors (vueOptions) { ...@@ -519,7 +504,7 @@ function getBehaviors (vueOptions) {
if (isPlainObject(vueExtends) && vueExtends.props) { if (isPlainObject(vueExtends) && vueExtends.props) {
behaviors.push( behaviors.push(
initBehavior({ initBehavior({
properties: getProperties(vueExtends.props, true) properties: initProperties(vueExtends.props, true)
}) })
); );
} }
...@@ -528,7 +513,7 @@ function getBehaviors (vueOptions) { ...@@ -528,7 +513,7 @@ function getBehaviors (vueOptions) {
if (isPlainObject(vueMixin) && vueMixin.props) { if (isPlainObject(vueMixin) && vueMixin.props) {
behaviors.push( behaviors.push(
initBehavior({ initBehavior({
properties: getProperties(vueMixin.props, true) properties: initProperties(vueMixin.props, true)
}) })
); );
} }
...@@ -545,9 +530,13 @@ function parsePropType (key, type, defaultValue, file) { ...@@ -545,9 +530,13 @@ function parsePropType (key, type, defaultValue, file) {
return type return type
} }
function getProperties (props, isBehavior = false, file = '') { function initProperties (props, isBehavior = false, file = '') {
const properties = {}; const properties = {};
if (!isBehavior) { if (!isBehavior) {
properties.vueId = {
type: String,
value: ''
};
properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
type: null, type: null,
value: [], value: [],
...@@ -798,23 +787,14 @@ function handleEvent (event) { ...@@ -798,23 +787,14 @@ function handleEvent (event) {
const hooks = [ const hooks = [
'onHide', 'onHide',
'onError', 'onError',
'onPageNotFound', 'onPageNotFound'
'onUniNViewMessage'
]; ];
function initVm (vm) { function parseBaseApp (vm, {
if (this.$vm) { // 百度竟然 onShow 在 onLaunch 之前? mocks,
return initRefs
} }) {
Vue.prototype.mpHost = "app-plus";
this.$vm = vm;
this.$vm.$mp = {
app: this
};
}
function createApp (vm) {
Vue.mixin({ Vue.mixin({
beforeCreate () { beforeCreate () {
...@@ -838,183 +818,182 @@ function createApp (vm) { ...@@ -838,183 +818,182 @@ function createApp (vm) {
initRefs(this); initRefs(this);
initMocks(this, mocks); initMocks(this, mocks);
} }
},
created () { // 处理 injections
this.__init_injections(this);
this.__init_provide(this);
} }
}); });
const appOptions = { const appOptions = {
onLaunch (args) { onLaunch (args) {
initVm.call(this, vm);
this.$vm = vm;
this.$vm.$mp = {
app: this
};
this.$vm.$scope = this;
this.$vm._isMounted = true; this.$vm._isMounted = true;
this.$vm.__call_hook('mounted'); this.$vm.__call_hook('mounted', args);
this.$vm.__call_hook('onLaunch', args); this.$vm.__call_hook('onLaunch', args);
},
onShow (args) {
initVm.call(this, vm);
this.$vm.__call_hook('onShow', args);
} }
}; };
// 兼容旧版本 globalData // 兼容旧版本 globalData
appOptions.globalData = vm.$options.globalData || {}; appOptions.globalData = vm.$options.globalData || {};
initHooks(appOptions, hooks); // 延迟执行,因为 App 的注册在 main.js 之前,可能导致生命周期内 Vue 原型上开发者注册的属性无法访问 initHooks(appOptions, hooks);
App(appOptions);
return vm return appOptions
} }
const hooks$1 = [ const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__'];
'onShow',
'onHide',
'onPullDownRefresh',
'onReachBottom',
'onShareAppMessage',
'onPageScroll',
'onResize',
'onTabItemTap',
'onBackPress',
'onNavigationBarButtonTap',
'onNavigationBarSearchInputChanged',
'onNavigationBarSearchInputConfirmed',
'onNavigationBarSearchInputClicked'
];
function initVm$1 (VueComponent) { // 百度的 onLoad 触发在 attached 之前 function findVmByVueId (vm, vuePid) {
if (this.$vm) { const $children = vm.$children;
return // 优先查找直属
let parentVm = $children.find(childVm => childVm.$scope._$vueId === vuePid);
if (parentVm) {
return parentVm
} }
// 反向递归查找
for (let i = $children.length - 1; i >= 0; i--) {
parentVm = findVmByVueId($children[i], vuePid);
if (parentVm) {
return parentVm
}
}
}
this.$vm = new VueComponent({ function initBehavior (options) {
mpType: 'page', return Behavior(options)
mpInstance: this
});
this.$vm.__call_hook('created');
this.$vm.$mount();
} }
function createPage (vueOptions) { function isPage () {
vueOptions = vueOptions.default || vueOptions; return !!this.route
let VueComponent; }
if (isFn(vueOptions)) {
VueComponent = vueOptions;
vueOptions = VueComponent.extendOptions;
} else {
VueComponent = Vue.extend(vueOptions);
}
const pageOptions = {
options: {
multipleSlots: true,
addGlobalClass: true
},
data: getData(vueOptions, Vue.prototype),
lifetimes: { // 当页面作为组件时
attached () {
initVm$1.call(this, VueComponent);
},
ready () {
this.$vm.__call_hook('beforeMount');
this.$vm._isMounted = true;
this.$vm.__call_hook('mounted');
this.$vm.__call_hook('onReady');
},
detached () {
this.$vm.$destroy();
}
},
methods: { // 作为页面时
onLoad (args) {
initVm$1.call(this, VueComponent);
this.$vm.$mp.query = args; // 又要兼容 mpvue
this.$vm.__call_hook('onLoad', args); // 开发者可能会在 onLoad 时赋值,提前到 mount 之前
},
onUnload () {
this.$vm.__call_hook('onUnload');
},
__e: handleEvent,
__l: handleLink
}
};
initHooks(pageOptions.methods, hooks$1); function initRelation (detail) {
this.triggerEvent('__l', detail);
}
return initPage$1(pageOptions, vueOptions) function initRefs (vm) {
} const mpInstance = vm.$scope;
Object.defineProperty(vm, '$refs', {
function initVm$2 (VueComponent) { get () {
if (this.$vm) { const $refs = {};
return const components = mpInstance.selectAllComponents('.vue-ref');
} components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
forComponents.forEach(component => {
const ref = component.dataset.ref;
if (!$refs[ref]) {
$refs[ref] = [];
}
$refs[ref].push(component.$vm || component);
});
return $refs
}
});
}
const properties = this.properties; function handleLink (event) {
const {
vuePid,
vueOptions
} = event.detail || event.value; // detail 是微信,value 是百度(dipatch)
const options = { let parentVm;
mpType: 'component',
mpInstance: this,
propsData: properties
};
// 初始化 vue 实例
this.$vm = new VueComponent(options);
// 处理$slots,$scopedSlots(暂不支持动态变化$slots) if (vuePid) {
const vueSlots = properties.vueSlots; parentVm = findVmByVueId(this.$vm, vuePid);
if (Array.isArray(vueSlots) && vueSlots.length) {
const $slots = Object.create(null);
vueSlots.forEach(slotName => {
$slots[slotName] = true;
});
this.$vm.$scopedSlots = this.$vm.$slots = $slots;
} }
// 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并
// 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性
this.$vm.$mount();
}
function createComponent (vueOptions) { if (!parentVm) {
vueOptions = vueOptions.default || vueOptions; parentVm = this.$vm;
let VueComponent;
if (isFn(vueOptions)) {
VueComponent = vueOptions; // TODO form-field props.name,props.value
vueOptions = VueComponent.extendOptions;
} else {
VueComponent = Vue.extend(vueOptions);
} }
const behaviors = getBehaviors(vueOptions); vueOptions.parent = parentVm;
}
function parseApp (vm) {
return parseBaseApp(vm, {
mocks,
initRefs
})
}
const hooks$1 = [
'onUniNViewMessage'
];
function parseApp$1 (vm) {
const appOptions = parseApp(vm);
const properties = getProperties(vueOptions.props, false, vueOptions.__file); initHooks(appOptions, hooks$1);
return appOptions
}
function createApp (vm) {
App(parseApp$1(vm));
return vm
}
function parseBaseComponent (vueComponentOptions, {
isPage: isPage$$1,
initRelation: initRelation$$1
} = {}) {
let [VueComponent, vueOptions] = initVueComponent(Vue, vueComponentOptions);
const componentOptions = { const componentOptions = {
options: { options: {
multipleSlots: true, multipleSlots: true,
addGlobalClass: true addGlobalClass: true
}, },
data: getData(vueOptions, Vue.prototype), data: initData(vueOptions, Vue.prototype),
behaviors, behaviors: initBehaviors(vueOptions, initBehavior),
properties, properties: initProperties(vueOptions.props, false, vueOptions.__file),
lifetimes: { lifetimes: {
attached () { attached () {
initVm$2.call(this, VueComponent); const properties = this.properties;
const options = {
mpType: isPage$$1.call(this) ? 'page' : 'component',
mpInstance: this,
propsData: properties
};
initVueIds(properties.vueId, this);
// 处理父子关系
initRelation$$1.call(this, {
vuePid: this._$vuePid,
vueOptions: options
});
// 初始化 vue 实例
this.$vm = new VueComponent(options);
// 处理$slots,$scopedSlots(暂不支持动态变化$slots)
initSlots(this.$vm, properties.vueSlots);
// 触发首次 setData
this.$vm.$mount();
}, },
ready () { ready () {
initVm$2.call(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 // 当组件 props 默认值为 true,初始化时传入 false 会导致 created,ready 触发, 但 attached 不触发
triggerLink(this); // 处理 parent,children // https://developers.weixin.qq.com/community/develop/doc/00066ae2844cc0f8eb883e2a557800
if (this.$vm) {
// 补充生命周期 this.$vm._isMounted = true;
this.$vm.__call_hook('created'); this.$vm.__call_hook('mounted');
this.$vm.__call_hook('beforeMount'); this.$vm.__call_hook('onReady');
this.$vm._isMounted = true; } else {
this.$vm.__call_hook('mounted'); this.is && console.warn(this.is + ' is not attached');
this.$vm.__call_hook('onReady'); }
}, },
detached () { detached () {
this.$vm.$destroy(); this.$vm.$destroy();
...@@ -1022,7 +1001,7 @@ function createComponent (vueOptions) { ...@@ -1022,7 +1001,7 @@ function createComponent (vueOptions) {
}, },
pageLifetimes: { pageLifetimes: {
show (args) { show (args) {
this.$vm.__call_hook('onPageShow', args); this.$vm && this.$vm.__call_hook('onPageShow', args);
}, },
hide () { hide () {
this.$vm && this.$vm.__call_hook('onPageHide'); this.$vm && this.$vm.__call_hook('onPageHide');
...@@ -1032,12 +1011,93 @@ function createComponent (vueOptions) { ...@@ -1032,12 +1011,93 @@ function createComponent (vueOptions) {
} }
}, },
methods: { methods: {
__e: handleEvent, __l: handleLink,
__l: handleLink __e: handleEvent
} }
}; };
return initComponent$1(componentOptions, vueOptions) if (isPage$$1) {
return componentOptions
}
return [componentOptions, VueComponent]
}
function parseComponent (vueComponentOptions) {
return parseBaseComponent(vueComponentOptions, {
isPage,
initRelation
})
}
function parseComponent$1 (vueComponentOptions) {
const componentOptions = parseComponent(vueComponentOptions);
componentOptions.methods.$getAppWebview = function () {
return plus.webview.getWebviewById(`${this.__wxWebviewId__}`)
};
return componentOptions
}
const hooks$2 = [
'onShow',
'onHide',
'onUnload'
];
hooks$2.push(...PAGE_EVENT_HOOKS);
function parseBasePage (vuePageOptions, {
isPage,
initRelation
}) {
const pageOptions = parseComponent$1(vuePageOptions, {
isPage,
initRelation
});
initHooks(pageOptions.methods, hooks$2);
pageOptions.methods.onLoad = function (args) {
this.$vm.$mp.query = args; // 兼容 mpvue
this.$vm.__call_hook('onLoad', args);
};
return pageOptions
}
function parsePage (vuePageOptions) {
return parseBasePage(vuePageOptions, {
isPage,
initRelation
})
}
const hooks$3 = [
'onBackPress',
'onNavigationBarButtonTap',
'onNavigationBarSearchInputChanged',
'onNavigationBarSearchInputConfirmed',
'onNavigationBarSearchInputClicked'
];
function parsePage$1 (vuePageOptions) {
const pageOptions = parsePage(vuePageOptions);
initHooks(pageOptions.methods, hooks$3);
return pageOptions
}
function createPage (vuePageOptions) {
{
return Component(parsePage$1(vuePageOptions))
}
}
function createComponent (vueOptions) {
{
return Component(parseComponent$1(vueOptions))
}
} }
todos.forEach(todoApi => { todos.forEach(todoApi => {
......
...@@ -88,16 +88,18 @@ function promisify (name, api) { ...@@ -88,16 +88,18 @@ function promisify (name, api) {
success: resolve, success: resolve,
fail: reject fail: reject
}), ...params); }), ...params);
/* eslint-disable no-extend-native */ /* eslint-disable no-extend-native */
Promise.prototype.finally = function (callback) { if (!Promise.prototype.finally) {
const promise = this.constructor; Promise.prototype.finally = function (callback) {
return this.then( const promise = this.constructor;
value => promise.resolve(callback()).then(() => value), return this.then(
reason => promise.resolve(callback()).then(() => { value => promise.resolve(callback()).then(() => value),
throw reason reason => promise.resolve(callback()).then(() => {
}) throw reason
) })
}; )
};
}
})) }))
} }
} }
...@@ -469,65 +471,15 @@ Component = function (options = {}) { ...@@ -469,65 +471,15 @@ Component = function (options = {}) {
return MPComponent(options) return MPComponent(options)
}; };
function initBehavior (options) { const PAGE_EVENT_HOOKS = [
return Behavior(options) 'onPullDownRefresh',
} 'onReachBottom',
function initRefs (vm) { 'onShareAppMessage',
const mpInstance = vm.$scope; 'onPageScroll',
Object.defineProperty(vm, '$refs', { 'onResize',
get () { 'onTabItemTap'
const $refs = {}; ];
const components = mpInstance.selectAllComponents('.vue-ref');
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
forComponents.forEach(component => {
const ref = component.dataset.ref;
if (!$refs[ref]) {
$refs[ref] = [];
}
$refs[ref].push(component.$vm || component);
});
return $refs
}
});
}
const mocks$1 = ['nodeId'];
function initPage$1 (pageOptions) {
return initComponent$1(pageOptions)
}
function initComponent$1 (componentOptions) {
componentOptions.messages = {
'__l': handleLink$1
};
return Component(componentOptions)
}
function triggerLink$1 (mpInstance, vueOptions) {
mpInstance.dispatch('__l', mpInstance.$vm || vueOptions);
}
function handleLink$1 (event) {
const target = event.value;
if (target.$mp) {
if (!target.$parent) {
target.$parent = this.$vm;
target.$parent.$children.push(target);
target.$root = this.$vm.$root;
}
} else {
if (!target.parent) {
target.parent = this.$vm;
}
}
}
function initMocks (vm, mocks) { function initMocks (vm, mocks) {
const mpInstance = vm.$mp[vm.mpType]; const mpInstance = vm.$mp[vm.mpType];
mocks.forEach(mock => { mocks.forEach(mock => {
...@@ -540,12 +492,46 @@ function initMocks (vm, mocks) { ...@@ -540,12 +492,46 @@ function initMocks (vm, mocks) {
function initHooks (mpOptions, hooks) { function initHooks (mpOptions, hooks) {
hooks.forEach(hook => { hooks.forEach(hook => {
mpOptions[hook] = function (args) { mpOptions[hook] = function (args) {
return this.$vm.__call_hook(hook, args) return this.$vm && this.$vm.__call_hook(hook, args)
}; };
}); });
} }
function getData (vueOptions, context) { function initVueComponent (Vue$$1, vueOptions) {
vueOptions = vueOptions.default || vueOptions;
let VueComponent;
if (isFn(vueOptions)) {
VueComponent = vueOptions;
vueOptions = VueComponent.extendOptions;
} else {
VueComponent = Vue$$1.extend(vueOptions);
}
return [VueComponent, vueOptions]
}
function initSlots (vm, vueSlots) {
if (Array.isArray(vueSlots) && vueSlots.length) {
const $slots = Object.create(null);
vueSlots.forEach(slotName => {
$slots[slotName] = true;
});
vm.$scopedSlots = vm.$slots = $slots;
}
}
function initVueIds (vueIds, mpInstance) {
vueIds = (vueIds || '').split(',');
const len = vueIds.length;
if (len === 1) {
mpInstance._$vueId = vueIds[0];
} else if (len === 2) {
mpInstance._$vueId = vueIds[0];
mpInstance._$vuePid = vueIds[1];
}
}
function initData (vueOptions, context) {
let data = vueOptions.data || {}; let data = vueOptions.data || {};
const methods = vueOptions.methods || {}; const methods = vueOptions.methods || {};
...@@ -587,7 +573,7 @@ function createObserver (name) { ...@@ -587,7 +573,7 @@ function createObserver (name) {
} }
} }
function getBehaviors (vueOptions) { function initBehaviors (vueOptions, initBehavior) {
const vueBehaviors = vueOptions['behaviors']; const vueBehaviors = vueOptions['behaviors'];
const vueExtends = vueOptions['extends']; const vueExtends = vueOptions['extends'];
const vueMixins = vueOptions['mixins']; const vueMixins = vueOptions['mixins'];
...@@ -616,7 +602,7 @@ function getBehaviors (vueOptions) { ...@@ -616,7 +602,7 @@ function getBehaviors (vueOptions) {
if (isPlainObject(vueExtends) && vueExtends.props) { if (isPlainObject(vueExtends) && vueExtends.props) {
behaviors.push( behaviors.push(
initBehavior({ initBehavior({
properties: getProperties(vueExtends.props, true) properties: initProperties(vueExtends.props, true)
}) })
); );
} }
...@@ -625,7 +611,7 @@ function getBehaviors (vueOptions) { ...@@ -625,7 +611,7 @@ function getBehaviors (vueOptions) {
if (isPlainObject(vueMixin) && vueMixin.props) { if (isPlainObject(vueMixin) && vueMixin.props) {
behaviors.push( behaviors.push(
initBehavior({ initBehavior({
properties: getProperties(vueMixin.props, true) properties: initProperties(vueMixin.props, true)
}) })
); );
} }
...@@ -658,9 +644,13 @@ function parsePropType (key, type, defaultValue, file) { ...@@ -658,9 +644,13 @@ function parsePropType (key, type, defaultValue, file) {
return type return type
} }
function getProperties (props, isBehavior = false, file = '') { function initProperties (props, isBehavior = false, file = '') {
const properties = {}; const properties = {};
if (!isBehavior) { if (!isBehavior) {
properties.vueId = {
type: String,
value: ''
};
properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
type: null, type: null,
value: [], value: [],
...@@ -916,42 +906,19 @@ function handleEvent (event) { ...@@ -916,42 +906,19 @@ function handleEvent (event) {
}); });
} }
}); });
}
function baiduComponentDestroy ($vm) {
$vm.$children.forEach(childVm => {
childVm.$mp.component.detached();
});
$vm.$mp.component.detached();
}
function baiduPageDestroy ($vm) {
$vm.$destroy();
$vm.$children.forEach(childVm => {
baiduComponentDestroy(childVm);
});
} }
const hooks = [ const hooks = [
'onHide', 'onHide',
'onError', 'onError',
'onPageNotFound', 'onPageNotFound'
'onUniNViewMessage'
]; ];
function initVm (vm) { function parseBaseApp (vm, {
if (this.$vm) { // 百度竟然 onShow 在 onLaunch 之前? mocks,
return initRefs
} }) {
Vue.prototype.mpHost = "mp-baidu";
this.$vm = vm;
this.$vm.$mp = {
app: this
};
}
function createApp (vm) {
Vue.mixin({ Vue.mixin({
beforeCreate () { beforeCreate () {
...@@ -973,195 +940,172 @@ function createApp (vm) { ...@@ -973,195 +940,172 @@ function createApp (vm) {
if (this.mpType !== 'app') { if (this.mpType !== 'app') {
initRefs(this); initRefs(this);
initMocks(this, mocks$1); initMocks(this, mocks);
} }
},
created () { // 处理 injections
this.__init_injections(this);
this.__init_provide(this);
} }
}); });
const appOptions = { const appOptions = {
onLaunch (args) { onLaunch (args) {
initVm.call(this, vm);
this.$vm = vm;
this.$vm.$mp = {
app: this
};
this.$vm.$scope = this;
this.$vm._isMounted = true; this.$vm._isMounted = true;
this.$vm.__call_hook('mounted'); this.$vm.__call_hook('mounted', args);
this.$vm.__call_hook('onLaunch', args); this.$vm.__call_hook('onLaunch', args);
},
onShow (args) {
initVm.call(this, vm);
this.$vm.__call_hook('onShow', args);
} }
}; };
// 兼容旧版本 globalData // 兼容旧版本 globalData
appOptions.globalData = vm.$options.globalData || {}; appOptions.globalData = vm.$options.globalData || {};
initHooks(appOptions, hooks); // 延迟执行,因为 App 的注册在 main.js 之前,可能导致生命周期内 Vue 原型上开发者注册的属性无法访问 initHooks(appOptions, hooks);
App(appOptions);
return vm return appOptions
} }
const hooks$1 = [ function findVmByVueId (vm, vuePid) {
'onShow', const $children = vm.$children;
'onHide', // 优先查找直属
'onPullDownRefresh', let parentVm = $children.find(childVm => childVm.$scope._$vueId === vuePid);
'onReachBottom', if (parentVm) {
'onShareAppMessage', return parentVm
'onPageScroll',
'onResize',
'onTabItemTap',
'onBackPress',
'onNavigationBarButtonTap',
'onNavigationBarSearchInputChanged',
'onNavigationBarSearchInputConfirmed',
'onNavigationBarSearchInputClicked'
];
function initVm$1 (VueComponent) { // 百度的 onLoad 触发在 attached 之前
if (this.$vm) {
return
} }
// 反向递归查找
this.$vm = new VueComponent({ for (let i = $children.length - 1; i >= 0; i--) {
mpType: 'page', parentVm = findVmByVueId($children[i], vuePid);
mpInstance: this if (parentVm) {
}); return parentVm
}
{
this.$vm.$baiduComponentInstances = Object.create(null);
} }
}
this.$vm.__call_hook('created'); function initBehavior (options) {
this.$vm.$mount(); return Behavior(options)
} }
function createPage (vueOptions) { function initRefs (vm) {
vueOptions = vueOptions.default || vueOptions; const mpInstance = vm.$scope;
let VueComponent; Object.defineProperty(vm, '$refs', {
if (isFn(vueOptions)) { get () {
VueComponent = vueOptions; const $refs = {};
vueOptions = VueComponent.extendOptions; const components = mpInstance.selectAllComponents('.vue-ref');
} else { components.forEach(component => {
VueComponent = Vue.extend(vueOptions); const ref = component.dataset.ref;
} $refs[ref] = component.$vm || component;
const pageOptions = { });
options: { const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
multipleSlots: true, forComponents.forEach(component => {
addGlobalClass: true const ref = component.dataset.ref;
}, if (!$refs[ref]) {
data: getData(vueOptions, Vue.prototype), $refs[ref] = [];
lifetimes: { // 当页面作为组件时
attached () {
initVm$1.call(this, VueComponent);
},
ready () {
this.$vm.__call_hook('beforeMount');
this.$vm._isMounted = true;
this.$vm.__call_hook('mounted');
this.$vm.__call_hook('onReady');
},
detached () {
this.$vm.$destroy();
}
},
methods: { // 作为页面时
onLoad (args) {
initVm$1.call(this, VueComponent);
{ // 百度当组件作为页面时 pageinstancce 不是原来组件的 instance
this.pageinstance.$vm = this.$vm;
}
this.$vm.$mp.query = args; // 又要兼容 mpvue
this.$vm.__call_hook('onLoad', args); // 开发者可能会在 onLoad 时赋值,提前到 mount 之前
},
onUnload () {
this.$vm.__call_hook('onUnload');
{ // 百度组件不会在页面 unload 时触发 detached
baiduPageDestroy(this.$vm);
} }
}, $refs[ref].push(component.$vm || component);
__e: handleEvent, });
__l: handleLink$1 return $refs
} }
}; });
}
initHooks(pageOptions.methods, hooks$1);
return initPage$1(pageOptions, vueOptions)
}
function initVm$2 (VueComponent) {
if (this.$vm) {
return
}
const properties = this.properties; function handleLink (event) {
const {
vuePid,
vueOptions
} = event.detail || event.value; // detail 是微信,value 是百度(dipatch)
const options = { let parentVm;
mpType: 'component',
mpInstance: this,
propsData: properties
};
// 初始化 vue 实例
this.$vm = new VueComponent(options);
// 处理$slots,$scopedSlots(暂不支持动态变化$slots) if (vuePid) {
const vueSlots = properties.vueSlots; parentVm = findVmByVueId(this.$vm, vuePid);
if (Array.isArray(vueSlots) && vueSlots.length) {
const $slots = Object.create(null);
vueSlots.forEach(slotName => {
$slots[slotName] = true;
});
this.$vm.$scopedSlots = this.$vm.$slots = $slots;
} }
// 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并
// 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性
this.$vm.$mount();
}
function createComponent (vueOptions) {
vueOptions = vueOptions.default || vueOptions;
let VueComponent; if (!parentVm) {
if (isFn(vueOptions)) { parentVm = this.$vm;
VueComponent = vueOptions; // TODO form-field props.name,props.value
vueOptions = VueComponent.extendOptions;
} else {
VueComponent = Vue.extend(vueOptions);
} }
const behaviors = getBehaviors(vueOptions); vueOptions.parent = parentVm;
}
const mocks$1 = ['nodeId'];
function isPage$1 () {
return !this.ownerId
}
const properties = getProperties(vueOptions.props, false, vueOptions.__file); function initRelation$1 (detail) {
this.dispatch('__l', detail);
}
function parseApp (vm) {
return parseBaseApp(vm, {
mocks: mocks$1,
initRefs
})
}
function createApp (vm) {
App(parseApp(vm));
return vm
}
function parseBaseComponent (vueComponentOptions, {
isPage: isPage$$1,
initRelation: initRelation$$1
} = {}) {
let [VueComponent, vueOptions] = initVueComponent(Vue, vueComponentOptions);
const componentOptions = { const componentOptions = {
options: { options: {
multipleSlots: true, multipleSlots: true,
addGlobalClass: true addGlobalClass: true
}, },
data: getData(vueOptions, Vue.prototype), data: initData(vueOptions, Vue.prototype),
behaviors, behaviors: initBehaviors(vueOptions, initBehavior),
properties, properties: initProperties(vueOptions.props, false, vueOptions.__file),
lifetimes: { lifetimes: {
attached () { attached () {
initVm$2.call(this, VueComponent); const properties = this.properties;
const options = {
mpType: isPage$$1.call(this) ? 'page' : 'component',
mpInstance: this,
propsData: properties
};
initVueIds(properties.vueId, this);
// 处理父子关系
initRelation$$1.call(this, {
vuePid: this._$vuePid,
vueOptions: options
});
// 初始化 vue 实例
this.$vm = new VueComponent(options);
// 处理$slots,$scopedSlots(暂不支持动态变化$slots)
initSlots(this.$vm, properties.vueSlots);
// 触发首次 setData
this.$vm.$mount();
}, },
ready () { ready () {
initVm$2.call(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 // 当组件 props 默认值为 true,初始化时传入 false 会导致 created,ready 触发, 但 attached 不触发
triggerLink$1(this); // 处理 parent,children // https://developers.weixin.qq.com/community/develop/doc/00066ae2844cc0f8eb883e2a557800
if (this.$vm) {
// 补充生命周期 this.$vm._isMounted = true;
this.$vm.__call_hook('created'); this.$vm.__call_hook('mounted');
this.$vm.__call_hook('beforeMount'); this.$vm.__call_hook('onReady');
this.$vm._isMounted = true; } else {
this.$vm.__call_hook('mounted'); this.is && console.warn(this.is + ' is not attached');
this.$vm.__call_hook('onReady'); }
}, },
detached () { detached () {
this.$vm.$destroy(); this.$vm.$destroy();
...@@ -1169,7 +1113,7 @@ function createComponent (vueOptions) { ...@@ -1169,7 +1113,7 @@ function createComponent (vueOptions) {
}, },
pageLifetimes: { pageLifetimes: {
show (args) { show (args) {
this.$vm.__call_hook('onPageShow', args); this.$vm && this.$vm.__call_hook('onPageShow', args);
}, },
hide () { hide () {
this.$vm && this.$vm.__call_hook('onPageHide'); this.$vm && this.$vm.__call_hook('onPageHide');
...@@ -1179,12 +1123,114 @@ function createComponent (vueOptions) { ...@@ -1179,12 +1123,114 @@ function createComponent (vueOptions) {
} }
}, },
methods: { methods: {
__e: handleEvent, __l: handleLink,
__l: handleLink$1 __e: handleEvent
}
};
if (isPage$$1) {
return componentOptions
}
return [componentOptions, VueComponent]
}
function parseComponent (vueOptions) {
const componentOptions = parseBaseComponent(vueOptions, {
isPage: isPage$1,
initRelation: initRelation$1
});
const oldAttached = componentOptions.lifetimes.attached;
componentOptions.lifetimes.attached = function attached () {
oldAttached.call(this);
if (isPage$1.call(this)) { // 百度 onLoad 在 attached 之前触发
// 百度 当组件作为页面时 pageinstancce 不是原来组件的 instance
this.pageinstance.$vm = this.$vm;
this.$vm.$mp.query = this.pageinstance._$args; // 兼容 mpvue
this.$vm.__call_hook('onLoad', this.pageinstance._$args);
} }
}; };
return initComponent$1(componentOptions, vueOptions) componentOptions.messages = {
'__l': componentOptions.methods['__l']
};
delete componentOptions.methods['__l'];
return componentOptions
}
const hooks$1 = [
'onShow',
'onHide',
'onUnload'
];
hooks$1.push(...PAGE_EVENT_HOOKS);
function parseBasePage (vuePageOptions, {
isPage,
initRelation
}) {
const pageOptions = parseComponent(vuePageOptions, {
isPage,
initRelation
});
initHooks(pageOptions.methods, hooks$1);
pageOptions.methods.onLoad = function (args) {
this.$vm.$mp.query = args; // 兼容 mpvue
this.$vm.__call_hook('onLoad', args);
};
return pageOptions
}
function detached ($vm) {
$vm.$children.forEach(childVm => {
childVm.$scope.detached();
});
$vm.$scope.detached();
}
function onPageUnload ($vm) {
$vm.$destroy();
$vm.$children.forEach(childVm => {
detached(childVm);
});
}
function parsePage (vuePageOptions) {
const pageOptions = parseBasePage(vuePageOptions, {
isPage: isPage$1,
initRelation: initRelation$1
});
pageOptions.methods.onLoad = function onLoad (args) {
// 百度 onLoad 在 attached 之前触发,先存储 args, 在 attached 里边触发 onLoad
this.pageinstance._$args = args;
};
pageOptions.methods.onUnload = function onUnload () {
this.$vm.__call_hook('onUnload');
onPageUnload(this.$vm);
};
return pageOptions
}
function createPage (vuePageOptions) {
{
return Component(parsePage(vuePageOptions))
}
}
function createComponent (vueOptions) {
{
return Component(parseComponent(vueOptions))
}
} }
todos.forEach(todoApi => { todos.forEach(todoApi => {
......
...@@ -88,16 +88,18 @@ function promisify (name, api) { ...@@ -88,16 +88,18 @@ function promisify (name, api) {
success: resolve, success: resolve,
fail: reject fail: reject
}), ...params); }), ...params);
/* eslint-disable no-extend-native */ /* eslint-disable no-extend-native */
Promise.prototype.finally = function (callback) { if (!Promise.prototype.finally) {
const promise = this.constructor; Promise.prototype.finally = function (callback) {
return this.then( const promise = this.constructor;
value => promise.resolve(callback()).then(() => value), return this.then(
reason => promise.resolve(callback()).then(() => { value => promise.resolve(callback()).then(() => value),
throw reason reason => promise.resolve(callback()).then(() => {
}) throw reason
) })
}; )
};
}
})) }))
} }
} }
...@@ -514,77 +516,15 @@ Component = function (options = {}) { ...@@ -514,77 +516,15 @@ Component = function (options = {}) {
return MPComponent(options) return MPComponent(options)
}; };
function initBehavior (options) { const PAGE_EVENT_HOOKS = [
return Behavior(options) 'onPullDownRefresh',
} 'onReachBottom',
'onShareAppMessage',
const instances = Object.create(null); 'onPageScroll',
'onResize',
const mocks$1 = ['__route__', '__webviewId__', '__nodeid__']; 'onTabItemTap'
];
function initPage$1 (pageOptions) {
return initComponent$1(pageOptions)
}
function initComponent$1 (componentOptions) {
if (componentOptions.properties) { // ref
componentOptions.properties.vueRef = {
type: String,
value: ''
};
}
return Component(componentOptions)
}
function initRefs$1 (vm) {
const mpInstance = vm.$scope;
mpInstance.selectAllComponents('.vue-ref', (components) => {
components.forEach(component => {
const ref = component.data.vueRef; // 头条的组件 dataset 竟然是空的
vm.$refs[ref] = component.$vm || component;
});
});
mpInstance.selectAllComponents('.vue-ref-in-for', (forComponents) => {
forComponents.forEach(component => {
const ref = component.data.vueRef;
if (!vm.$refs[ref]) {
vm.$refs[ref] = [];
}
vm.$refs[ref].push(component.$vm || component);
});
});
}
function triggerLink$1 (mpInstance) {
const nodeId = mpInstance.__nodeid__ + '';
const webviewId = mpInstance.__webviewId__ + '';
instances[webviewId + '_' + nodeId] = mpInstance.$vm;
mpInstance.triggerEvent('__l', {
nodeId,
webviewId
}, {
bubbles: true,
composed: true
});
}
// TODO 目前有 bug,composed 不生效
function handleLink$1 (event) {
const nodeId = event.detail.nodeId;
const webviewId = event.detail.webviewId;
const childVm = instances[webviewId + '_' + nodeId];
if (childVm) {
childVm.$parent = this.$vm;
childVm.$parent.$children.push(event.detail);
childVm.$root = this.$vm.$root;
delete instances[webviewId + '_' + nodeId];
}
}
function initMocks (vm, mocks) { function initMocks (vm, mocks) {
const mpInstance = vm.$mp[vm.mpType]; const mpInstance = vm.$mp[vm.mpType];
mocks.forEach(mock => { mocks.forEach(mock => {
...@@ -597,12 +537,46 @@ function initMocks (vm, mocks) { ...@@ -597,12 +537,46 @@ function initMocks (vm, mocks) {
function initHooks (mpOptions, hooks) { function initHooks (mpOptions, hooks) {
hooks.forEach(hook => { hooks.forEach(hook => {
mpOptions[hook] = function (args) { mpOptions[hook] = function (args) {
return this.$vm.__call_hook(hook, args) return this.$vm && this.$vm.__call_hook(hook, args)
}; };
}); });
} }
function getData (vueOptions, context) { function initVueComponent (Vue$$1, vueOptions) {
vueOptions = vueOptions.default || vueOptions;
let VueComponent;
if (isFn(vueOptions)) {
VueComponent = vueOptions;
vueOptions = VueComponent.extendOptions;
} else {
VueComponent = Vue$$1.extend(vueOptions);
}
return [VueComponent, vueOptions]
}
function initSlots (vm, vueSlots) {
if (Array.isArray(vueSlots) && vueSlots.length) {
const $slots = Object.create(null);
vueSlots.forEach(slotName => {
$slots[slotName] = true;
});
vm.$scopedSlots = vm.$slots = $slots;
}
}
function initVueIds (vueIds, mpInstance) {
vueIds = (vueIds || '').split(',');
const len = vueIds.length;
if (len === 1) {
mpInstance._$vueId = vueIds[0];
} else if (len === 2) {
mpInstance._$vueId = vueIds[0];
mpInstance._$vuePid = vueIds[1];
}
}
function initData (vueOptions, context) {
let data = vueOptions.data || {}; let data = vueOptions.data || {};
const methods = vueOptions.methods || {}; const methods = vueOptions.methods || {};
...@@ -644,7 +618,7 @@ function createObserver (name) { ...@@ -644,7 +618,7 @@ function createObserver (name) {
} }
} }
function getBehaviors (vueOptions) { function initBehaviors (vueOptions, initBehavior) {
const vueBehaviors = vueOptions['behaviors']; const vueBehaviors = vueOptions['behaviors'];
const vueExtends = vueOptions['extends']; const vueExtends = vueOptions['extends'];
const vueMixins = vueOptions['mixins']; const vueMixins = vueOptions['mixins'];
...@@ -673,7 +647,7 @@ function getBehaviors (vueOptions) { ...@@ -673,7 +647,7 @@ function getBehaviors (vueOptions) {
if (isPlainObject(vueExtends) && vueExtends.props) { if (isPlainObject(vueExtends) && vueExtends.props) {
behaviors.push( behaviors.push(
initBehavior({ initBehavior({
properties: getProperties(vueExtends.props, true) properties: initProperties(vueExtends.props, true)
}) })
); );
} }
...@@ -682,7 +656,7 @@ function getBehaviors (vueOptions) { ...@@ -682,7 +656,7 @@ function getBehaviors (vueOptions) {
if (isPlainObject(vueMixin) && vueMixin.props) { if (isPlainObject(vueMixin) && vueMixin.props) {
behaviors.push( behaviors.push(
initBehavior({ initBehavior({
properties: getProperties(vueMixin.props, true) properties: initProperties(vueMixin.props, true)
}) })
); );
} }
...@@ -699,9 +673,13 @@ function parsePropType (key, type, defaultValue, file) { ...@@ -699,9 +673,13 @@ function parsePropType (key, type, defaultValue, file) {
return type return type
} }
function getProperties (props, isBehavior = false, file = '') { function initProperties (props, isBehavior = false, file = '') {
const properties = {}; const properties = {};
if (!isBehavior) { if (!isBehavior) {
properties.vueId = {
type: String,
value: ''
};
properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
type: null, type: null,
value: [], value: [],
...@@ -952,23 +930,14 @@ function handleEvent (event) { ...@@ -952,23 +930,14 @@ function handleEvent (event) {
const hooks = [ const hooks = [
'onHide', 'onHide',
'onError', 'onError',
'onPageNotFound', 'onPageNotFound'
'onUniNViewMessage'
]; ];
function initVm (vm) { function parseBaseApp (vm, {
if (this.$vm) { // 百度竟然 onShow 在 onLaunch 之前? mocks,
return initRefs
} }) {
Vue.prototype.mpHost = "mp-toutiao";
this.$vm = vm;
this.$vm.$mp = {
app: this
};
}
function createApp (vm) {
Vue.mixin({ Vue.mixin({
beforeCreate () { beforeCreate () {
...@@ -989,186 +958,230 @@ function createApp (vm) { ...@@ -989,186 +958,230 @@ function createApp (vm) {
delete this.$options.mpInstance; delete this.$options.mpInstance;
if (this.mpType !== 'app') { if (this.mpType !== 'app') {
initRefs$1(this); initRefs(this);
initMocks(this, mocks$1); initMocks(this, mocks);
} }
},
created () { // 处理 injections
this.__init_injections(this);
this.__init_provide(this);
} }
}); });
const appOptions = { const appOptions = {
onLaunch (args) { onLaunch (args) {
initVm.call(this, vm);
this.$vm = vm;
this.$vm.$mp = {
app: this
};
this.$vm.$scope = this;
this.$vm._isMounted = true; this.$vm._isMounted = true;
this.$vm.__call_hook('mounted'); this.$vm.__call_hook('mounted', args);
this.$vm.__call_hook('onLaunch', args); this.$vm.__call_hook('onLaunch', args);
},
onShow (args) {
initVm.call(this, vm);
this.$vm.__call_hook('onShow', args);
} }
}; };
// 兼容旧版本 globalData // 兼容旧版本 globalData
appOptions.globalData = vm.$options.globalData || {}; appOptions.globalData = vm.$options.globalData || {};
initHooks(appOptions, hooks); // 延迟执行,因为 App 的注册在 main.js 之前,可能导致生命周期内 Vue 原型上开发者注册的属性无法访问 initHooks(appOptions, hooks);
App(appOptions);
return vm return appOptions
} }
const hooks$1 = [ function findVmByVueId (vm, vuePid) {
'onShow', const $children = vm.$children;
'onHide', // 优先查找直属
'onPullDownRefresh', let parentVm = $children.find(childVm => childVm.$scope._$vueId === vuePid);
'onReachBottom', if (parentVm) {
'onShareAppMessage', return parentVm
'onPageScroll',
'onResize',
'onTabItemTap',
'onBackPress',
'onNavigationBarButtonTap',
'onNavigationBarSearchInputChanged',
'onNavigationBarSearchInputConfirmed',
'onNavigationBarSearchInputClicked'
];
function initVm$1 (VueComponent) { // 百度的 onLoad 触发在 attached 之前
if (this.$vm) {
return
} }
// 反向递归查找
for (let i = $children.length - 1; i >= 0; i--) {
parentVm = findVmByVueId($children[i], vuePid);
if (parentVm) {
return parentVm
}
}
}
this.$vm = new VueComponent({ function initBehavior (options) {
mpType: 'page', return Behavior(options)
mpInstance: this
});
this.$vm.__call_hook('created');
this.$vm.$mount();
} }
function createPage (vueOptions) { function handleLink (event) {
vueOptions = vueOptions.default || vueOptions; const {
let VueComponent; vuePid,
if (isFn(vueOptions)) { vueOptions
VueComponent = vueOptions; } = event.detail || event.value; // detail 是微信,value 是百度(dipatch)
vueOptions = VueComponent.extendOptions;
} else { let parentVm;
VueComponent = Vue.extend(vueOptions);
if (vuePid) {
parentVm = findVmByVueId(this.$vm, vuePid);
} }
const pageOptions = {
options: {
multipleSlots: true,
addGlobalClass: true
},
data: getData(vueOptions, Vue.prototype),
lifetimes: { // 当页面作为组件时
attached () {
initVm$1.call(this, VueComponent);
},
ready () {
this.$vm.__call_hook('beforeMount');
this.$vm._isMounted = true;
this.$vm.__call_hook('mounted');
this.$vm.__call_hook('onReady');
},
detached () {
this.$vm.$destroy();
}
},
methods: { // 作为页面时
onLoad (args) {
initVm$1.call(this, VueComponent);
this.$vm.$mp.query = args; // 又要兼容 mpvue
this.$vm.__call_hook('onLoad', args); // 开发者可能会在 onLoad 时赋值,提前到 mount 之前
},
onUnload () {
this.$vm.__call_hook('onUnload');
},
__e: handleEvent,
__l: handleLink$1
}
};
initHooks(pageOptions.methods, hooks$1); if (!parentVm) {
parentVm = this.$vm;
}
return initPage$1(pageOptions, vueOptions) vueOptions.parent = parentVm;
} }
function initVm$2 (VueComponent) { const mocks$1 = ['__route__', '__webviewId__', '__nodeid__', '__nodeId__'];
if (this.$vm) {
function isPage$1 () {
return this.__nodeid__ === 0 || this.__nodeId__ === 0
}
function initRefs$1 (vm) {
const mpInstance = vm.$scope;
mpInstance.selectAllComponents('.vue-ref', (components) => {
components.forEach(component => {
const ref = component.dataset.ref;
vm.$refs[ref] = component.$vm || component;
});
});
mpInstance.selectAllComponents('.vue-ref-in-for', (forComponents) => {
forComponents.forEach(component => {
const ref = component.dataset.ref;
if (!vm.$refs[ref]) {
vm.$refs[ref] = [];
}
vm.$refs[ref].push(component.$vm || component);
});
});
}
const instances = Object.create(null);
function initRelation$1 ({
vuePid,
mpInstance
}) {
// 头条 triggerEvent 后,接收事件时机特别晚,已经到了 ready 之后
const nodeId = (mpInstance.__nodeId__ || mpInstance.__nodeid__) + '';
const webviewId = mpInstance.__webviewId__ + '';
instances[webviewId + '_' + nodeId] = mpInstance.$vm;
this.triggerEvent('__l', {
vuePid,
nodeId,
webviewId
});
}
function handleLink$1 ({
detail: {
vuePid,
nodeId,
webviewId
}
}) {
const vm = instances[webviewId + '_' + nodeId];
if (!vm) {
return return
} }
const properties = this.properties; let parentVm;
const options = { if (vuePid) {
mpType: 'component', parentVm = findVmByVueId(this.$vm, vuePid);
mpInstance: this, }
propsData: properties
};
// 初始化 vue 实例
this.$vm = new VueComponent(options);
// 处理$slots,$scopedSlots(暂不支持动态变化$slots) if (!parentVm) {
const vueSlots = properties.vueSlots; parentVm = this.$vm;
if (Array.isArray(vueSlots) && vueSlots.length) {
const $slots = Object.create(null);
vueSlots.forEach(slotName => {
$slots[slotName] = true;
});
this.$vm.$scopedSlots = this.$vm.$slots = $slots;
} }
// 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并
// 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性
this.$vm.$mount();
}
function createComponent (vueOptions) { vm.$parent = parentVm;
vueOptions = vueOptions.default || vueOptions; vm.$root = parentVm.$root;
parentVm.$children.push(vm);
let VueComponent; vm.__call_hook('created');
if (isFn(vueOptions)) { vm.__call_hook('beforeMount');
VueComponent = vueOptions; // TODO form-field props.name,props.value vm._isMounted = true;
vueOptions = VueComponent.extendOptions; vm.__call_hook('mounted');
} else { vm.__call_hook('onReady');
VueComponent = Vue.extend(vueOptions); }
}
function parseApp (vm) {
Vue.prototype._$fallback = true; // 降级(调整原 vue 的部分生命周期,如 created,beforeMount,inject,provide)
Vue.mixin({
created () { // 处理 injections,头条 triggerEvent 是异步,且触发时机很慢,故延迟 relation 设置
if (this.mpType !== 'app') {
initRefs$1(this);
const behaviors = getBehaviors(vueOptions); this.__init_injections(this);
this.__init_provide(this);
}
}
});
const properties = getProperties(vueOptions.props, false, vueOptions.__file); return parseBaseApp(vm, {
mocks: mocks$1,
initRefs: function () {} // attached 时,可能查询不到
})
}
function createApp (vm) {
App(parseApp(vm));
return vm
}
function parseBaseComponent (vueComponentOptions, {
isPage: isPage$$1,
initRelation: initRelation$$1
} = {}) {
let [VueComponent, vueOptions] = initVueComponent(Vue, vueComponentOptions);
const componentOptions = { const componentOptions = {
options: { options: {
multipleSlots: true, multipleSlots: true,
addGlobalClass: true addGlobalClass: true
}, },
data: getData(vueOptions, Vue.prototype), data: initData(vueOptions, Vue.prototype),
behaviors, behaviors: initBehaviors(vueOptions, initBehavior),
properties, properties: initProperties(vueOptions.props, false, vueOptions.__file),
lifetimes: { lifetimes: {
attached () { attached () {
initVm$2.call(this, VueComponent); const properties = this.properties;
const options = {
mpType: isPage$$1.call(this) ? 'page' : 'component',
mpInstance: this,
propsData: properties
};
initVueIds(properties.vueId, this);
// 处理父子关系
initRelation$$1.call(this, {
vuePid: this._$vuePid,
vueOptions: options
});
// 初始化 vue 实例
this.$vm = new VueComponent(options);
// 处理$slots,$scopedSlots(暂不支持动态变化$slots)
initSlots(this.$vm, properties.vueSlots);
// 触发首次 setData
this.$vm.$mount();
}, },
ready () { ready () {
initVm$2.call(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 // 当组件 props 默认值为 true,初始化时传入 false 会导致 created,ready 触发, 但 attached 不触发
triggerLink$1(this); // 处理 parent,children // https://developers.weixin.qq.com/community/develop/doc/00066ae2844cc0f8eb883e2a557800
if (this.$vm) {
// 补充生命周期 this.$vm._isMounted = true;
this.$vm.__call_hook('created'); this.$vm.__call_hook('mounted');
this.$vm.__call_hook('beforeMount'); this.$vm.__call_hook('onReady');
this.$vm._isMounted = true; } else {
this.$vm.__call_hook('mounted'); this.is && console.warn(this.is + ' is not attached');
this.$vm.__call_hook('onReady'); }
}, },
detached () { detached () {
this.$vm.$destroy(); this.$vm.$destroy();
...@@ -1176,7 +1189,7 @@ function createComponent (vueOptions) { ...@@ -1176,7 +1189,7 @@ function createComponent (vueOptions) {
}, },
pageLifetimes: { pageLifetimes: {
show (args) { show (args) {
this.$vm.__call_hook('onPageShow', args); this.$vm && this.$vm.__call_hook('onPageShow', args);
}, },
hide () { hide () {
this.$vm && this.$vm.__call_hook('onPageHide'); this.$vm && this.$vm.__call_hook('onPageHide');
...@@ -1186,12 +1199,113 @@ function createComponent (vueOptions) { ...@@ -1186,12 +1199,113 @@ function createComponent (vueOptions) {
} }
}, },
methods: { methods: {
__e: handleEvent, __l: handleLink,
__l: handleLink$1 __e: handleEvent
}
};
if (isPage$$1) {
return componentOptions
}
return [componentOptions, VueComponent]
}
function parseComponent (vueOptions) {
const [componentOptions, VueComponent] = parseBaseComponent(vueOptions);
componentOptions.lifetimes.attached = function attached () {
const properties = this.properties;
const options = {
mpType: isPage$1.call(this) ? 'page' : 'component',
mpInstance: this,
propsData: properties
};
initVueIds(properties.vueId, this);
// 初始化 vue 实例
this.$vm = new VueComponent(options);
// 处理$slots,$scopedSlots(暂不支持动态变化$slots)
initSlots(this.$vm, properties.vueSlots);
// 处理父子关系
initRelation$1.call(this, {
vuePid: this._$vuePid,
mpInstance: this
});
// 触发首次 setData
this.$vm.$mount();
};
// ready 比 handleLink 还早,初始化逻辑放到 handleLink 中
delete componentOptions.lifetimes.ready;
componentOptions.methods.__l = handleLink$1;
return componentOptions
}
const hooks$1 = [
'onShow',
'onHide',
'onUnload'
];
hooks$1.push(...PAGE_EVENT_HOOKS);
function parseBasePage (vuePageOptions, {
isPage,
initRelation
}) {
const pageOptions = parseComponent(vuePageOptions, {
isPage,
initRelation
});
initHooks(pageOptions.methods, hooks$1);
pageOptions.methods.onLoad = function (args) {
this.$vm.$mp.query = args; // 兼容 mpvue
this.$vm.__call_hook('onLoad', args);
};
return pageOptions
}
function parsePage (vuePageOptions) {
const pageOptions = parseBasePage(vuePageOptions, {
isPage: isPage$1,
initRelation: initRelation$1
});
// 页面需要在 ready 中触发,其他组件是在 handleLink 中触发
pageOptions.lifetimes.ready = function ready () {
if (this.$vm && this.$vm.mpType === 'page') {
this.$vm.__call_hook('created');
this.$vm.__call_hook('beforeMount');
this.$vm._isMounted = true;
this.$vm.__call_hook('mounted');
this.$vm.__call_hook('onReady');
} else {
this.is && console.warn(this.is + ' is not ready');
} }
}; };
return initComponent$1(componentOptions, vueOptions) return pageOptions
}
function createPage (vuePageOptions) {
{
return Component(parsePage(vuePageOptions))
}
}
function createComponent (vueOptions) {
{
return Component(parseComponent(vueOptions))
}
} }
todos.forEach(todoApi => { todos.forEach(todoApi => {
......
...@@ -40,7 +40,7 @@ const camelize = cached((str) => { ...@@ -40,7 +40,7 @@ const camelize = cached((str) => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
}); });
const SYNC_API_RE = /subNVue|requireNativePlugin|upx2px|hideKeyboard|canIUse|^create|Sync$|Manager$/; const SYNC_API_RE = /subNVue|requireNativePlugin|upx2px|hideKeyboard|canIUse|^create|Sync$|Manager$|base64ToArrayBuffer|arrayBufferToBase64/;
const CONTEXT_API_RE = /^create|Manager$/; const CONTEXT_API_RE = /^create|Manager$/;
...@@ -89,15 +89,17 @@ function promisify (name, api) { ...@@ -89,15 +89,17 @@ function promisify (name, api) {
fail: reject fail: reject
}), ...params); }), ...params);
/* eslint-disable no-extend-native */ /* eslint-disable no-extend-native */
Promise.prototype.finally = function (callback) { if (!Promise.prototype.finally) {
const promise = this.constructor; Promise.prototype.finally = function (callback) {
return this.then( const promise = this.constructor;
value => promise.resolve(callback()).then(() => value), return this.then(
reason => promise.resolve(callback()).then(() => { value => promise.resolve(callback()).then(() => value),
throw reason reason => promise.resolve(callback()).then(() => {
}) throw reason
) })
}; )
};
}
})) }))
} }
} }
...@@ -371,66 +373,18 @@ Component = function (options = {}) { ...@@ -371,66 +373,18 @@ Component = function (options = {}) {
return MPComponent(options) return MPComponent(options)
}; };
const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__']; const PAGE_EVENT_HOOKS = [
'onPullDownRefresh',
function initPage (pageOptions) { 'onReachBottom',
return initComponent(pageOptions) 'onShareAppMessage',
} 'onPageScroll',
'onResize',
function initComponent (componentOptions) { 'onTabItemTap'
return Component(componentOptions) ];
}
function initBehavior (options) {
return Behavior(options)
}
function initRefs (vm) {
const mpInstance = vm.$scope;
Object.defineProperty(vm, '$refs', {
get () {
const $refs = {};
const components = mpInstance.selectAllComponents('.vue-ref');
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
forComponents.forEach(component => {
const ref = component.dataset.ref;
if (!$refs[ref]) {
$refs[ref] = [];
}
$refs[ref].push(component.$vm || component);
});
return $refs
}
});
}
function triggerLink (mpInstance, vueOptions) {
mpInstance.triggerEvent('__l', mpInstance.$vm || vueOptions, {
bubbles: true,
composed: true
});
}
function handleLink (event) {
if (event.detail.$mp) { // vm
if (!event.detail.$parent) {
event.detail.$parent = this.$vm;
event.detail.$parent.$children.push(event.detail);
event.detail.$root = this.$vm.$root; function initMocks (vm, mocks) {
}
} else { // vueOptions
if (!event.detail.parent) {
event.detail.parent = this.$vm;
}
}
}
function initMocks (vm, mocks$$1) {
const mpInstance = vm.$mp[vm.mpType]; const mpInstance = vm.$mp[vm.mpType];
mocks$$1.forEach(mock => { mocks.forEach(mock => {
if (hasOwn(mpInstance, mock)) { if (hasOwn(mpInstance, mock)) {
vm[mock] = mpInstance[mock]; vm[mock] = mpInstance[mock];
} }
...@@ -440,12 +394,46 @@ function initMocks (vm, mocks$$1) { ...@@ -440,12 +394,46 @@ function initMocks (vm, mocks$$1) {
function initHooks (mpOptions, hooks) { function initHooks (mpOptions, hooks) {
hooks.forEach(hook => { hooks.forEach(hook => {
mpOptions[hook] = function (args) { mpOptions[hook] = function (args) {
return this.$vm.__call_hook(hook, args) return this.$vm && this.$vm.__call_hook(hook, args)
}; };
}); });
} }
function getData (vueOptions, context) { function initVueComponent (Vue$$1, vueOptions) {
vueOptions = vueOptions.default || vueOptions;
let VueComponent;
if (isFn(vueOptions)) {
VueComponent = vueOptions;
vueOptions = VueComponent.extendOptions;
} else {
VueComponent = Vue$$1.extend(vueOptions);
}
return [VueComponent, vueOptions]
}
function initSlots (vm, vueSlots) {
if (Array.isArray(vueSlots) && vueSlots.length) {
const $slots = Object.create(null);
vueSlots.forEach(slotName => {
$slots[slotName] = true;
});
vm.$scopedSlots = vm.$slots = $slots;
}
}
function initVueIds (vueIds, mpInstance) {
vueIds = (vueIds || '').split(',');
const len = vueIds.length;
if (len === 1) {
mpInstance._$vueId = vueIds[0];
} else if (len === 2) {
mpInstance._$vueId = vueIds[0];
mpInstance._$vuePid = vueIds[1];
}
}
function initData (vueOptions, context) {
let data = vueOptions.data || {}; let data = vueOptions.data || {};
const methods = vueOptions.methods || {}; const methods = vueOptions.methods || {};
...@@ -487,7 +475,7 @@ function createObserver (name) { ...@@ -487,7 +475,7 @@ function createObserver (name) {
} }
} }
function getBehaviors (vueOptions) { function initBehaviors (vueOptions, initBehavior) {
const vueBehaviors = vueOptions['behaviors']; const vueBehaviors = vueOptions['behaviors'];
const vueExtends = vueOptions['extends']; const vueExtends = vueOptions['extends'];
const vueMixins = vueOptions['mixins']; const vueMixins = vueOptions['mixins'];
...@@ -516,7 +504,7 @@ function getBehaviors (vueOptions) { ...@@ -516,7 +504,7 @@ function getBehaviors (vueOptions) {
if (isPlainObject(vueExtends) && vueExtends.props) { if (isPlainObject(vueExtends) && vueExtends.props) {
behaviors.push( behaviors.push(
initBehavior({ initBehavior({
properties: getProperties(vueExtends.props, true) properties: initProperties(vueExtends.props, true)
}) })
); );
} }
...@@ -525,7 +513,7 @@ function getBehaviors (vueOptions) { ...@@ -525,7 +513,7 @@ function getBehaviors (vueOptions) {
if (isPlainObject(vueMixin) && vueMixin.props) { if (isPlainObject(vueMixin) && vueMixin.props) {
behaviors.push( behaviors.push(
initBehavior({ initBehavior({
properties: getProperties(vueMixin.props, true) properties: initProperties(vueMixin.props, true)
}) })
); );
} }
...@@ -542,9 +530,13 @@ function parsePropType (key, type, defaultValue, file) { ...@@ -542,9 +530,13 @@ function parsePropType (key, type, defaultValue, file) {
return type return type
} }
function getProperties (props, isBehavior = false, file = '') { function initProperties (props, isBehavior = false, file = '') {
const properties = {}; const properties = {};
if (!isBehavior) { if (!isBehavior) {
properties.vueId = {
type: String,
value: ''
};
properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
type: null, type: null,
value: [], value: [],
...@@ -795,28 +787,14 @@ function handleEvent (event) { ...@@ -795,28 +787,14 @@ function handleEvent (event) {
const hooks = [ const hooks = [
'onHide', 'onHide',
'onError', 'onError',
'onPageNotFound', 'onPageNotFound'
'onUniNViewMessage'
]; ];
function initVm (vm) { function parseBaseApp (vm, {
if (this.$vm) { // 百度竟然 onShow 在 onLaunch 之前? mocks,
return initRefs
} }) {
{ Vue.prototype.mpHost = "mp-weixin";
if (!wx.canIUse('nextTick')) { // 事实 上2.2.3 即可,简单使用 2.3.0 的 nextTick 判断
console.error('当前微信基础库版本过低,请将 微信开发者工具-详情-项目设置-调试基础库版本 更换为`2.3.0`以上');
}
}
this.$vm = vm;
this.$vm.$mp = {
app: this
};
}
function createApp (vm) {
Vue.mixin({ Vue.mixin({
beforeCreate () { beforeCreate () {
...@@ -840,183 +818,175 @@ function createApp (vm) { ...@@ -840,183 +818,175 @@ function createApp (vm) {
initRefs(this); initRefs(this);
initMocks(this, mocks); initMocks(this, mocks);
} }
},
created () { // 处理 injections
this.__init_injections(this);
this.__init_provide(this);
} }
}); });
const appOptions = { const appOptions = {
onLaunch (args) { onLaunch (args) {
initVm.call(this, vm); {
if (!wx.canIUse('nextTick')) { // 事实 上2.2.3 即可,简单使用 2.3.0 的 nextTick 判断
console.error('当前微信基础库版本过低,请将 微信开发者工具-详情-项目设置-调试基础库版本 更换为`2.3.0`以上');
}
}
this.$vm = vm;
this.$vm.$mp = {
app: this
};
this.$vm.$scope = this;
this.$vm._isMounted = true; this.$vm._isMounted = true;
this.$vm.__call_hook('mounted'); this.$vm.__call_hook('mounted', args);
this.$vm.__call_hook('onLaunch', args); this.$vm.__call_hook('onLaunch', args);
},
onShow (args) {
initVm.call(this, vm);
this.$vm.__call_hook('onShow', args);
} }
}; };
// 兼容旧版本 globalData // 兼容旧版本 globalData
appOptions.globalData = vm.$options.globalData || {}; appOptions.globalData = vm.$options.globalData || {};
initHooks(appOptions, hooks); // 延迟执行,因为 App 的注册在 main.js 之前,可能导致生命周期内 Vue 原型上开发者注册的属性无法访问 initHooks(appOptions, hooks);
App(appOptions);
return vm return appOptions
} }
const hooks$1 = [ const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__'];
'onShow',
'onHide',
'onPullDownRefresh',
'onReachBottom',
'onShareAppMessage',
'onPageScroll',
'onResize',
'onTabItemTap',
'onBackPress',
'onNavigationBarButtonTap',
'onNavigationBarSearchInputChanged',
'onNavigationBarSearchInputConfirmed',
'onNavigationBarSearchInputClicked'
];
function initVm$1 (VueComponent) { // 百度的 onLoad 触发在 attached 之前 function findVmByVueId (vm, vuePid) {
if (this.$vm) { const $children = vm.$children;
return // 优先查找直属
let parentVm = $children.find(childVm => childVm.$scope._$vueId === vuePid);
if (parentVm) {
return parentVm
} }
// 反向递归查找
for (let i = $children.length - 1; i >= 0; i--) {
parentVm = findVmByVueId($children[i], vuePid);
if (parentVm) {
return parentVm
}
}
}
this.$vm = new VueComponent({ function initBehavior (options) {
mpType: 'page', return Behavior(options)
mpInstance: this
});
this.$vm.__call_hook('created');
this.$vm.$mount();
} }
function createPage (vueOptions) { function isPage () {
vueOptions = vueOptions.default || vueOptions; return !!this.route
let VueComponent; }
if (isFn(vueOptions)) {
VueComponent = vueOptions;
vueOptions = VueComponent.extendOptions;
} else {
VueComponent = Vue.extend(vueOptions);
}
const pageOptions = {
options: {
multipleSlots: true,
addGlobalClass: true
},
data: getData(vueOptions, Vue.prototype),
lifetimes: { // 当页面作为组件时
attached () {
initVm$1.call(this, VueComponent);
},
ready () {
this.$vm.__call_hook('beforeMount');
this.$vm._isMounted = true;
this.$vm.__call_hook('mounted');
this.$vm.__call_hook('onReady');
},
detached () {
this.$vm.$destroy();
}
},
methods: { // 作为页面时
onLoad (args) {
initVm$1.call(this, VueComponent);
this.$vm.$mp.query = args; // 又要兼容 mpvue
this.$vm.__call_hook('onLoad', args); // 开发者可能会在 onLoad 时赋值,提前到 mount 之前
},
onUnload () {
this.$vm.__call_hook('onUnload');
},
__e: handleEvent,
__l: handleLink
}
};
initHooks(pageOptions.methods, hooks$1); function initRelation (detail) {
this.triggerEvent('__l', detail);
}
return initPage(pageOptions, vueOptions) function initRefs (vm) {
} const mpInstance = vm.$scope;
Object.defineProperty(vm, '$refs', {
function initVm$2 (VueComponent) { get () {
if (this.$vm) { const $refs = {};
return const components = mpInstance.selectAllComponents('.vue-ref');
} components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
forComponents.forEach(component => {
const ref = component.dataset.ref;
if (!$refs[ref]) {
$refs[ref] = [];
}
$refs[ref].push(component.$vm || component);
});
return $refs
}
});
}
const properties = this.properties; function handleLink (event) {
const {
vuePid,
vueOptions
} = event.detail || event.value; // detail 是微信,value 是百度(dipatch)
const options = { let parentVm;
mpType: 'component',
mpInstance: this,
propsData: properties
};
// 初始化 vue 实例
this.$vm = new VueComponent(options);
// 处理$slots,$scopedSlots(暂不支持动态变化$slots) if (vuePid) {
const vueSlots = properties.vueSlots; parentVm = findVmByVueId(this.$vm, vuePid);
if (Array.isArray(vueSlots) && vueSlots.length) {
const $slots = Object.create(null);
vueSlots.forEach(slotName => {
$slots[slotName] = true;
});
this.$vm.$scopedSlots = this.$vm.$slots = $slots;
} }
// 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并
// 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性
this.$vm.$mount();
}
function createComponent (vueOptions) { if (!parentVm) {
vueOptions = vueOptions.default || vueOptions; parentVm = this.$vm;
let VueComponent;
if (isFn(vueOptions)) {
VueComponent = vueOptions; // TODO form-field props.name,props.value
vueOptions = VueComponent.extendOptions;
} else {
VueComponent = Vue.extend(vueOptions);
} }
const behaviors = getBehaviors(vueOptions); vueOptions.parent = parentVm;
}
const properties = getProperties(vueOptions.props, false, vueOptions.__file);
function parseApp (vm) {
return parseBaseApp(vm, {
mocks,
initRefs
})
}
function createApp (vm) {
App(parseApp(vm));
return vm
}
function parseBaseComponent (vueComponentOptions, {
isPage: isPage$$1,
initRelation: initRelation$$1
} = {}) {
let [VueComponent, vueOptions] = initVueComponent(Vue, vueComponentOptions);
const componentOptions = { const componentOptions = {
options: { options: {
multipleSlots: true, multipleSlots: true,
addGlobalClass: true addGlobalClass: true
}, },
data: getData(vueOptions, Vue.prototype), data: initData(vueOptions, Vue.prototype),
behaviors, behaviors: initBehaviors(vueOptions, initBehavior),
properties, properties: initProperties(vueOptions.props, false, vueOptions.__file),
lifetimes: { lifetimes: {
attached () { attached () {
initVm$2.call(this, VueComponent); const properties = this.properties;
const options = {
mpType: isPage$$1.call(this) ? 'page' : 'component',
mpInstance: this,
propsData: properties
};
initVueIds(properties.vueId, this);
// 处理父子关系
initRelation$$1.call(this, {
vuePid: this._$vuePid,
vueOptions: options
});
// 初始化 vue 实例
this.$vm = new VueComponent(options);
// 处理$slots,$scopedSlots(暂不支持动态变化$slots)
initSlots(this.$vm, properties.vueSlots);
// 触发首次 setData
this.$vm.$mount();
}, },
ready () { ready () {
initVm$2.call(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 // 当组件 props 默认值为 true,初始化时传入 false 会导致 created,ready 触发, 但 attached 不触发
triggerLink(this); // 处理 parent,children // https://developers.weixin.qq.com/community/develop/doc/00066ae2844cc0f8eb883e2a557800
if (this.$vm) {
// 补充生命周期 this.$vm._isMounted = true;
this.$vm.__call_hook('created'); this.$vm.__call_hook('mounted');
this.$vm.__call_hook('beforeMount'); this.$vm.__call_hook('onReady');
this.$vm._isMounted = true; } else {
this.$vm.__call_hook('mounted'); this.is && console.warn(this.is + ' is not attached');
this.$vm.__call_hook('onReady'); }
}, },
detached () { detached () {
this.$vm.$destroy(); this.$vm.$destroy();
...@@ -1024,7 +994,7 @@ function createComponent (vueOptions) { ...@@ -1024,7 +994,7 @@ function createComponent (vueOptions) {
}, },
pageLifetimes: { pageLifetimes: {
show (args) { show (args) {
this.$vm.__call_hook('onPageShow', args); this.$vm && this.$vm.__call_hook('onPageShow', args);
}, },
hide () { hide () {
this.$vm && this.$vm.__call_hook('onPageHide'); this.$vm && this.$vm.__call_hook('onPageHide');
...@@ -1034,12 +1004,68 @@ function createComponent (vueOptions) { ...@@ -1034,12 +1004,68 @@ function createComponent (vueOptions) {
} }
}, },
methods: { methods: {
__e: handleEvent, __l: handleLink,
__l: handleLink __e: handleEvent
} }
}; };
return initComponent(componentOptions, vueOptions) if (isPage$$1) {
return componentOptions
}
return [componentOptions, VueComponent]
}
function parseComponent (vueComponentOptions) {
return parseBaseComponent(vueComponentOptions, {
isPage,
initRelation
})
}
const hooks$1 = [
'onShow',
'onHide',
'onUnload'
];
hooks$1.push(...PAGE_EVENT_HOOKS);
function parseBasePage (vuePageOptions, {
isPage,
initRelation
}) {
const pageOptions = parseComponent(vuePageOptions, {
isPage,
initRelation
});
initHooks(pageOptions.methods, hooks$1);
pageOptions.methods.onLoad = function (args) {
this.$vm.$mp.query = args; // 兼容 mpvue
this.$vm.__call_hook('onLoad', args);
};
return pageOptions
}
function parsePage (vuePageOptions) {
return parseBasePage(vuePageOptions, {
isPage,
initRelation
})
}
function createPage (vuePageOptions) {
{
return Component(parsePage(vuePageOptions))
}
}
function createComponent (vueOptions) {
{
return Component(parseComponent(vueOptions))
}
} }
todos.forEach(todoApi => { todos.forEach(todoApi => {
......
import 'uni-platform/runtime/index' import 'uni-platform/runtime/index'
import Vue from 'vue' import parseApp from 'uni-platform/runtime/wrapper/app-parser'
import {
mocks,
initRefs
} from 'uni-platform/runtime/wrapper/index'
import {
initHooks,
initMocks
} from './util'
const hooks = [
'onHide',
'onError',
'onPageNotFound',
'onUniNViewMessage'
]
function initVm (vm) {
if (this.$vm) { // 百度竟然 onShow 在 onLaunch 之前?
return
}
if (__PLATFORM__ === 'mp-weixin') {
if (!wx.canIUse('nextTick')) { // 事实 上2.2.3 即可,简单使用 2.3.0 的 nextTick 判断
console.error('当前微信基础库版本过低,请将 微信开发者工具-详情-项目设置-调试基础库版本 更换为`2.3.0`以上')
}
}
this.$vm = vm
this.$vm.$mp = {
app: this
}
}
export function createApp (vm) { export function createApp (vm) {
// 外部初始化时 Vue 还未初始化,放到 createApp 内部初始化 mixin App(parseApp(vm))
if (__PLATFORM__ === 'mp-alipay') {
Object.defineProperty(Vue.prototype, '$slots', {
get () {
return this.$scope && this.$scope.props.$slots
},
set () {
}
})
Object.defineProperty(Vue.prototype, '$scopedSlots', {
get () {
return this.$scope && this.$scope.props.$scopedSlots
},
set () {
}
})
}
Vue.mixin({
beforeCreate () {
if (!this.$options.mpType) {
return
}
this.mpType = this.$options.mpType
this.$mp = {
data: {},
[this.mpType]: this.$options.mpInstance
}
this.$scope = this.$options.mpInstance
delete this.$options.mpType
delete this.$options.mpInstance
if (this.mpType !== 'app') {
initRefs(this)
initMocks(this, mocks)
}
},
created () { // 处理 injections
this.__init_injections(this)
this.__init_provide(this)
}
})
const appOptions = {
onLaunch (args) {
initVm.call(this, vm)
this.$vm._isMounted = true
this.$vm.__call_hook('mounted')
this.$vm.__call_hook('onLaunch', args)
},
onShow (args) {
initVm.call(this, vm)
this.$vm.__call_hook('onShow', args)
}
}
// 兼容旧版本 globalData
appOptions.globalData = vm.$options.globalData || {}
initHooks(appOptions, hooks) // 延迟执行,因为 App 的注册在 main.js 之前,可能导致生命周期内 Vue 原型上开发者注册的属性无法访问
App(appOptions)
return vm return vm
} }
import Vue from 'vue' import parseComponent from 'uni-platform/runtime/wrapper/component-parser'
import {
isFn
} from 'uni-shared'
import {
handleLink,
triggerLink,
initComponent
} from 'uni-platform/runtime/wrapper/index'
import {
getData,
handleEvent,
getBehaviors,
getProperties
} from './util'
function initVm (VueComponent) {
if (this.$vm) {
return
}
const properties = __PLATFORM__ === 'mp-alipay'
? this.props
: this.properties
const options = {
mpType: 'component',
mpInstance: this,
propsData: properties
}
// 初始化 vue 实例
this.$vm = new VueComponent(options)
// 处理$slots,$scopedSlots(暂不支持动态变化$slots)
const vueSlots = properties.vueSlots
if (Array.isArray(vueSlots) && vueSlots.length) {
const $slots = Object.create(null)
vueSlots.forEach(slotName => {
$slots[slotName] = true
})
this.$vm.$scopedSlots = this.$vm.$slots = $slots
}
// 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并
// 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性
this.$vm.$mount()
}
export function createComponent (vueOptions) { export function createComponent (vueOptions) {
vueOptions = vueOptions.default || vueOptions if (__PLATFORM__ === 'mp-alipay') {
return my.createComponent(parseComponent(vueOptions))
let VueComponent
if (isFn(vueOptions)) {
VueComponent = vueOptions // TODO form-field props.name,props.value
vueOptions = VueComponent.extendOptions
} else { } else {
VueComponent = Vue.extend(vueOptions) return Component(parseComponent(vueOptions))
}
const behaviors = getBehaviors(vueOptions)
const properties = getProperties(vueOptions.props, false, vueOptions.__file)
const componentOptions = {
options: {
multipleSlots: true,
addGlobalClass: true
},
data: getData(vueOptions, Vue.prototype),
behaviors,
properties,
lifetimes: {
attached () {
initVm.call(this, VueComponent)
},
ready () {
initVm.call(this, VueComponent) // 目前发现部分情况小程序 attached 不触发
triggerLink(this) // 处理 parent,children
// 补充生命周期
this.$vm.__call_hook('created')
this.$vm.__call_hook('beforeMount')
this.$vm._isMounted = true
this.$vm.__call_hook('mounted')
this.$vm.__call_hook('onReady')
},
detached () {
this.$vm.$destroy()
}
},
pageLifetimes: {
show (args) {
this.$vm.__call_hook('onPageShow', args)
},
hide () {
this.$vm && this.$vm.__call_hook('onPageHide')
},
resize (size) {
this.$vm && this.$vm.__call_hook('onPageResize', size)
}
},
methods: {
__e: handleEvent,
__l: handleLink
}
} }
return initComponent(componentOptions, vueOptions)
} }
import Vue from 'vue' import parsePage from 'uni-platform/runtime/wrapper/page-parser'
import { export function createPage (vuePageOptions) {
isFn if (__PLATFORM__ === 'mp-alipay') {
} from 'uni-shared' return Page(parsePage(vuePageOptions))
import {
initPage,
handleLink
} from 'uni-platform/runtime/wrapper/index'
import {
getData,
initHooks,
handleEvent,
baiduPageDestroy
} from './util'
const hooks = [
'onShow',
'onHide',
'onPullDownRefresh',
'onReachBottom',
'onShareAppMessage',
'onPageScroll',
'onResize',
'onTabItemTap',
'onBackPress',
'onNavigationBarButtonTap',
'onNavigationBarSearchInputChanged',
'onNavigationBarSearchInputConfirmed',
'onNavigationBarSearchInputClicked'
]
function initVm (VueComponent) { // 百度的 onLoad 触发在 attached 之前
if (this.$vm) {
return
}
this.$vm = new VueComponent({
mpType: 'page',
mpInstance: this
})
if (__PLATFORM__ === 'mp-baidu') {
this.$vm.$baiduComponentInstances = Object.create(null)
}
this.$vm.__call_hook('created')
this.$vm.$mount()
}
export function createPage (vueOptions) {
vueOptions = vueOptions.default || vueOptions
let VueComponent
if (isFn(vueOptions)) {
VueComponent = vueOptions
vueOptions = VueComponent.extendOptions
} else { } else {
VueComponent = Vue.extend(vueOptions) return Component(parsePage(vuePageOptions))
} }
const pageOptions = {
options: {
multipleSlots: true,
addGlobalClass: true
},
data: getData(vueOptions, Vue.prototype),
lifetimes: { // 当页面作为组件时
attached () {
initVm.call(this, VueComponent)
},
ready () {
this.$vm.__call_hook('beforeMount')
this.$vm._isMounted = true
this.$vm.__call_hook('mounted')
this.$vm.__call_hook('onReady')
},
detached () {
this.$vm.$destroy()
}
},
methods: { // 作为页面时
onLoad (args) {
initVm.call(this, VueComponent)
if (__PLATFORM__ === 'mp-baidu') { // 百度当组件作为页面时 pageinstancce 不是原来组件的 instance
this.pageinstance.$vm = this.$vm
}
this.$vm.$mp.query = args // 又要兼容 mpvue
this.$vm.__call_hook('onLoad', args) // 开发者可能会在 onLoad 时赋值,提前到 mount 之前
},
onUnload () {
this.$vm.__call_hook('onUnload')
if (__PLATFORM__ === 'mp-baidu') { // 百度组件不会在页面 unload 时触发 detached
baiduPageDestroy(this.$vm)
}
},
__e: handleEvent,
__l: handleLink
}
}
initHooks(pageOptions.methods, hooks)
return initPage(pageOptions, vueOptions)
} }
...@@ -5,9 +5,14 @@ import { ...@@ -5,9 +5,14 @@ import {
isPlainObject isPlainObject
} from 'uni-shared' } from 'uni-shared'
import { export const PAGE_EVENT_HOOKS = [
initBehavior 'onPullDownRefresh',
} from 'uni-platform/runtime/wrapper/index' 'onReachBottom',
'onShareAppMessage',
'onPageScroll',
'onResize',
'onTabItemTap'
]
export function initMocks (vm, mocks) { export function initMocks (vm, mocks) {
const mpInstance = vm.$mp[vm.mpType] const mpInstance = vm.$mp[vm.mpType]
...@@ -21,12 +26,46 @@ export function initMocks (vm, mocks) { ...@@ -21,12 +26,46 @@ export function initMocks (vm, mocks) {
export function initHooks (mpOptions, hooks) { export function initHooks (mpOptions, hooks) {
hooks.forEach(hook => { hooks.forEach(hook => {
mpOptions[hook] = function (args) { mpOptions[hook] = function (args) {
return this.$vm.__call_hook(hook, args) return this.$vm && this.$vm.__call_hook(hook, args)
} }
}) })
} }
export function getData (vueOptions, context) { export function initVueComponent (Vue, vueOptions) {
vueOptions = vueOptions.default || vueOptions
let VueComponent
if (isFn(vueOptions)) {
VueComponent = vueOptions
vueOptions = VueComponent.extendOptions
} else {
VueComponent = Vue.extend(vueOptions)
}
return [VueComponent, vueOptions]
}
export function initSlots (vm, vueSlots) {
if (Array.isArray(vueSlots) && vueSlots.length) {
const $slots = Object.create(null)
vueSlots.forEach(slotName => {
$slots[slotName] = true
})
vm.$scopedSlots = vm.$slots = $slots
}
}
export function initVueIds (vueIds, mpInstance) {
vueIds = (vueIds || '').split(',')
const len = vueIds.length
if (len === 1) {
mpInstance._$vueId = vueIds[0]
} else if (len === 2) {
mpInstance._$vueId = vueIds[0]
mpInstance._$vuePid = vueIds[1]
}
}
export function initData (vueOptions, context) {
let data = vueOptions.data || {} let data = vueOptions.data || {}
const methods = vueOptions.methods || {} const methods = vueOptions.methods || {}
...@@ -68,7 +107,7 @@ function createObserver (name) { ...@@ -68,7 +107,7 @@ function createObserver (name) {
} }
} }
export function getBehaviors (vueOptions) { export function initBehaviors (vueOptions, initBehavior) {
const vueBehaviors = vueOptions['behaviors'] const vueBehaviors = vueOptions['behaviors']
const vueExtends = vueOptions['extends'] const vueExtends = vueOptions['extends']
const vueMixins = vueOptions['mixins'] const vueMixins = vueOptions['mixins']
...@@ -97,7 +136,7 @@ export function getBehaviors (vueOptions) { ...@@ -97,7 +136,7 @@ export function getBehaviors (vueOptions) {
if (isPlainObject(vueExtends) && vueExtends.props) { if (isPlainObject(vueExtends) && vueExtends.props) {
behaviors.push( behaviors.push(
initBehavior({ initBehavior({
properties: getProperties(vueExtends.props, true) properties: initProperties(vueExtends.props, true)
}) })
) )
} }
...@@ -106,7 +145,7 @@ export function getBehaviors (vueOptions) { ...@@ -106,7 +145,7 @@ export function getBehaviors (vueOptions) {
if (isPlainObject(vueMixin) && vueMixin.props) { if (isPlainObject(vueMixin) && vueMixin.props) {
behaviors.push( behaviors.push(
initBehavior({ initBehavior({
properties: getProperties(vueMixin.props, true) properties: initProperties(vueMixin.props, true)
}) })
) )
} }
...@@ -139,9 +178,13 @@ function parsePropType (key, type, defaultValue, file) { ...@@ -139,9 +178,13 @@ function parsePropType (key, type, defaultValue, file) {
return type return type
} }
export function getProperties (props, isBehavior = false, file = '') { export function initProperties (props, isBehavior = false, file = '') {
const properties = {} const properties = {}
if (!isBehavior) { if (!isBehavior) {
properties.vueId = {
type: String,
value: ''
}
properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
type: null, type: null,
value: [], value: [],
...@@ -397,18 +440,4 @@ export function handleEvent (event) { ...@@ -397,18 +440,4 @@ export function handleEvent (event) {
}) })
} }
}) })
}
function baiduComponentDestroy ($vm) {
$vm.$children.forEach(childVm => {
childVm.$mp.component.detached()
})
$vm.$mp.component.detached()
}
export function baiduPageDestroy ($vm) {
$vm.$destroy()
$vm.$children.forEach(childVm => {
baiduComponentDestroy(childVm)
})
} }
import {
initHooks
} from 'uni-wrapper/util'
import parseBaseApp from '../../../mp-weixin/runtime/wrapper/app-parser'
const hooks = [
'onUniNViewMessage'
]
export default function parseApp (vm) {
const appOptions = parseBaseApp(vm)
initHooks(appOptions, hooks)
return appOptions
}
import parseBaseComponent from '../../../mp-weixin/runtime/wrapper/component-parser'
export default function parseComponent (vueComponentOptions) {
const componentOptions = parseBaseComponent(vueComponentOptions)
componentOptions.methods.$getAppWebview = function () {
return plus.webview.getWebviewById(`${this.__wxWebviewId__}`)
}
return componentOptions
}
export {
mocks,
initRefs,
handleLink,
triggerLink,
initBehavior
}
from '../../../mp-weixin/runtime/wrapper/index'
export function initPage (pageOptions) {
return initComponent(pageOptions)
}
export function initComponent (componentOptions) {
componentOptions.methods.$getAppWebview = function () {
return plus.webview.getWebviewById(`${this.__wxWebviewId__}`)
}
return Component(componentOptions)
}
import {
initHooks
} from 'uni-wrapper/util'
import parseBasePage from '../../../mp-weixin/runtime/wrapper/page-parser'
const hooks = [
'onBackPress',
'onNavigationBarButtonTap',
'onNavigationBarSearchInputChanged',
'onNavigationBarSearchInputConfirmed',
'onNavigationBarSearchInputClicked'
]
export default function parsePage (vuePageOptions) {
const pageOptions = parseBasePage(vuePageOptions)
initHooks(pageOptions.methods, hooks)
return pageOptions
}
import Vue from 'vue'
import parseBaseApp from '../../../mp-weixin/runtime/wrapper/app-base-parser'
import {
mocks,
initRefs
} from './util'
export default function parseApp (vm) {
Object.defineProperty(Vue.prototype, '$slots', {
get () {
return this.$scope && this.$scope.props.$slots
},
set () {
}
})
Object.defineProperty(Vue.prototype, '$scopedSlots', {
get () {
return this.$scope && this.$scope.props.$scopedSlots
},
set () {
}
})
return parseBaseApp(vm, {
mocks,
initRefs
})
}
import Vue from 'vue'
import {
initData,
initVueIds,
handleEvent,
initBehaviors,
initProperties,
initVueComponent
} from 'uni-wrapper/util'
import {
handleRef,
handleLink,
initBehavior,
initRelation,
triggerEvent,
createObserver,
isComponent2,
initChildVues
} from './util'
function initVm (VueComponent) {
if (this.$vm) {
return
}
const properties = this.props
const options = {
mpType: 'component',
mpInstance: this,
propsData: properties
}
initVueIds(properties.vueId, this)
if (isComponent2) {
// 处理父子关系
initRelation.call(this, {
vuePid: this._$vuePid,
vueOptions: options
})
// 初始化 vue 实例
this.$vm = new VueComponent(options)
// 触发首次 setData
this.$vm.$mount()
} else {
initChildVues(this)
// 处理父子关系
initRelation.call(this, {
vuePid: this._$vuePid,
vueOptions: options,
VueComponent,
mpInstance: this
})
if (options.parent) { // 父组件已经初始化,直接初始化子,否则放到父组件的 didMount 中处理
// 初始化 vue 实例
this.$vm = new VueComponent(options)
handleRef.call(options.parent.$scope, this)
// 触发首次 setData
this.$vm.$mount()
this.$vm._isMounted = true
this.$vm.__call_hook('mounted')
this.$vm.__call_hook('onReady')
}
}
}
export default function parseComponent (vueComponentOptions) {
let [VueComponent, vueOptions] = initVueComponent(Vue, vueComponentOptions)
const properties = initProperties(vueOptions.props, false, vueOptions.__file)
const props = {
onVueInit: function () {}
}
Object.keys(properties).forEach(key => {
if (key !== 'vueSlots') {
props[key] = properties[key].value
}
})
const componentOptions = {
mixins: initBehaviors(vueOptions, initBehavior),
data: initData(vueOptions, Vue.prototype),
props,
didMount () {
initVm.call(this, VueComponent)
if (isComponent2) {
this.$vm._isMounted = true
this.$vm.__call_hook('mounted')
this.$vm.__call_hook('onReady')
}
},
didUnmount () {
this.$vm.$destroy()
},
methods: {
__r: handleRef,
__e: handleEvent,
__l: handleLink,
triggerEvent
}
}
if (isComponent2) {
componentOptions.onInit = function onInit () {
initVm.call(this, VueComponent)
}
componentOptions.deriveDataFromProps = createObserver()
} else {
componentOptions.didUpdate = createObserver(true)
}
if (vueOptions.methods && vueOptions.methods.formReset) {
componentOptions.methods.formReset = vueOptions.methods.formReset
}
return componentOptions
}
import Vue from 'vue'
import {
initData,
initHooks,
handleEvent,
initBehaviors,
initVueComponent,
PAGE_EVENT_HOOKS
} from 'uni-wrapper/util'
import {
handleRef,
handleLink,
initBehavior,
initChildVues
} from './util'
const hooks = [
'onShow',
'onHide',
// mp-alipay 特有
'onTitleClick',
'onOptionMenuClick',
'onPopMenuClick',
'onPullIntercept'
]
hooks.push(...PAGE_EVENT_HOOKS)
export default function parsePage (vuePageOptions) {
let [VueComponent, vueOptions] = initVueComponent(Vue, vuePageOptions)
const pageOptions = {
mixins: initBehaviors(vueOptions, initBehavior),
data: initData(vueOptions, Vue.prototype),
onLoad (args) {
const properties = this.props
const options = {
mpType: 'page',
mpInstance: this,
propsData: properties
}
// 初始化 vue 实例
this.$vm = new VueComponent(options)
// 触发首次 setData
this.$vm.$mount()
this.$vm.$mp.query = args // 兼容 mpvue
this.$vm.__call_hook('onLoad', args)
},
onReady () {
initChildVues(this)
this.$vm._isMounted = true
this.$vm.__call_hook('mounted')
this.$vm.__call_hook('onReady')
},
onUnload () {
this.$vm.__call_hook('onUnload')
this.$vm.$destroy()
},
__r: handleRef,
__e: handleEvent,
__l: handleLink
}
initHooks(pageOptions, hooks)
return pageOptions
}
...@@ -4,6 +4,10 @@ import { ...@@ -4,6 +4,10 @@ import {
camelize camelize
} from 'uni-shared' } from 'uni-shared'
import {
handleLink as handleBaseLink
} from '../../../mp-weixin/runtime/wrapper/util'
import deepEqual from './deep-equal' import deepEqual from './deep-equal'
const customizeRE = /:/g const customizeRE = /:/g
...@@ -12,13 +16,62 @@ const customize = cached((str) => { ...@@ -12,13 +16,62 @@ const customize = cached((str) => {
return camelize(str.replace(customizeRE, '-')) return camelize(str.replace(customizeRE, '-'))
}) })
export const isComponent2 = my.canIUse('component2')
export const mocks = ['$id'] export const mocks = ['$id']
export function initRefs () { export function initRefs () {
} }
function handleRef (ref) { export function initBehavior ({
properties
}) {
const props = {}
Object.keys(properties).forEach(key => {
props[key] = properties[key].value
})
return {
props
}
}
export function initRelation (detail) {
this.props.onVueInit(detail)
}
export function initChildVues (mpInstance) {
mpInstance._$childVues && mpInstance._$childVues.forEach(({
vuePid,
vueOptions,
VueComponent,
mpInstance: childMPInstance
}) => {
// 父子关系
handleBaseLink.call(mpInstance, {
detail: {
vuePid,
vueOptions
}
})
childMPInstance.$vm = new VueComponent(vueOptions)
handleRef.call(vueOptions.parent.$scope, childMPInstance)
childMPInstance.$vm.$mount()
childMPInstance.$vm._isMounted = true
childMPInstance.$vm.__call_hook('mounted')
childMPInstance.$vm.__call_hook('onReady')
})
delete mpInstance._$childVues
}
export function handleRef (ref) {
if (!ref) { if (!ref) {
return return
} }
...@@ -31,37 +84,7 @@ function handleRef (ref) { ...@@ -31,37 +84,7 @@ function handleRef (ref) {
} }
} }
export function initPage (pageOptions, vueOptions) { export function triggerEvent (type, detail, options) {
const {
lifetimes,
methods
} = pageOptions
pageOptions.onReady = lifetimes.ready
pageOptions.onUnload = function () {
lifetimes.detached.call(this)
methods.onUnload.call(this)
}
Object.keys(methods).forEach(method => {
if (method !== 'onUnload') {
pageOptions[method] = methods[method]
}
})
pageOptions['__r'] = handleRef
if (vueOptions.methods && vueOptions.methods.formReset) {
pageOptions['formReset'] = vueOptions.methods.formReset
}
delete pageOptions.lifetimes
delete pageOptions.methods
return Page(pageOptions)
}
function triggerEvent (type, detail, options) {
const handler = this.props[customize('on-' + type)] const handler = this.props[customize('on-' + type)]
if (!handler) { if (!handler) {
return return
...@@ -85,7 +108,7 @@ function triggerEvent (type, detail, options) { ...@@ -85,7 +108,7 @@ function triggerEvent (type, detail, options) {
const IGNORES = ['$slots', '$scopedSlots'] const IGNORES = ['$slots', '$scopedSlots']
function createObserver (isDidUpdate) { export function createObserver (isDidUpdate) {
return function observe (props) { return function observe (props) {
const prevProps = isDidUpdate ? props : this.props const prevProps = isDidUpdate ? props : this.props
const nextProps = isDidUpdate ? this.props : props const nextProps = isDidUpdate ? this.props : props
...@@ -104,92 +127,24 @@ function createObserver (isDidUpdate) { ...@@ -104,92 +127,24 @@ function createObserver (isDidUpdate) {
} }
} }
export function initComponent (componentOptions, vueOptions) { export const handleLink = (function () {
const { if (isComponent2) {
lifetimes, return function handleLink (detail) {
properties, return handleBaseLink.call(this, {
behaviors detail
} = componentOptions })
componentOptions.mixins = behaviors
const props = {
onTriggerLink: function () {}
}
Object.keys(properties).forEach(key => {
if (key !== 'vueSlots') {
props[key] = properties[key].value
} }
})
componentOptions.props = props
if (my.canIUse('component2')) {
componentOptions.onInit = lifetimes.attached
}
componentOptions.didMount = lifetimes.ready
componentOptions.didUnmount = lifetimes.detached
if (my.canIUse('component2')) {
componentOptions.deriveDataFromProps = createObserver() // nextProps
} else {
componentOptions.didUpdate = createObserver(true) // prevProps
}
if (!componentOptions.methods) {
componentOptions.methods = {}
}
if (vueOptions.methods && vueOptions.methods.formReset) {
componentOptions.methods['formReset'] = vueOptions.methods.formReset
} }
return function handleLink (detail) {
componentOptions.methods['__r'] = handleRef if (this.$vm) { // 父已初始化
componentOptions.methods.triggerEvent = triggerEvent return handleBaseLink.call(this, {
detail: {
delete componentOptions.properties vuePid: detail.vuePid,
delete componentOptions.behaviors vueOptions: detail.vueOptions
delete componentOptions.lifetimes
delete componentOptions.pageLifetimes
return my.createComponent(componentOptions)
}
export function initBehavior ({
properties
}) {
const props = {}
Object.keys(properties).forEach(key => {
props[key] = properties[key].value
})
return {
props
}
}
export function triggerLink (mpInstance, vueOptions) {
mpInstance.props.onTriggerLink(mpInstance.$vm || vueOptions)
}
export function handleLink (detail) {
if (detail.$mp) { // vm
if (!detail.$parent) {
detail.$parent = this.$vm
if (detail.$parent) {
detail.$parent.$children.push(detail)
detail.$root = this.$vm.$root
if (!my.canIUse('component2')) {
handleRef.call(this, detail.$scope)
} }
} })
}
} else { // vueOptions
if (!detail.parent) {
detail.parent = this.$vm
} }
// 支付宝通过 didMount 来实现,先子后父,故等父 ready 之后,统一初始化
(this._$childVues || (this._$childVues = [])).unshift(detail)
} }
} })()
import parseBaseApp from '../../../mp-weixin/runtime/wrapper/app-base-parser'
import {
initRefs
} from '../../../mp-weixin/runtime/wrapper/util'
import {
mocks
} from './util'
export default function parseApp (vm) {
return parseBaseApp(vm, {
mocks,
initRefs
})
}
import {
isPage,
initRelation
} from './util'
import parseBaseComponent from '../../../mp-weixin/runtime/wrapper/component-base-parser'
export default function parseComponent (vueOptions) {
const componentOptions = parseBaseComponent(vueOptions, {
isPage,
initRelation
})
const oldAttached = componentOptions.lifetimes.attached
componentOptions.lifetimes.attached = function attached () {
oldAttached.call(this)
if (isPage.call(this)) { // 百度 onLoad 在 attached 之前触发
// 百度 当组件作为页面时 pageinstancce 不是原来组件的 instance
this.pageinstance.$vm = this.$vm
this.$vm.$mp.query = this.pageinstance._$args // 兼容 mpvue
this.$vm.__call_hook('onLoad', this.pageinstance._$args)
}
}
componentOptions.messages = {
'__l': componentOptions.methods['__l']
}
delete componentOptions.methods['__l']
return componentOptions
}
export {
initRefs,
initBehavior
}
from '../../../mp-weixin/runtime/wrapper/index'
export const mocks = ['nodeId']
export function initPage (pageOptions) {
return initComponent(pageOptions)
}
export function initComponent (componentOptions) {
componentOptions.messages = {
'__l': handleLink
}
return Component(componentOptions)
}
export function triggerLink (mpInstance, vueOptions) {
mpInstance.dispatch('__l', mpInstance.$vm || vueOptions)
}
export function handleLink (event) {
const target = event.value
if (target.$mp) {
if (!target.$parent) {
target.$parent = this.$vm
target.$parent.$children.push(target)
target.$root = this.$vm.$root
}
} else {
if (!target.parent) {
target.parent = this.$vm
}
}
}
import {
isPage,
initRelation
} from './util'
import parseBasePage from '../../../mp-weixin/runtime/wrapper/page-base-parser'
function detached ($vm) {
$vm.$children.forEach(childVm => {
childVm.$scope.detached()
})
$vm.$scope.detached()
}
function onPageUnload ($vm) {
$vm.$destroy()
$vm.$children.forEach(childVm => {
detached(childVm)
})
}
export default function parsePage (vuePageOptions) {
const pageOptions = parseBasePage(vuePageOptions, {
isPage,
initRelation
})
pageOptions.methods.onLoad = function onLoad (args) {
// 百度 onLoad 在 attached 之前触发,先存储 args, 在 attached 里边触发 onLoad
this.pageinstance._$args = args
}
pageOptions.methods.onUnload = function onUnload () {
this.$vm.__call_hook('onUnload')
onPageUnload(this.$vm)
}
return pageOptions
}
export const mocks = ['nodeId']
export function isPage () {
return !this.ownerId
}
export function initRelation (detail) {
this.dispatch('__l', detail)
}
import Vue from 'vue'
import parseBaseApp from '../../../mp-weixin/runtime/wrapper/app-base-parser'
import {
mocks,
initRefs
} from './util'
export default function parseApp (vm) {
Vue.prototype._$fallback = true // 降级(调整原 vue 的部分生命周期,如 created,beforeMount,inject,provide)
Vue.mixin({
created () { // 处理 injections,头条 triggerEvent 是异步,且触发时机很慢,故延迟 relation 设置
if (this.mpType !== 'app') {
initRefs(this)
this.__init_injections(this)
this.__init_provide(this)
}
}
})
return parseBaseApp(vm, {
mocks,
initRefs: function () {} // attached 时,可能查询不到
})
}
import {
isPage,
initRelation,
handleLink
} from './util'
import {
initSlots,
initVueIds
} from 'uni-wrapper/util'
import parseBaseComponent from '../../../mp-weixin/runtime/wrapper/component-base-parser'
export default function parseComponent (vueOptions) {
const [componentOptions, VueComponent] = parseBaseComponent(vueOptions)
componentOptions.lifetimes.attached = function attached () {
const properties = this.properties
const options = {
mpType: isPage.call(this) ? 'page' : 'component',
mpInstance: this,
propsData: properties
}
initVueIds(properties.vueId, this)
// 初始化 vue 实例
this.$vm = new VueComponent(options)
// 处理$slots,$scopedSlots(暂不支持动态变化$slots)
initSlots(this.$vm, properties.vueSlots)
// 处理父子关系
initRelation.call(this, {
vuePid: this._$vuePid,
mpInstance: this
})
// 触发首次 setData
this.$vm.$mount()
}
// ready 比 handleLink 还早,初始化逻辑放到 handleLink 中
delete componentOptions.lifetimes.ready
componentOptions.methods.__l = handleLink
return componentOptions
}
import {
isPage,
initRelation
} from './util'
import parseBasePage from '../../../mp-weixin/runtime/wrapper/page-base-parser'
export default function parsePage (vuePageOptions) {
const pageOptions = parseBasePage(vuePageOptions, {
isPage,
initRelation
})
// 页面需要在 ready 中触发,其他组件是在 handleLink 中触发
pageOptions.lifetimes.ready = function ready () {
if (this.$vm && this.$vm.mpType === 'page') {
this.$vm.__call_hook('created')
this.$vm.__call_hook('beforeMount')
this.$vm._isMounted = true
this.$vm.__call_hook('mounted')
this.$vm.__call_hook('onReady')
} else {
this.is && console.warn(this.is + ' is not ready')
}
}
return pageOptions
}
import Vue from 'vue'
import {
initHooks,
initMocks
} from 'uni-wrapper/util'
const hooks = [
'onHide',
'onError',
'onPageNotFound'
]
export default function parseBaseApp (vm, {
mocks,
initRefs
}) {
Vue.prototype.mpHost = __PLATFORM__
Vue.mixin({
beforeCreate () {
if (!this.$options.mpType) {
return
}
this.mpType = this.$options.mpType
this.$mp = {
data: {},
[this.mpType]: this.$options.mpInstance
}
this.$scope = this.$options.mpInstance
delete this.$options.mpType
delete this.$options.mpInstance
if (this.mpType !== 'app') {
initRefs(this)
initMocks(this, mocks)
}
}
})
const appOptions = {
onLaunch (args) {
if (__PLATFORM__ === 'mp-weixin') {
if (!wx.canIUse('nextTick')) { // 事实 上2.2.3 即可,简单使用 2.3.0 的 nextTick 判断
console.error('当前微信基础库版本过低,请将 微信开发者工具-详情-项目设置-调试基础库版本 更换为`2.3.0`以上')
}
}
this.$vm = vm
this.$vm.$mp = {
app: this
}
this.$vm.$scope = this
this.$vm._isMounted = true
this.$vm.__call_hook('mounted', args)
this.$vm.__call_hook('onLaunch', args)
}
}
// 兼容旧版本 globalData
appOptions.globalData = vm.$options.globalData || {}
initHooks(appOptions, hooks)
return appOptions
}
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册