You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1115 lines
35 KiB
1115 lines
35 KiB
import { SLOT_DEFAULT_NAME, EventChannel, invokeArrayFns, MINI_PROGRAM_PAGE_RUNTIME_HOOKS, ON_LOAD, ON_SHOW, ON_HIDE, ON_UNLOAD, ON_RESIZE, ON_TAB_ITEM_TAP, ON_REACH_BOTTOM, ON_PULL_DOWN_REFRESH, ON_ADD_TO_FAVORITES, isUniLifecycleHook, ON_READY, once, ON_LAUNCH, ON_ERROR, ON_THEME_CHANGE, ON_PAGE_NOT_FOUND, ON_UNHANDLE_REJECTION, addLeadingSlash, stringifyQuery, ON_INIT, customizeEvent } from '@dcloudio/uni-shared'; |
|
import { isArray, isFunction, hasOwn, extend, hyphenate, isPlainObject, isObject } from '@vue/shared'; |
|
import { ref, nextTick, findComponentPropsData, toRaw, updateProps, hasQueueJob, invalidateJob, devtoolsComponentAdded, getExposeProxy, pruneComponentPropsCache } from 'vue'; |
|
import { normalizeLocale, LOCALE_EN } from '@dcloudio/uni-i18n'; |
|
|
|
const MP_METHODS = [ |
|
'createSelectorQuery', |
|
'createIntersectionObserver', |
|
'selectAllComponents', |
|
'selectComponent', |
|
]; |
|
function createEmitFn(oldEmit, ctx) { |
|
return function emit(event, ...args) { |
|
const scope = ctx.$scope; |
|
if (scope && event) { |
|
const detail = { __args__: args }; |
|
// 百度小程序,快手小程序,自定义组件不能绑定动态事件 |
|
{ |
|
detail.__ins__ = scope; |
|
} |
|
{ |
|
scope.triggerEvent(event, detail); |
|
} |
|
} |
|
return oldEmit.apply(this, [event, ...args]); |
|
}; |
|
} |
|
function initBaseInstance(instance, options) { |
|
const ctx = instance.ctx; |
|
// mp |
|
ctx.mpType = options.mpType; // @deprecated |
|
ctx.$mpType = options.mpType; |
|
ctx.$mpPlatform = "mp-baidu"; |
|
ctx.$scope = options.mpInstance; |
|
// TODO @deprecated |
|
ctx.$mp = {}; |
|
if (__VUE_OPTIONS_API__) { |
|
ctx._self = {}; |
|
} |
|
// slots |
|
instance.slots = {}; |
|
if (isArray(options.slots) && options.slots.length) { |
|
options.slots.forEach((name) => { |
|
instance.slots[name] = true; |
|
}); |
|
if (instance.slots[SLOT_DEFAULT_NAME]) { |
|
instance.slots.default = true; |
|
} |
|
} |
|
ctx.getOpenerEventChannel = function () { |
|
if (!this.__eventChannel__) { |
|
this.__eventChannel__ = new EventChannel(); |
|
} |
|
return this.__eventChannel__; |
|
}; |
|
ctx.$hasHook = hasHook; |
|
ctx.$callHook = callHook; |
|
// $emit |
|
instance.emit = createEmitFn(instance.emit, ctx); |
|
} |
|
function initComponentInstance(instance, options) { |
|
initBaseInstance(instance, options); |
|
const ctx = instance.ctx; |
|
MP_METHODS.forEach((method) => { |
|
ctx[method] = function (...args) { |
|
const mpInstance = ctx.$scope; |
|
if (mpInstance && mpInstance[method]) { |
|
return mpInstance[method].apply(mpInstance, args); |
|
} |
|
}; |
|
}); |
|
} |
|
function initMocks(instance, mpInstance, mocks) { |
|
const ctx = instance.ctx; |
|
mocks.forEach((mock) => { |
|
if (hasOwn(mpInstance, mock)) { |
|
instance[mock] = ctx[mock] = mpInstance[mock]; |
|
} |
|
}); |
|
} |
|
function hasHook(name) { |
|
const hooks = this.$[name]; |
|
if (hooks && hooks.length) { |
|
return true; |
|
} |
|
return false; |
|
} |
|
function callHook(name, args) { |
|
if (name === 'mounted') { |
|
callHook.call(this, 'bm'); // beforeMount |
|
this.$.isMounted = true; |
|
name = 'm'; |
|
} |
|
{ |
|
if (name === 'onLoad' && |
|
args && |
|
args.__id__ && |
|
isFunction(swan.getEventChannel)) { |
|
this.__eventChannel__ = swan.getEventChannel(args.__id__); |
|
delete args.__id__; |
|
} |
|
} |
|
const hooks = this.$[name]; |
|
return hooks && invokeArrayFns(hooks, args); |
|
} |
|
|
|
const PAGE_INIT_HOOKS = [ |
|
ON_LOAD, |
|
ON_SHOW, |
|
ON_HIDE, |
|
ON_UNLOAD, |
|
ON_RESIZE, |
|
ON_TAB_ITEM_TAP, |
|
ON_REACH_BOTTOM, |
|
ON_PULL_DOWN_REFRESH, |
|
ON_ADD_TO_FAVORITES, |
|
// 'onReady', // lifetimes.ready |
|
// 'onPageScroll', // 影响性能,开发者手动注册 |
|
// 'onShareTimeline', // 右上角菜单,开发者手动注册 |
|
// 'onShareAppMessage' // 右上角菜单,开发者手动注册 |
|
]; |
|
function findHooks(vueOptions, hooks = new Set()) { |
|
if (vueOptions) { |
|
Object.keys(vueOptions).forEach((name) => { |
|
if (isUniLifecycleHook(name, vueOptions[name])) { |
|
hooks.add(name); |
|
} |
|
}); |
|
if (__VUE_OPTIONS_API__) { |
|
const { extends: extendsOptions, mixins } = vueOptions; |
|
if (mixins) { |
|
mixins.forEach((mixin) => findHooks(mixin, hooks)); |
|
} |
|
if (extendsOptions) { |
|
findHooks(extendsOptions, hooks); |
|
} |
|
} |
|
} |
|
return hooks; |
|
} |
|
function initHook(mpOptions, hook, excludes) { |
|
if (excludes.indexOf(hook) === -1 && !hasOwn(mpOptions, hook)) { |
|
mpOptions[hook] = function (args) { |
|
return this.$vm && this.$vm.$callHook(hook, args); |
|
}; |
|
} |
|
} |
|
const EXCLUDE_HOOKS = [ON_READY]; |
|
function initHooks(mpOptions, hooks, excludes = EXCLUDE_HOOKS) { |
|
hooks.forEach((hook) => initHook(mpOptions, hook, excludes)); |
|
} |
|
function initUnknownHooks(mpOptions, vueOptions, excludes = EXCLUDE_HOOKS) { |
|
findHooks(vueOptions).forEach((hook) => initHook(mpOptions, hook, excludes)); |
|
} |
|
function initRuntimeHooks(mpOptions, runtimeHooks) { |
|
if (!runtimeHooks) { |
|
return; |
|
} |
|
const hooks = Object.keys(MINI_PROGRAM_PAGE_RUNTIME_HOOKS); |
|
hooks.forEach((hook) => { |
|
if (runtimeHooks & MINI_PROGRAM_PAGE_RUNTIME_HOOKS[hook]) { |
|
initHook(mpOptions, hook, []); |
|
} |
|
}); |
|
} |
|
const findMixinRuntimeHooks = /*#__PURE__*/ once(() => { |
|
const runtimeHooks = []; |
|
const app = isFunction(getApp) && getApp({ allowDefault: true }); |
|
if (app && app.$vm && app.$vm.$) { |
|
const mixins = app.$vm.$.appContext.mixins; |
|
if (isArray(mixins)) { |
|
const hooks = Object.keys(MINI_PROGRAM_PAGE_RUNTIME_HOOKS); |
|
mixins.forEach((mixin) => { |
|
hooks.forEach((hook) => { |
|
if (hasOwn(mixin, hook) && !runtimeHooks.includes(hook)) { |
|
runtimeHooks.push(hook); |
|
} |
|
}); |
|
}); |
|
} |
|
} |
|
return runtimeHooks; |
|
}); |
|
function initMixinRuntimeHooks(mpOptions) { |
|
initHooks(mpOptions, findMixinRuntimeHooks()); |
|
} |
|
|
|
const HOOKS = [ |
|
ON_SHOW, |
|
ON_HIDE, |
|
ON_ERROR, |
|
ON_THEME_CHANGE, |
|
ON_PAGE_NOT_FOUND, |
|
ON_UNHANDLE_REJECTION, |
|
]; |
|
function parseApp(instance, parseAppOptions) { |
|
const internalInstance = instance.$; |
|
if (__VUE_PROD_DEVTOOLS__) { |
|
// 定制 App 的 $children |
|
Object.defineProperty(internalInstance.ctx, '$children', { |
|
get() { |
|
return getCurrentPages().map((page) => page.$vm); |
|
}, |
|
}); |
|
} |
|
const appOptions = { |
|
globalData: (instance.$options && instance.$options.globalData) || {}, |
|
$vm: instance, |
|
onLaunch(options) { |
|
this.$vm = instance; // 飞书小程序可能会把 AppOptions 序列化,导致 $vm 对象部分属性丢失 |
|
const ctx = internalInstance.ctx; |
|
if (this.$vm && ctx.$scope) { |
|
// 已经初始化过了,主要是为了百度,百度 onShow 在 onLaunch 之前 |
|
return; |
|
} |
|
initBaseInstance(internalInstance, { |
|
mpType: 'app', |
|
mpInstance: this, |
|
slots: [], |
|
}); |
|
ctx.globalData = this.globalData; |
|
instance.$callHook(ON_LAUNCH, options); |
|
}, |
|
}; |
|
initLocale(instance); |
|
const vueOptions = instance.$.type; |
|
initHooks(appOptions, HOOKS); |
|
initUnknownHooks(appOptions, vueOptions); |
|
if (__VUE_OPTIONS_API__) { |
|
const methods = vueOptions.methods; |
|
methods && extend(appOptions, methods); |
|
} |
|
if (parseAppOptions) { |
|
parseAppOptions.parse(appOptions); |
|
} |
|
return appOptions; |
|
} |
|
function initCreateApp(parseAppOptions) { |
|
return function createApp(vm) { |
|
return App(parseApp(vm, parseAppOptions)); |
|
}; |
|
} |
|
function initCreateSubpackageApp(parseAppOptions) { |
|
return function createApp(vm) { |
|
const appOptions = parseApp(vm, parseAppOptions); |
|
const app = isFunction(getApp) && |
|
getApp({ |
|
allowDefault: true, |
|
}); |
|
if (!app) |
|
return; |
|
vm.$.ctx.$scope = app; |
|
const globalData = app.globalData; |
|
if (globalData) { |
|
Object.keys(appOptions.globalData).forEach((name) => { |
|
if (!hasOwn(globalData, name)) { |
|
globalData[name] = appOptions.globalData[name]; |
|
} |
|
}); |
|
} |
|
Object.keys(appOptions).forEach((name) => { |
|
if (!hasOwn(app, name)) { |
|
app[name] = appOptions[name]; |
|
} |
|
}); |
|
initAppLifecycle(appOptions, vm); |
|
if (process.env.UNI_SUBPACKAGE) { |
|
(swan.$subpackages || (swan.$subpackages = {}))[process.env.UNI_SUBPACKAGE] = { |
|
$vm: vm, |
|
}; |
|
} |
|
}; |
|
} |
|
function initAppLifecycle(appOptions, vm) { |
|
if (isFunction(appOptions.onLaunch)) { |
|
const args = swan.getLaunchOptionsSync && swan.getLaunchOptionsSync(); |
|
appOptions.onLaunch(args); |
|
} |
|
if (isFunction(appOptions.onShow) && swan.onAppShow) { |
|
swan.onAppShow((args) => { |
|
vm.$callHook('onShow', args); |
|
}); |
|
} |
|
if (isFunction(appOptions.onHide) && swan.onAppHide) { |
|
swan.onAppHide((args) => { |
|
vm.$callHook('onHide', args); |
|
}); |
|
} |
|
} |
|
function initLocale(appVm) { |
|
const locale = ref(normalizeLocale(swan.getSystemInfoSync().language) || LOCALE_EN); |
|
Object.defineProperty(appVm, '$locale', { |
|
get() { |
|
return locale.value; |
|
}, |
|
set(v) { |
|
locale.value = v; |
|
}, |
|
}); |
|
} |
|
|
|
function initVueIds(vueIds, mpInstance) { |
|
if (!vueIds) { |
|
return; |
|
} |
|
const ids = vueIds.split(','); |
|
const len = ids.length; |
|
if (len === 1) { |
|
mpInstance._$vueId = ids[0]; |
|
} |
|
else if (len === 2) { |
|
mpInstance._$vueId = ids[0]; |
|
mpInstance._$vuePid = ids[1]; |
|
} |
|
} |
|
const EXTRAS = ['externalClasses']; |
|
function initExtraOptions(miniProgramComponentOptions, vueOptions) { |
|
EXTRAS.forEach((name) => { |
|
if (hasOwn(vueOptions, name)) { |
|
miniProgramComponentOptions[name] = vueOptions[name]; |
|
} |
|
}); |
|
} |
|
function initWxsCallMethods(methods, wxsCallMethods) { |
|
if (!isArray(wxsCallMethods)) { |
|
return; |
|
} |
|
wxsCallMethods.forEach((callMethod) => { |
|
methods[callMethod] = function (args) { |
|
return this.$vm[callMethod](args); |
|
}; |
|
}); |
|
} |
|
function selectAllComponents(mpInstance, selector, $refs) { |
|
const components = mpInstance.selectAllComponents(selector); |
|
components.forEach((component) => { |
|
const ref = component.properties.uR; |
|
$refs[ref] = component.$vm || component; |
|
}); |
|
} |
|
function initRefs(instance, mpInstance) { |
|
Object.defineProperty(instance, 'refs', { |
|
get() { |
|
const $refs = {}; |
|
selectAllComponents(mpInstance, '.r', $refs); |
|
const forComponents = mpInstance.selectAllComponents('.r-i-f'); |
|
forComponents.forEach((component) => { |
|
const ref = component.properties.uR; |
|
if (!ref) { |
|
return; |
|
} |
|
if (!$refs[ref]) { |
|
$refs[ref] = []; |
|
} |
|
$refs[ref].push(component.$vm || component); |
|
}); |
|
return $refs; |
|
}, |
|
}); |
|
} |
|
function findVmByVueId(instance, vuePid) { |
|
// 标准 vue3 中 没有 $children,定制了内核 |
|
const $children = instance.$children; |
|
// 优先查找直属(反向查找:https://github.com/dcloudio/uni-app/issues/1200) |
|
for (let i = $children.length - 1; i >= 0; i--) { |
|
const childVm = $children[i]; |
|
if (childVm.$scope._$vueId === vuePid) { |
|
return childVm; |
|
} |
|
} |
|
// 反向递归查找 |
|
let parentVm; |
|
for (let i = $children.length - 1; i >= 0; i--) { |
|
parentVm = findVmByVueId($children[i], vuePid); |
|
if (parentVm) { |
|
return parentVm; |
|
} |
|
} |
|
} |
|
const EVENT_OPTS = 'eO'; |
|
/** |
|
* 需要搭配: |
|
* ./componentInstance/index.ts:24 triggerEvent 时传递 __ins__ |
|
* ./componentProps.ts:49 增加 properties eO |
|
* @param this |
|
* @param event |
|
* @returns |
|
*/ |
|
function handleEvent(event) { |
|
const { type, currentTarget: { dataset }, detail: { __ins__ }, } = event; |
|
let methodName = type; |
|
// 快手小程序的 __l 方法也会走此处逻辑,但没有 __ins__ |
|
if (__ins__) { |
|
// 自定义事件,通过 triggerEvent 传递 __ins__ |
|
let eventObj = {}; |
|
try { |
|
// https://github.com/dcloudio/uni-app/issues/3647 |
|
// 通过字符串序列化解决百度小程序修改对象不触发组件properties变化的Bug |
|
eventObj = JSON.parse(__ins__.properties[EVENT_OPTS]); |
|
} |
|
catch (e) { } |
|
methodName = resolveMethodName(type, eventObj); |
|
} |
|
else if (dataset && dataset[EVENT_OPTS]) { |
|
// 快手小程序 input 等内置组件的 input 事件也会走此逻辑,所以从 dataset 中读取 |
|
methodName = resolveMethodName(type, dataset[EVENT_OPTS]); |
|
} |
|
if (!this[methodName]) { |
|
return console.warn(type + ' not found'); |
|
} |
|
this[methodName](event); |
|
} |
|
function resolveMethodName(name, obj) { |
|
return obj[name] || obj[hyphenate(name)]; |
|
} |
|
function nextSetDataTick(mpInstance, fn) { |
|
// 随便设置一个字段来触发回调(部分平台必须有字段才可以,比如头条) |
|
mpInstance.setData({ r1: 1 }, () => fn()); |
|
} |
|
function initSetRef(mpInstance) { |
|
if (!mpInstance._$setRef) { |
|
mpInstance._$setRef = (fn) => { |
|
nextTick(() => nextSetDataTick(mpInstance, fn)); |
|
}; |
|
} |
|
} |
|
|
|
const builtInProps = [ |
|
// 百度小程序,快手小程序自定义组件不支持绑定动态事件,动态dataset,故通过props传递事件信息 |
|
// event-opts |
|
'eO', |
|
// 组件 ref |
|
'uR', |
|
// 组件 ref-in-for |
|
'uRIF', |
|
// 组件 id |
|
'uI', |
|
// 组件类型 m: 小程序组件 |
|
'uT', |
|
// 组件 props |
|
'uP', |
|
// 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots |
|
'uS', |
|
]; |
|
function initDefaultProps(options, isBehavior = false) { |
|
const properties = {}; |
|
if (!isBehavior) { |
|
// 均不指定类型,避免微信小程序 property received type-uncompatible value 警告 |
|
builtInProps.forEach((name) => { |
|
properties[name] = { |
|
type: null, |
|
value: '', |
|
}; |
|
}); |
|
// 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots |
|
properties.uS = { |
|
type: null, |
|
value: [], |
|
observer: function (newVal) { |
|
const $slots = Object.create(null); |
|
newVal && |
|
newVal.forEach((slotName) => { |
|
$slots[slotName] = true; |
|
}); |
|
this.setData({ |
|
$slots, |
|
}); |
|
}, |
|
}; |
|
} |
|
if (options.behaviors) { |
|
// wx://form-field |
|
if (options.behaviors.includes('swan://form-field')) { |
|
if (!options.properties || !options.properties.name) { |
|
properties.name = { |
|
type: null, |
|
value: '', |
|
}; |
|
} |
|
if (!options.properties || !options.properties.value) { |
|
properties.value = { |
|
type: null, |
|
value: '', |
|
}; |
|
} |
|
} |
|
} |
|
return properties; |
|
} |
|
function initVirtualHostProps(options) { |
|
const properties = {}; |
|
return properties; |
|
} |
|
/** |
|
* |
|
* @param mpComponentOptions |
|
* @param isBehavior |
|
*/ |
|
function initProps(mpComponentOptions) { |
|
if (!mpComponentOptions.properties) { |
|
mpComponentOptions.properties = {}; |
|
} |
|
extend(mpComponentOptions.properties, initDefaultProps(mpComponentOptions), initVirtualHostProps(mpComponentOptions.options)); |
|
} |
|
const PROP_TYPES = [String, Number, Boolean, Object, Array, null]; |
|
function parsePropType(type, defaultValue) { |
|
// [String]=>String |
|
if (isArray(type) && type.length === 1) { |
|
return type[0]; |
|
} |
|
{ |
|
if ( |
|
// [String,Boolean]=>Boolean |
|
defaultValue === false && |
|
isArray(type) && |
|
type.length === 2 && |
|
type.indexOf(String) !== -1 && |
|
type.indexOf(Boolean) !== -1) { |
|
return Boolean; |
|
} |
|
} |
|
return type; |
|
} |
|
function normalizePropType(type, defaultValue) { |
|
const res = parsePropType(type, defaultValue); |
|
return PROP_TYPES.indexOf(res) !== -1 ? res : null; |
|
} |
|
/** |
|
* 初始化页面 props,方便接收页面参数,类型均为String,默认值均为'' |
|
* @param param |
|
* @param rawProps |
|
*/ |
|
function initPageProps({ properties }, rawProps) { |
|
if (isArray(rawProps)) { |
|
rawProps.forEach((key) => { |
|
properties[key] = { |
|
type: String, |
|
value: '', |
|
}; |
|
}); |
|
} |
|
else if (isPlainObject(rawProps)) { |
|
Object.keys(rawProps).forEach((key) => { |
|
const opts = rawProps[key]; |
|
if (isPlainObject(opts)) { |
|
// title:{type:String,default:''} |
|
let value = opts.default; |
|
if (isFunction(value)) { |
|
value = value(); |
|
} |
|
const type = opts.type; |
|
opts.type = normalizePropType(type, value); |
|
properties[key] = { |
|
type: opts.type, |
|
value, |
|
}; |
|
} |
|
else { |
|
// content:String |
|
properties[key] = { |
|
type: normalizePropType(opts, null), |
|
}; |
|
} |
|
}); |
|
} |
|
} |
|
function findPropsData(properties, isPage) { |
|
return ((isPage |
|
? findPagePropsData(properties) |
|
: findComponentPropsData(properties.uP)) || {}); |
|
} |
|
function findPagePropsData(properties) { |
|
const propsData = {}; |
|
if (isPlainObject(properties)) { |
|
Object.keys(properties).forEach((name) => { |
|
if (builtInProps.indexOf(name) === -1) { |
|
propsData[name] = properties[name]; |
|
} |
|
}); |
|
} |
|
return propsData; |
|
} |
|
function initFormField(vm) { |
|
// 同步 form-field 的 name,value 值 |
|
const vueOptions = vm.$options; |
|
if (isArray(vueOptions.behaviors) && |
|
vueOptions.behaviors.includes('uni://form-field')) { |
|
vm.$watch('modelValue', () => { |
|
vm.$scope && |
|
vm.$scope.setData({ |
|
name: vm.name, |
|
value: vm.modelValue, |
|
}); |
|
}, { |
|
immediate: true, |
|
}); |
|
} |
|
} |
|
|
|
function initData(_) { |
|
return {}; |
|
} |
|
function initPropsObserver(componentOptions) { |
|
const observe = function observe() { |
|
const up = this.properties.uP; |
|
if (!up) { |
|
return; |
|
} |
|
if (this.$vm) { |
|
updateComponentProps(up, this.$vm.$); |
|
} |
|
else if (this.properties.uT === 'm') { |
|
// 小程序组件 |
|
updateMiniProgramComponentProperties(up, this); |
|
} |
|
}; |
|
{ |
|
componentOptions.properties.uP.observer = observe; |
|
} |
|
} |
|
function updateMiniProgramComponentProperties(up, mpInstance) { |
|
const prevProps = mpInstance.properties; |
|
const nextProps = findComponentPropsData(up) || {}; |
|
if (hasPropsChanged(prevProps, nextProps, false)) { |
|
mpInstance.setData(nextProps); |
|
} |
|
} |
|
function updateComponentProps(up, instance) { |
|
const prevProps = toRaw(instance.props); |
|
const nextProps = findComponentPropsData(up) || {}; |
|
if (hasPropsChanged(prevProps, nextProps)) { |
|
updateProps(instance, nextProps, prevProps, false); |
|
if (hasQueueJob(instance.update)) { |
|
invalidateJob(instance.update); |
|
} |
|
{ |
|
// 字节跳动小程序 https://github.com/dcloudio/uni-app/issues/3340 |
|
// 百度小程序 https://github.com/dcloudio/uni-app/issues/3612 |
|
if (!hasQueueJob(instance.update)) { |
|
instance.update(); |
|
} |
|
} |
|
} |
|
} |
|
function hasPropsChanged(prevProps, nextProps, checkLen = true) { |
|
const nextKeys = Object.keys(nextProps); |
|
if (checkLen && nextKeys.length !== Object.keys(prevProps).length) { |
|
return true; |
|
} |
|
for (let i = 0; i < nextKeys.length; i++) { |
|
const key = nextKeys[i]; |
|
if (nextProps[key] !== prevProps[key]) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
function initBehaviors(vueOptions) { |
|
const vueBehaviors = vueOptions.behaviors; |
|
let vueProps = vueOptions.props; |
|
if (!vueProps) { |
|
vueOptions.props = vueProps = []; |
|
} |
|
const behaviors = []; |
|
if (isArray(vueBehaviors)) { |
|
vueBehaviors.forEach((behavior) => { |
|
behaviors.push(behavior.replace('uni://', 'swan://')); |
|
if (behavior === 'uni://form-field') { |
|
if (isArray(vueProps)) { |
|
vueProps.push('name'); |
|
vueProps.push('modelValue'); |
|
} |
|
else { |
|
vueProps.name = { |
|
type: String, |
|
default: '', |
|
}; |
|
vueProps.modelValue = { |
|
type: [String, Number, Boolean, Array, Object, Date], |
|
default: '', |
|
}; |
|
} |
|
} |
|
}); |
|
} |
|
return behaviors; |
|
} |
|
function applyOptions(componentOptions, vueOptions) { |
|
componentOptions.data = initData(); |
|
componentOptions.behaviors = initBehaviors(vueOptions); |
|
} |
|
|
|
function parseComponent(vueOptions, { parse, mocks, isPage, initRelation, handleLink, initLifetimes, }) { |
|
vueOptions = vueOptions.default || vueOptions; |
|
const options = { |
|
multipleSlots: true, |
|
// styleIsolation: 'apply-shared', |
|
addGlobalClass: true, |
|
pureDataPattern: /^uP$/, |
|
}; |
|
if (isArray(vueOptions.mixins)) { |
|
vueOptions.mixins.forEach((item) => { |
|
if (isObject(item.options)) { |
|
extend(options, item.options); |
|
} |
|
}); |
|
} |
|
if (vueOptions.options) { |
|
extend(options, vueOptions.options); |
|
} |
|
const mpComponentOptions = { |
|
options, |
|
lifetimes: initLifetimes({ mocks, isPage, initRelation, vueOptions }), |
|
pageLifetimes: { |
|
show() { |
|
if (__VUE_PROD_DEVTOOLS__) { |
|
devtoolsComponentAdded(this.$vm.$); |
|
} |
|
this.$vm && this.$vm.$callHook('onPageShow'); |
|
}, |
|
hide() { |
|
this.$vm && this.$vm.$callHook('onPageHide'); |
|
}, |
|
resize(size) { |
|
this.$vm && this.$vm.$callHook('onPageResize', size); |
|
}, |
|
}, |
|
methods: { |
|
__l: handleLink, |
|
}, |
|
}; |
|
if (__VUE_OPTIONS_API__) { |
|
applyOptions(mpComponentOptions, vueOptions); |
|
} |
|
initProps(mpComponentOptions); |
|
initPropsObserver(mpComponentOptions); |
|
initExtraOptions(mpComponentOptions, vueOptions); |
|
initWxsCallMethods(mpComponentOptions.methods, vueOptions.wxsCallMethods); |
|
if (parse) { |
|
parse(mpComponentOptions, { handleLink }); |
|
} |
|
return mpComponentOptions; |
|
} |
|
function initCreateComponent(parseOptions) { |
|
return function createComponent(vueComponentOptions) { |
|
return Component(parseComponent(vueComponentOptions, parseOptions)); |
|
}; |
|
} |
|
let $createComponentFn; |
|
let $destroyComponentFn; |
|
function getAppVm() { |
|
if (process.env.UNI_MP_PLUGIN) { |
|
return swan.$vm; |
|
} |
|
if (process.env.UNI_SUBPACKAGE) { |
|
return swan.$subpackages[process.env.UNI_SUBPACKAGE].$vm; |
|
} |
|
return getApp().$vm; |
|
} |
|
function $createComponent(initialVNode, options) { |
|
if (!$createComponentFn) { |
|
$createComponentFn = getAppVm().$createComponent; |
|
} |
|
const proxy = $createComponentFn(initialVNode, options); |
|
return getExposeProxy(proxy.$) || proxy; |
|
} |
|
function $destroyComponent(instance) { |
|
if (!$destroyComponentFn) { |
|
$destroyComponentFn = getAppVm().$destroyComponent; |
|
} |
|
return $destroyComponentFn(instance); |
|
} |
|
|
|
function parsePage(vueOptions, parseOptions) { |
|
const { parse, mocks, isPage, initRelation, handleLink, initLifetimes } = parseOptions; |
|
const miniProgramPageOptions = parseComponent(vueOptions, { |
|
mocks, |
|
isPage, |
|
initRelation, |
|
handleLink, |
|
initLifetimes, |
|
}); |
|
initPageProps(miniProgramPageOptions, (vueOptions.default || vueOptions).props); |
|
const methods = miniProgramPageOptions.methods; |
|
methods.onLoad = function (query) { |
|
this.options = query; |
|
this.$page = { |
|
fullPath: addLeadingSlash(this.route + stringifyQuery(query)), |
|
}; |
|
return this.$vm && this.$vm.$callHook(ON_LOAD, query); |
|
}; |
|
initHooks(methods, PAGE_INIT_HOOKS); |
|
{ |
|
initUnknownHooks(methods, vueOptions, [ON_INIT, ON_READY]); |
|
} |
|
initRuntimeHooks(methods, vueOptions.__runtimeHooks); |
|
initMixinRuntimeHooks(methods); |
|
parse && parse(miniProgramPageOptions, { handleLink }); |
|
return miniProgramPageOptions; |
|
} |
|
function initCreatePage(parseOptions) { |
|
return function createPage(vuePageOptions) { |
|
return Component(parsePage(vuePageOptions, parseOptions)); |
|
}; |
|
} |
|
|
|
/** |
|
* 用于延迟调用 setData |
|
* 在 setData 真实调用的时机需执行 fixSetDataEnd |
|
* @param {*} mpInstance |
|
*/ |
|
function fixSetDataStart(mpInstance) { |
|
const setData = mpInstance.setData; |
|
const setDataArgs = []; |
|
mpInstance.setData = function () { |
|
setDataArgs.push(arguments); |
|
}; |
|
mpInstance.__fixInitData = function () { |
|
this.setData = setData; |
|
const fn = () => { |
|
setDataArgs.forEach((args) => { |
|
setData.apply(this, args); |
|
}); |
|
}; |
|
if (setDataArgs.length) { |
|
if (this.groupSetData) { |
|
this.groupSetData(fn); |
|
} |
|
else { |
|
fn(); |
|
} |
|
} |
|
}; |
|
} |
|
/** |
|
* 恢复真实的 setData 方法 |
|
* @param {*} mpInstance |
|
*/ |
|
function fixSetDataEnd(mpInstance) { |
|
if (mpInstance.__fixInitData) { |
|
mpInstance.__fixInitData(); |
|
delete mpInstance.__fixInitData; |
|
} |
|
} |
|
|
|
const MPPage = Page; |
|
const MPComponent = Component; |
|
function initTriggerEvent(mpInstance) { |
|
const oldTriggerEvent = mpInstance.triggerEvent; |
|
const newTriggerEvent = function (event, ...args) { |
|
return oldTriggerEvent.apply(mpInstance, [customizeEvent(event), ...args]); |
|
}; |
|
// 京东小程序triggerEvent为只读属性 |
|
try { |
|
mpInstance.triggerEvent = newTriggerEvent; |
|
} |
|
catch (error) { |
|
mpInstance._triggerEvent = newTriggerEvent; |
|
} |
|
} |
|
function initMiniProgramHook(name, options, isComponent) { |
|
const oldHook = options[name]; |
|
if (!oldHook) { |
|
options[name] = function () { |
|
initTriggerEvent(this); |
|
}; |
|
} |
|
else { |
|
options[name] = function (...args) { |
|
initTriggerEvent(this); |
|
return oldHook.apply(this, args); |
|
}; |
|
} |
|
} |
|
Page = function (options) { |
|
initMiniProgramHook(ON_LOAD, options); |
|
return MPPage(options); |
|
}; |
|
{ |
|
Page.after = MPPage.after; |
|
} |
|
Component = function (options) { |
|
initMiniProgramHook('created', options); |
|
// 小程序组件 |
|
const isVueComponent = options.properties && options.properties.uP; |
|
if (!isVueComponent) { |
|
initProps(options); |
|
initPropsObserver(options); |
|
} |
|
return MPComponent(options); |
|
}; |
|
|
|
function parse$2(appOptions) { |
|
// 百度 onShow 竟然会在 onLaunch 之前 |
|
appOptions.onShow = function onShow(args) { |
|
if (!this.$vm) { |
|
this.onLaunch(args); |
|
} |
|
this.$vm.$callHook(ON_SHOW, args); |
|
}; |
|
} |
|
|
|
var parseAppOptions = /*#__PURE__*/Object.freeze({ |
|
__proto__: null, |
|
parse: parse$2 |
|
}); |
|
|
|
// @ts-ignore |
|
function initLifetimes({ mocks, isPage, initRelation, vueOptions, }) { |
|
return { |
|
attached() { |
|
// 微信 和 QQ 不需要延迟 setRef |
|
{ |
|
initSetRef(this); |
|
} |
|
let properties = this.properties; |
|
initVueIds(properties.uI, this); |
|
const relationOptions = { |
|
vuePid: this._$vuePid, |
|
}; |
|
// 处理父子关系 |
|
initRelation(this, relationOptions); |
|
// 初始化 vue 实例 |
|
const mpInstance = this; |
|
const isMiniProgramPage = isPage(mpInstance); |
|
let propsData = properties; |
|
if (isMiniProgramPage) { |
|
{ |
|
propsData = this.pageinstance._$props; |
|
delete this.pageinstance._$props; |
|
} |
|
} |
|
this.$vm = $createComponent({ |
|
type: vueOptions, |
|
props: findPropsData(propsData, isMiniProgramPage), |
|
}, { |
|
mpType: isMiniProgramPage ? 'page' : 'component', |
|
mpInstance, |
|
slots: properties.uS || {}, |
|
parentComponent: relationOptions.parent && relationOptions.parent.$, |
|
onBeforeSetup(instance, options) { |
|
initRefs(instance, mpInstance); |
|
initMocks(instance, mpInstance, mocks); |
|
initComponentInstance(instance, options); |
|
}, |
|
}); |
|
if (!isMiniProgramPage) { |
|
initFormField(this.$vm); |
|
} |
|
}, |
|
ready() { |
|
// 当组件 props 默认值为 true,初始化时传入 false 会导致 created,ready 触发, 但 attached 不触发 |
|
// https://developers.weixin.qq.com/community/develop/doc/00066ae2844cc0f8eb883e2a557800 |
|
if (this.$vm) { |
|
{ |
|
nextSetDataTick(this, () => { |
|
this.$vm.$callHook('mounted'); |
|
this.$vm.$callHook(ON_READY); |
|
}); |
|
} |
|
} |
|
}, |
|
detached() { |
|
if (this.$vm) { |
|
pruneComponentPropsCache(this.$vm.$.uid); |
|
$destroyComponent(this.$vm); |
|
} |
|
}, |
|
}; |
|
} |
|
|
|
function handleLink(event) { |
|
// detail 是微信,value 是百度(dipatch) |
|
const detail = (event.detail || |
|
event.value); |
|
const vuePid = detail.vuePid; |
|
let parentVm; |
|
if (vuePid) { |
|
parentVm = findVmByVueId(this.$vm, vuePid); |
|
} |
|
if (!parentVm) { |
|
parentVm = this.$vm; |
|
} |
|
detail.parent = parentVm; |
|
} |
|
|
|
const mocks = ['nodeId', 'componentName', '_componentId', 'uniquePrefix']; |
|
function isPage(mpInstance) { |
|
return !!mpInstance.methods.onLoad; |
|
} |
|
function initRelation(mpInstance, detail) { |
|
mpInstance.dispatch('__l', detail); |
|
} |
|
const newLifecycle = /*#__PURE__*/ swan.canIUse('lifecycle-2-0'); |
|
function parse$1(componentOptions) { |
|
const methods = componentOptions.methods; |
|
const lifetimes = componentOptions.lifetimes; |
|
// 关于百度小程序生命周期的说明(组件作为页面时): |
|
// lifetimes:attached --> methods:onShow --> methods:onLoad --> methods:onReady |
|
// 这里在强制将onShow挪到onLoad之后触发,另外一处修改在page-parser.js |
|
const oldAttached = lifetimes.attached; |
|
// 百度小程序基础库 3.260 以上支持页面 onInit 生命周期,提前创建 vm 实例 |
|
lifetimes.onInit = function onInit(query) { |
|
// 百度小程序后续可能移除 pageinstance 属性,为向后兼容进行补充 |
|
if (!this.pageinstance || !this.pageinstance.setData) { |
|
const pages = getCurrentPages(); |
|
this.pageinstance = pages[pages.length - 1]; |
|
} |
|
this.pageinstance._$props = query; |
|
// 处理百度小程序 onInit 生命周期调用 setData 无效的问题 |
|
fixSetDataStart(this); |
|
oldAttached.call(this); |
|
this.pageinstance.$vm = this.$vm; |
|
this.$vm.$callHook(ON_INIT, query); |
|
}; |
|
lifetimes.attached = function attached() { |
|
if (!this.$vm) { |
|
oldAttached.call(this); |
|
} |
|
else { |
|
initMocks(this.$vm.$, this, mocks); |
|
fixSetDataEnd(this); |
|
} |
|
if (isPage(this) && this.$vm) { |
|
// 百度 onLoad 在 attached 之前触发(基础库小于 3.70) |
|
// 百度 当组件作为页面时 pageinstance 不是原来组件的 instance |
|
const pageInstance = this.pageinstance; |
|
pageInstance.$vm = this.$vm; |
|
if (hasOwn(pageInstance, '_$args')) { |
|
this.$vm.$callHook(ON_LOAD, pageInstance._$args); |
|
this.$vm.$callHook(ON_SHOW); |
|
delete pageInstance._$args; |
|
} |
|
} |
|
else { |
|
// 百度小程序组件不触发 methods 内的 onReady |
|
if (this.$vm) { |
|
nextSetDataTick(this, () => { |
|
this.$vm.$callHook('mounted'); |
|
}); |
|
} |
|
} |
|
}; |
|
if (newLifecycle) { |
|
methods.onReady = lifetimes.ready; |
|
delete lifetimes.ready; |
|
} |
|
componentOptions.messages = { |
|
__l: methods.__l, |
|
}; |
|
delete methods.__l; |
|
// 百度小程序自定义组件,不支持绑定动态事件,故由 __e 分发 |
|
methods.__e = handleEvent; |
|
} |
|
|
|
var parseComponentOptions = /*#__PURE__*/Object.freeze({ |
|
__proto__: null, |
|
handleLink: handleLink, |
|
initLifetimes: initLifetimes, |
|
initRelation: initRelation, |
|
isPage: isPage, |
|
mocks: mocks, |
|
parse: parse$1 |
|
}); |
|
|
|
function parse(pageOptions) { |
|
parse$1(pageOptions); |
|
const methods = pageOptions.methods; |
|
// 纠正百度小程序生命周期methods:onShow在methods:onLoad之前触发的问题 |
|
methods.onShow = function onShow() { |
|
if (this.$vm && this._$loaded) { |
|
this.$vm.$callHook(ON_SHOW); |
|
} |
|
}; |
|
methods.onLoad = function onLoad(query) { |
|
// 百度 onLoad 在 attached 之前触发,先存储 args, 在 attached 里边触发 onLoad |
|
if (this.$vm) { |
|
this._$loaded = true; |
|
const copyQuery = extend({}, query); |
|
delete copyQuery.__id__; |
|
this.options = copyQuery; |
|
this.pageinstance.$page = this.$page = { |
|
fullPath: '/' + this.pageinstance.route + stringifyQuery(copyQuery), |
|
}; |
|
this.$vm.$callHook(ON_LOAD, query); |
|
this.$vm.$callHook(ON_SHOW); |
|
} |
|
else { |
|
this.pageinstance._$args = query; |
|
} |
|
}; |
|
} |
|
|
|
var parsePageOptions = /*#__PURE__*/Object.freeze({ |
|
__proto__: null, |
|
handleLink: handleLink, |
|
initLifetimes: initLifetimes, |
|
initRelation: initRelation, |
|
isPage: isPage, |
|
mocks: mocks, |
|
parse: parse |
|
}); |
|
|
|
const createApp = initCreateApp(parseAppOptions); |
|
const createPage = initCreatePage(parsePageOptions); |
|
const createComponent = initCreateComponent(parseComponentOptions); |
|
const createSubpackageApp = initCreateSubpackageApp(parseAppOptions); |
|
swan.EventChannel = EventChannel; |
|
swan.createApp = global.createApp = createApp; |
|
swan.createPage = createPage; |
|
swan.createComponent = createComponent; |
|
swan.createSubpackageApp = global.createSubpackageApp = |
|
createSubpackageApp; |
|
|
|
export { createApp, createComponent, createPage, createSubpackageApp };
|
|
|