diff --git a/build/rollup.config.js b/build/rollup.config.js index f261bbc7c2560b53675382387cf8703df9cac3c4..f4d73565529d6322031247a184af2d02fbd430d3 100644 --- a/build/rollup.config.js +++ b/build/rollup.config.js @@ -36,7 +36,8 @@ module.exports = { plugins: [ alias({ '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({ __GLOBAL__: platform.prefix, diff --git a/packages/uni-app-plus/dist/index.js b/packages/uni-app-plus/dist/index.js index c975b86d3ff0190e9dee481ead3b5bdd9cc169dc..39408f4053e04ba48eeba5a877cd326e1b302be4 100644 --- a/packages/uni-app-plus/dist/index.js +++ b/packages/uni-app-plus/dist/index.js @@ -40,7 +40,7 @@ const camelize = cached((str) => { 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$/; @@ -89,15 +89,17 @@ function promisify (name, api) { fail: reject }), ...params); /* eslint-disable no-extend-native */ - Promise.prototype.finally = function (callback) { - const promise = this.constructor; - return this.then( - value => promise.resolve(callback()).then(() => value), - reason => promise.resolve(callback()).then(() => { - throw reason - }) - ) - }; + if (!Promise.prototype.finally) { + Promise.prototype.finally = function (callback) { + const promise = this.constructor; + return this.then( + value => promise.resolve(callback()).then(() => value), + reason => promise.resolve(callback()).then(() => { + throw reason + }) + ) + }; + } })) } } @@ -371,69 +373,18 @@ Component = function (options = {}) { return MPComponent(options) }; -const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__']; - -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; - } - } else { // vueOptions - if (!event.detail.parent) { - event.detail.parent = this.$vm; - } - } -} - -function initPage$1 (pageOptions) { - return initComponent$1(pageOptions) -} +const PAGE_EVENT_HOOKS = [ + 'onPullDownRefresh', + 'onReachBottom', + 'onShareAppMessage', + 'onPageScroll', + 'onResize', + 'onTabItemTap' +]; -function initComponent$1 (componentOptions) { - componentOptions.methods.$getAppWebview = function () { - return plus.webview.getWebviewById(`${this.__wxWebviewId__}`) - }; - return Component(componentOptions) -} - -function initMocks (vm, mocks$$1) { +function initMocks (vm, mocks) { const mpInstance = vm.$mp[vm.mpType]; - mocks$$1.forEach(mock => { + mocks.forEach(mock => { if (hasOwn(mpInstance, mock)) { vm[mock] = mpInstance[mock]; } @@ -443,12 +394,46 @@ function initMocks (vm, mocks$$1) { function initHooks (mpOptions, hooks) { hooks.forEach(hook => { 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 || {}; const methods = vueOptions.methods || {}; @@ -490,7 +475,7 @@ function createObserver (name) { } } -function getBehaviors (vueOptions) { +function initBehaviors (vueOptions, initBehavior) { const vueBehaviors = vueOptions['behaviors']; const vueExtends = vueOptions['extends']; const vueMixins = vueOptions['mixins']; @@ -519,7 +504,7 @@ function getBehaviors (vueOptions) { if (isPlainObject(vueExtends) && vueExtends.props) { behaviors.push( initBehavior({ - properties: getProperties(vueExtends.props, true) + properties: initProperties(vueExtends.props, true) }) ); } @@ -528,7 +513,7 @@ function getBehaviors (vueOptions) { if (isPlainObject(vueMixin) && vueMixin.props) { behaviors.push( initBehavior({ - properties: getProperties(vueMixin.props, true) + properties: initProperties(vueMixin.props, true) }) ); } @@ -545,9 +530,13 @@ function parsePropType (key, type, defaultValue, file) { return type } -function getProperties (props, isBehavior = false, file = '') { +function initProperties (props, isBehavior = false, file = '') { const properties = {}; if (!isBehavior) { + properties.vueId = { + type: String, + value: '' + }; properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [], @@ -798,23 +787,14 @@ function handleEvent (event) { const hooks = [ 'onHide', 'onError', - 'onPageNotFound', - 'onUniNViewMessage' + 'onPageNotFound' ]; -function initVm (vm) { - if (this.$vm) { // 百度竟然 onShow 在 onLaunch 之前? - return - } - - this.$vm = vm; - - this.$vm.$mp = { - app: this - }; -} - -function createApp (vm) { +function parseBaseApp (vm, { + mocks, + initRefs +}) { + Vue.prototype.mpHost = "app-plus"; Vue.mixin({ beforeCreate () { @@ -838,183 +818,182 @@ function createApp (vm) { 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 = vm; + + this.$vm.$mp = { + app: this + }; + + this.$vm.$scope = this; this.$vm._isMounted = true; - this.$vm.__call_hook('mounted'); + this.$vm.__call_hook('mounted', args); 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); + initHooks(appOptions, hooks); - return vm + return appOptions } -const hooks$1 = [ - 'onShow', - 'onHide', - 'onPullDownRefresh', - 'onReachBottom', - 'onShareAppMessage', - 'onPageScroll', - 'onResize', - 'onTabItemTap', - 'onBackPress', - 'onNavigationBarButtonTap', - 'onNavigationBarSearchInputChanged', - 'onNavigationBarSearchInputConfirmed', - 'onNavigationBarSearchInputClicked' -]; +const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__']; -function initVm$1 (VueComponent) { // 百度的 onLoad 触发在 attached 之前 - if (this.$vm) { - return +function findVmByVueId (vm, vuePid) { + const $children = vm.$children; + // 优先查找直属 + 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({ - mpType: 'page', - mpInstance: this - }); - - this.$vm.__call_hook('created'); - this.$vm.$mount(); +function initBehavior (options) { + return Behavior(options) } -function createPage (vueOptions) { - vueOptions = vueOptions.default || vueOptions; - 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 - } - }; +function isPage () { + return !!this.route +} - initHooks(pageOptions.methods, hooks$1); +function initRelation (detail) { + this.triggerEvent('__l', detail); +} - return initPage$1(pageOptions, vueOptions) -} - -function initVm$2 (VueComponent) { - if (this.$vm) { - return - } +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 + } + }); +} - const properties = this.properties; +function handleLink (event) { + const { + vuePid, + vueOptions + } = event.detail || event.value; // detail 是微信,value 是百度(dipatch) - const options = { - mpType: 'component', - mpInstance: this, - propsData: properties - }; - // 初始化 vue 实例 - this.$vm = new VueComponent(options); + let parentVm; - // 处理$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; + if (vuePid) { + parentVm = findVmByVueId(this.$vm, vuePid); } - // 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并 - // 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性 - this.$vm.$mount(); -} -function createComponent (vueOptions) { - vueOptions = vueOptions.default || vueOptions; - - let VueComponent; - if (isFn(vueOptions)) { - VueComponent = vueOptions; // TODO form-field props.name,props.value - vueOptions = VueComponent.extendOptions; - } else { - VueComponent = Vue.extend(vueOptions); + if (!parentVm) { + parentVm = this.$vm; } - 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 = { options: { multipleSlots: true, addGlobalClass: true }, - data: getData(vueOptions, Vue.prototype), - behaviors, - properties, + data: initData(vueOptions, Vue.prototype), + behaviors: initBehaviors(vueOptions, initBehavior), + properties: initProperties(vueOptions.props, false, vueOptions.__file), lifetimes: { 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 () { - initVm$2.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'); + // 当组件 props 默认值为 true,初始化时传入 false 会导致 created,ready 触发, 但 attached 不触发 + // https://developers.weixin.qq.com/community/develop/doc/00066ae2844cc0f8eb883e2a557800 + if (this.$vm) { + this.$vm._isMounted = true; + this.$vm.__call_hook('mounted'); + this.$vm.__call_hook('onReady'); + } else { + this.is && console.warn(this.is + ' is not attached'); + } }, detached () { this.$vm.$destroy(); @@ -1022,7 +1001,7 @@ function createComponent (vueOptions) { }, pageLifetimes: { show (args) { - this.$vm.__call_hook('onPageShow', args); + this.$vm && this.$vm.__call_hook('onPageShow', args); }, hide () { this.$vm && this.$vm.__call_hook('onPageHide'); @@ -1032,12 +1011,93 @@ function createComponent (vueOptions) { } }, 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 => { diff --git a/packages/uni-mp-alipay/dist/index.js b/packages/uni-mp-alipay/dist/index.js index c7d3b20eb62075ce5e5618bf9c3b6df6856ff4d8..6ef9cfdebd2fb3c26aaaafa4beb963d8bfc3f224 100644 --- a/packages/uni-mp-alipay/dist/index.js +++ b/packages/uni-mp-alipay/dist/index.js @@ -88,16 +88,18 @@ function promisify (name, api) { success: resolve, fail: reject }), ...params); - /* eslint-disable no-extend-native */ - Promise.prototype.finally = function (callback) { - const promise = this.constructor; - return this.then( - value => promise.resolve(callback()).then(() => value), - reason => promise.resolve(callback()).then(() => { - throw reason - }) - ) - }; + /* eslint-disable no-extend-native */ + if (!Promise.prototype.finally) { + Promise.prototype.finally = function (callback) { + const promise = this.constructor; + return this.then( + value => promise.resolve(callback()).then(() => value), + reason => promise.resolve(callback()).then(() => { + throw reason + }) + ) + }; + } })) } } @@ -688,257 +690,57 @@ var api = /*#__PURE__*/Object.freeze({ startGyroscope: startGyroscope }); -const isArray = Array.isArray; -const keyList = Object.keys; - -function equal (a, b) { - if (a === b) return true - - if (a && b && typeof a === 'object' && typeof b === 'object') { - const arrA = isArray(a); - const arrB = isArray(b); - let i, length, key; - if (arrA && arrB) { - length = a.length; - if (length !== b.length) return false - for (i = length; i-- !== 0;) { - if (!equal(a[i], b[i])) return false - } - return true - } - if (arrA !== arrB) return false - - const dateA = a instanceof Date; - const dateB = b instanceof Date; - if (dateA !== dateB) return false - if (dateA && dateB) return a.getTime() === b.getTime() - - const regexpA = a instanceof RegExp; - const regexpB = b instanceof RegExp; - if (regexpA !== regexpB) return false - if (regexpA && regexpB) return a.toString() === b.toString() - - const keys = keyList(a); - length = keys.length; - if (length !== keyList(b).length) { - return false - } - for (i = length; i-- !== 0;) { - if (!hasOwn.call(b, keys[i])) return false - } - for (i = length; i-- !== 0;) { - key = keys[i]; - if (!equal(a[key], b[key])) return false - } - - return true - } - - return false -} - -const customizeRE = /:/g; - -const customize = cached((str) => { - return camelize(str.replace(customizeRE, '-')) -}); - -const mocks = ['$id']; - -function handleRef (ref) { - if (!ref) { - return - } - const refName = ref.props['data-ref']; - const refInForName = ref.props['data-ref-in-for']; - if (refName) { - this.$vm.$refs[refName] = ref.$vm || ref; - } else if (refInForName) { - this.$vm.$refs[refInForName] = [ref.$vm || ref]; - } -} - -function initPage (pageOptions, vueOptions) { - const { - lifetimes, - methods - } = pageOptions; - - pageOptions.onReady = lifetimes.ready; - pageOptions.onUnload = function () { - lifetimes.detached.call(this); - methods.onUnload.call(this); - }; +const PAGE_EVENT_HOOKS = [ + 'onPullDownRefresh', + 'onReachBottom', + 'onShareAppMessage', + 'onPageScroll', + 'onResize', + 'onTabItemTap' +]; - Object.keys(methods).forEach(method => { - if (method !== 'onUnload') { - pageOptions[method] = methods[method]; +function initMocks (vm, mocks) { + const mpInstance = vm.$mp[vm.mpType]; + mocks.forEach(mock => { + if (hasOwn(mpInstance, mock)) { + vm[mock] = mpInstance[mock]; } }); - - 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)]; - if (!handler) { - return - } - - const eventOpts = this.props['data-event-opts']; - - const target = { - dataset: { - eventOpts - } - }; - - handler({ - type: customize(type), - target, - currentTarget: target, - detail +function initHooks (mpOptions, hooks) { + hooks.forEach(hook => { + mpOptions[hook] = function (args) { + return this.$vm && this.$vm.__call_hook(hook, args) + }; }); } -const IGNORES = ['$slots', '$scopedSlots']; - -function createObserver (isDidUpdate) { - return function observe (props) { - const prevProps = isDidUpdate ? props : this.props; - const nextProps = isDidUpdate ? this.props : props; - if (equal(prevProps, nextProps)) { - return - } - Object.keys(prevProps).forEach(name => { - if (IGNORES.indexOf(name) === -1) { - const prevValue = prevProps[name]; - const nextValue = nextProps[name]; - if (!isFn(prevValue) && !isFn(nextValue) && !equal(prevValue, nextValue)) { - this.$vm[name] = nextProps[name]; - } - } - }); - } -} - -function initComponent (componentOptions, vueOptions) { - const { - lifetimes, - properties, - behaviors - } = 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 +function initVueComponent (Vue$$1, vueOptions) { + vueOptions = vueOptions.default || vueOptions; + let VueComponent; + if (isFn(vueOptions)) { + VueComponent = vueOptions; + vueOptions = VueComponent.extendOptions; } else { - componentOptions.didUpdate = createObserver(true); // prevProps - } - - if (!componentOptions.methods) { - componentOptions.methods = {}; + VueComponent = Vue$$1.extend(vueOptions); } - - if (vueOptions.methods && vueOptions.methods.formReset) { - componentOptions.methods['formReset'] = vueOptions.methods.formReset; - } - - componentOptions.methods['__r'] = handleRef; - componentOptions.methods.triggerEvent = triggerEvent; - - delete componentOptions.properties; - delete componentOptions.behaviors; - delete componentOptions.lifetimes; - delete componentOptions.pageLifetimes; - - return my.createComponent(componentOptions) + return [VueComponent, vueOptions] } -function initBehavior ({ - properties -}) { - const props = {}; - - Object.keys(properties).forEach(key => { - props[key] = properties[key].value; - }); +function initVueIds (vueIds, mpInstance) { + vueIds = (vueIds || '').split(','); + const len = vueIds.length; - return { - props + if (len === 1) { + mpInstance._$vueId = vueIds[0]; + } else if (len === 2) { + mpInstance._$vueId = vueIds[0]; + mpInstance._$vuePid = vueIds[1]; } } -function triggerLink (mpInstance, vueOptions) { - mpInstance.props.onTriggerLink(mpInstance.$vm || vueOptions); -} - -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; - } - } -} - -function initMocks (vm, mocks$$1) { - const mpInstance = vm.$mp[vm.mpType]; - mocks$$1.forEach(mock => { - if (hasOwn(mpInstance, mock)) { - vm[mock] = mpInstance[mock]; - } - }); -} - -function initHooks (mpOptions, hooks) { - hooks.forEach(hook => { - mpOptions[hook] = function (args) { - return this.$vm.__call_hook(hook, args) - }; - }); -} - -function getData (vueOptions, context) { +function initData (vueOptions, context) { let data = vueOptions.data || {}; const methods = vueOptions.methods || {}; @@ -972,7 +774,7 @@ function getData (vueOptions, context) { const PROP_TYPES = [String, Number, Boolean, Object, Array, null]; -function createObserver$1 (name) { +function createObserver (name) { return function observer (newVal, oldVal) { if (this.$vm) { this.$vm[name] = newVal; // 为了触发其他非 render watcher @@ -980,7 +782,7 @@ function createObserver$1 (name) { } } -function getBehaviors (vueOptions) { +function initBehaviors (vueOptions, initBehavior) { const vueBehaviors = vueOptions['behaviors']; const vueExtends = vueOptions['extends']; const vueMixins = vueOptions['mixins']; @@ -1009,7 +811,7 @@ function getBehaviors (vueOptions) { if (isPlainObject(vueExtends) && vueExtends.props) { behaviors.push( initBehavior({ - properties: getProperties(vueExtends.props, true) + properties: initProperties(vueExtends.props, true) }) ); } @@ -1018,7 +820,7 @@ function getBehaviors (vueOptions) { if (isPlainObject(vueMixin) && vueMixin.props) { behaviors.push( initBehavior({ - properties: getProperties(vueMixin.props, true) + properties: initProperties(vueMixin.props, true) }) ); } @@ -1035,9 +837,13 @@ function parsePropType (key, type, defaultValue, file) { return type } -function getProperties (props, isBehavior = false, file = '') { +function initProperties (props, isBehavior = false, file = '') { const properties = {}; if (!isBehavior) { + properties.vueId = { + type: String, + value: '' + }; properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [], @@ -1056,7 +862,7 @@ function getProperties (props, isBehavior = false, file = '') { props.forEach(key => { properties[key] = { type: null, - observer: createObserver$1(key) + observer: createObserver(key) }; }); } else if (isPlainObject(props)) { // {title:{type:String,default:''},content:String} @@ -1073,13 +879,13 @@ function getProperties (props, isBehavior = false, file = '') { properties[key] = { type: PROP_TYPES.indexOf(opts.type) !== -1 ? opts.type : null, value, - observer: createObserver$1(key) + observer: createObserver(key) }; } else { // content:String const type = parsePropType(key, opts, null, file); properties[key] = { type: PROP_TYPES.indexOf(type) !== -1 ? type : null, - observer: createObserver$1(key) + observer: createObserver(key) }; } }); @@ -1288,42 +1094,14 @@ function handleEvent (event) { const hooks = [ 'onHide', 'onError', - 'onPageNotFound', - 'onUniNViewMessage' + 'onPageNotFound' ]; -function initVm (vm) { - if (this.$vm) { // 百度竟然 onShow 在 onLaunch 之前? - return - } - - this.$vm = vm; - - this.$vm.$mp = { - app: this - }; -} - -function createApp (vm) { - // 外部初始化时 Vue 还未初始化,放到 createApp 内部初始化 mixin - { - 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 () { - - } - }); - } +function parseBaseApp (vm, { + mocks, + initRefs +}) { + Vue.prototype.mpHost = "mp-alipay"; Vue.mixin({ beforeCreate () { @@ -1344,124 +1122,356 @@ function createApp (vm) { 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 = vm; + + this.$vm.$mp = { + app: this + }; + + this.$vm.$scope = this; this.$vm._isMounted = true; - this.$vm.__call_hook('mounted'); + this.$vm.__call_hook('mounted', args); 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 原型上开发者注册的属性无法访问 + initHooks(appOptions, hooks); - App(appOptions); + return appOptions +} + +function findVmByVueId (vm, vuePid) { + const $children = vm.$children; + // 优先查找直属 + 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 + } + } +} - return vm +function handleLink (event) { + const { + vuePid, + vueOptions + } = event.detail || event.value; // detail 是微信,value 是百度(dipatch) + + let parentVm; + + if (vuePid) { + parentVm = findVmByVueId(this.$vm, vuePid); + } + + if (!parentVm) { + parentVm = this.$vm; + } + + vueOptions.parent = parentVm; } -const hooks$1 = [ - 'onShow', - 'onHide', - 'onPullDownRefresh', - 'onReachBottom', - 'onShareAppMessage', - 'onPageScroll', - 'onResize', - 'onTabItemTap', - 'onBackPress', - 'onNavigationBarButtonTap', - 'onNavigationBarSearchInputChanged', - 'onNavigationBarSearchInputConfirmed', - 'onNavigationBarSearchInputClicked' -]; +const isArray = Array.isArray; +const keyList = Object.keys; -function initVm$1 (VueComponent) { // 百度的 onLoad 触发在 attached 之前 - if (this.$vm) { +function equal (a, b) { + if (a === b) return true + + if (a && b && typeof a === 'object' && typeof b === 'object') { + const arrA = isArray(a); + const arrB = isArray(b); + let i, length, key; + if (arrA && arrB) { + length = a.length; + if (length !== b.length) return false + for (i = length; i-- !== 0;) { + if (!equal(a[i], b[i])) return false + } + return true + } + if (arrA !== arrB) return false + + const dateA = a instanceof Date; + const dateB = b instanceof Date; + if (dateA !== dateB) return false + if (dateA && dateB) return a.getTime() === b.getTime() + + const regexpA = a instanceof RegExp; + const regexpB = b instanceof RegExp; + if (regexpA !== regexpB) return false + if (regexpA && regexpB) return a.toString() === b.toString() + + const keys = keyList(a); + length = keys.length; + if (length !== keyList(b).length) { + return false + } + for (i = length; i-- !== 0;) { + if (!hasOwn.call(b, keys[i])) return false + } + for (i = length; i-- !== 0;) { + key = keys[i]; + if (!equal(a[key], b[key])) return false + } + + return true + } + + return false +} + +const customizeRE = /:/g; + +const customize = cached((str) => { + return camelize(str.replace(customizeRE, '-')) +}); + +const isComponent2 = my.canIUse('component2'); + +const mocks$1 = ['$id']; + +function initRefs$1 () { + +} + +function initBehavior$1 ({ + properties +}) { + const props = {}; + + Object.keys(properties).forEach(key => { + props[key] = properties[key].value; + }); + + return { + props + } +} + +function initRelation$1 (detail) { + this.props.onVueInit(detail); +} + +function initChildVues (mpInstance) { + mpInstance._$childVues && mpInstance._$childVues.forEach(({ + vuePid, + vueOptions, + VueComponent, + mpInstance: childMPInstance + }) => { + // 父子关系 + handleLink.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; +} + +function handleRef (ref) { + if (!ref) { return } + const refName = ref.props['data-ref']; + const refInForName = ref.props['data-ref-in-for']; + if (refName) { + this.$vm.$refs[refName] = ref.$vm || ref; + } else if (refInForName) { + this.$vm.$refs[refInForName] = [ref.$vm || ref]; + } +} - this.$vm = new VueComponent({ - mpType: 'page', - mpInstance: this - }); +function triggerEvent (type, detail, options) { + const handler = this.props[customize('on-' + type)]; + if (!handler) { + return + } - this.$vm.__call_hook('created'); - this.$vm.$mount(); + const eventOpts = this.props['data-event-opts']; + + const target = { + dataset: { + eventOpts + } + }; + + handler({ + type: customize(type), + target, + currentTarget: target, + detail + }); } -function createPage (vueOptions) { - vueOptions = vueOptions.default || vueOptions; - let VueComponent; - if (isFn(vueOptions)) { - VueComponent = vueOptions; - vueOptions = VueComponent.extendOptions; - } else { - VueComponent = Vue.extend(vueOptions); +const IGNORES = ['$slots', '$scopedSlots']; + +function createObserver$1 (isDidUpdate) { + return function observe (props) { + const prevProps = isDidUpdate ? props : this.props; + const nextProps = isDidUpdate ? this.props : props; + if (equal(prevProps, nextProps)) { + return + } + Object.keys(prevProps).forEach(name => { + if (IGNORES.indexOf(name) === -1) { + const prevValue = prevProps[name]; + const nextValue = nextProps[name]; + if (!isFn(prevValue) && !isFn(nextValue) && !equal(prevValue, nextValue)) { + this.$vm[name] = nextProps[name]; + } + } + }); } - const pageOptions = { - options: { - multipleSlots: true, - addGlobalClass: true +} + +const handleLink$1 = (function () { + if (isComponent2) { + return function handleLink$$1 (detail) { + return handleLink.call(this, { + detail + }) + } + } + return function handleLink$$1 (detail) { + if (this.$vm) { // 父已初始化 + return handleLink.call(this, { + detail: { + vuePid: detail.vuePid, + vueOptions: detail.vueOptions + } + }) + } + // 支付宝通过 didMount 来实现,先子后父,故等父 ready 之后,统一初始化 + (this._$childVues || (this._$childVues = [])).unshift(detail); + } +})(); + +function parseApp (vm) { + Object.defineProperty(Vue.prototype, '$slots', { + get () { + return this.$scope && this.$scope.props.$slots }, - 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(); - } + set () { + + } + }); + Object.defineProperty(Vue.prototype, '$scopedSlots', { + get () { + return this.$scope && this.$scope.props.$scopedSlots }, - 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 + set () { + } + }); + + return parseBaseApp(vm, { + mocks: mocks$1, + initRefs: initRefs$1 + }) +} + +function createApp (vm) { + App(parseApp(vm)); + return vm +} + +const hooks$1 = [ + 'onShow', + 'onHide', + // mp-alipay 特有 + 'onTitleClick', + 'onOptionMenuClick', + 'onPopMenuClick', + 'onPullIntercept' +]; + +hooks$1.push(...PAGE_EVENT_HOOKS); + +function parsePage (vuePageOptions) { + let [VueComponent, vueOptions] = initVueComponent(Vue, vuePageOptions); + + const pageOptions = { + mixins: initBehaviors(vueOptions, initBehavior$1), + 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$1 }; - initHooks(pageOptions.methods, hooks$1); + initHooks(pageOptions, hooks$1); - return initPage(pageOptions, vueOptions) + return pageOptions +} + +function createPage (vuePageOptions) { + { + return Page(parsePage(vuePageOptions)) + } } -function initVm$2 (VueComponent) { +function initVm (VueComponent) { if (this.$vm) { return } - const properties = this.props; const options = { @@ -1469,83 +1479,101 @@ function initVm$2 (VueComponent) { 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; + + initVueIds(properties.vueId, this); + + if (isComponent2) { + // 处理父子关系 + initRelation$1.call(this, { + vuePid: this._$vuePid, + vueOptions: options }); - this.$vm.$scopedSlots = this.$vm.$slots = $slots; - } - // 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并 - // 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性 - this.$vm.$mount(); -} -function createComponent (vueOptions) { - vueOptions = vueOptions.default || vueOptions; + // 初始化 vue 实例 + this.$vm = new VueComponent(options); - let VueComponent; - if (isFn(vueOptions)) { - VueComponent = vueOptions; // TODO form-field props.name,props.value - vueOptions = VueComponent.extendOptions; + // 触发首次 setData + this.$vm.$mount(); } else { - VueComponent = Vue.extend(vueOptions); + initChildVues(this); + // 处理父子关系 + initRelation$1.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'); + } } +} - const behaviors = getBehaviors(vueOptions); +function parseComponent (vueComponentOptions) { + let [VueComponent, vueOptions] = initVueComponent(Vue, vueComponentOptions); - const properties = getProperties(vueOptions.props, false, vueOptions.__file); + const properties = initProperties(vueOptions.props, false, vueOptions.__file); - const componentOptions = { - options: { - multipleSlots: true, - addGlobalClass: true - }, - data: getData(vueOptions, Vue.prototype), - behaviors, - properties, - lifetimes: { - attached () { - initVm$2.call(this, VueComponent); - }, - ready () { - initVm$2.call(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 - triggerLink(this); // 处理 parent,children + const props = { + onVueInit: function () {} + }; - // 补充生命周期 - this.$vm.__call_hook('created'); - this.$vm.__call_hook('beforeMount'); + Object.keys(properties).forEach(key => { + if (key !== 'vueSlots') { + props[key] = properties[key].value; + } + }); + + const componentOptions = { + mixins: initBehaviors(vueOptions, initBehavior$1), + 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'); - }, - 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); - } + didUnmount () { + this.$vm.$destroy(); }, methods: { + __r: handleRef, __e: handleEvent, - __l: handleLink + __l: handleLink$1, + triggerEvent } }; - return initComponent(componentOptions, vueOptions) + if (isComponent2) { + componentOptions.onInit = function onInit () { + initVm.call(this, VueComponent); + }; + componentOptions.deriveDataFromProps = createObserver$1(); + } else { + componentOptions.didUpdate = createObserver$1(true); + } + + if (vueOptions.methods && vueOptions.methods.formReset) { + componentOptions.methods.formReset = vueOptions.methods.formReset; + } + return componentOptions +} + +function createComponent (vueOptions) { + { + return my.createComponent(parseComponent(vueOptions)) + } } todos.forEach(todoApi => { diff --git a/packages/uni-mp-baidu/dist/index.js b/packages/uni-mp-baidu/dist/index.js index 6bdf4e3cb939a17f86e6e47bc381787e5a63ebb3..2c39c3ec05cb1a7f3d913a9c77be064114e66449 100644 --- a/packages/uni-mp-baidu/dist/index.js +++ b/packages/uni-mp-baidu/dist/index.js @@ -88,16 +88,18 @@ function promisify (name, api) { success: resolve, fail: reject }), ...params); - /* eslint-disable no-extend-native */ - Promise.prototype.finally = function (callback) { - const promise = this.constructor; - return this.then( - value => promise.resolve(callback()).then(() => value), - reason => promise.resolve(callback()).then(() => { - throw reason - }) - ) - }; + /* eslint-disable no-extend-native */ + if (!Promise.prototype.finally) { + Promise.prototype.finally = function (callback) { + const promise = this.constructor; + return this.then( + value => promise.resolve(callback()).then(() => value), + reason => promise.resolve(callback()).then(() => { + throw reason + }) + ) + }; + } })) } } @@ -469,65 +471,15 @@ Component = function (options = {}) { return MPComponent(options) }; -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 - } - }); -} - -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); +const PAGE_EVENT_HOOKS = [ + 'onPullDownRefresh', + 'onReachBottom', + 'onShareAppMessage', + 'onPageScroll', + 'onResize', + 'onTabItemTap' +]; - target.$root = this.$vm.$root; - } - } else { - if (!target.parent) { - target.parent = this.$vm; - } - } -} - function initMocks (vm, mocks) { const mpInstance = vm.$mp[vm.mpType]; mocks.forEach(mock => { @@ -540,12 +492,46 @@ function initMocks (vm, mocks) { function initHooks (mpOptions, hooks) { hooks.forEach(hook => { 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 || {}; const methods = vueOptions.methods || {}; @@ -587,7 +573,7 @@ function createObserver (name) { } } -function getBehaviors (vueOptions) { +function initBehaviors (vueOptions, initBehavior) { const vueBehaviors = vueOptions['behaviors']; const vueExtends = vueOptions['extends']; const vueMixins = vueOptions['mixins']; @@ -616,7 +602,7 @@ function getBehaviors (vueOptions) { if (isPlainObject(vueExtends) && vueExtends.props) { behaviors.push( initBehavior({ - properties: getProperties(vueExtends.props, true) + properties: initProperties(vueExtends.props, true) }) ); } @@ -625,7 +611,7 @@ function getBehaviors (vueOptions) { if (isPlainObject(vueMixin) && vueMixin.props) { behaviors.push( initBehavior({ - properties: getProperties(vueMixin.props, true) + properties: initProperties(vueMixin.props, true) }) ); } @@ -658,9 +644,13 @@ function parsePropType (key, type, defaultValue, file) { return type } -function getProperties (props, isBehavior = false, file = '') { +function initProperties (props, isBehavior = false, file = '') { const properties = {}; if (!isBehavior) { + properties.vueId = { + type: String, + value: '' + }; properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [], @@ -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 = [ 'onHide', 'onError', - 'onPageNotFound', - 'onUniNViewMessage' + 'onPageNotFound' ]; -function initVm (vm) { - if (this.$vm) { // 百度竟然 onShow 在 onLaunch 之前? - return - } - - this.$vm = vm; - - this.$vm.$mp = { - app: this - }; -} - -function createApp (vm) { +function parseBaseApp (vm, { + mocks, + initRefs +}) { + Vue.prototype.mpHost = "mp-baidu"; Vue.mixin({ beforeCreate () { @@ -973,195 +940,172 @@ function createApp (vm) { if (this.mpType !== 'app') { initRefs(this); - initMocks(this, mocks$1); + initMocks(this, mocks); } - }, - created () { // 处理 injections - this.__init_injections(this); - this.__init_provide(this); } }); const appOptions = { onLaunch (args) { - initVm.call(this, vm); + + this.$vm = vm; + + this.$vm.$mp = { + app: this + }; + + this.$vm.$scope = this; this.$vm._isMounted = true; - this.$vm.__call_hook('mounted'); + this.$vm.__call_hook('mounted', args); 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); + initHooks(appOptions, hooks); - return vm + return appOptions } -const hooks$1 = [ - 'onShow', - 'onHide', - 'onPullDownRefresh', - 'onReachBottom', - 'onShareAppMessage', - 'onPageScroll', - 'onResize', - 'onTabItemTap', - 'onBackPress', - 'onNavigationBarButtonTap', - 'onNavigationBarSearchInputChanged', - 'onNavigationBarSearchInputConfirmed', - 'onNavigationBarSearchInputClicked' -]; - -function initVm$1 (VueComponent) { // 百度的 onLoad 触发在 attached 之前 - if (this.$vm) { - return +function findVmByVueId (vm, vuePid) { + const $children = vm.$children; + // 优先查找直属 + let parentVm = $children.find(childVm => childVm.$scope._$vueId === vuePid); + if (parentVm) { + return parentVm } - - this.$vm = new VueComponent({ - mpType: 'page', - mpInstance: this - }); - - { - this.$vm.$baiduComponentInstances = Object.create(null); + // 反向递归查找 + for (let i = $children.length - 1; i >= 0; i--) { + parentVm = findVmByVueId($children[i], vuePid); + if (parentVm) { + return parentVm + } } +} - this.$vm.__call_hook('created'); - this.$vm.$mount(); +function initBehavior (options) { + return Behavior(options) } -function createPage (vueOptions) { - vueOptions = vueOptions.default || vueOptions; - 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); - { // 百度当组件作为页面时 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); +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] = []; } - }, - __e: handleEvent, - __l: handleLink$1 + $refs[ref].push(component.$vm || component); + }); + 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 = { - mpType: 'component', - mpInstance: this, - propsData: properties - }; - // 初始化 vue 实例 - this.$vm = new VueComponent(options); + let parentVm; - // 处理$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; + if (vuePid) { + parentVm = findVmByVueId(this.$vm, vuePid); } - // 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并 - // 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性 - this.$vm.$mount(); -} - -function createComponent (vueOptions) { - vueOptions = vueOptions.default || vueOptions; - let VueComponent; - if (isFn(vueOptions)) { - VueComponent = vueOptions; // TODO form-field props.name,props.value - vueOptions = VueComponent.extendOptions; - } else { - VueComponent = Vue.extend(vueOptions); + if (!parentVm) { + parentVm = this.$vm; } - 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 = { options: { multipleSlots: true, addGlobalClass: true }, - data: getData(vueOptions, Vue.prototype), - behaviors, - properties, + data: initData(vueOptions, Vue.prototype), + behaviors: initBehaviors(vueOptions, initBehavior), + properties: initProperties(vueOptions.props, false, vueOptions.__file), lifetimes: { 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 () { - initVm$2.call(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 - triggerLink$1(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'); + // 当组件 props 默认值为 true,初始化时传入 false 会导致 created,ready 触发, 但 attached 不触发 + // https://developers.weixin.qq.com/community/develop/doc/00066ae2844cc0f8eb883e2a557800 + if (this.$vm) { + this.$vm._isMounted = true; + this.$vm.__call_hook('mounted'); + this.$vm.__call_hook('onReady'); + } else { + this.is && console.warn(this.is + ' is not attached'); + } }, detached () { this.$vm.$destroy(); @@ -1169,7 +1113,7 @@ function createComponent (vueOptions) { }, pageLifetimes: { show (args) { - this.$vm.__call_hook('onPageShow', args); + this.$vm && this.$vm.__call_hook('onPageShow', args); }, hide () { this.$vm && this.$vm.__call_hook('onPageHide'); @@ -1179,12 +1123,114 @@ function createComponent (vueOptions) { } }, methods: { - __e: handleEvent, - __l: handleLink$1 + __l: handleLink, + __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 => { diff --git a/packages/uni-mp-toutiao/dist/index.js b/packages/uni-mp-toutiao/dist/index.js index 6add7ff27bb4bfaea72f53c24f97a54f2325ed5c..f7e0e20e4a3be964cabd412fd4f1ccca3b8fa625 100644 --- a/packages/uni-mp-toutiao/dist/index.js +++ b/packages/uni-mp-toutiao/dist/index.js @@ -88,16 +88,18 @@ function promisify (name, api) { success: resolve, fail: reject }), ...params); - /* eslint-disable no-extend-native */ - Promise.prototype.finally = function (callback) { - const promise = this.constructor; - return this.then( - value => promise.resolve(callback()).then(() => value), - reason => promise.resolve(callback()).then(() => { - throw reason - }) - ) - }; + /* eslint-disable no-extend-native */ + if (!Promise.prototype.finally) { + Promise.prototype.finally = function (callback) { + const promise = this.constructor; + return this.then( + value => promise.resolve(callback()).then(() => value), + reason => promise.resolve(callback()).then(() => { + throw reason + }) + ) + }; + } })) } } @@ -514,77 +516,15 @@ Component = function (options = {}) { return MPComponent(options) }; -function initBehavior (options) { - return Behavior(options) -} - -const instances = Object.create(null); - -const mocks$1 = ['__route__', '__webviewId__', '__nodeid__']; - -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); +const PAGE_EVENT_HOOKS = [ + 'onPullDownRefresh', + 'onReachBottom', + 'onShareAppMessage', + 'onPageScroll', + 'onResize', + 'onTabItemTap' +]; - childVm.$root = this.$vm.$root; - delete instances[webviewId + '_' + nodeId]; - } -} - function initMocks (vm, mocks) { const mpInstance = vm.$mp[vm.mpType]; mocks.forEach(mock => { @@ -597,12 +537,46 @@ function initMocks (vm, mocks) { function initHooks (mpOptions, hooks) { hooks.forEach(hook => { 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 || {}; const methods = vueOptions.methods || {}; @@ -644,7 +618,7 @@ function createObserver (name) { } } -function getBehaviors (vueOptions) { +function initBehaviors (vueOptions, initBehavior) { const vueBehaviors = vueOptions['behaviors']; const vueExtends = vueOptions['extends']; const vueMixins = vueOptions['mixins']; @@ -673,7 +647,7 @@ function getBehaviors (vueOptions) { if (isPlainObject(vueExtends) && vueExtends.props) { behaviors.push( initBehavior({ - properties: getProperties(vueExtends.props, true) + properties: initProperties(vueExtends.props, true) }) ); } @@ -682,7 +656,7 @@ function getBehaviors (vueOptions) { if (isPlainObject(vueMixin) && vueMixin.props) { behaviors.push( initBehavior({ - properties: getProperties(vueMixin.props, true) + properties: initProperties(vueMixin.props, true) }) ); } @@ -699,9 +673,13 @@ function parsePropType (key, type, defaultValue, file) { return type } -function getProperties (props, isBehavior = false, file = '') { +function initProperties (props, isBehavior = false, file = '') { const properties = {}; if (!isBehavior) { + properties.vueId = { + type: String, + value: '' + }; properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [], @@ -952,23 +930,14 @@ function handleEvent (event) { const hooks = [ 'onHide', 'onError', - 'onPageNotFound', - 'onUniNViewMessage' + 'onPageNotFound' ]; -function initVm (vm) { - if (this.$vm) { // 百度竟然 onShow 在 onLaunch 之前? - return - } - - this.$vm = vm; - - this.$vm.$mp = { - app: this - }; -} - -function createApp (vm) { +function parseBaseApp (vm, { + mocks, + initRefs +}) { + Vue.prototype.mpHost = "mp-toutiao"; Vue.mixin({ beforeCreate () { @@ -989,186 +958,230 @@ function createApp (vm) { delete this.$options.mpInstance; if (this.mpType !== 'app') { - initRefs$1(this); - initMocks(this, mocks$1); + 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 = vm; + + this.$vm.$mp = { + app: this + }; + + this.$vm.$scope = this; this.$vm._isMounted = true; - this.$vm.__call_hook('mounted'); + this.$vm.__call_hook('mounted', args); 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); + initHooks(appOptions, hooks); - return vm + return appOptions } -const hooks$1 = [ - 'onShow', - 'onHide', - 'onPullDownRefresh', - 'onReachBottom', - 'onShareAppMessage', - 'onPageScroll', - 'onResize', - 'onTabItemTap', - 'onBackPress', - 'onNavigationBarButtonTap', - 'onNavigationBarSearchInputChanged', - 'onNavigationBarSearchInputConfirmed', - 'onNavigationBarSearchInputClicked' -]; - -function initVm$1 (VueComponent) { // 百度的 onLoad 触发在 attached 之前 - if (this.$vm) { - return +function findVmByVueId (vm, vuePid) { + const $children = vm.$children; + // 优先查找直属 + 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({ - mpType: 'page', - mpInstance: this - }); - - this.$vm.__call_hook('created'); - this.$vm.$mount(); +function initBehavior (options) { + return Behavior(options) } -function createPage (vueOptions) { - vueOptions = vueOptions.default || vueOptions; - let VueComponent; - if (isFn(vueOptions)) { - VueComponent = vueOptions; - vueOptions = VueComponent.extendOptions; - } else { - VueComponent = Vue.extend(vueOptions); +function handleLink (event) { + const { + vuePid, + vueOptions + } = event.detail || event.value; // detail 是微信,value 是百度(dipatch) + + let parentVm; + + 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) { - if (this.$vm) { +const mocks$1 = ['__route__', '__webviewId__', '__nodeid__', '__nodeId__']; + +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 } - const properties = this.properties; + let parentVm; - const options = { - mpType: 'component', - mpInstance: this, - propsData: properties - }; - // 初始化 vue 实例 - this.$vm = new VueComponent(options); + if (vuePid) { + parentVm = findVmByVueId(this.$vm, vuePid); + } - // 处理$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; + if (!parentVm) { + parentVm = this.$vm; } - // 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并 - // 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性 - this.$vm.$mount(); -} -function createComponent (vueOptions) { - vueOptions = vueOptions.default || vueOptions; + vm.$parent = parentVm; + vm.$root = parentVm.$root; + parentVm.$children.push(vm); - let VueComponent; - if (isFn(vueOptions)) { - VueComponent = vueOptions; // TODO form-field props.name,props.value - vueOptions = VueComponent.extendOptions; - } else { - VueComponent = Vue.extend(vueOptions); - } + vm.__call_hook('created'); + vm.__call_hook('beforeMount'); + vm._isMounted = true; + vm.__call_hook('mounted'); + vm.__call_hook('onReady'); +} + +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 = { options: { multipleSlots: true, addGlobalClass: true }, - data: getData(vueOptions, Vue.prototype), - behaviors, - properties, + data: initData(vueOptions, Vue.prototype), + behaviors: initBehaviors(vueOptions, initBehavior), + properties: initProperties(vueOptions.props, false, vueOptions.__file), lifetimes: { 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 () { - initVm$2.call(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 - triggerLink$1(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'); + // 当组件 props 默认值为 true,初始化时传入 false 会导致 created,ready 触发, 但 attached 不触发 + // https://developers.weixin.qq.com/community/develop/doc/00066ae2844cc0f8eb883e2a557800 + if (this.$vm) { + this.$vm._isMounted = true; + this.$vm.__call_hook('mounted'); + this.$vm.__call_hook('onReady'); + } else { + this.is && console.warn(this.is + ' is not attached'); + } }, detached () { this.$vm.$destroy(); @@ -1176,7 +1189,7 @@ function createComponent (vueOptions) { }, pageLifetimes: { show (args) { - this.$vm.__call_hook('onPageShow', args); + this.$vm && this.$vm.__call_hook('onPageShow', args); }, hide () { this.$vm && this.$vm.__call_hook('onPageHide'); @@ -1186,12 +1199,113 @@ function createComponent (vueOptions) { } }, methods: { - __e: handleEvent, - __l: handleLink$1 + __l: handleLink, + __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 => { diff --git a/packages/uni-mp-weixin/dist/index.js b/packages/uni-mp-weixin/dist/index.js index e6eab42a263bd2f2d728d7cbce16174f07c7c555..e34eb37128ddab56c7cecf1a6c12b82b198267f3 100644 --- a/packages/uni-mp-weixin/dist/index.js +++ b/packages/uni-mp-weixin/dist/index.js @@ -40,7 +40,7 @@ const camelize = cached((str) => { 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$/; @@ -89,15 +89,17 @@ function promisify (name, api) { fail: reject }), ...params); /* eslint-disable no-extend-native */ - Promise.prototype.finally = function (callback) { - const promise = this.constructor; - return this.then( - value => promise.resolve(callback()).then(() => value), - reason => promise.resolve(callback()).then(() => { - throw reason - }) - ) - }; + if (!Promise.prototype.finally) { + Promise.prototype.finally = function (callback) { + const promise = this.constructor; + return this.then( + value => promise.resolve(callback()).then(() => value), + reason => promise.resolve(callback()).then(() => { + throw reason + }) + ) + }; + } })) } } @@ -371,66 +373,18 @@ Component = function (options = {}) { return MPComponent(options) }; -const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__']; - -function initPage (pageOptions) { - return initComponent(pageOptions) -} - -function initComponent (componentOptions) { - 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); +const PAGE_EVENT_HOOKS = [ + 'onPullDownRefresh', + 'onReachBottom', + 'onShareAppMessage', + 'onPageScroll', + 'onResize', + 'onTabItemTap' +]; - event.detail.$root = this.$vm.$root; - } - } else { // vueOptions - if (!event.detail.parent) { - event.detail.parent = this.$vm; - } - } -} - -function initMocks (vm, mocks$$1) { +function initMocks (vm, mocks) { const mpInstance = vm.$mp[vm.mpType]; - mocks$$1.forEach(mock => { + mocks.forEach(mock => { if (hasOwn(mpInstance, mock)) { vm[mock] = mpInstance[mock]; } @@ -440,12 +394,46 @@ function initMocks (vm, mocks$$1) { function initHooks (mpOptions, hooks) { hooks.forEach(hook => { 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 || {}; const methods = vueOptions.methods || {}; @@ -487,7 +475,7 @@ function createObserver (name) { } } -function getBehaviors (vueOptions) { +function initBehaviors (vueOptions, initBehavior) { const vueBehaviors = vueOptions['behaviors']; const vueExtends = vueOptions['extends']; const vueMixins = vueOptions['mixins']; @@ -516,7 +504,7 @@ function getBehaviors (vueOptions) { if (isPlainObject(vueExtends) && vueExtends.props) { behaviors.push( initBehavior({ - properties: getProperties(vueExtends.props, true) + properties: initProperties(vueExtends.props, true) }) ); } @@ -525,7 +513,7 @@ function getBehaviors (vueOptions) { if (isPlainObject(vueMixin) && vueMixin.props) { behaviors.push( initBehavior({ - properties: getProperties(vueMixin.props, true) + properties: initProperties(vueMixin.props, true) }) ); } @@ -542,9 +530,13 @@ function parsePropType (key, type, defaultValue, file) { return type } -function getProperties (props, isBehavior = false, file = '') { +function initProperties (props, isBehavior = false, file = '') { const properties = {}; if (!isBehavior) { + properties.vueId = { + type: String, + value: '' + }; properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [], @@ -795,28 +787,14 @@ function handleEvent (event) { const hooks = [ 'onHide', 'onError', - 'onPageNotFound', - 'onUniNViewMessage' + 'onPageNotFound' ]; -function initVm (vm) { - if (this.$vm) { // 百度竟然 onShow 在 onLaunch 之前? - return - } - { - 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) { +function parseBaseApp (vm, { + mocks, + initRefs +}) { + Vue.prototype.mpHost = "mp-weixin"; Vue.mixin({ beforeCreate () { @@ -840,183 +818,175 @@ function createApp (vm) { initRefs(this); initMocks(this, mocks); } - }, - created () { // 处理 injections - this.__init_injections(this); - this.__init_provide(this); } }); const appOptions = { 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.__call_hook('mounted'); + this.$vm.__call_hook('mounted', args); 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); + initHooks(appOptions, hooks); - return vm + return appOptions } -const hooks$1 = [ - 'onShow', - 'onHide', - 'onPullDownRefresh', - 'onReachBottom', - 'onShareAppMessage', - 'onPageScroll', - 'onResize', - 'onTabItemTap', - 'onBackPress', - 'onNavigationBarButtonTap', - 'onNavigationBarSearchInputChanged', - 'onNavigationBarSearchInputConfirmed', - 'onNavigationBarSearchInputClicked' -]; +const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__']; -function initVm$1 (VueComponent) { // 百度的 onLoad 触发在 attached 之前 - if (this.$vm) { - return +function findVmByVueId (vm, vuePid) { + const $children = vm.$children; + // 优先查找直属 + 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({ - mpType: 'page', - mpInstance: this - }); - - this.$vm.__call_hook('created'); - this.$vm.$mount(); +function initBehavior (options) { + return Behavior(options) } -function createPage (vueOptions) { - vueOptions = vueOptions.default || vueOptions; - 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 - } - }; +function isPage () { + return !!this.route +} - initHooks(pageOptions.methods, hooks$1); +function initRelation (detail) { + this.triggerEvent('__l', detail); +} - return initPage(pageOptions, vueOptions) -} - -function initVm$2 (VueComponent) { - if (this.$vm) { - return - } +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 + } + }); +} - const properties = this.properties; +function handleLink (event) { + const { + vuePid, + vueOptions + } = event.detail || event.value; // detail 是微信,value 是百度(dipatch) - const options = { - mpType: 'component', - mpInstance: this, - propsData: properties - }; - // 初始化 vue 实例 - this.$vm = new VueComponent(options); + let parentVm; - // 处理$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; + if (vuePid) { + parentVm = findVmByVueId(this.$vm, vuePid); } - // 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并 - // 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性 - this.$vm.$mount(); -} -function createComponent (vueOptions) { - vueOptions = vueOptions.default || vueOptions; - - let VueComponent; - if (isFn(vueOptions)) { - VueComponent = vueOptions; // TODO form-field props.name,props.value - vueOptions = VueComponent.extendOptions; - } else { - VueComponent = Vue.extend(vueOptions); + if (!parentVm) { + parentVm = this.$vm; } - const behaviors = getBehaviors(vueOptions); - - const properties = getProperties(vueOptions.props, false, vueOptions.__file); + vueOptions.parent = parentVm; +} + +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 = { options: { multipleSlots: true, addGlobalClass: true }, - data: getData(vueOptions, Vue.prototype), - behaviors, - properties, + data: initData(vueOptions, Vue.prototype), + behaviors: initBehaviors(vueOptions, initBehavior), + properties: initProperties(vueOptions.props, false, vueOptions.__file), lifetimes: { 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 () { - initVm$2.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'); + // 当组件 props 默认值为 true,初始化时传入 false 会导致 created,ready 触发, 但 attached 不触发 + // https://developers.weixin.qq.com/community/develop/doc/00066ae2844cc0f8eb883e2a557800 + if (this.$vm) { + this.$vm._isMounted = true; + this.$vm.__call_hook('mounted'); + this.$vm.__call_hook('onReady'); + } else { + this.is && console.warn(this.is + ' is not attached'); + } }, detached () { this.$vm.$destroy(); @@ -1024,7 +994,7 @@ function createComponent (vueOptions) { }, pageLifetimes: { show (args) { - this.$vm.__call_hook('onPageShow', args); + this.$vm && this.$vm.__call_hook('onPageShow', args); }, hide () { this.$vm && this.$vm.__call_hook('onPageHide'); @@ -1034,12 +1004,68 @@ function createComponent (vueOptions) { } }, 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 => { diff --git a/src/core/runtime/wrapper/create-app.js b/src/core/runtime/wrapper/create-app.js index 366e1ffbc2b6ac81e89aab45905a26ed752cc8b9..8775a33f8cc5c62e54e74116de490bad5bcb289b 100644 --- a/src/core/runtime/wrapper/create-app.js +++ b/src/core/runtime/wrapper/create-app.js @@ -1,113 +1,8 @@ -import 'uni-platform/runtime/index' - -import Vue from 'vue' - -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 - } -} +import 'uni-platform/runtime/index' + +import parseApp from 'uni-platform/runtime/wrapper/app-parser' export function createApp (vm) { - // 外部初始化时 Vue 还未初始化,放到 createApp 内部初始化 mixin - 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) - + App(parseApp(vm)) return vm } diff --git a/src/core/runtime/wrapper/create-component.js b/src/core/runtime/wrapper/create-component.js index 0b7f9cceeb028ba776bebe5d5dd8da6acc1bc511..6d979a2fc37697aee8dd0eebb792522c0c698d8c 100644 --- a/src/core/runtime/wrapper/create-component.js +++ b/src/core/runtime/wrapper/create-component.js @@ -1,111 +1,9 @@ -import Vue from 'vue' - -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() -} +import parseComponent from 'uni-platform/runtime/wrapper/component-parser' export function createComponent (vueOptions) { - vueOptions = vueOptions.default || vueOptions - - let VueComponent - if (isFn(vueOptions)) { - VueComponent = vueOptions // TODO form-field props.name,props.value - vueOptions = VueComponent.extendOptions + if (__PLATFORM__ === 'mp-alipay') { + return my.createComponent(parseComponent(vueOptions)) } else { - VueComponent = Vue.extend(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 Component(parseComponent(vueOptions)) } - - return initComponent(componentOptions, vueOptions) } diff --git a/src/core/runtime/wrapper/create-page.js b/src/core/runtime/wrapper/create-page.js index d419d36f9ada46f8ea27c4c877d6c62742d97a16..0314e040e3cf1e0b719287c8dde2f3af5a8e513e 100644 --- a/src/core/runtime/wrapper/create-page.js +++ b/src/core/runtime/wrapper/create-page.js @@ -1,105 +1,9 @@ -import Vue from 'vue' +import parsePage from 'uni-platform/runtime/wrapper/page-parser' -import { - isFn -} from 'uni-shared' - -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 +export function createPage (vuePageOptions) { + if (__PLATFORM__ === 'mp-alipay') { + return Page(parsePage(vuePageOptions)) } 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) } diff --git a/src/core/runtime/wrapper/util.js b/src/core/runtime/wrapper/util.js index facd49f628ebddfebc04b148d11ebb8dcdf01ada..be19c912707417eb9a5d37ecfae56e0503e5d94e 100644 --- a/src/core/runtime/wrapper/util.js +++ b/src/core/runtime/wrapper/util.js @@ -5,9 +5,14 @@ import { isPlainObject } from 'uni-shared' -import { - initBehavior -} from 'uni-platform/runtime/wrapper/index' +export const PAGE_EVENT_HOOKS = [ + 'onPullDownRefresh', + 'onReachBottom', + 'onShareAppMessage', + 'onPageScroll', + 'onResize', + 'onTabItemTap' +] export function initMocks (vm, mocks) { const mpInstance = vm.$mp[vm.mpType] @@ -21,12 +26,46 @@ export function initMocks (vm, mocks) { export function initHooks (mpOptions, hooks) { hooks.forEach(hook => { 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 || {} const methods = vueOptions.methods || {} @@ -68,7 +107,7 @@ function createObserver (name) { } } -export function getBehaviors (vueOptions) { +export function initBehaviors (vueOptions, initBehavior) { const vueBehaviors = vueOptions['behaviors'] const vueExtends = vueOptions['extends'] const vueMixins = vueOptions['mixins'] @@ -97,7 +136,7 @@ export function getBehaviors (vueOptions) { if (isPlainObject(vueExtends) && vueExtends.props) { behaviors.push( initBehavior({ - properties: getProperties(vueExtends.props, true) + properties: initProperties(vueExtends.props, true) }) ) } @@ -106,7 +145,7 @@ export function getBehaviors (vueOptions) { if (isPlainObject(vueMixin) && vueMixin.props) { behaviors.push( initBehavior({ - properties: getProperties(vueMixin.props, true) + properties: initProperties(vueMixin.props, true) }) ) } @@ -139,9 +178,13 @@ function parsePropType (key, type, defaultValue, file) { return type } -export function getProperties (props, isBehavior = false, file = '') { +export function initProperties (props, isBehavior = false, file = '') { const properties = {} if (!isBehavior) { + properties.vueId = { + type: String, + value: '' + } properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [], @@ -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) - }) } diff --git a/src/platforms/app-plus/runtime/wrapper/app-parser.js b/src/platforms/app-plus/runtime/wrapper/app-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..f81aa73131d89906e67fec822984376ae76e38c9 --- /dev/null +++ b/src/platforms/app-plus/runtime/wrapper/app-parser.js @@ -0,0 +1,17 @@ +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 +} diff --git a/src/platforms/app-plus/runtime/wrapper/component-parser.js b/src/platforms/app-plus/runtime/wrapper/component-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..9c2411f85267e7b3c617eee0ebe8d8f6982f17da --- /dev/null +++ b/src/platforms/app-plus/runtime/wrapper/component-parser.js @@ -0,0 +1,10 @@ +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 +} diff --git a/src/platforms/app-plus/runtime/wrapper/index.js b/src/platforms/app-plus/runtime/wrapper/index.js deleted file mode 100644 index 6056918eb655dd55799cf91d9ceeeeda0a9dd622..0000000000000000000000000000000000000000 --- a/src/platforms/app-plus/runtime/wrapper/index.js +++ /dev/null @@ -1,19 +0,0 @@ -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) -} diff --git a/src/platforms/app-plus/runtime/wrapper/page-parser.js b/src/platforms/app-plus/runtime/wrapper/page-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..642764001012904e83e37ce218f54914e2790e60 --- /dev/null +++ b/src/platforms/app-plus/runtime/wrapper/page-parser.js @@ -0,0 +1,21 @@ +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 +} diff --git a/src/platforms/mp-alipay/runtime/wrapper/app-parser.js b/src/platforms/mp-alipay/runtime/wrapper/app-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..eae3496ea81e73ef9b695f814673be77147392b8 --- /dev/null +++ b/src/platforms/mp-alipay/runtime/wrapper/app-parser.js @@ -0,0 +1,32 @@ +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 + }) +} diff --git a/src/platforms/mp-alipay/runtime/wrapper/component-parser.js b/src/platforms/mp-alipay/runtime/wrapper/component-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..f86f87f0e6b42446cea28609e80fd9224ec6d805 --- /dev/null +++ b/src/platforms/mp-alipay/runtime/wrapper/component-parser.js @@ -0,0 +1,123 @@ +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 +} diff --git a/src/platforms/mp-alipay/runtime/wrapper/index.js b/src/platforms/mp-alipay/runtime/wrapper/index.js deleted file mode 100644 index d73e749f8bef1849c6a65bde79a3074454d6617b..0000000000000000000000000000000000000000 --- a/src/platforms/mp-alipay/runtime/wrapper/index.js +++ /dev/null @@ -1,195 +0,0 @@ -import { - isFn, - cached, - camelize -} from 'uni-shared' - -import deepEqual from './deep-equal' - -const customizeRE = /:/g - -const customize = cached((str) => { - return camelize(str.replace(customizeRE, '-')) -}) - -export const mocks = ['$id'] - -export function initRefs () { - -} - -function handleRef (ref) { - if (!ref) { - return - } - const refName = ref.props['data-ref'] - const refInForName = ref.props['data-ref-in-for'] - if (refName) { - this.$vm.$refs[refName] = ref.$vm || ref - } else if (refInForName) { - this.$vm.$refs[refInForName] = [ref.$vm || ref] - } -} - -export function initPage (pageOptions, vueOptions) { - 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)] - if (!handler) { - return - } - - const eventOpts = this.props['data-event-opts'] - - const target = { - dataset: { - eventOpts - } - } - - handler({ - type: customize(type), - target, - currentTarget: target, - detail - }) -} - -const IGNORES = ['$slots', '$scopedSlots'] - -function createObserver (isDidUpdate) { - return function observe (props) { - const prevProps = isDidUpdate ? props : this.props - const nextProps = isDidUpdate ? this.props : props - if (deepEqual(prevProps, nextProps)) { - return - } - Object.keys(prevProps).forEach(name => { - if (IGNORES.indexOf(name) === -1) { - const prevValue = prevProps[name] - const nextValue = nextProps[name] - if (!isFn(prevValue) && !isFn(nextValue) && !deepEqual(prevValue, nextValue)) { - this.$vm[name] = nextProps[name] - } - } - }) - } -} - -export function initComponent (componentOptions, vueOptions) { - const { - lifetimes, - properties, - behaviors - } = 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 - } - - componentOptions.methods['__r'] = handleRef - componentOptions.methods.triggerEvent = triggerEvent - - delete componentOptions.properties - delete componentOptions.behaviors - 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 - } - } -} diff --git a/src/platforms/mp-alipay/runtime/wrapper/page-parser.js b/src/platforms/mp-alipay/runtime/wrapper/page-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..c8995bef7258ebdb759442fe2797bd9500a2fe01 --- /dev/null +++ b/src/platforms/mp-alipay/runtime/wrapper/page-parser.js @@ -0,0 +1,73 @@ +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 +} diff --git a/src/platforms/mp-alipay/runtime/wrapper/util.js b/src/platforms/mp-alipay/runtime/wrapper/util.js new file mode 100644 index 0000000000000000000000000000000000000000..4ca694d9c88e345822ada04d84850ce850e86b3a --- /dev/null +++ b/src/platforms/mp-alipay/runtime/wrapper/util.js @@ -0,0 +1,150 @@ +import { + isFn, + cached, + camelize +} from 'uni-shared' + +import { + handleLink as handleBaseLink +} from '../../../mp-weixin/runtime/wrapper/util' + +import deepEqual from './deep-equal' + +const customizeRE = /:/g + +const customize = cached((str) => { + return camelize(str.replace(customizeRE, '-')) +}) + +export const isComponent2 = my.canIUse('component2') + +export const mocks = ['$id'] + +export function initRefs () { + +} + +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) { + return + } + const refName = ref.props['data-ref'] + const refInForName = ref.props['data-ref-in-for'] + if (refName) { + this.$vm.$refs[refName] = ref.$vm || ref + } else if (refInForName) { + this.$vm.$refs[refInForName] = [ref.$vm || ref] + } +} + +export function triggerEvent (type, detail, options) { + const handler = this.props[customize('on-' + type)] + if (!handler) { + return + } + + const eventOpts = this.props['data-event-opts'] + + const target = { + dataset: { + eventOpts + } + } + + handler({ + type: customize(type), + target, + currentTarget: target, + detail + }) +} + +const IGNORES = ['$slots', '$scopedSlots'] + +export function createObserver (isDidUpdate) { + return function observe (props) { + const prevProps = isDidUpdate ? props : this.props + const nextProps = isDidUpdate ? this.props : props + if (deepEqual(prevProps, nextProps)) { + return + } + Object.keys(prevProps).forEach(name => { + if (IGNORES.indexOf(name) === -1) { + const prevValue = prevProps[name] + const nextValue = nextProps[name] + if (!isFn(prevValue) && !isFn(nextValue) && !deepEqual(prevValue, nextValue)) { + this.$vm[name] = nextProps[name] + } + } + }) + } +} + +export const handleLink = (function () { + if (isComponent2) { + return function handleLink (detail) { + return handleBaseLink.call(this, { + detail + }) + } + } + return function handleLink (detail) { + if (this.$vm) { // 父已初始化 + return handleBaseLink.call(this, { + detail: { + vuePid: detail.vuePid, + vueOptions: detail.vueOptions + } + }) + } + // 支付宝通过 didMount 来实现,先子后父,故等父 ready 之后,统一初始化 + (this._$childVues || (this._$childVues = [])).unshift(detail) + } +})() diff --git a/src/platforms/mp-baidu/runtime/wrapper/app-parser.js b/src/platforms/mp-baidu/runtime/wrapper/app-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..437ecbb21fcb0688fc09bc0e7eb6ecced5a74103 --- /dev/null +++ b/src/platforms/mp-baidu/runtime/wrapper/app-parser.js @@ -0,0 +1,16 @@ +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 + }) +} diff --git a/src/platforms/mp-baidu/runtime/wrapper/component-parser.js b/src/platforms/mp-baidu/runtime/wrapper/component-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..01ce01f319cc28814ad84b69a5b2f9ef904ca2a8 --- /dev/null +++ b/src/platforms/mp-baidu/runtime/wrapper/component-parser.js @@ -0,0 +1,33 @@ +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 +} diff --git a/src/platforms/mp-baidu/runtime/wrapper/index.js b/src/platforms/mp-baidu/runtime/wrapper/index.js deleted file mode 100644 index e64c659c3693e1c556fbf08e865d2b0dffaad50f..0000000000000000000000000000000000000000 --- a/src/platforms/mp-baidu/runtime/wrapper/index.js +++ /dev/null @@ -1,38 +0,0 @@ -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 - } - } -} diff --git a/src/platforms/mp-baidu/runtime/wrapper/page-parser.js b/src/platforms/mp-baidu/runtime/wrapper/page-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..8923c6d09a9ccdbb8b52f5a702654e2cf62fdda2 --- /dev/null +++ b/src/platforms/mp-baidu/runtime/wrapper/page-parser.js @@ -0,0 +1,39 @@ +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 +} diff --git a/src/platforms/mp-baidu/runtime/wrapper/util.js b/src/platforms/mp-baidu/runtime/wrapper/util.js new file mode 100644 index 0000000000000000000000000000000000000000..6e7217e3247140c7e8cc7271cc00768ea635d92f --- /dev/null +++ b/src/platforms/mp-baidu/runtime/wrapper/util.js @@ -0,0 +1,9 @@ +export const mocks = ['nodeId'] + +export function isPage () { + return !this.ownerId +} + +export function initRelation (detail) { + this.dispatch('__l', detail) +} diff --git a/src/platforms/mp-toutiao/runtime/wrapper/app-parser.js b/src/platforms/mp-toutiao/runtime/wrapper/app-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..6da217e2b09efa5d15308e1b87f2bebe5c926c40 --- /dev/null +++ b/src/platforms/mp-toutiao/runtime/wrapper/app-parser.js @@ -0,0 +1,28 @@ +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 时,可能查询不到 + }) +} diff --git a/src/platforms/mp-toutiao/runtime/wrapper/component-parser.js b/src/platforms/mp-toutiao/runtime/wrapper/component-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..da644ba7a93bd624af50f58f1706969a23b21a8e --- /dev/null +++ b/src/platforms/mp-toutiao/runtime/wrapper/component-parser.js @@ -0,0 +1,50 @@ +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 +} diff --git a/src/platforms/mp-toutiao/runtime/wrapper/index.js b/src/platforms/mp-toutiao/runtime/wrapper/index.js deleted file mode 100644 index af9852d340872e7efdd66b8ef3f2946a1bf98f97..0000000000000000000000000000000000000000 --- a/src/platforms/mp-toutiao/runtime/wrapper/index.js +++ /dev/null @@ -1,71 +0,0 @@ -export { - initBehavior -} - from '../../../mp-weixin/runtime/wrapper/index' - -const instances = Object.create(null) - -export const mocks = ['__route__', '__webviewId__', '__nodeid__'] - -export function initPage (pageOptions) { - return initComponent(pageOptions) -} - -export function initComponent (componentOptions) { - if (componentOptions.properties) { // ref - componentOptions.properties.vueRef = { - type: String, - value: '' - } - } - return Component(componentOptions) -} - -export function initRefs (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) - }) - }) -} - -export function triggerLink (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 不生效 -export function handleLink (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] - } -} diff --git a/src/platforms/mp-toutiao/runtime/wrapper/page-parser.js b/src/platforms/mp-toutiao/runtime/wrapper/page-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..26959e8d61b6c621790e5ecba6796d4771d85418 --- /dev/null +++ b/src/platforms/mp-toutiao/runtime/wrapper/page-parser.js @@ -0,0 +1,27 @@ +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 +} diff --git a/src/platforms/mp-toutiao/runtime/wrapper/util.js b/src/platforms/mp-toutiao/runtime/wrapper/util.js new file mode 100644 index 0000000000000000000000000000000000000000..eee1e2695dd521ccb0365ccd6fde81dba1ece1b1 --- /dev/null +++ b/src/platforms/mp-toutiao/runtime/wrapper/util.js @@ -0,0 +1,80 @@ +import { + findVmByVueId +} from '../../../mp-weixin/runtime/wrapper/util' + +export const mocks = ['__route__', '__webviewId__', '__nodeid__', '__nodeId__'] + +export function isPage () { + return this.__nodeid__ === 0 || this.__nodeId__ === 0 +} + +export function initRefs (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) + +export function initRelation ({ + 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 + }) +} + +export function handleLink ({ + detail: { + vuePid, + nodeId, + webviewId + } +}) { + const vm = instances[webviewId + '_' + nodeId] + if (!vm) { + return + } + + let parentVm + + if (vuePid) { + parentVm = findVmByVueId(this.$vm, vuePid) + } + + if (!parentVm) { + parentVm = this.$vm + } + + vm.$parent = parentVm + vm.$root = parentVm.$root + parentVm.$children.push(vm) + + vm.__call_hook('created') + vm.__call_hook('beforeMount') + vm._isMounted = true + vm.__call_hook('mounted') + vm.__call_hook('onReady') +} diff --git a/src/platforms/mp-weixin/runtime/wrapper/app-base-parser.js b/src/platforms/mp-weixin/runtime/wrapper/app-base-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..6feb2fd34a8dd62f8b6b9e24303dbf0b6eaeb278 --- /dev/null +++ b/src/platforms/mp-weixin/runtime/wrapper/app-base-parser.js @@ -0,0 +1,74 @@ +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 +} diff --git a/src/platforms/mp-weixin/runtime/wrapper/app-parser.js b/src/platforms/mp-weixin/runtime/wrapper/app-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..5f8d432791ff30c4bc2eea1caa0e4ba5c76f8cda --- /dev/null +++ b/src/platforms/mp-weixin/runtime/wrapper/app-parser.js @@ -0,0 +1,13 @@ +import parseBaseApp from './app-base-parser' + +import { + mocks, + initRefs +} from './util' + +export default function parseApp (vm) { + return parseBaseApp(vm, { + mocks, + initRefs + }) +} diff --git a/src/platforms/mp-weixin/runtime/wrapper/component-base-parser.js b/src/platforms/mp-weixin/runtime/wrapper/component-base-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..5bf054a9e5065de58c796415f8d2fb654a3f7de6 --- /dev/null +++ b/src/platforms/mp-weixin/runtime/wrapper/component-base-parser.js @@ -0,0 +1,95 @@ +import Vue from 'vue' + +import { + initData, + initSlots, + initVueIds, + handleEvent, + initBehaviors, + initProperties, + initVueComponent +} from 'uni-wrapper/util' + +import { + handleLink, + initBehavior +} from './util' + +export default function parseBaseComponent (vueComponentOptions, { + isPage, + initRelation +} = {}) { + let [VueComponent, vueOptions] = initVueComponent(Vue, vueComponentOptions) + + const componentOptions = { + options: { + multipleSlots: true, + addGlobalClass: true + }, + data: initData(vueOptions, Vue.prototype), + behaviors: initBehaviors(vueOptions, initBehavior), + properties: initProperties(vueOptions.props, false, vueOptions.__file), + lifetimes: { + attached () { + const properties = this.properties + + const options = { + mpType: isPage.call(this) ? 'page' : 'component', + mpInstance: this, + propsData: properties + } + + initVueIds(properties.vueId, this) + + // 处理父子关系 + initRelation.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 () { + // 当组件 props 默认值为 true,初始化时传入 false 会导致 created,ready 触发, 但 attached 不触发 + // https://developers.weixin.qq.com/community/develop/doc/00066ae2844cc0f8eb883e2a557800 + if (this.$vm) { + this.$vm._isMounted = true + this.$vm.__call_hook('mounted') + this.$vm.__call_hook('onReady') + } else { + this.is && console.warn(this.is + ' is not attached') + } + }, + detached () { + this.$vm.$destroy() + } + }, + pageLifetimes: { + show (args) { + this.$vm && 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: { + __l: handleLink, + __e: handleEvent + } + } + + if (isPage) { + return componentOptions + } + return [componentOptions, VueComponent] +} diff --git a/src/platforms/mp-weixin/runtime/wrapper/component-parser.js b/src/platforms/mp-weixin/runtime/wrapper/component-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..e84b4456e72a258b95e6e4496d8f6c9b1a6a6298 --- /dev/null +++ b/src/platforms/mp-weixin/runtime/wrapper/component-parser.js @@ -0,0 +1,13 @@ +import parseBaseComponent from './component-base-parser' + +import { + isPage, + initRelation +} from './util' + +export default function parseComponent (vueComponentOptions) { + return parseBaseComponent(vueComponentOptions, { + isPage, + initRelation + }) +} diff --git a/src/platforms/mp-weixin/runtime/wrapper/page-base-parser.js b/src/platforms/mp-weixin/runtime/wrapper/page-base-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..2b9bd682597fe6f0f486d566055cc8611fa469df --- /dev/null +++ b/src/platforms/mp-weixin/runtime/wrapper/page-base-parser.js @@ -0,0 +1,33 @@ +import { + initHooks, + PAGE_EVENT_HOOKS +} from 'uni-wrapper/util' + +import parseComponent from 'uni-platform/runtime/wrapper/component-parser' + +const hooks = [ + 'onShow', + 'onHide', + 'onUnload' +] + +hooks.push(...PAGE_EVENT_HOOKS) + +export default function parseBasePage (vuePageOptions, { + isPage, + initRelation +}) { + const pageOptions = parseComponent(vuePageOptions, { + isPage, + initRelation + }) + + initHooks(pageOptions.methods, hooks) + + pageOptions.methods.onLoad = function (args) { + this.$vm.$mp.query = args // 兼容 mpvue + this.$vm.__call_hook('onLoad', args) + } + + return pageOptions +} diff --git a/src/platforms/mp-weixin/runtime/wrapper/page-parser.js b/src/platforms/mp-weixin/runtime/wrapper/page-parser.js new file mode 100644 index 0000000000000000000000000000000000000000..0a64ca8610c4c4a8152bb1a8ddbda6644ee9636f --- /dev/null +++ b/src/platforms/mp-weixin/runtime/wrapper/page-parser.js @@ -0,0 +1,13 @@ +import parseBasePage from './page-base-parser' + +import { + isPage, + initRelation +} from './util' + +export default function parsePage (vuePageOptions) { + return parseBasePage(vuePageOptions, { + isPage, + initRelation + }) +} diff --git a/src/platforms/mp-weixin/runtime/wrapper/index.js b/src/platforms/mp-weixin/runtime/wrapper/util.js similarity index 50% rename from src/platforms/mp-weixin/runtime/wrapper/index.js rename to src/platforms/mp-weixin/runtime/wrapper/util.js index ff1f52952952c6c4982a002d90fc0d8176e58974..926f4d09e8bb26405ae1bdb228c66cff9ba63d37 100644 --- a/src/platforms/mp-weixin/runtime/wrapper/index.js +++ b/src/platforms/mp-weixin/runtime/wrapper/util.js @@ -1,16 +1,33 @@ export const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__'] -export function initPage (pageOptions) { - return initComponent(pageOptions) -} - -export function initComponent (componentOptions) { - return Component(componentOptions) +export function findVmByVueId (vm, vuePid) { + const $children = vm.$children + // 优先查找直属 + 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 + } + } } export function initBehavior (options) { return Behavior(options) } + +export function isPage () { + return !!this.route +} + +export function initRelation (detail) { + this.triggerEvent('__l', detail) +} + export function initRefs (vm) { const mpInstance = vm.$scope Object.defineProperty(vm, '$refs', { @@ -33,24 +50,22 @@ export function initRefs (vm) { } }) } -export function triggerLink (mpInstance, vueOptions) { - mpInstance.triggerEvent('__l', mpInstance.$vm || vueOptions, { - bubbles: true, - composed: true - }) -} export function handleLink (event) { - if (event.detail.$mp) { // vm - if (!event.detail.$parent) { - event.detail.$parent = this.$vm - event.detail.$parent.$children.push(event.detail) + const { + vuePid, + vueOptions + } = event.detail || event.value // detail 是微信,value 是百度(dipatch) - event.detail.$root = this.$vm.$root - } - } else { // vueOptions - if (!event.detail.parent) { - event.detail.parent = this.$vm - } + let parentVm + + if (vuePid) { + parentVm = findVmByVueId(this.$vm, vuePid) + } + + if (!parentVm) { + parentVm = this.$vm } + + vueOptions.parent = parentVm }