diff --git a/build/rollup.config.js b/build/rollup.config.js index f25112de4a842a3174d7e752a893b708a590fb2f..94e782a9fcffef928385b485c8cdd1971b224f35 100644 --- a/build/rollup.config.js +++ b/build/rollup.config.js @@ -40,7 +40,8 @@ module.exports = { }), replace({ __GLOBAL__: platform.prefix, - __PLATFORM_TITLE__: platform.title + __PLATFORM_TITLE__: platform.title, + __PLATFORM__: JSON.stringify(process.env.UNI_PLATFORM) }) ], external: ['vue'] diff --git a/packages/uni-mp-baidu/dist/index.js b/packages/uni-mp-baidu/dist/index.js index f9ef198ed73306dc64e36e67ceefd773035a934d..24a4939580650222334d9d882d15c093295d0ead 100644 --- a/packages/uni-mp-baidu/dist/index.js +++ b/packages/uni-mp-baidu/dist/index.js @@ -1,3 +1,5 @@ +import Vue from 'vue'; + const _toString = Object.prototype.toString; const hasOwnProperty = Object.prototype.hasOwnProperty; @@ -15,7 +17,9 @@ function isPlainObject (obj) { function hasOwn (obj, key) { return hasOwnProperty.call(obj, key) -} +} + +function noop () {} const SYNC_API_RE = /hideKeyboard|upx2px|canIUse|^create|Sync$|Manager$/; @@ -209,6 +213,9 @@ const protocols = { }, navigateBackMiniProgram: { name: 'navigateBackSmartProgram' + }, + showShareMenu: { + name: 'openShare' } }; @@ -374,6 +381,418 @@ var api = /*#__PURE__*/Object.freeze({ requestPayment: requestPayment }); +const MOCKS = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__']; + +function initMocks (vm) { + const mpInstance = vm.$mp[vm.mpType]; + MOCKS.forEach(mock => { + if (hasOwn(mpInstance, mock)) { + vm[mock] = mpInstance[mock]; + } + }); +} + +function initHooks (mpOptions, hooks, delay = false) { + hooks.forEach(hook => { + mpOptions[hook] = function (args) { + if (delay) { + setTimeout(() => this.$vm.__call_hook(hook, args)); + } else { + this.$vm.__call_hook(hook, args); + } + }; + }); +} + +function getData (vueOptions) { + let data = vueOptions.data || {}; + const methods = vueOptions.methods || {}; + + if (typeof data === 'function') { + try { + data = data(); + } catch (e) { + console.warn('根据 Vue 的 data 函数初始化小程序 data 失败,请尽量确保 data 函数中不访问 vm 对象,否则可能影响首次数据渲染速度。', data); + } + } + + return Object.assign(data, methods) +} + +const PROP_TYPES = [String, Number, Boolean, Object, Array, null]; + +function createObserver (name) { + return function observer (newVal, oldVal) { + if (this.$vm) { + this.$vm[name] = newVal; // 为了触发其他非 render watcher + } + } +} + +function getProperties (props) { + const properties = {}; + if (Array.isArray(props)) { // ['title'] + props.forEach(key => { + properties[key] = { + type: null, + observer: createObserver(key) + }; + }); + } else if (isPlainObject(props)) { // {title:{type:String,default:''},content:String} + Object.keys(props).forEach(key => { + const opts = props[key]; + if (isPlainObject(opts)) { // title:{type:String,default:''} + let value = opts['default']; + if (isFn(value)) { + value = value(); + } + properties[key] = { + type: PROP_TYPES.includes(opts.type) ? opts.type : null, + value, + observer: createObserver(key) + }; + } else { // content:String + properties[key] = { + type: PROP_TYPES.includes(opts) ? opts : null, + observer: createObserver(key) + }; + } + }); + } + return properties +} + +function wrapper$1 (event) { + event.stopPropagation = noop; + event.preventDefault = noop; + + event.target = event.target || {}; + event.detail = event.detail || {}; + + { // mp-baidu,checked=>value + if (hasOwn(event.detail, 'checked') && !hasOwn(event.detail, 'value')) { + event.detail.value = event.detail.checked; + } + } + + // TODO 又得兼容 mpvue 的 mp 对象 + event.mp = event; + event.target = Object.assign({}, event.target, event.detail); + return event +} + +function processEventArgs (event, args = [], isCustom) { + if (isCustom && !args.length) { // 无参数,直接传入 detail 数组 + return event.detail + } + const ret = []; + args.forEach(arg => { + if (arg === '$event') { + ret.push(isCustom ? event.detail[0] : event); + } else { + ret.push(arg); + } + }); + + return ret +} + +const ONCE = '~'; +const CUSTOM = '^'; + +function handleEvent (event) { + event = wrapper$1(event); + + // [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]] + const eventOpts = (event.currentTarget || event.target).dataset.eventOpts; + if (!eventOpts) { + return console.warn(`事件信息不存在`) + } + + // [['handle',[1,2,a]],['handle1',[1,2,a]]] + const eventType = event.type; + eventOpts.forEach(eventOpt => { + let type = eventOpt[0]; + const eventsArray = eventOpt[1]; + + const isCustom = type.charAt(0) === CUSTOM; + type = isCustom ? type.slice(1) : type; + const isOnce = type.charAt(0) === ONCE; + type = isOnce ? type.slice(1) : type; + + if (eventsArray && eventType === type) { + eventsArray.forEach(eventArray => { + const handler = this.$vm[eventArray[0]]; + if (!isFn(handler)) { + throw new Error(` _vm.${eventArray[0]} is not a function`) + } + if (isOnce) { + if (handler.once) { + return + } + handler.once = true; + } + handler.apply(this.$vm, processEventArgs(event, eventArray[1], isCustom)); + }); + } + }); +} + +function handleLink (event) { + event.detail.$parent = this.$vm; +} + +function initRefs (vm) { + const mpInstance = vm.$mp[vm.mpType]; + Object.defineProperty(vm, '$refs', { + get () { + const $refs = Object.create(null); + const components = mpInstance.selectAllComponents('.vue-ref'); + components.forEach(component => { + const ref = component.dataset.ref; + $refs[ref] = component.$vm; + }); + 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); + }); + return $refs + } + }); +} + +function initChildren (vm) { + const mpInstance = vm.$mp[vm.mpType]; + Object.defineProperty(vm, '$children', { + get () { + const $children = []; + const components = mpInstance.selectAllComponents('.vue-com'); + components.forEach(component => { + $children.push(component.$vm); + }); + return $children + } + }); +} + +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 = [ + 'onShow', + 'onHide', + 'onError', + 'onPageNotFound' +]; + +function createApp (vueOptions) { + vueOptions = vueOptions.default || vueOptions; + // 外部初始化时 Vue 还未初始化,放到 createApp 内部初始化 mixin + Vue.mixin({ + beforeCreate () { + if (!this.$options.mpType) { + return + } + this.mpType = this.$options.mpType; + this.$mp = { + data: {}, + [this.mpType]: this.$options.mpInstance + }; + delete this.$options.mpType; + delete this.$options.mpInstance; + + if (this.mpType !== 'app') { + initRefs(this); + initMocks(this); + initChildren(this); + } + } + }); + + const appOptions = { + onLaunch (args) { + this.$vm = new Vue(Object.assign(vueOptions, { + mpType: 'app', + mpInstance: this + })); + + this.$vm.$mount(); + setTimeout(() => this.$vm.__call_hook('onLaunch', args)); + } + }; + + initHooks(appOptions, hooks, true); // 延迟执行,因为 App 的注册在 main.js 之前,可能导致生命周期内 Vue 原型上开发者注册的属性无法访问 + + App(appOptions); + + return vueOptions +} + +const hooks$1 = [ + 'onShow', + 'onHide', + 'onPullDownRefresh', + 'onReachBottom', + 'onShareAppMessage', + 'onPageScroll', + 'onResize', + 'onTabItemTap', + 'onBackPress', + 'onNavigationBarButtonTap', + 'onNavigationBarSearchInputChanged', + 'onNavigationBarSearchInputConfirmed', + 'onNavigationBarSearchInputClicked' +]; + +function createPage (vueOptions) { + vueOptions = vueOptions.default || vueOptions; + const pageOptions = { + data: getData(vueOptions), + onLoad (args) { + { + this.$baiduComponentInstances = Object.create(null); + } + + this.$vm = new Vue(Object.assign(vueOptions, { + mpType: 'page', + mpInstance: this + })); + + this.$vm.$mount(); + this.$vm.__call_hook('onLoad', args); + }, + onReady () { + this.$vm._isMounted = true; + this.$vm.__call_hook('onReady'); + }, + onUnload () { + this.$vm.__call_hook('onUnload'); + { // 百度组件不会在页面 unload 时触发 detached + baiduPageDestroy(this.$vm); + } + }, + __e: handleEvent, + __l: handleLink + }; + + initHooks(pageOptions, hooks$1); + + return Page(pageOptions) +} + +function initVueComponent (mpInstace, VueComponent) { + if (mpInstace.$vm) { + return + } + + const options = { + mpType: 'component', + mpInstance: mpInstace, + propsData: mpInstace.properties + }; + // 初始化 vue 实例 + mpInstace.$vm = new VueComponent(options); + + // 初始化渲染数据 + mpInstace.$vm.$mount(); +} + +function createComponent (vueOptions) { + vueOptions = vueOptions.default || vueOptions; + + const properties = getProperties(vueOptions.props); + + const VueComponent = Vue.extend(vueOptions); + + const componentOptions = { + options: { + multipleSlots: true, + addGlobalClass: true + }, + data: getData(vueOptions), + properties, + lifetimes: { + attached () { + initVueComponent(this, VueComponent); + }, + ready () { + initVueComponent(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 + + { + const baiduComponentInstances = this.pageinstance.$baiduComponentInstances; + + baiduComponentInstances[this.id] = this; + if (this.ownerId) { // 组件嵌组件 + const parentBaiduComponentInstance = baiduComponentInstances[this.ownerId]; + if (parentBaiduComponentInstance) { + this.$vm.$parent = parentBaiduComponentInstance.$vm; + } else { + console.error(`查找父组件失败${this.ownerId}`); + } + } else { // 页面直属组件 + this.$vm.$parent = this.pageinstance.$vm; + } + } + + const eventId = this.dataset.eventId; + if (eventId) { + const listeners = this.$vm.$parent.$mp.listeners; + if (listeners) { + const listenerOpts = listeners[eventId]; + Object.keys(listenerOpts).forEach(eventType => { + listenerOpts[eventType].forEach(handler => { + this.$vm[handler.once ? '$once' : '$on'](eventType, handler); + }); + }); + } + } + + this.$vm._isMounted = true; + this.$vm.__call_hook('mounted'); + this.$vm.__call_hook('onReady'); + }, + detached () { + { + delete this.pageinstance.$baiduComponentInstances[this.id]; + } + this.$vm.$destroy(); + } + }, + pageLifetimes: { + show (args) { + this.$vm.__call_hook('onPageShow', args); + }, + hide () { + this.$vm.__call_hook('onPageHide'); + }, + resize (size) { + this.$vm.__call_hook('onPageResize', size); + } + }, + methods: { + __e: handleEvent, + __l: handleLink + } + }; + + return Component(componentOptions) +} + let uni = {}; if (typeof Proxy !== 'undefined') { @@ -422,3 +841,4 @@ if (typeof Proxy !== 'undefined') { var uni$1 = uni; export default uni$1; +export { createApp, createPage, createComponent }; diff --git a/packages/uni-mp-baidu/package.json b/packages/uni-mp-baidu/package.json index cd9eb48b955e14968d240f560b5e7a2b45df2bbe..a90dded9e4735f98645aae4cfba1e06eccac1e88 100644 --- a/packages/uni-mp-baidu/package.json +++ b/packages/uni-mp-baidu/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/uni-mp-baidu", - "version": "0.0.7", + "version": "0.0.804", "description": "uni-app mp-baidu", "main": "dist/index.js", "scripts": { diff --git a/packages/uni-mp-toutiao/dist/index.js b/packages/uni-mp-toutiao/dist/index.js index e6da70983ab84bf521e6f13ad6d730aecd3be2fd..8d4bfca6daf4c49833062d647fe08100350df4d0 100644 --- a/packages/uni-mp-toutiao/dist/index.js +++ b/packages/uni-mp-toutiao/dist/index.js @@ -1,3 +1,5 @@ +import Vue from 'vue'; + const _toString = Object.prototype.toString; const hasOwnProperty = Object.prototype.hasOwnProperty; @@ -15,7 +17,9 @@ function isPlainObject (obj) { function hasOwn (obj, key) { return hasOwnProperty.call(obj, key) -} +} + +function noop () {} const SYNC_API_RE = /hideKeyboard|upx2px|canIUse|^create|Sync$|Manager$/; @@ -422,6 +426,380 @@ var api = /*#__PURE__*/Object.freeze({ }); +const MOCKS = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__']; + +function initMocks (vm) { + const mpInstance = vm.$mp[vm.mpType]; + MOCKS.forEach(mock => { + if (hasOwn(mpInstance, mock)) { + vm[mock] = mpInstance[mock]; + } + }); +} + +function initHooks (mpOptions, hooks, delay = false) { + hooks.forEach(hook => { + mpOptions[hook] = function (args) { + if (delay) { + setTimeout(() => this.$vm.__call_hook(hook, args)); + } else { + this.$vm.__call_hook(hook, args); + } + }; + }); +} + +function getData (vueOptions) { + let data = vueOptions.data || {}; + const methods = vueOptions.methods || {}; + + if (typeof data === 'function') { + try { + data = data(); + } catch (e) { + console.warn('根据 Vue 的 data 函数初始化小程序 data 失败,请尽量确保 data 函数中不访问 vm 对象,否则可能影响首次数据渲染速度。', data); + } + } + + return Object.assign(data, methods) +} + +const PROP_TYPES = [String, Number, Boolean, Object, Array, null]; + +function createObserver (name) { + return function observer (newVal, oldVal) { + if (this.$vm) { + this.$vm[name] = newVal; // 为了触发其他非 render watcher + } + } +} + +function getProperties (props) { + const properties = {}; + if (Array.isArray(props)) { // ['title'] + props.forEach(key => { + properties[key] = { + type: null, + observer: createObserver(key) + }; + }); + } else if (isPlainObject(props)) { // {title:{type:String,default:''},content:String} + Object.keys(props).forEach(key => { + const opts = props[key]; + if (isPlainObject(opts)) { // title:{type:String,default:''} + let value = opts['default']; + if (isFn(value)) { + value = value(); + } + properties[key] = { + type: PROP_TYPES.includes(opts.type) ? opts.type : null, + value, + observer: createObserver(key) + }; + } else { // content:String + properties[key] = { + type: PROP_TYPES.includes(opts) ? opts : null, + observer: createObserver(key) + }; + } + }); + } + return properties +} + +function wrapper$1 (event) { + event.stopPropagation = noop; + event.preventDefault = noop; + + event.target = event.target || {}; + event.detail = event.detail || {}; + + // TODO 又得兼容 mpvue 的 mp 对象 + event.mp = event; + event.target = Object.assign({}, event.target, event.detail); + return event +} + +function processEventArgs (event, args = [], isCustom) { + if (isCustom && !args.length) { // 无参数,直接传入 detail 数组 + return event.detail + } + const ret = []; + args.forEach(arg => { + if (arg === '$event') { + ret.push(isCustom ? event.detail[0] : event); + } else { + ret.push(arg); + } + }); + + return ret +} + +const ONCE = '~'; +const CUSTOM = '^'; + +function handleEvent (event) { + event = wrapper$1(event); + + // [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]] + const eventOpts = (event.currentTarget || event.target).dataset.eventOpts; + if (!eventOpts) { + return console.warn(`事件信息不存在`) + } + + // [['handle',[1,2,a]],['handle1',[1,2,a]]] + const eventType = event.type; + eventOpts.forEach(eventOpt => { + let type = eventOpt[0]; + const eventsArray = eventOpt[1]; + + const isCustom = type.charAt(0) === CUSTOM; + type = isCustom ? type.slice(1) : type; + const isOnce = type.charAt(0) === ONCE; + type = isOnce ? type.slice(1) : type; + + if (eventsArray && eventType === type) { + eventsArray.forEach(eventArray => { + const handler = this.$vm[eventArray[0]]; + if (!isFn(handler)) { + throw new Error(` _vm.${eventArray[0]} is not a function`) + } + if (isOnce) { + if (handler.once) { + return + } + handler.once = true; + } + handler.apply(this.$vm, processEventArgs(event, eventArray[1], isCustom)); + }); + } + }); +} + +function handleLink (event) { + event.detail.$parent = this.$vm; +} + +function initRefs (vm) { + const mpInstance = vm.$mp[vm.mpType]; + Object.defineProperty(vm, '$refs', { + get () { + const $refs = Object.create(null); + const components = mpInstance.selectAllComponents('.vue-ref'); + components.forEach(component => { + const ref = component.dataset.ref; + $refs[ref] = component.$vm; + }); + 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); + }); + return $refs + } + }); +} + +function initChildren (vm) { + const mpInstance = vm.$mp[vm.mpType]; + Object.defineProperty(vm, '$children', { + get () { + const $children = []; + const components = mpInstance.selectAllComponents('.vue-com'); + components.forEach(component => { + $children.push(component.$vm); + }); + return $children + } + }); +} + +const hooks = [ + 'onShow', + 'onHide', + 'onError', + 'onPageNotFound' +]; + +function createApp (vueOptions) { + vueOptions = vueOptions.default || vueOptions; + // 外部初始化时 Vue 还未初始化,放到 createApp 内部初始化 mixin + Vue.mixin({ + beforeCreate () { + if (!this.$options.mpType) { + return + } + this.mpType = this.$options.mpType; + this.$mp = { + data: {}, + [this.mpType]: this.$options.mpInstance + }; + delete this.$options.mpType; + delete this.$options.mpInstance; + + if (this.mpType !== 'app') { + initRefs(this); + initMocks(this); + initChildren(this); + } + } + }); + + const appOptions = { + onLaunch (args) { + this.$vm = new Vue(Object.assign(vueOptions, { + mpType: 'app', + mpInstance: this + })); + + this.$vm.$mount(); + setTimeout(() => this.$vm.__call_hook('onLaunch', args)); + } + }; + + initHooks(appOptions, hooks, true); // 延迟执行,因为 App 的注册在 main.js 之前,可能导致生命周期内 Vue 原型上开发者注册的属性无法访问 + + App(appOptions); + + return vueOptions +} + +const hooks$1 = [ + 'onShow', + 'onHide', + 'onPullDownRefresh', + 'onReachBottom', + 'onShareAppMessage', + 'onPageScroll', + 'onResize', + 'onTabItemTap', + 'onBackPress', + 'onNavigationBarButtonTap', + 'onNavigationBarSearchInputChanged', + 'onNavigationBarSearchInputConfirmed', + 'onNavigationBarSearchInputClicked' +]; + +function createPage (vueOptions) { + vueOptions = vueOptions.default || vueOptions; + const pageOptions = { + data: getData(vueOptions), + onLoad (args) { + + this.$vm = new Vue(Object.assign(vueOptions, { + mpType: 'page', + mpInstance: this + })); + + this.$vm.$mount(); + this.$vm.__call_hook('onLoad', args); + }, + onReady () { + this.$vm._isMounted = true; + this.$vm.__call_hook('onReady'); + }, + onUnload () { + this.$vm.__call_hook('onUnload'); + { + this.$vm.$destroy(); + } + }, + __e: handleEvent, + __l: handleLink + }; + + initHooks(pageOptions, hooks$1); + + return Page(pageOptions) +} + +function initVueComponent (mpInstace, VueComponent) { + if (mpInstace.$vm) { + return + } + + const options = { + mpType: 'component', + mpInstance: mpInstace, + propsData: mpInstace.properties + }; + // 初始化 vue 实例 + mpInstace.$vm = new VueComponent(options); + + // 初始化渲染数据 + mpInstace.$vm.$mount(); +} + +function createComponent (vueOptions) { + vueOptions = vueOptions.default || vueOptions; + + const properties = getProperties(vueOptions.props); + + const VueComponent = Vue.extend(vueOptions); + + const componentOptions = { + options: { + multipleSlots: true, + addGlobalClass: true + }, + data: getData(vueOptions), + properties, + lifetimes: { + attached () { + initVueComponent(this, VueComponent); + }, + ready () { + initVueComponent(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 + + { + this.triggerEvent('__l', this.$vm); // TODO 百度仅能传递 json 对象 + } + + const eventId = this.dataset.eventId; + if (eventId) { + const listeners = this.$vm.$parent.$mp.listeners; + if (listeners) { + const listenerOpts = listeners[eventId]; + Object.keys(listenerOpts).forEach(eventType => { + listenerOpts[eventType].forEach(handler => { + this.$vm[handler.once ? '$once' : '$on'](eventType, handler); + }); + }); + } + } + + 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.__call_hook('onPageHide'); + }, + resize (size) { + this.$vm.__call_hook('onPageResize', size); + } + }, + methods: { + __e: handleEvent, + __l: handleLink + } + }; + + return Component(componentOptions) +} + let uni = {}; if (typeof Proxy !== 'undefined') { @@ -470,3 +848,4 @@ if (typeof Proxy !== 'undefined') { var uni$1 = uni; export default uni$1; +export { createApp, createPage, createComponent }; diff --git a/packages/uni-mp-toutiao/package.json b/packages/uni-mp-toutiao/package.json index 599df7c6ecc824601274055cc5a9d89ed9b49b59..4432239d17dc1274e55e2595f7e004b9a21cbd94 100644 --- a/packages/uni-mp-toutiao/package.json +++ b/packages/uni-mp-toutiao/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/uni-mp-toutiao", - "version": "0.0.3", + "version": "0.0.301", "description": "uni-app mp-toutiao", "main": "dist/index.js", "scripts": { diff --git a/packages/uni-mp-weixin/dist/index.js b/packages/uni-mp-weixin/dist/index.js index 4bdcb4566485cddbc1c2b7dd9f5d7f3b7679e96f..16b09394e4526f770d10708bff90f92f5ee4a0c0 100644 --- a/packages/uni-mp-weixin/dist/index.js +++ b/packages/uni-mp-weixin/dist/index.js @@ -279,33 +279,51 @@ function initMocks (vm) { }); } -function initHooks (mpOptions, hooks) { +function initHooks (mpOptions, hooks, delay = false) { hooks.forEach(hook => { mpOptions[hook] = function (args) { - this.$vm.__call_hook(hook, args); + if (delay) { + setTimeout(() => this.$vm.__call_hook(hook, args)); + } else { + this.$vm.__call_hook(hook, args); + } }; }); } -function getData (data) { +function getData (vueOptions) { + let data = vueOptions.data || {}; + const methods = vueOptions.methods || {}; + if (typeof data === 'function') { try { - return data() + data = data(); } catch (e) { - console.warn('根据 Vue 的 data 函数初始化小程序 data 失败,请尽量确保 data 函数中不访问 vm 对象,否则可能影响首次数据渲染速度。'); + console.warn('根据 Vue 的 data 函数初始化小程序 data 失败,请尽量确保 data 函数中不访问 vm 对象,否则可能影响首次数据渲染速度。', data); } - return {} } - return data || {} + + return Object.assign(data, methods) } const PROP_TYPES = [String, Number, Boolean, Object, Array, null]; +function createObserver (name) { + return function observer (newVal, oldVal) { + if (this.$vm) { + this.$vm[name] = newVal; // 为了触发其他非 render watcher + } + } +} + function getProperties (props) { const properties = {}; if (Array.isArray(props)) { // ['title'] props.forEach(key => { - properties[key] = null; + properties[key] = { + type: null, + observer: createObserver(key) + }; }); } else if (isPlainObject(props)) { // {title:{type:String,default:''},content:String} Object.keys(props).forEach(key => { @@ -317,10 +335,14 @@ function getProperties (props) { } properties[key] = { type: PROP_TYPES.includes(opts.type) ? opts.type : null, - value + value, + observer: createObserver(key) }; } else { // content:String - properties[key] = PROP_TYPES.includes(opts) ? opts : null; + properties[key] = { + type: PROP_TYPES.includes(opts) ? opts : null, + observer: createObserver(key) + }; } }); } @@ -333,6 +355,7 @@ function wrapper$1 (event) { event.target = event.target || {}; event.detail = event.detail || {}; + // TODO 又得兼容 mpvue 的 mp 对象 event.mp = event; event.target = Object.assign({}, event.target, event.detail); @@ -381,6 +404,9 @@ function handleEvent (event) { if (eventsArray && eventType === type) { eventsArray.forEach(eventArray => { const handler = this.$vm[eventArray[0]]; + if (!isFn(handler)) { + throw new Error(` _vm.${eventArray[0]} is not a function`) + } if (isOnce) { if (handler.once) { return @@ -402,12 +428,12 @@ function initRefs (vm) { Object.defineProperty(vm, '$refs', { get () { const $refs = Object.create(null); - const components = mpInstance.selectAllComponents('.__ref__'); + const components = mpInstance.selectAllComponents('.vue-ref'); components.forEach(component => { const ref = component.dataset.ref; $refs[ref] = component.$vm; }); - const forComponents = mpInstance.selectAllComponents('.__ref-in-for__'); + const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for'); forComponents.forEach(component => { const ref = component.dataset.ref; if (!$refs[ref]) { @@ -418,6 +444,20 @@ function initRefs (vm) { return $refs } }); +} + +function initChildren (vm) { + const mpInstance = vm.$mp[vm.mpType]; + Object.defineProperty(vm, '$children', { + get () { + const $children = []; + const components = mpInstance.selectAllComponents('.vue-com'); + components.forEach(component => { + $children.push(component.$vm); + }); + return $children + } + }); } const hooks = [ @@ -429,20 +469,41 @@ const hooks = [ function createApp (vueOptions) { vueOptions = vueOptions.default || vueOptions; + // 外部初始化时 Vue 还未初始化,放到 createApp 内部初始化 mixin + Vue.mixin({ + beforeCreate () { + if (!this.$options.mpType) { + return + } + this.mpType = this.$options.mpType; + this.$mp = { + data: {}, + [this.mpType]: this.$options.mpInstance + }; + delete this.$options.mpType; + delete this.$options.mpInstance; + + if (this.mpType !== 'app') { + initRefs(this); + initMocks(this); + initChildren(this); + } + } + }); const appOptions = { onLaunch (args) { - this.$vm = new Vue(vueOptions); - this.$vm.mpType = 'app'; - this.$vm.$mp = { - app: this - }; + this.$vm = new Vue(Object.assign(vueOptions, { + mpType: 'app', + mpInstance: this + })); + this.$vm.$mount(); - this.$vm.__call_hook('onLaunch', args); + setTimeout(() => this.$vm.__call_hook('onLaunch', args)); } }; - initHooks(appOptions, hooks); + initHooks(appOptions, hooks, true); // 延迟执行,因为 App 的注册在 main.js 之前,可能导致生命周期内 Vue 原型上开发者注册的属性无法访问 App(appOptions); @@ -468,17 +529,13 @@ const hooks$1 = [ function createPage (vueOptions) { vueOptions = vueOptions.default || vueOptions; const pageOptions = { - data: getData(vueOptions.data), + data: getData(vueOptions), onLoad (args) { - this.$vm = new Vue(vueOptions); - this.$vm.mpType = 'page'; - this.$vm.$mp = { - data: {}, - page: this - }; - initRefs(this.$vm); - initMocks(this.$vm); + this.$vm = new Vue(Object.assign(vueOptions, { + mpType: 'page', + mpInstance: this + })); this.$vm.$mount(); this.$vm.__call_hook('onLoad', args); @@ -489,7 +546,9 @@ function createPage (vueOptions) { }, onUnload () { this.$vm.__call_hook('onUnload'); - this.$vm.$destroy(); + { + this.$vm.$destroy(); + } }, __e: handleEvent, __l: handleLink @@ -500,6 +559,23 @@ function createPage (vueOptions) { return Page(pageOptions) } +function initVueComponent (mpInstace, VueComponent) { + if (mpInstace.$vm) { + return + } + + const options = { + mpType: 'component', + mpInstance: mpInstace, + propsData: mpInstace.properties + }; + // 初始化 vue 实例 + mpInstace.$vm = new VueComponent(options); + + // 初始化渲染数据 + mpInstace.$vm.$mount(); +} + function createComponent (vueOptions) { vueOptions = vueOptions.default || vueOptions; @@ -512,50 +588,39 @@ function createComponent (vueOptions) { multipleSlots: true, addGlobalClass: true }, - data: getData(vueOptions.data), + data: getData(vueOptions), properties, - attached () { - // props的处理,一个是直接 与 mp 的 properties 对接,另一个是要做成 reactive,且排除掉 render watch - const options = { - propsData: this.properties, - $component: this - }; - // 初始化 vue 实例 - this.$vm = new VueComponent(options); - this.$vm.mpType = 'component'; - this.$vm.$mp = { - data: {}, - component: this - }; + lifetimes: { + attached () { + initVueComponent(this, VueComponent); + }, + ready () { + initVueComponent(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 - initRefs(this.$vm); - initMocks(this.$vm); + { + this.triggerEvent('__l', this.$vm); // TODO 百度仅能传递 json 对象 + } - // 初始化渲染数据 - this.$vm.$mount(); - }, - ready () { - this.triggerEvent('__l', this.$vm); - - const eventId = this.dataset.eventId; - if (eventId) { - const listeners = this.$vm.$parent.$mp.listeners; - if (listeners) { - const listenerOpts = listeners[eventId]; - Object.keys(listenerOpts).forEach(eventType => { - listenerOpts[eventType].forEach(handler => { - this.$vm[handler.once ? '$once' : '$on'](eventType, handler); + const eventId = this.dataset.eventId; + if (eventId) { + const listeners = this.$vm.$parent.$mp.listeners; + if (listeners) { + const listenerOpts = listeners[eventId]; + Object.keys(listenerOpts).forEach(eventType => { + listenerOpts[eventType].forEach(handler => { + this.$vm[handler.once ? '$once' : '$on'](eventType, handler); + }); }); - }); + } } - } - this.$vm._isMounted = true; - this.$vm.__call_hook('mounted'); - this.$vm.__call_hook('onReady'); - }, - detached () { - this.$vm.$destroy(); + this.$vm._isMounted = true; + this.$vm.__call_hook('mounted'); + this.$vm.__call_hook('onReady'); + }, + detached () { + this.$vm.$destroy(); + } }, pageLifetimes: { show (args) { diff --git a/packages/uni-mp-weixin/package.json b/packages/uni-mp-weixin/package.json index 213557202b67276d6e7eea2f9ddf1c87bbd3d13f..9dbe6715d40aa5db9684f35ad5daea3461c526be 100644 --- a/packages/uni-mp-weixin/package.json +++ b/packages/uni-mp-weixin/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/uni-mp-weixin", - "version": "0.0.8", + "version": "0.0.905", "description": "uni-app mp-weixin", "main": "dist/index.js", "scripts": { diff --git a/src/core/runtime/wrapper/create-app.js b/src/core/runtime/wrapper/create-app.js index 37da88e10b47172be0342b82759113ce85e36428..5a01bc2a98bb8217cb6ff8023bf91eec0ed71791 100644 --- a/src/core/runtime/wrapper/create-app.js +++ b/src/core/runtime/wrapper/create-app.js @@ -1,7 +1,10 @@ import Vue from 'vue' import { - initHooks + initRefs, + initHooks, + initMocks, + initChildren } from './util' const hooks = [ @@ -13,20 +16,41 @@ const hooks = [ export function createApp (vueOptions) { vueOptions = vueOptions.default || vueOptions + // 外部初始化时 Vue 还未初始化,放到 createApp 内部初始化 mixin + Vue.mixin({ + beforeCreate () { + if (!this.$options.mpType) { + return + } + this.mpType = this.$options.mpType + this.$mp = { + data: {}, + [this.mpType]: this.$options.mpInstance + } + delete this.$options.mpType + delete this.$options.mpInstance + + if (this.mpType !== 'app') { + initRefs(this) + initMocks(this) + initChildren(this) + } + } + }) const appOptions = { onLaunch (args) { - this.$vm = new Vue(vueOptions) - this.$vm.mpType = 'app' - this.$vm.$mp = { - app: this - } + this.$vm = new Vue(Object.assign(vueOptions, { + mpType: 'app', + mpInstance: this + })) + this.$vm.$mount() - this.$vm.__call_hook('onLaunch', args) + setTimeout(() => this.$vm.__call_hook('onLaunch', args)) } } - initHooks(appOptions, hooks) + initHooks(appOptions, hooks, true) // 延迟执行,因为 App 的注册在 main.js 之前,可能导致生命周期内 Vue 原型上开发者注册的属性无法访问 App(appOptions) diff --git a/src/core/runtime/wrapper/create-component.js b/src/core/runtime/wrapper/create-component.js index 7ec91d55be071fdbc219947792578e8aa235d32f..114b2b3a5c2807cf1555aaf881fd6417bfb9c57c 100644 --- a/src/core/runtime/wrapper/create-component.js +++ b/src/core/runtime/wrapper/create-component.js @@ -2,14 +2,28 @@ import Vue from 'vue' import { getData, - initRefs, - initMocks, - initMethods, handleLink, handleEvent, getProperties } from './util' +function initVueComponent (mpInstace, VueComponent) { + if (mpInstace.$vm) { + return + } + + const options = { + mpType: 'component', + mpInstance: mpInstace, + propsData: mpInstace.properties + } + // 初始化 vue 实例 + mpInstace.$vm = new VueComponent(options) + + // 初始化渲染数据 + mpInstace.$vm.$mount() +} + export function createComponent (vueOptions) { vueOptions = vueOptions.default || vueOptions @@ -22,50 +36,56 @@ export function createComponent (vueOptions) { multipleSlots: true, addGlobalClass: true }, - data: getData(vueOptions.data), + data: getData(vueOptions), properties, - attached () { - // props的处理,一个是直接 与 mp 的 properties 对接,另一个是要做成 reactive,且排除掉 render watch - const options = { - propsData: this.properties, - $component: this - } - // 初始化 vue 实例 - this.$vm = new VueComponent(options) - this.$vm.mpType = 'component' - this.$vm.$mp = { - data: {}, - component: this - } + lifetimes: { + attached () { + initVueComponent(this, VueComponent) + }, + ready () { + initVueComponent(this, VueComponent) // 目前发现部分情况小程序 attached 不触发 - initRefs(this.$vm) - initMocks(this.$vm) + if (__PLATFORM__ === 'mp-baidu') { + const baiduComponentInstances = this.pageinstance.$baiduComponentInstances - // 初始化渲染数据 - this.$vm.$mount() - }, - ready () { - this.triggerEvent('__l', this.$vm) + baiduComponentInstances[this.id] = this + if (this.ownerId) { // 组件嵌组件 + const parentBaiduComponentInstance = baiduComponentInstances[this.ownerId] + if (parentBaiduComponentInstance) { + this.$vm.$parent = parentBaiduComponentInstance.$vm + } else { + console.error(`查找父组件失败${this.ownerId}`) + } + } else { // 页面直属组件 + this.$vm.$parent = this.pageinstance.$vm + } + } else { + this.triggerEvent('__l', this.$vm) // TODO 百度仅能传递 json 对象 + } - const eventId = this.dataset.eventId - if (eventId) { - const listeners = this.$vm.$parent.$mp.listeners - if (listeners) { - const listenerOpts = listeners[eventId] - Object.keys(listenerOpts).forEach(eventType => { - listenerOpts[eventType].forEach(handler => { - this.$vm[handler.once ? '$once' : '$on'](eventType, handler) + const eventId = this.dataset.eventId + if (eventId) { + const listeners = this.$vm.$parent.$mp.listeners + if (listeners) { + const listenerOpts = listeners[eventId] + Object.keys(listenerOpts).forEach(eventType => { + listenerOpts[eventType].forEach(handler => { + this.$vm[handler.once ? '$once' : '$on'](eventType, handler) + }) }) - }) + } } - } - this.$vm._isMounted = true - this.$vm.__call_hook('mounted') - this.$vm.__call_hook('onReady') - }, - detached () { - this.$vm.$destroy() + this.$vm._isMounted = true + this.$vm.__call_hook('mounted') + this.$vm.__call_hook('onReady') + }, + detached () { + if (__PLATFORM__ === 'mp-baidu') { + delete this.pageinstance.$baiduComponentInstances[this.id] + } + this.$vm.$destroy() + } }, pageLifetimes: { show (args) { @@ -84,7 +104,5 @@ export function createComponent (vueOptions) { } } - initMethods(componentOptions.methods, vueOptions) - return Component(componentOptions) } diff --git a/src/core/runtime/wrapper/create-page.js b/src/core/runtime/wrapper/create-page.js index 053e1956e12fd0d28805b1deea70317c6bca1813..b3d40667f3ed630a501ca0b297d58db7397a869a 100644 --- a/src/core/runtime/wrapper/create-page.js +++ b/src/core/runtime/wrapper/create-page.js @@ -2,12 +2,10 @@ import Vue from 'vue' import { getData, - initRefs, initHooks, - initMocks, - initMethods, handleLink, - handleEvent + handleEvent, + baiduPageDestroy } from './util' const hooks = [ @@ -29,17 +27,16 @@ const hooks = [ export function createPage (vueOptions) { vueOptions = vueOptions.default || vueOptions const pageOptions = { - data: getData(vueOptions.data), + data: getData(vueOptions), onLoad (args) { - this.$vm = new Vue(vueOptions) - this.$vm.mpType = 'page' - this.$vm.$mp = { - data: {}, - page: this + if (__PLATFORM__ === 'mp-baidu') { + this.$baiduComponentInstances = Object.create(null) } - initRefs(this.$vm) - initMocks(this.$vm) + this.$vm = new Vue(Object.assign(vueOptions, { + mpType: 'page', + mpInstance: this + })) this.$vm.$mount() this.$vm.__call_hook('onLoad', args) @@ -50,7 +47,11 @@ export function createPage (vueOptions) { }, onUnload () { this.$vm.__call_hook('onUnload') - this.$vm.$destroy() + if (__PLATFORM__ === 'mp-baidu') { // 百度组件不会在页面 unload 时触发 detached + baiduPageDestroy(this.$vm) + } else { + this.$vm.$destroy() + } }, __e: handleEvent, __l: handleLink @@ -58,7 +59,5 @@ export function createPage (vueOptions) { initHooks(pageOptions, hooks) - initMethods(pageOptions, vueOptions) - return Page(pageOptions) } diff --git a/src/core/runtime/wrapper/util.js b/src/core/runtime/wrapper/util.js index aff117da46a5fc26fe1115d3accaecfb457b5ed0..00fc9cbc79d36b8ba24029e2d27bfec04ab7ae9f 100644 --- a/src/core/runtime/wrapper/util.js +++ b/src/core/runtime/wrapper/util.js @@ -16,39 +16,51 @@ export function initMocks (vm) { }) } -export function initHooks (mpOptions, hooks) { +export function initHooks (mpOptions, hooks, delay = false) { hooks.forEach(hook => { mpOptions[hook] = function (args) { - this.$vm.__call_hook(hook, args) + if (delay) { + setTimeout(() => this.$vm.__call_hook(hook, args)) + } else { + this.$vm.__call_hook(hook, args) + } } }) } -export function initMethods (mpOptions, vueOptions) { - // if (vueOptions.methods) { - // Object.assign(mpOptions, vueOptions.methods) - // } -} +export function getData (vueOptions) { + let data = vueOptions.data || {} + const methods = vueOptions.methods || {} -export function getData (data) { if (typeof data === 'function') { try { - return data() + data = data() } catch (e) { - console.warn('根据 Vue 的 data 函数初始化小程序 data 失败,请尽量确保 data 函数中不访问 vm 对象,否则可能影响首次数据渲染速度。') + console.warn('根据 Vue 的 data 函数初始化小程序 data 失败,请尽量确保 data 函数中不访问 vm 对象,否则可能影响首次数据渲染速度。', data) } - return {} } - return data || {} + + return Object.assign(data, methods) } const PROP_TYPES = [String, Number, Boolean, Object, Array, null] +function createObserver (name) { + return function observer (newVal, oldVal) { + if (this.$vm) { + this.$vm[name] = newVal // 为了触发其他非 render watcher + } + } +} + export function getProperties (props) { const properties = {} if (Array.isArray(props)) { // ['title'] props.forEach(key => { - properties[key] = null + properties[key] = { + type: null, + observer: createObserver(key) + } }) } else if (isPlainObject(props)) { // {title:{type:String,default:''},content:String} Object.keys(props).forEach(key => { @@ -60,10 +72,14 @@ export function getProperties (props) { } properties[key] = { type: PROP_TYPES.includes(opts.type) ? opts.type : null, - value + value, + observer: createObserver(key) } } else { // content:String - properties[key] = PROP_TYPES.includes(opts) ? opts : null + properties[key] = { + type: PROP_TYPES.includes(opts) ? opts : null, + observer: createObserver(key) + } } }) } @@ -76,6 +92,13 @@ function wrapper (event) { event.target = event.target || {} event.detail = event.detail || {} + + if (__PLATFORM__ === 'mp-baidu') { // mp-baidu,checked=>value + if (hasOwn(event.detail, 'checked') && !hasOwn(event.detail, 'value')) { + event.detail.value = event.detail.checked + } + } + // TODO 又得兼容 mpvue 的 mp 对象 event.mp = event event.target = Object.assign({}, event.target, event.detail) @@ -124,6 +147,9 @@ export function handleEvent (event) { if (eventsArray && eventType === type) { eventsArray.forEach(eventArray => { const handler = this.$vm[eventArray[0]] + if (!isFn(handler)) { + throw new Error(` _vm.${eventArray[0]} is not a function`) + } if (isOnce) { if (handler.once) { return @@ -145,12 +171,12 @@ export function initRefs (vm) { Object.defineProperty(vm, '$refs', { get () { const $refs = Object.create(null) - const components = mpInstance.selectAllComponents('.__ref__') + const components = mpInstance.selectAllComponents('.vue-ref') components.forEach(component => { const ref = component.dataset.ref $refs[ref] = component.$vm }) - const forComponents = mpInstance.selectAllComponents('.__ref-in-for__') + const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for') forComponents.forEach(component => { const ref = component.dataset.ref if (!$refs[ref]) { @@ -161,4 +187,32 @@ export function initRefs (vm) { return $refs } }) +} + +export function initChildren (vm) { + const mpInstance = vm.$mp[vm.mpType] + Object.defineProperty(vm, '$children', { + get () { + const $children = [] + const components = mpInstance.selectAllComponents('.vue-com') + components.forEach(component => { + $children.push(component.$vm) + }) + return $children + } + }) +} + +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) + }) }