diff --git a/packages/uni-app-plus/dist/index.js b/packages/uni-app-plus/dist/index.js index 1fe07d7a8f173e33b479fb557695fc1c56dc4e88..75c469213a6a589f812c2f0d0cc723c254ec25ec 100644 --- a/packages/uni-app-plus/dist/index.js +++ b/packages/uni-app-plus/dist/index.js @@ -282,8 +282,7 @@ function initTriggerEvent (mpInstance) { }; } -Page = function (options = {}) { - const name = 'onLoad'; +function initHook (name, options) { const oldHook = options[name]; if (!oldHook) { options[name] = function () { @@ -295,22 +294,45 @@ Page = function (options = {}) { return oldHook.apply(this, args) }; } +} + +Page = function (options = {}) { + initHook('onLoad', options); return MPPage(options) }; -const behavior = Behavior({ - created () { - initTriggerEvent(this); - } -}); - Component = function (options = {}) { - (options.behaviors || (options.behaviors = [])).unshift(behavior); + initHook('created', 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, @@ -334,18 +356,19 @@ function handleLink (event) { } function initPage$1 (pageOptions) { - initComponent$1(pageOptions); + return initComponent$1(pageOptions) } function initComponent$1 (componentOptions) { componentOptions.methods.$getAppWebview = function () { return plus.webview.getWebviewById(`${this.__wxWebviewId__}`) }; + return Component(componentOptions) } -function initMocks (vm, mocks) { +function initMocks (vm, mocks$$1) { const mpInstance = vm.$mp[vm.mpType]; - mocks.forEach(mock => { + mocks$$1.forEach(mock => { if (hasOwn(mpInstance, mock)) { vm[mock] = mpInstance[mock]; } @@ -402,7 +425,7 @@ function createObserver (name) { } } -function getBehaviors (vueOptions) { +function getBehaviors (vueOptions) { const vueBehaviors = vueOptions['behaviors']; const vueExtends = vueOptions['extends']; const vueMixins = vueOptions['mixins']; @@ -430,7 +453,7 @@ function getBehaviors (vueOptions) { } if (isPlainObject(vueExtends) && vueExtends.props) { behaviors.push( - Behavior({ + initBehavior({ properties: getProperties(vueExtends.props, true) }) ); @@ -439,7 +462,7 @@ function getBehaviors (vueOptions) { vueMixins.forEach(vueMixin => { if (isPlainObject(vueMixin) && vueMixin.props) { behaviors.push( - Behavior({ + initBehavior({ properties: getProperties(vueMixin.props, true) }) ); @@ -705,29 +728,6 @@ function handleEvent (event) { }); } }); -} - -function initRefs (vm) { - const mpInstance = vm.$mp[vm.mpType]; - 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 hooks = [ @@ -750,24 +750,27 @@ function initVm (vm) { } function createApp (vm) { - // 外部初始化时 Vue 还未初始化,放到 createApp 内部初始化 mixin + 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') { - { // 头条的 selectComponent 竟然是异步的 - initRefs(this); - } + initRefs(this); initMocks(this, mocks); } }, @@ -878,9 +881,7 @@ function createPage (vueOptions) { initHooks(pageOptions.methods, hooks$1); - initPage$1(pageOptions); - - return Component(pageOptions) + return initPage$1(pageOptions, vueOptions) } function initVm$2 (VueComponent) { @@ -888,16 +889,18 @@ function initVm$2 (VueComponent) { return } + const properties = this.properties; + const options = { mpType: 'component', mpInstance: this, - propsData: this.properties + propsData: properties }; // 初始化 vue 实例 this.$vm = new VueComponent(options); // 处理$slots,$scopedSlots(暂不支持动态变化$slots) - const vueSlots = this.properties.vueSlots; + const vueSlots = properties.vueSlots; if (Array.isArray(vueSlots) && vueSlots.length) { const $slots = Object.create(null); vueSlots.forEach(slotName => { @@ -907,18 +910,24 @@ function initVm$2 (VueComponent) { } // 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并 // 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性 - this.$vm.$mount(); + 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); + } + const behaviors = getBehaviors(vueOptions); const properties = getProperties(vueOptions.props, false, vueOptions.__file); - const VueComponent = Vue.extend(vueOptions); - const componentOptions = { options: { multipleSlots: true, @@ -963,9 +972,7 @@ function createComponent (vueOptions) { } }; - initComponent$1(componentOptions); - - return Component(componentOptions) + return initComponent$1(componentOptions, vueOptions) } let uni = {}; diff --git a/packages/uni-app-plus/package.json b/packages/uni-app-plus/package.json index 748956c71aa024682b018cea57dcdb4a23904eb0..8884210803516066ae078af8478a74cb4d108405 100644 --- a/packages/uni-app-plus/package.json +++ b/packages/uni-app-plus/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/uni-app-plus", - "version": "0.0.229", + "version": "0.0.230", "description": "uni-app app-plus", "main": "dist/index.js", "scripts": { diff --git a/packages/uni-mp-alipay/dist/index.js b/packages/uni-mp-alipay/dist/index.js index bc2fe38b7476515a977484e31a8b0e82aca69f32..274ee1af10a90a705be16c7868e8c37ab66a3ac1 100644 --- a/packages/uni-mp-alipay/dist/index.js +++ b/packages/uni-mp-alipay/dist/index.js @@ -1,3 +1,5 @@ +import Vue from 'vue'; + const _toString = Object.prototype.toString; const hasOwnProperty = Object.prototype.hasOwnProperty; @@ -15,9 +17,30 @@ function isPlainObject (obj) { function hasOwn (obj, key) { return hasOwnProperty.call(obj, key) -} +} + +function noop () {} + +/** + * Create a cached version of a pure function. + */ +function cached (fn) { + const cache = Object.create(null); + return function cachedFn (str) { + const hit = cache[str]; + return hit || (cache[str] = fn(str)) + } +} + +/** + * Camelize a hyphen-delimited string. + */ +const camelizeRE = /-(\w)/g; +const camelize = cached((str) => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') +}); -const SYNC_API_RE = /hideKeyboard|upx2px|canIUse|^create|Sync$|Manager$/; +const SYNC_API_RE = /requireNativePlugin|upx2px|hideKeyboard|canIUse|^create|Sync$|Manager$/; const CONTEXT_API_RE = /^create|Manager$/; @@ -42,10 +65,11 @@ function handlePromise (promise) { } function shouldPromise (name) { - if (isSyncApi(name)) { - return false - } - if (isCallbackApi(name)) { + if ( + isContextApi(name) || + isSyncApi(name) || + isCallbackApi(name) + ) { return false } return true @@ -203,7 +227,7 @@ const protocols = { // 需要做转换的 API 列表 return res }, request: { - name: 'httpRequest', + name: my.canIUse('request') ? 'request' : 'httpRequest', args (fromArgs) { if (!fromArgs.header) { // 默认增加 header 参数,方便格式化 content-type fromArgs.header = {}; @@ -461,6 +485,23 @@ const protocols = { // 需要做转换的 API 列表 }, getSystemInfoSync: { returnValue: _handleSystemInfo + }, + // 文档没提到,但是实测可用。 + canvasToTempFilePath: { + returnValue (result) { + // 真机的情况下会有 tempFilePath 这个值,因此需要主动修改。 + result.tempFilePath = result.apFilePath; + } + }, + setScreenBrightness: { + args: { + value: 'brightness' + } + }, + getScreenBrightness: { + returnValue: { + brightness: 'value' + } } }; @@ -495,7 +536,7 @@ function processArgs (methodName, fromArgs, argsOption = {}, returnValue = {}, k } else if (isPlainObject(keyOption)) { // {name:newName,value:value}可重新指定参数 key:value toArgs[keyOption.name ? keyOption.name : key] = keyOption.value; } - } else if (CALLBACKS.includes(key)) { + } else if (CALLBACKS.indexOf(key) !== -1) { toArgs[key] = processCallback(methodName, fromArgs[key], returnValue); } else { if (!keepFromArgs) { @@ -646,6 +687,862 @@ 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); + }; + + 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 (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 + } 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 Component(componentOptions) +} + +function initBehavior ({ + properties +}) { + const props = {}; + + Object.keys(properties).forEach(key => { + props[key] = properties[key].value; + }); + + return { + props + } +} + +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; + } + } + } 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) { + let data = vueOptions.data || {}; + const methods = vueOptions.methods || {}; + + if (typeof data === 'function') { + try { + data = data.call(context); // 支持 Vue.prototype 上挂的数据 + } catch (e) { + if (process.env.VUE_APP_DEBUG) { + console.warn('根据 Vue 的 data 函数初始化小程序 data 失败,请尽量确保 data 函数中不访问 vm 对象,否则可能影响首次数据渲染速度。', data); + } + } + } else { + try { + // 对 data 格式化 + data = JSON.parse(JSON.stringify(data)); + } catch (e) {} + } + + if (!isPlainObject(data)) { + data = {}; + } + + Object.keys(methods).forEach(methodName => { + if (context.__lifecycle_hooks__.indexOf(methodName) === -1 && !hasOwn(data, methodName)) { + data[methodName] = methods[methodName]; + } + }); + + return data +} + +const PROP_TYPES = [String, Number, Boolean, Object, Array, null]; + +function createObserver$1 (name) { + return function observer (newVal, oldVal) { + if (this.$vm) { + this.$vm[name] = newVal; // 为了触发其他非 render watcher + } + } +} + +function getBehaviors (vueOptions) { + const vueBehaviors = vueOptions['behaviors']; + const vueExtends = vueOptions['extends']; + const vueMixins = vueOptions['mixins']; + + let vueProps = vueOptions['props']; + + if (!vueProps) { + vueOptions['props'] = vueProps = []; + } + + const behaviors = []; + if (Array.isArray(vueBehaviors)) { + vueBehaviors.forEach(behavior => { + behaviors.push(behavior.replace('uni://', `${"my"}://`)); + if (behavior === 'uni://form-field') { + if (Array.isArray(vueProps)) { + vueProps.push('name'); + vueProps.push('value'); + } else { + vueProps['name'] = String; + vueProps['value'] = null; + } + } + }); + } + if (isPlainObject(vueExtends) && vueExtends.props) { + behaviors.push( + initBehavior({ + properties: getProperties(vueExtends.props, true) + }) + ); + } + if (Array.isArray(vueMixins)) { + vueMixins.forEach(vueMixin => { + if (isPlainObject(vueMixin) && vueMixin.props) { + behaviors.push( + initBehavior({ + properties: getProperties(vueMixin.props, true) + }) + ); + } + }); + } + return behaviors +} + +function parsePropType (key, type, defaultValue, file) { + // [String]=>String + if (Array.isArray(type) && type.length === 1) { + return type[0] + } + return type +} + +function getProperties (props, isBehavior = false, file = '') { + const properties = {}; + if (!isBehavior) { + properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots + type: null, + value: [], + observer: function (newVal, oldVal) { + const $slots = Object.create(null); + newVal.forEach(slotName => { + $slots[slotName] = true; + }); + this.setData({ + $slots + }); + } + }; + } + if (Array.isArray(props)) { // ['title'] + props.forEach(key => { + properties[key] = { + type: null, + observer: createObserver$1(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(); + } + + opts.type = parsePropType(key, opts.type, value, file); + + properties[key] = { + type: PROP_TYPES.indexOf(opts.type) !== -1 ? opts.type : null, + value, + observer: createObserver$1(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) + }; + } + }); + } + return properties +} + +function wrapper$1 (event) { + // TODO 又得兼容 mpvue 的 mp 对象 + try { + event.mp = JSON.parse(JSON.stringify(event)); + } catch (e) {} + + event.stopPropagation = noop; + event.preventDefault = noop; + + event.target = event.target || {}; + + if (!hasOwn(event, 'detail')) { + event.detail = {}; + } + + if (isPlainObject(event.detail)) { + event.target = Object.assign({}, event.target, event.detail); + } + + return event +} + +function getExtraValue (vm, dataPathsArray) { + let context = vm; + dataPathsArray.forEach(dataPathArray => { + const dataPath = dataPathArray[0]; + const value = dataPathArray[2]; + if (dataPath || typeof value !== 'undefined') { // ['','',index,'disable'] + const propPath = dataPathArray[1]; + const valuePath = dataPathArray[3]; + + const vFor = dataPath ? vm.__get_value(dataPath, context) : context; + + if (Number.isInteger(vFor)) { + context = value; + } else if (!propPath) { + context = vFor[value]; + } else { + if (Array.isArray(vFor)) { + context = vFor.find(vForItem => { + return vm.__get_value(propPath, vForItem) === value + }); + } else if (isPlainObject(vFor)) { + context = Object.keys(vFor).find(vForKey => { + return vm.__get_value(propPath, vFor[vForKey]) === value + }); + } else { + console.error('v-for 暂不支持循环数据:', vFor); + } + } + + if (valuePath) { + context = vm.__get_value(valuePath, context); + } + } + }); + return context +} + +function processEventExtra (vm, extra, event) { + const extraObj = {}; + + if (Array.isArray(extra) && extra.length) { + /** + *[ + * ['data.items', 'data.id', item.data.id], + * ['metas', 'id', meta.id] + *], + *[ + * ['data.items', 'data.id', item.data.id], + * ['metas', 'id', meta.id] + *], + *'test' + */ + extra.forEach((dataPath, index) => { + if (typeof dataPath === 'string') { + if (!dataPath) { // model,prop.sync + extraObj['$' + index] = vm; + } else { + if (dataPath === '$event') { // $event + extraObj['$' + index] = event; + } else if (dataPath.indexOf('$event.') === 0) { // $event.target.value + extraObj['$' + index] = vm.__get_value(dataPath.replace('$event.', ''), event); + } else { + extraObj['$' + index] = vm.__get_value(dataPath); + } + } + } else { + extraObj['$' + index] = getExtraValue(vm, dataPath); + } + }); + } + + return extraObj +} + +function getObjByArray (arr) { + const obj = {}; + for (let i = 1; i < arr.length; i++) { + const element = arr[i]; + obj[element[0]] = element[1]; + } + return obj +} + +function processEventArgs (vm, event, args = [], extra = [], isCustom, methodName) { + let isCustomMPEvent = false; // wxcomponent 组件,传递原始 event 对象 + if (isCustom) { // 自定义事件 + isCustomMPEvent = event.currentTarget && + event.currentTarget.dataset && + event.currentTarget.dataset.comType === 'wx'; + if (!args.length) { // 无参数,直接传入 event 或 detail 数组 + if (isCustomMPEvent) { + return [event] + } + return event.detail.__args__ || event.detail + } + } + + const extraObj = processEventExtra(vm, extra, event); + + const ret = []; + args.forEach(arg => { + if (arg === '$event') { + if (methodName === '__set_model' && !isCustom) { // input v-model value + ret.push(event.target.value); + } else { + if (isCustom && !isCustomMPEvent) { + ret.push(event.detail.__args__[0]); + } else { // wxcomponent 组件或内置组件 + ret.push(event); + } + } + } else { + if (Array.isArray(arg) && arg[0] === 'o') { + ret.push(getObjByArray(arg)); + } else if (typeof arg === 'string' && hasOwn(extraObj, arg)) { + ret.push(extraObj[arg]); + } 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 methodName = eventArray[0]; + if (methodName) { + const handler = this.$vm[methodName]; + if (!isFn(handler)) { + throw new Error(` _vm.${methodName} is not a function`) + } + if (isOnce) { + if (handler.once) { + return + } + handler.once = true; + } + handler.apply(this.$vm, processEventArgs( + this.$vm, + event, + eventArray[1], + eventArray[2], + isCustom, + methodName + )); + } + }); + } + }); +} + +const hooks = [ + 'onHide', + 'onError', + 'onPageNotFound', + 'onUniNViewMessage' +]; + +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 () { + + } + }); + } + + 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') { + initMocks(this, mocks); + } + }, + created () { // 处理 injections + this.__init_injections(this); + this.__init_provide(this); + } + }); + + const appOptions = { + onLaunch (args) { + initVm.call(this, vm); + + this.$vm._isMounted = true; + this.$vm.__call_hook('mounted'); + + this.$vm.__call_hook('onLaunch', args); + }, + onShow (args) { + initVm.call(this, vm); + + this.$vm.__call_hook('onShow', args); + } + }; + + // 兼容旧版本 globalData + appOptions.globalData = vm.$options.globalData || {}; + + initHooks(appOptions, hooks); // 延迟执行,因为 App 的注册在 main.js 之前,可能导致生命周期内 Vue 原型上开发者注册的属性无法访问 + + App(appOptions); + + return vm +} + +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 + } + + this.$vm = new VueComponent({ + mpType: 'page', + mpInstance: this + }); + + this.$vm.__call_hook('created'); + this.$vm.$mount(); +} + +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 + } + }; + + initHooks(pageOptions.methods, hooks$1); + + return initPage(pageOptions, vueOptions) +} + +function initVm$2 (VueComponent) { + if (this.$vm) { + return + } + + const properties = this.props; + + 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(); +} + +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); + } + + 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$2.call(this, VueComponent); + }, + 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'); + }, + detached () { + this.$vm.$destroy(); + } + }, + pageLifetimes: { + show (args) { + this.$vm.__call_hook('onPageShow', args); + }, + hide () { + this.$vm && this.$vm.__call_hook('onPageHide'); + }, + resize (size) { + this.$vm && this.$vm.__call_hook('onPageResize', size); + } + }, + methods: { + __e: handleEvent, + __l: handleLink + } + }; + + return initComponent(componentOptions, vueOptions) +} + let uni = {}; if (typeof Proxy !== 'undefined') { @@ -656,12 +1553,14 @@ if (typeof Proxy !== 'undefined') { } if (api[name]) { return promisify(name, api[name]) - } - if (extraApi[name]) { - return promisify(name, extraApi[name]) - } - if (todoApis[name]) { - return promisify(name, todoApis[name]) + } + { + if (extraApi[name]) { + return promisify(name, extraApi[name]) + } + if (todoApis[name]) { + return promisify(name, todoApis[name]) + } } if (!hasOwn(my, name) && !hasOwn(protocols, name)) { return @@ -672,13 +1571,14 @@ if (typeof Proxy !== 'undefined') { } else { uni.upx2px = upx2px; - Object.keys(todoApis).forEach(name => { - uni[name] = promisify(name, todoApis[name]); - }); - - Object.keys(extraApi).forEach(name => { - uni[name] = promisify(name, todoApis[name]); - }); + { + Object.keys(todoApis).forEach(name => { + uni[name] = promisify(name, todoApis[name]); + }); + Object.keys(extraApi).forEach(name => { + uni[name] = promisify(name, todoApis[name]); + }); + } Object.keys(api).forEach(name => { uni[name] = promisify(name, api[name]); @@ -694,3 +1594,4 @@ if (typeof Proxy !== 'undefined') { var uni$1 = uni; export default uni$1; +export { createApp, createPage, createComponent }; diff --git a/packages/uni-mp-alipay/package.json b/packages/uni-mp-alipay/package.json index 1aa3d71de9da2b1cb8041f8d51620a0a9307c52f..001c626f40c337e81c781240f61d081b17d625d4 100644 --- a/packages/uni-mp-alipay/package.json +++ b/packages/uni-mp-alipay/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/uni-mp-alipay", - "version": "0.0.8", + "version": "0.0.801", "description": "uni-app mp-alipay", "main": "dist/index.js", "scripts": { diff --git a/packages/uni-mp-baidu/dist/index.js b/packages/uni-mp-baidu/dist/index.js index a981de4e9d253950ae3fec8d7ead67f7d0e16fb8..4fccafcc4cf78f4056501fecfe648afcd382e7f7 100644 --- a/packages/uni-mp-baidu/dist/index.js +++ b/packages/uni-mp-baidu/dist/index.js @@ -417,8 +417,7 @@ function initTriggerEvent (mpInstance) { }; } -Page = function (options = {}) { - const name = 'onLoad'; +function initHook (name, options) { const oldHook = options[name]; if (!oldHook) { options[name] = function () { @@ -430,37 +429,62 @@ Page = function (options = {}) { return oldHook.apply(this, args) }; } +} + +Page = function (options = {}) { + initHook('onLoad', options); return MPPage(options) }; -const behavior = Behavior({ - created () { - initTriggerEvent(this); - } -}); - Component = function (options = {}) { - (options.behaviors || (options.behaviors = [])).unshift(behavior); + initHook('created', options); return MPComponent(options) }; -const mocks = ['nodeId']; +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 (pageOptions) { - initComponent(pageOptions); +function initPage$1 (pageOptions) { + return initComponent$1(pageOptions) } -function initComponent (componentOptions) { +function initComponent$1 (componentOptions) { componentOptions.messages = { - '__l': handleLink + '__l': handleLink$1 }; + return Component(componentOptions) } -function triggerLink (mpInstance, vueOptions) { +function triggerLink$1 (mpInstance, vueOptions) { mpInstance.dispatch('__l', mpInstance.$vm || vueOptions); } -function handleLink (event) { +function handleLink$1 (event) { const target = event.value; if (target.$mp) { if (!target.$parent) { @@ -535,7 +559,7 @@ function createObserver (name) { } } -function getBehaviors (vueOptions) { +function getBehaviors (vueOptions) { const vueBehaviors = vueOptions['behaviors']; const vueExtends = vueOptions['extends']; const vueMixins = vueOptions['mixins']; @@ -563,7 +587,7 @@ function getBehaviors (vueOptions) { } if (isPlainObject(vueExtends) && vueExtends.props) { behaviors.push( - Behavior({ + initBehavior({ properties: getProperties(vueExtends.props, true) }) ); @@ -572,7 +596,7 @@ function getBehaviors (vueOptions) { vueMixins.forEach(vueMixin => { if (isPlainObject(vueMixin) && vueMixin.props) { behaviors.push( - Behavior({ + initBehavior({ properties: getProperties(vueMixin.props, true) }) ); @@ -866,29 +890,6 @@ function handleEvent (event) { }); } -function initRefs (vm) { - const mpInstance = vm.$mp[vm.mpType]; - 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 baiduComponentDestroy ($vm) { $vm.$children.forEach(childVm => { childVm.$mp.component.detached(); @@ -923,25 +924,28 @@ function initVm (vm) { } function createApp (vm) { - // 外部初始化时 Vue 还未初始化,放到 createApp 内部初始化 mixin + 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') { - { // 头条的 selectComponent 竟然是异步的 - initRefs(this); - } - initMocks(this, mocks); + initRefs(this); + initMocks(this, mocks$1); } }, created () { // 处理 injections @@ -1055,15 +1059,13 @@ function createPage (vueOptions) { } }, __e: handleEvent, - __l: handleLink + __l: handleLink$1 } }; initHooks(pageOptions.methods, hooks$1); - initPage(pageOptions); - - return Component(pageOptions) + return initPage$1(pageOptions, vueOptions) } function initVm$2 (VueComponent) { @@ -1071,16 +1073,18 @@ function initVm$2 (VueComponent) { return } + const properties = this.properties; + const options = { mpType: 'component', mpInstance: this, - propsData: this.properties + propsData: properties }; // 初始化 vue 实例 this.$vm = new VueComponent(options); // 处理$slots,$scopedSlots(暂不支持动态变化$slots) - const vueSlots = this.properties.vueSlots; + const vueSlots = properties.vueSlots; if (Array.isArray(vueSlots) && vueSlots.length) { const $slots = Object.create(null); vueSlots.forEach(slotName => { @@ -1090,18 +1094,24 @@ function initVm$2 (VueComponent) { } // 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并 // 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性 - this.$vm.$mount(); + 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); + } + const behaviors = getBehaviors(vueOptions); const properties = getProperties(vueOptions.props, false, vueOptions.__file); - const VueComponent = Vue.extend(vueOptions); - const componentOptions = { options: { multipleSlots: true, @@ -1116,7 +1126,7 @@ function createComponent (vueOptions) { }, ready () { initVm$2.call(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 - triggerLink(this); // 处理 parent,children + triggerLink$1(this); // 处理 parent,children // 补充生命周期 this.$vm.__call_hook('created'); @@ -1142,13 +1152,11 @@ function createComponent (vueOptions) { }, methods: { __e: handleEvent, - __l: handleLink + __l: handleLink$1 } }; - initComponent(componentOptions); - - return Component(componentOptions) + return initComponent$1(componentOptions, vueOptions) } let uni = {}; diff --git a/packages/uni-mp-baidu/package.json b/packages/uni-mp-baidu/package.json index 6cf702fa04b69c8fae07367a1bf10b560a0abe84..d6fd93c41b6b1163dbca42df6117fe323bf0a628 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.826", + "version": "0.0.827", "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 7db66e24c8825882952e10ff52ef8dfeeb27ddc1..cc3a154fcd798ce7ba5ba987fafacde361bee02c 100644 --- a/packages/uni-mp-toutiao/dist/index.js +++ b/packages/uni-mp-toutiao/dist/index.js @@ -462,8 +462,7 @@ function initTriggerEvent (mpInstance) { }; } -Page = function (options = {}) { - const name = 'onLoad'; +function initHook (name, options) { const oldHook = options[name]; if (!oldHook) { options[name] = function () { @@ -475,62 +474,60 @@ Page = function (options = {}) { return oldHook.apply(this, args) }; } +} + +Page = function (options = {}) { + initHook('onLoad', options); return MPPage(options) }; -const behavior = Behavior({ - created () { - initTriggerEvent(this); - } -}); - Component = function (options = {}) { - (options.behaviors || (options.behaviors = [])).unshift(behavior); + initHook('created', options); return MPComponent(options) }; +function initBehavior (options) { + return Behavior(options) +} + const instances = Object.create(null); -const mocks = ['__route__', '__webviewId__', '__nodeid__']; +const mocks$1 = ['__route__', '__webviewId__', '__nodeid__']; -function initPage (pageOptions) { - initComponent(pageOptions); +function initPage$1 (pageOptions) { + return initComponent$1(pageOptions) } -function initComponent (componentOptions) { +function initComponent$1 (componentOptions) { if (componentOptions.properties) { // ref componentOptions.properties.vueRef = { type: String, value: '' }; } - const oldAttached = componentOptions.lifetimes.attached; - componentOptions.lifetimes.attached = function () { - oldAttached.call(this); - // TODO 需要处理动态变化后的 refs - initRefs.call(this); - }; + return Component(componentOptions) } -function initRefs () { - this.selectAllComponents('.vue-ref', (components) => { +function initRefs$1 (vm) { + const mpInstance = vm.$scope; + mpInstance.selectAllComponents('.vue-ref', (components) => { components.forEach(component => { const ref = component.data.vueRef; // 头条的组件 dataset 竟然是空的 - this.$vm.$refs[ref] = component.$vm || component; + vm.$refs[ref] = component.$vm || component; }); }); - this.selectAllComponents('.vue-ref-in-for', (forComponents) => { + mpInstance.selectAllComponents('.vue-ref-in-for', (forComponents) => { forComponents.forEach(component => { const ref = component.data.vueRef; - if (!this.$vm.$refs[ref]) { - this.$vm.$refs[ref] = []; + if (!vm.$refs[ref]) { + vm.$refs[ref] = []; } - this.$vm.$refs[ref].push(component.$vm || component); + vm.$refs[ref].push(component.$vm || component); }); }); } -function triggerLink (mpInstance) { +function triggerLink$1 (mpInstance) { const nodeId = mpInstance.__nodeid__ + ''; const webviewId = mpInstance.__webviewId__ + ''; @@ -545,7 +542,7 @@ function triggerLink (mpInstance) { }); } // TODO 目前有 bug,composed 不生效 -function handleLink (event) { +function handleLink$1 (event) { const nodeId = event.detail.nodeId; const webviewId = event.detail.webviewId; @@ -619,7 +616,7 @@ function createObserver (name) { } } -function getBehaviors (vueOptions) { +function getBehaviors (vueOptions) { const vueBehaviors = vueOptions['behaviors']; const vueExtends = vueOptions['extends']; const vueMixins = vueOptions['mixins']; @@ -647,7 +644,7 @@ function getBehaviors (vueOptions) { } if (isPlainObject(vueExtends) && vueExtends.props) { behaviors.push( - Behavior({ + initBehavior({ properties: getProperties(vueExtends.props, true) }) ); @@ -656,7 +653,7 @@ function getBehaviors (vueOptions) { vueMixins.forEach(vueMixin => { if (isPlainObject(vueMixin) && vueMixin.props) { behaviors.push( - Behavior({ + initBehavior({ properties: getProperties(vueMixin.props, true) }) ); @@ -944,22 +941,28 @@ function initVm (vm) { } function createApp (vm) { - // 外部初始化时 Vue 还未初始化,放到 createApp 内部初始化 mixin + 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') { - initMocks(this, mocks); + initRefs$1(this); + initMocks(this, mocks$1); } }, created () { // 处理 injections @@ -1063,15 +1066,13 @@ function createPage (vueOptions) { this.$vm.__call_hook('onUnload'); }, __e: handleEvent, - __l: handleLink + __l: handleLink$1 } }; initHooks(pageOptions.methods, hooks$1); - initPage(pageOptions); - - return Component(pageOptions) + return initPage$1(pageOptions, vueOptions) } function initVm$2 (VueComponent) { @@ -1079,16 +1080,18 @@ function initVm$2 (VueComponent) { return } + const properties = this.properties; + const options = { mpType: 'component', mpInstance: this, - propsData: this.properties + propsData: properties }; // 初始化 vue 实例 this.$vm = new VueComponent(options); // 处理$slots,$scopedSlots(暂不支持动态变化$slots) - const vueSlots = this.properties.vueSlots; + const vueSlots = properties.vueSlots; if (Array.isArray(vueSlots) && vueSlots.length) { const $slots = Object.create(null); vueSlots.forEach(slotName => { @@ -1098,18 +1101,24 @@ function initVm$2 (VueComponent) { } // 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并 // 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性 - this.$vm.$mount(); + 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); + } + const behaviors = getBehaviors(vueOptions); const properties = getProperties(vueOptions.props, false, vueOptions.__file); - const VueComponent = Vue.extend(vueOptions); - const componentOptions = { options: { multipleSlots: true, @@ -1124,7 +1133,7 @@ function createComponent (vueOptions) { }, ready () { initVm$2.call(this, VueComponent); // 目前发现部分情况小程序 attached 不触发 - triggerLink(this); // 处理 parent,children + triggerLink$1(this); // 处理 parent,children // 补充生命周期 this.$vm.__call_hook('created'); @@ -1150,13 +1159,11 @@ function createComponent (vueOptions) { }, methods: { __e: handleEvent, - __l: handleLink + __l: handleLink$1 } }; - initComponent(componentOptions); - - return Component(componentOptions) + return initComponent$1(componentOptions, vueOptions) } let uni = {}; diff --git a/packages/uni-mp-toutiao/package.json b/packages/uni-mp-toutiao/package.json index 5e9dd76d6c9c2c123530c0087d749e1ba12e3cf4..d8d06b67208bfc5f5f36d1e616360815d8f7f19a 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.324", + "version": "0.0.325", "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 cc49efb8b2919fb56893e2e36cbd94650f50e398..c8a57b73bc9475f5db2443d762ff2b0d68fa79f1 100644 --- a/packages/uni-mp-weixin/dist/index.js +++ b/packages/uni-mp-weixin/dist/index.js @@ -309,8 +309,7 @@ function initTriggerEvent (mpInstance) { }; } -Page = function (options = {}) { - const name = 'onLoad'; +function initHook (name, options) { const oldHook = options[name]; if (!oldHook) { options[name] = function () { @@ -322,22 +321,53 @@ Page = function (options = {}) { return oldHook.apply(this, args) }; } +} + +Page = function (options = {}) { + initHook('onLoad', options); return MPPage(options) }; -const behavior = Behavior({ - created () { - initTriggerEvent(this); - } -}); - Component = function (options = {}) { - (options.behaviors || (options.behaviors = [])).unshift(behavior); + initHook('created', 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, @@ -360,9 +390,9 @@ function handleLink (event) { } } -function initMocks (vm, mocks) { +function initMocks (vm, mocks$$1) { const mpInstance = vm.$mp[vm.mpType]; - mocks.forEach(mock => { + mocks$$1.forEach(mock => { if (hasOwn(mpInstance, mock)) { vm[mock] = mpInstance[mock]; } @@ -419,7 +449,7 @@ function createObserver (name) { } } -function getBehaviors (vueOptions) { +function getBehaviors (vueOptions) { const vueBehaviors = vueOptions['behaviors']; const vueExtends = vueOptions['extends']; const vueMixins = vueOptions['mixins']; @@ -447,7 +477,7 @@ function getBehaviors (vueOptions) { } if (isPlainObject(vueExtends) && vueExtends.props) { behaviors.push( - Behavior({ + initBehavior({ properties: getProperties(vueExtends.props, true) }) ); @@ -456,7 +486,7 @@ function getBehaviors (vueOptions) { vueMixins.forEach(vueMixin => { if (isPlainObject(vueMixin) && vueMixin.props) { behaviors.push( - Behavior({ + initBehavior({ properties: getProperties(vueMixin.props, true) }) ); @@ -722,29 +752,6 @@ function handleEvent (event) { }); } }); -} - -function initRefs (vm) { - const mpInstance = vm.$mp[vm.mpType]; - 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 hooks = [ @@ -772,24 +779,27 @@ function initVm (vm) { } function createApp (vm) { - // 外部初始化时 Vue 还未初始化,放到 createApp 内部初始化 mixin + 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') { - { // 头条的 selectComponent 竟然是异步的 - initRefs(this); - } + initRefs(this); initMocks(this, mocks); } }, @@ -900,7 +910,7 @@ function createPage (vueOptions) { initHooks(pageOptions.methods, hooks$1); - return Component(pageOptions) + return initPage(pageOptions, vueOptions) } function initVm$2 (VueComponent) { @@ -908,16 +918,18 @@ function initVm$2 (VueComponent) { return } + const properties = this.properties; + const options = { mpType: 'component', mpInstance: this, - propsData: this.properties + propsData: properties }; // 初始化 vue 实例 this.$vm = new VueComponent(options); // 处理$slots,$scopedSlots(暂不支持动态变化$slots) - const vueSlots = this.properties.vueSlots; + const vueSlots = properties.vueSlots; if (Array.isArray(vueSlots) && vueSlots.length) { const $slots = Object.create(null); vueSlots.forEach(slotName => { @@ -927,18 +939,24 @@ function initVm$2 (VueComponent) { } // 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并 // 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性 - this.$vm.$mount(); + 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); + } + const behaviors = getBehaviors(vueOptions); const properties = getProperties(vueOptions.props, false, vueOptions.__file); - const VueComponent = Vue.extend(vueOptions); - const componentOptions = { options: { multipleSlots: true, @@ -983,7 +1001,7 @@ function createComponent (vueOptions) { } }; - return Component(componentOptions) + return initComponent(componentOptions, vueOptions) } let uni = {}; diff --git a/packages/uni-mp-weixin/package.json b/packages/uni-mp-weixin/package.json index 2f3638fa7026d4105b09839d3e36c17d6e9d251e..29fc3fa9ea6c9b0c96e61008e5040c300586b69f 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.948", + "version": "0.0.949", "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 667b70863d8ed3df6f276222390056055028e4fc..366e1ffbc2b6ac81e89aab45905a26ed752cc8b9 100644 --- a/src/core/runtime/wrapper/create-app.js +++ b/src/core/runtime/wrapper/create-app.js @@ -1,13 +1,13 @@ import 'uni-platform/runtime/index' import Vue from 'vue' - -import { - mocks -} from 'uni-platform/runtime/wrapper/index' import { - initRefs, + mocks, + initRefs +} from 'uni-platform/runtime/wrapper/index' + +import { initHooks, initMocks } from './util' @@ -38,23 +38,45 @@ function initVm (vm) { 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') { - if (__PLATFORM__ !== 'mp-toutiao') { // 头条的 selectComponent 竟然是异步的 - initRefs(this) - } + initRefs(this) initMocks(this, mocks) } }, diff --git a/src/core/runtime/wrapper/create-component.js b/src/core/runtime/wrapper/create-component.js index f987c017784c4fe2ad652bb8e7c98e179246415b..0b7f9cceeb028ba776bebe5d5dd8da6acc1bc511 100644 --- a/src/core/runtime/wrapper/create-component.js +++ b/src/core/runtime/wrapper/create-component.js @@ -1,5 +1,9 @@ import Vue from 'vue' +import { + isFn +} from 'uni-shared' + import { handleLink, triggerLink, @@ -18,16 +22,20 @@ function initVm (VueComponent) { return } + const properties = __PLATFORM__ === 'mp-alipay' + ? this.props + : this.properties + const options = { mpType: 'component', mpInstance: this, - propsData: this.properties + propsData: properties } // 初始化 vue 实例 this.$vm = new VueComponent(options) // 处理$slots,$scopedSlots(暂不支持动态变化$slots) - const vueSlots = this.properties.vueSlots + const vueSlots = properties.vueSlots if (Array.isArray(vueSlots) && vueSlots.length) { const $slots = Object.create(null) vueSlots.forEach(slotName => { @@ -37,18 +45,24 @@ function initVm (VueComponent) { } // 性能优先,mount 提前到 attached 中,保证组件首次渲染数据被合并 // 导致与标准 Vue 的差异,data 和 computed 中不能使用$parent,provide等组件属性 - this.$vm.$mount() + this.$vm.$mount() } 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 + } else { + VueComponent = Vue.extend(vueOptions) + } + const behaviors = getBehaviors(vueOptions) const properties = getProperties(vueOptions.props, false, vueOptions.__file) - const VueComponent = Vue.extend(vueOptions) - const componentOptions = { options: { multipleSlots: true, @@ -93,7 +107,5 @@ export function createComponent (vueOptions) { } } - initComponent(componentOptions) - - return Component(componentOptions) + return initComponent(componentOptions, vueOptions) } diff --git a/src/core/runtime/wrapper/create-page.js b/src/core/runtime/wrapper/create-page.js index a97c86cb5a65a71391fddd37b02c0e9a5d5f9fce..d419d36f9ada46f8ea27c4c877d6c62742d97a16 100644 --- a/src/core/runtime/wrapper/create-page.js +++ b/src/core/runtime/wrapper/create-page.js @@ -101,7 +101,5 @@ export function createPage (vueOptions) { initHooks(pageOptions.methods, hooks) - initPage(pageOptions) - - return Component(pageOptions) + return initPage(pageOptions, vueOptions) } diff --git a/src/core/runtime/wrapper/util.js b/src/core/runtime/wrapper/util.js index e08c4cf68cf3fd3c8d40bf4e1abc000ae1ce5444..facd49f628ebddfebc04b148d11ebb8dcdf01ada 100644 --- a/src/core/runtime/wrapper/util.js +++ b/src/core/runtime/wrapper/util.js @@ -5,6 +5,10 @@ import { isPlainObject } from 'uni-shared' +import { + initBehavior +} from 'uni-platform/runtime/wrapper/index' + export function initMocks (vm, mocks) { const mpInstance = vm.$mp[vm.mpType] mocks.forEach(mock => { @@ -64,7 +68,7 @@ function createObserver (name) { } } -export function getBehaviors (vueOptions) { +export function getBehaviors (vueOptions) { const vueBehaviors = vueOptions['behaviors'] const vueExtends = vueOptions['extends'] const vueMixins = vueOptions['mixins'] @@ -92,7 +96,7 @@ export function getBehaviors (vueOptions) { } if (isPlainObject(vueExtends) && vueExtends.props) { behaviors.push( - Behavior({ + initBehavior({ properties: getProperties(vueExtends.props, true) }) ) @@ -101,7 +105,7 @@ export function getBehaviors (vueOptions) { vueMixins.forEach(vueMixin => { if (isPlainObject(vueMixin) && vueMixin.props) { behaviors.push( - Behavior({ + initBehavior({ properties: getProperties(vueMixin.props, true) }) ) @@ -395,29 +399,6 @@ export function handleEvent (event) { }) } -export function initRefs (vm) { - const mpInstance = vm.$mp[vm.mpType] - 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 baiduComponentDestroy ($vm) { $vm.$children.forEach(childVm => { childVm.$mp.component.detached() diff --git a/src/platforms/app-plus/runtime/wrapper/index.js b/src/platforms/app-plus/runtime/wrapper/index.js index 2628b2b95222c1eed2e626f8a6561e5ec6d292da..6056918eb655dd55799cf91d9ceeeeda0a9dd622 100644 --- a/src/platforms/app-plus/runtime/wrapper/index.js +++ b/src/platforms/app-plus/runtime/wrapper/index.js @@ -1,16 +1,19 @@ export { mocks, + initRefs, handleLink, - triggerLink + triggerLink, + initBehavior } from '../../../mp-weixin/runtime/wrapper/index' export function initPage (pageOptions) { - initComponent(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/mp-alipay/runtime/wrapper/deep-equal.js b/src/platforms/mp-alipay/runtime/wrapper/deep-equal.js new file mode 100644 index 0000000000000000000000000000000000000000..e6dc770f83d0b4544e77373f14e39e45f5d9908b --- /dev/null +++ b/src/platforms/mp-alipay/runtime/wrapper/deep-equal.js @@ -0,0 +1,52 @@ +import { + hasOwn +} from 'uni-shared' + +const isArray = Array.isArray +const keyList = Object.keys + +export default 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 +} diff --git a/src/platforms/mp-alipay/runtime/wrapper/index.js b/src/platforms/mp-alipay/runtime/wrapper/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ed756cd7451c106f28a0fb66f701268180a8f9fc --- /dev/null +++ b/src/platforms/mp-alipay/runtime/wrapper/index.js @@ -0,0 +1,191 @@ +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 Component(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 + } + } + } else { // vueOptions + if (!detail.parent) { + detail.parent = this.$vm + } + } +} diff --git a/src/platforms/mp-baidu/runtime/wrapper/index.js b/src/platforms/mp-baidu/runtime/wrapper/index.js index 160ac73e6d34c41dc0f65c8fdb4bcda131b57962..e64c659c3693e1c556fbf08e865d2b0dffaad50f 100644 --- a/src/platforms/mp-baidu/runtime/wrapper/index.js +++ b/src/platforms/mp-baidu/runtime/wrapper/index.js @@ -1,13 +1,20 @@ +export { + initRefs, + initBehavior +} + from '../../../mp-weixin/runtime/wrapper/index' + export const mocks = ['nodeId'] export function initPage (pageOptions) { - initComponent(pageOptions) + return initComponent(pageOptions) } export function initComponent (componentOptions) { componentOptions.messages = { '__l': handleLink } + return Component(componentOptions) } export function triggerLink (mpInstance, vueOptions) { diff --git a/src/platforms/mp-toutiao/runtime/wrapper/index.js b/src/platforms/mp-toutiao/runtime/wrapper/index.js index 4c3a710f126b4ffc0f76165f836cee5697b4bb15..af9852d340872e7efdd66b8ef3f2946a1bf98f97 100644 --- a/src/platforms/mp-toutiao/runtime/wrapper/index.js +++ b/src/platforms/mp-toutiao/runtime/wrapper/index.js @@ -1,9 +1,14 @@ +export { + initBehavior +} + from '../../../mp-weixin/runtime/wrapper/index' + const instances = Object.create(null) export const mocks = ['__route__', '__webviewId__', '__nodeid__'] export function initPage (pageOptions) { - initComponent(pageOptions) + return initComponent(pageOptions) } export function initComponent (componentOptions) { @@ -13,28 +18,24 @@ export function initComponent (componentOptions) { value: '' } } - const oldAttached = componentOptions.lifetimes.attached - componentOptions.lifetimes.attached = function () { - oldAttached.call(this) - // TODO 需要处理动态变化后的 refs - initRefs.call(this) - } + return Component(componentOptions) } -function initRefs () { - this.selectAllComponents('.vue-ref', (components) => { +export function initRefs (vm) { + const mpInstance = vm.$scope + mpInstance.selectAllComponents('.vue-ref', (components) => { components.forEach(component => { const ref = component.data.vueRef // 头条的组件 dataset 竟然是空的 - this.$vm.$refs[ref] = component.$vm || component + vm.$refs[ref] = component.$vm || component }) }) - this.selectAllComponents('.vue-ref-in-for', (forComponents) => { + mpInstance.selectAllComponents('.vue-ref-in-for', (forComponents) => { forComponents.forEach(component => { const ref = component.data.vueRef - if (!this.$vm.$refs[ref]) { - this.$vm.$refs[ref] = [] + if (!vm.$refs[ref]) { + vm.$refs[ref] = [] } - this.$vm.$refs[ref].push(component.$vm || component) + vm.$refs[ref].push(component.$vm || component) }) }) } diff --git a/src/platforms/mp-weixin/runtime/index.js b/src/platforms/mp-weixin/runtime/index.js index ce6bd269469ca5d64e3a1726e47040130b03624a..882e159c4d0ea0fe1fce9702c0557eb7c7d8e27c 100644 --- a/src/platforms/mp-weixin/runtime/index.js +++ b/src/platforms/mp-weixin/runtime/index.js @@ -24,8 +24,7 @@ function initTriggerEvent (mpInstance) { } } -Page = function (options = {}) { - const name = 'onLoad' +function initHook (name, options) { const oldHook = options[name] if (!oldHook) { options[name] = function () { @@ -37,16 +36,14 @@ Page = function (options = {}) { return oldHook.apply(this, args) } } - return MPPage(options) } -const behavior = Behavior({ - created () { - initTriggerEvent(this) - } -}) +Page = function (options = {}) { + initHook('onLoad', options) + return MPPage(options) +} Component = function (options = {}) { - (options.behaviors || (options.behaviors = [])).unshift(behavior) + initHook('created', options) return MPComponent(options) } diff --git a/src/platforms/mp-weixin/runtime/wrapper/index.js b/src/platforms/mp-weixin/runtime/wrapper/index.js index e4c21a48717c756f9c218ef7ce15d24c6bf6c960..ff1f52952952c6c4982a002d90fc0d8176e58974 100644 --- a/src/platforms/mp-weixin/runtime/wrapper/index.js +++ b/src/platforms/mp-weixin/runtime/wrapper/index.js @@ -1,13 +1,38 @@ export const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__'] -export function initPage () { - +export function initPage (pageOptions) { + return initComponent(pageOptions) } -export function initComponent () { - +export function initComponent (componentOptions) { + return Component(componentOptions) } +export function initBehavior (options) { + return Behavior(options) +} +export 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 + } + }) +} export function triggerLink (mpInstance, vueOptions) { mpInstance.triggerEvent('__l', mpInstance.$vm || vueOptions, { bubbles: true,