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

wip(mp): scoped slots

上级 f63216a6
......@@ -21,10 +21,12 @@ declare module '@vue/runtime-core' {
type UniLifecycleHookInstance = {
[name in UniLifecycleHooks]: LifecycleHook
}
interface ComponentInternalInstance extends UniLifecycleHookInstance {
interface ComponentInternalInstance {
__isUnload: boolean
__isVisible: boolean
__isActive?: boolean // tabBar
// h5 | app
$wxsModules?: string[]
}
export const callSyncHook: (
......
......@@ -36,7 +36,9 @@ export function invokeHook(
return (vm as any).__call_hook(name, args)
}
}
const hooks = vm.$[name as string]
const hooks = (vm.$ as unknown as { [name: string]: Function[] })[
name as string
]
return hooks && invokeArrayFns(hooks, args)
}
......@@ -52,6 +54,8 @@ export function hasHook(vm: ComponentPublicInstance | number, name: string) {
if (!vm) {
return false
}
const hooks = vm.$[name]
const hooks = (vm.$ as unknown as { [name: string]: Function[] })[
name as string
]
return !!(hooks && hooks.length)
}
import { isPlainObject, isArray, extend, hyphenate, isObject, hasOwn, toNumber, capitalize, isFunction, NOOP, EMPTY_OBJ, camelize } from '@vue/shared';
import { onUnmounted, injectHook, ref } from 'vue';
import { isPlainObject, extend, hyphenate, isObject, hasOwn, isArray, toNumber, capitalize, isFunction, EMPTY_OBJ, camelize } from '@vue/shared';
import { injectHook, ref } from 'vue';
const encode = encodeURIComponent;
function stringifyQuery(obj, encodeStr = encode) {
......@@ -21,6 +21,18 @@ function stringifyQuery(obj, encodeStr = encode) {
return res ? `?${res}` : '';
}
function getDataByPath(obj, path) {
const parts = path.split('.');
const key = parts[0];
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getDataByPath(obj[key], parts.slice(1).join('.'));
}
function cache(fn) {
const cache = Object.create(null);
return (str) => {
......@@ -131,67 +143,8 @@ function getEventChannel(id) {
return eventChannelStack.shift();
}
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];
}
}
function initWxsCallMethods(methods, wxsCallMethods) {
if (!isArray(wxsCallMethods)) {
return;
}
wxsCallMethods.forEach((callMethod) => {
methods[callMethod] = function (args) {
return this.$vm[callMethod](args);
};
});
}
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;
}
}
}
function getTarget(obj, path) {
const parts = path.split('.');
let key = parts[0];
if (key.indexOf('__$n') === 0) {
//number index
key = parseInt(key.replace('__$n', ''));
}
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getTarget(obj[key], parts.slice(1).join('.'));
}
function getValue(dataPath, target) {
return getTarget(target || this, dataPath);
return getDataByPath(target || this, dataPath);
}
function getClass(dynamicClass, staticClass) {
return renderClass(staticClass, dynamicClass);
......@@ -393,9 +346,6 @@ function initBaseInstance(instance, options) {
}
function initComponentInstance(instance, options) {
initBaseInstance(instance, options);
{
initScopedSlotsParams(instance);
}
const ctx = instance.ctx;
MP_METHODS.forEach((method) => {
ctx[method] = function (...args) {
......@@ -445,53 +395,6 @@ function callHook(name, args) {
}
const hooks = this.$[name];
return hooks && invokeArrayFns(hooks, args);
}
const center = {};
const parents = {};
function initScopedSlotsParams(instance) {
const ctx = instance.ctx;
ctx.$hasScopedSlotsParams = function (vueId) {
const has = center[vueId];
if (!has) {
parents[vueId] = this;
onUnmounted(() => {
delete parents[vueId];
}, instance);
}
return has;
};
ctx.$getScopedSlotsParams = function (vueId, name, key) {
const data = center[vueId];
if (data) {
const object = data[name] || {};
return key ? object[key] : object;
}
else {
parents[vueId] = this;
onUnmounted(() => {
delete parents[vueId];
}, instance);
}
};
ctx.$setScopedSlotsParams = function (name, value) {
const vueIds = instance.attrs.vueId;
if (vueIds) {
const vueId = vueIds.split(',')[0];
const object = (center[vueId] = center[vueId] || {});
object[name] = value;
if (parents[vueId]) {
parents[vueId].$forceUpdate();
}
}
};
onUnmounted(function () {
const propsData = instance.attrs;
const vueId = propsData && propsData.vueId;
if (vueId) {
delete center[vueId];
delete parents[vueId];
}
}, instance);
}
const PAGE_HOOKS = [
......@@ -612,6 +515,50 @@ function initLocale(appVm) {
});
}
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];
}
}
function initWxsCallMethods(methods, wxsCallMethods) {
if (!isArray(wxsCallMethods)) {
return;
}
wxsCallMethods.forEach((callMethod) => {
methods[callMethod] = function (args) {
return this.$vm[callMethod](args);
};
});
}
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 PROP_TYPES = [String, Number, Boolean, Object, Array, null];
function parsePropType(key, type, defaultValue) {
// [String]=>String
......@@ -628,7 +575,7 @@ function initDefaultProps(isBehavior = false) {
value: '',
};
// 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
properties.vueSlots = {
properties.vS = {
type: null,
value: [],
observer: function (newVal) {
......@@ -758,252 +705,6 @@ function initBehaviors(vueOptions, initBehavior) {
return behaviors;
}
function getExtraValue(instance, dataPathsArray) {
let context = instance;
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];
let vFor;
if (Number.isInteger(dataPath)) {
vFor = dataPath;
}
else if (!dataPath) {
vFor = context;
}
else if (typeof dataPath === 'string' && dataPath) {
if (dataPath.indexOf('#s#') === 0) {
vFor = dataPath.substr(3);
}
else {
vFor = getTarget(context, dataPath);
}
}
if (Number.isInteger(vFor)) {
context = value;
}
else if (!propPath) {
context = vFor[value];
}
else {
if (isArray(vFor)) {
context = vFor.find((vForItem) => {
return getTarget(vForItem, propPath) === value;
});
}
else if (isPlainObject(vFor)) {
context = Object.keys(vFor).find((vForKey) => {
return getTarget(vFor[vForKey], propPath) === value;
});
}
else {
console.error('v-for 暂不支持循环数据:', vFor);
}
}
if (valuePath) {
context = getTarget(context, valuePath);
}
}
});
return context;
}
function processEventExtra(instance, extra, event) {
const extraObj = {};
if (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] = instance;
}
else {
if (dataPath === '$event') {
// $event
extraObj['$' + index] = event;
}
else if (dataPath === 'arguments') {
if (event.detail && event.detail.__args__) {
extraObj['$' + index] = event.detail.__args__;
}
else {
extraObj['$' + index] = [event];
}
}
else if (dataPath.indexOf('$event.') === 0) {
// $event.target.value
extraObj['$' + index] = getTarget(event, dataPath.replace('$event.', ''));
}
else {
extraObj['$' + index] = getTarget(instance, dataPath);
}
}
}
else {
extraObj['$' + index] = getExtraValue(instance, 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(instance, 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(instance, 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 (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;
}
function wrapper(event) {
event.stopPropagation = NOOP;
event.preventDefault = NOOP;
event.target = event.target || {};
if (!hasOwn(event, 'detail')) {
event.detail = {};
}
if (hasOwn(event, 'markerId')) {
event.detail = typeof event.detail === 'object' ? event.detail : {};
event.detail.markerId = event.markerId;
}
if (isPlainObject(event.detail)) {
event.target = extend({}, event.target, event.detail);
}
return event;
}
const ONCE = '~';
const CUSTOM = '^';
function matchEventType(eventType, optType) {
return (eventType === optType ||
(optType === 'regionchange' &&
(eventType === 'begin' || eventType === 'end')));
}
function handleEvent(event) {
event = wrapper(event);
// [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]]
const dataset = (event.currentTarget || event.target).dataset;
if (!dataset) {
return console.warn('事件信息不存在');
}
const eventOpts = (dataset.eventOpts ||
dataset['event-opts']); // 支付宝 web-view 组件 dataset 非驼峰
if (!eventOpts) {
return console.warn('事件信息不存在');
}
// [['handle',[1,2,a]],['handle1',[1,2,a]]]
const eventType = event.type;
const ret = [];
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 && matchEventType(eventType, type)) {
eventsArray.forEach((eventArray) => {
const methodName = eventArray[0];
if (methodName) {
let handlerCtx = this.$vm;
if (handlerCtx.$options.generic &&
handlerCtx.$parent &&
handlerCtx.$parent.$parent) {
// mp-weixin,mp-toutiao 抽象节点模拟 scoped slots
handlerCtx = handlerCtx.$parent.$parent;
}
if (methodName === '$emit') {
handlerCtx.$emit.apply(handlerCtx, processEventArgs(this.$vm, event, eventArray[1], eventArray[2], isCustom, methodName));
return;
}
const handler = handlerCtx[methodName];
if (!isFunction(handler)) {
throw new Error(` _vm.${methodName} is not a function`);
}
if (isOnce) {
if (handler.once) {
return;
}
handler.once = true;
}
let params = processEventArgs(this.$vm, event, eventArray[1], eventArray[2], isCustom, methodName);
params = Array.isArray(params) ? params : [];
// 参数尾部增加原始事件对象用于复杂表达式内获取额外数据
if (/=\s*\S+\.eventParams\s*\|\|\s*\S+\[['"]event-params['"]\]/.test(handler.toString())) {
// eslint-disable-next-line no-sparse-arrays
params = params.concat([, , , , , , , , , , event]);
}
ret.push(handler.apply(handlerCtx, params));
}
});
}
});
if (eventType === 'input' &&
ret.length === 1 &&
typeof ret[0] !== 'undefined') {
return ret[0];
}
}
let $createComponentFn;
let $destroyComponentFn;
function $createComponent(initialVNode, options) {
......@@ -1324,7 +1025,6 @@ function createPage$1(vueOptions) {
},
},
__r: handleRef,
__e: handleEvent,
__l: handleLink,
};
if (__VUE_OPTIONS_API__) {
......@@ -1346,7 +1046,8 @@ function initComponentProps(rawProps) {
onVueInit: function () { },
};
Object.keys(properties).forEach((key) => {
if (key !== 'vueSlots') {
// vueSlots
if (key !== 'vS') {
props[key] = properties[key].value;
}
});
......@@ -1409,7 +1110,6 @@ function createComponent$1(vueOptions) {
},
methods: {
__r: handleRef,
__e: handleEvent,
__l: handleLink,
triggerEvent,
},
......
......@@ -3,7 +3,6 @@ import {
initProps,
initBehaviors,
initData,
handleEvent,
$destroyComponent,
initVueIds,
initWxsCallMethods,
......@@ -37,7 +36,8 @@ function initComponentProps(rawProps: Record<string, any>) {
onVueInit: function () {},
}
Object.keys(properties).forEach((key) => {
if (key !== 'vueSlots') {
// vueSlots
if (key !== 'vS') {
props[key] = properties[key].value
}
})
......@@ -104,7 +104,6 @@ export function createComponent(vueOptions: ComponentOptions) {
},
methods: {
__r: handleRef,
__e: handleEvent,
__l: handleLink,
triggerEvent,
},
......
......@@ -2,7 +2,6 @@ import { ComponentOptions } from 'vue'
import {
PAGE_HOOKS,
handleEvent,
initData,
initHooks,
initUnknownHooks,
......@@ -59,7 +58,6 @@ export function createPage(vueOptions: ComponentOptions) {
},
},
__r: handleRef,
__e: handleEvent,
__l: handleLink,
}
if (__VUE_OPTIONS_API__) {
......
import { isPlainObject, hasOwn, isArray, extend, hyphenate, isObject, toNumber, isFunction, NOOP, camelize } from '@vue/shared';
import { onUnmounted, injectHook, ref } from 'vue';
import { isPlainObject, extend, hyphenate, isObject, isArray, hasOwn, toNumber, isFunction, camelize } from '@vue/shared';
import { injectHook, ref } from 'vue';
const encode = encodeURIComponent;
function stringifyQuery(obj, encodeStr = encode) {
......@@ -21,6 +21,18 @@ function stringifyQuery(obj, encodeStr = encode) {
return res ? `?${res}` : '';
}
function getDataByPath(obj, path) {
const parts = path.split('.');
const key = parts[0];
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getDataByPath(obj[key], parts.slice(1).join('.'));
}
function cache(fn) {
const cache = Object.create(null);
return (str) => {
......@@ -129,102 +141,8 @@ function getEventChannel(id) {
return eventChannelStack.shift();
}
function initBehavior(options) {
return Behavior(options);
}
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.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs(instance, mpInstance) {
Object.defineProperty(instance, 'refs', {
get() {
const $refs = {};
selectAllComponents(mpInstance, '.v-r', $refs);
const forComponents = mpInstance.selectAllComponents('.v-r-i-f');
forComponents.forEach((component) => {
const ref = component.dataset.ref;
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;
}
}
}
function getTarget(obj, path) {
const parts = path.split('.');
let key = parts[0];
if (key.indexOf('__$n') === 0) {
//number index
key = parseInt(key.replace('__$n', ''));
}
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getTarget(obj[key], parts.slice(1).join('.'));
}
function getValue(dataPath, target) {
return getTarget(target || this, dataPath);
return getDataByPath(target || this, dataPath);
}
function getClass(dynamicClass, staticClass) {
return renderClass(staticClass, dynamicClass);
......@@ -420,9 +338,6 @@ function initBaseInstance(instance, options) {
}
function initComponentInstance(instance, options) {
initBaseInstance(instance, options);
{
initScopedSlotsParams(instance);
}
const ctx = instance.ctx;
MP_METHODS.forEach((method) => {
ctx[method] = function (...args) {
......@@ -469,53 +384,6 @@ function callHook(name, args) {
}
const hooks = this.$[name];
return hooks && invokeArrayFns(hooks, args);
}
const center = {};
const parents = {};
function initScopedSlotsParams(instance) {
const ctx = instance.ctx;
ctx.$hasScopedSlotsParams = function (vueId) {
const has = center[vueId];
if (!has) {
parents[vueId] = this;
onUnmounted(() => {
delete parents[vueId];
}, instance);
}
return has;
};
ctx.$getScopedSlotsParams = function (vueId, name, key) {
const data = center[vueId];
if (data) {
const object = data[name] || {};
return key ? object[key] : object;
}
else {
parents[vueId] = this;
onUnmounted(() => {
delete parents[vueId];
}, instance);
}
};
ctx.$setScopedSlotsParams = function (name, value) {
const vueIds = instance.attrs.vueId;
if (vueIds) {
const vueId = vueIds.split(',')[0];
const object = (center[vueId] = center[vueId] || {});
object[name] = value;
if (parents[vueId]) {
parents[vueId].$forceUpdate();
}
}
};
onUnmounted(function () {
const propsData = instance.attrs;
const vueId = propsData && propsData.vueId;
if (vueId) {
delete center[vueId];
delete parents[vueId];
}
}, instance);
}
const PAGE_HOOKS = [
......@@ -633,6 +501,85 @@ function initLocale(appVm) {
});
}
function initBehavior(options) {
return Behavior(options);
}
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.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs(instance, mpInstance) {
Object.defineProperty(instance, 'refs', {
get() {
const $refs = {};
selectAllComponents(mpInstance, '.v-r', $refs);
const forComponents = mpInstance.selectAllComponents('.v-r-i-f');
forComponents.forEach((component) => {
const ref = component.dataset.ref;
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 PROP_TYPES = [String, Number, Boolean, Object, Array, null];
function createObserver(name) {
return function observer(newVal) {
......@@ -667,7 +614,7 @@ function initDefaultProps(isBehavior = false) {
value: '',
};
// 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
properties.vueSlots = {
properties.vS = {
type: null,
value: [],
observer: function (newVal) {
......@@ -800,260 +747,6 @@ function applyOptions(componentOptions, vueOptions, initBehavior) {
componentOptions.behaviors = initBehaviors(vueOptions, initBehavior);
}
function getExtraValue(instance, dataPathsArray) {
let context = instance;
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];
let vFor;
if (Number.isInteger(dataPath)) {
vFor = dataPath;
}
else if (!dataPath) {
vFor = context;
}
else if (typeof dataPath === 'string' && dataPath) {
if (dataPath.indexOf('#s#') === 0) {
vFor = dataPath.substr(3);
}
else {
vFor = getTarget(context, dataPath);
}
}
if (Number.isInteger(vFor)) {
context = value;
}
else if (!propPath) {
context = vFor[value];
}
else {
if (isArray(vFor)) {
context = vFor.find((vForItem) => {
return getTarget(vForItem, propPath) === value;
});
}
else if (isPlainObject(vFor)) {
context = Object.keys(vFor).find((vForKey) => {
return getTarget(vFor[vForKey], propPath) === value;
});
}
else {
console.error('v-for 暂不支持循环数据:', vFor);
}
}
if (valuePath) {
context = getTarget(context, valuePath);
}
}
});
return context;
}
function processEventExtra(instance, extra, event) {
const extraObj = {};
if (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] = instance;
}
else {
if (dataPath === '$event') {
// $event
extraObj['$' + index] = event;
}
else if (dataPath === 'arguments') {
if (event.detail && event.detail.__args__) {
extraObj['$' + index] = event.detail.__args__;
}
else {
extraObj['$' + index] = [event];
}
}
else if (dataPath.indexOf('$event.') === 0) {
// $event.target.value
extraObj['$' + index] = getTarget(event, dataPath.replace('$event.', ''));
}
else {
extraObj['$' + index] = getTarget(instance, dataPath);
}
}
}
else {
extraObj['$' + index] = getExtraValue(instance, 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(instance, 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(instance, 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 (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;
}
function wrapper(event) {
event.stopPropagation = NOOP;
event.preventDefault = NOOP;
event.target = event.target || {};
if (!hasOwn(event, 'detail')) {
event.detail = {};
}
if (hasOwn(event, 'markerId')) {
event.detail = typeof event.detail === 'object' ? event.detail : {};
event.detail.markerId = event.markerId;
}
{
// mp-baidu,checked=>value
if (isPlainObject(event.detail) &&
hasOwn(event.detail, 'checked') &&
!hasOwn(event.detail, 'value')) {
event.detail.value = event.detail.checked;
}
}
if (isPlainObject(event.detail)) {
event.target = extend({}, event.target, event.detail);
}
return event;
}
const ONCE = '~';
const CUSTOM = '^';
function matchEventType(eventType, optType) {
return (eventType === optType ||
(optType === 'regionchange' &&
(eventType === 'begin' || eventType === 'end')));
}
function handleEvent(event) {
event = wrapper(event);
// [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]]
const dataset = (event.currentTarget || event.target).dataset;
if (!dataset) {
return console.warn('事件信息不存在');
}
const eventOpts = (dataset.eventOpts ||
dataset['event-opts']); // 支付宝 web-view 组件 dataset 非驼峰
if (!eventOpts) {
return console.warn('事件信息不存在');
}
// [['handle',[1,2,a]],['handle1',[1,2,a]]]
const eventType = event.type;
const ret = [];
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 && matchEventType(eventType, type)) {
eventsArray.forEach((eventArray) => {
const methodName = eventArray[0];
if (methodName) {
let handlerCtx = this.$vm;
if (handlerCtx.$options.generic &&
handlerCtx.$parent &&
handlerCtx.$parent.$parent) {
// mp-weixin,mp-toutiao 抽象节点模拟 scoped slots
handlerCtx = handlerCtx.$parent.$parent;
}
if (methodName === '$emit') {
handlerCtx.$emit.apply(handlerCtx, processEventArgs(this.$vm, event, eventArray[1], eventArray[2], isCustom, methodName));
return;
}
const handler = handlerCtx[methodName];
if (!isFunction(handler)) {
throw new Error(` _vm.${methodName} is not a function`);
}
if (isOnce) {
if (handler.once) {
return;
}
handler.once = true;
}
let params = processEventArgs(this.$vm, event, eventArray[1], eventArray[2], isCustom, methodName);
params = Array.isArray(params) ? params : [];
// 参数尾部增加原始事件对象用于复杂表达式内获取额外数据
if (/=\s*\S+\.eventParams\s*\|\|\s*\S+\[['"]event-params['"]\]/.test(handler.toString())) {
// eslint-disable-next-line no-sparse-arrays
params = params.concat([, , , , , , , , , , event]);
}
ret.push(handler.apply(handlerCtx, params));
}
});
}
});
if (eventType === 'input' &&
ret.length === 1 &&
typeof ret[0] !== 'undefined') {
return ret[0];
}
}
function parseComponent(vueOptions, { parse, mocks, isPage, initRelation, handleLink, initLifetimes, }) {
vueOptions = vueOptions.default || vueOptions;
const options = {
......@@ -1079,7 +772,6 @@ function parseComponent(vueOptions, { parse, mocks, isPage, initRelation, handle
},
methods: {
__l: handleLink,
__e: handleEvent,
},
};
if (__VUE_OPTIONS_API__) {
......@@ -1248,7 +940,7 @@ function initLifetimes({ mocks, isPage, initRelation, vueOptions, }) {
}, {
mpType: isPage(mpInstance) ? 'page' : 'component',
mpInstance,
slots: properties.vueSlots,
slots: properties.vS,
parentComponent: relationOptions.parent && relationOptions.parent.$,
onBeforeSetup(instance, options) {
initRefs(instance, mpInstance);
......
......@@ -17,7 +17,7 @@ describe('compiler: transform component', () => {
test('component + component', () => {
assert(
`<custom><custom1/></custom>`,
`<custom vue-slots="{{['default']}}" class="v-r" v-i="2a9ec0b0-0" bind:__l="__l"><custom1 class="v-r" v-i="2a9ec0b0-1,2a9ec0b0-0" bind:__l="__l"/></custom>`,
`<custom class="v-r" v-s="{{['default']}}" v-i="2a9ec0b0-0" bind:__l="__l"><custom1 class="v-r" v-i="2a9ec0b0-1,2a9ec0b0-0" bind:__l="__l"/></custom>`,
`(_ctx, _cache) => {
return {}
}`,
......@@ -29,7 +29,7 @@ describe('compiler: transform component', () => {
test('component + component + component', () => {
assert(
`<custom><custom1><custom2/><custom2/></custom1></custom>`,
`<custom vue-slots="{{['default']}}" class="v-r" v-i="2a9ec0b0-0" bind:__l="__l"><custom1 vue-slots="{{['default']}}" class="v-r" v-i="2a9ec0b0-1,2a9ec0b0-0" bind:__l="__l"><custom2 class="v-r" v-i="2a9ec0b0-2,2a9ec0b0-1" bind:__l="__l"/><custom2 class="v-r" v-i="2a9ec0b0-3,2a9ec0b0-1" bind:__l="__l"/></custom1></custom>`,
`<custom class="v-r" v-s="{{['default']}}" v-i="2a9ec0b0-0" bind:__l="__l"><custom1 class="v-r" v-s="{{['default']}}" v-i="2a9ec0b0-1,2a9ec0b0-0" bind:__l="__l"><custom2 class="v-r" v-i="2a9ec0b0-2,2a9ec0b0-1" bind:__l="__l"/><custom2 class="v-r" v-i="2a9ec0b0-3,2a9ec0b0-1" bind:__l="__l"/></custom1></custom>`,
`(_ctx, _cache) => {
return {}
}`,
......@@ -63,7 +63,7 @@ describe('compiler: transform component', () => {
test('component + component with v-for', () => {
assert(
`<custom><custom1 v-for="item in items"/></custom>`,
`<custom vue-slots="{{['default']}}" class="v-r" v-i="2a9ec0b0-0" bind:__l="__l"><custom1 wx:for="{{a}}" wx:for-item="item" class="v-r-i-f" v-i="{{item.a}}" bind:__l="__l"/></custom>`,
`<custom class="v-r" v-s="{{['default']}}" v-i="2a9ec0b0-0" bind:__l="__l"><custom1 wx:for="{{a}}" wx:for-item="item" class="v-r-i-f" v-i="{{item.a}}" bind:__l="__l"/></custom>`,
`(_ctx, _cache) => {
return { a: _f(_ctx.items, (item, k0, i0) => { return { a: '2a9ec0b0-1' + '-' + i0 + ',' + '2a9ec0b0-0' }; }) }
}`,
......@@ -75,7 +75,7 @@ describe('compiler: transform component', () => {
test('component with v-for + component', () => {
assert(
`<custom v-for="item in items"><custom1/></custom>`,
`<custom wx:for="{{a}}" wx:for-item="item" vue-slots="{{['default']}}" class="v-r-i-f" v-i="{{item.b}}" bind:__l="__l"><custom1 class="v-r-i-f" v-i="{{item.a}}" bind:__l="__l"/></custom>`,
`<custom wx:for="{{a}}" wx:for-item="item" class="v-r-i-f" v-s="{{['default']}}" v-i="{{item.b}}" bind:__l="__l"><custom1 class="v-r-i-f" v-i="{{item.a}}" bind:__l="__l"/></custom>`,
`(_ctx, _cache) => {
return { a: _f(_ctx.items, (item, k0, i0) => { return { a: '2a9ec0b0-1' + '-' + i0 + ',' + ('2a9ec0b0-0' + '-' + i0), b: '2a9ec0b0-0' + '-' + i0 }; }) }
}`,
......@@ -87,7 +87,7 @@ describe('compiler: transform component', () => {
test('component with v-for + component with v-for', () => {
assert(
`<custom v-for="item in items"><custom1 v-for="item1 in item.items"/></custom>`,
`<custom wx:for="{{a}}" wx:for-item="item" vue-slots="{{['default']}}" class="v-r-i-f" v-i="{{item.b}}" bind:__l="__l"><custom1 wx:for="{{item.a}}" wx:for-item="item1" class="v-r-i-f" v-i="{{item1.a}}" bind:__l="__l"/></custom>`,
`<custom wx:for="{{a}}" wx:for-item="item" class="v-r-i-f" v-s="{{['default']}}" v-i="{{item.b}}" bind:__l="__l"><custom1 wx:for="{{item.a}}" wx:for-item="item1" class="v-r-i-f" v-i="{{item1.a}}" bind:__l="__l"/></custom>`,
`(_ctx, _cache) => {
return { a: _f(_ctx.items, (item, k0, i0) => { return { a: _f(item.items, (item1, k1, i1) => { return { a: '2a9ec0b0-1' + '-' + i0 + '-' + i1 + ',' + ('2a9ec0b0-0' + '-' + i0) }; }), b: '2a9ec0b0-0' + '-' + i0 }; }) }
}`,
......
import { assert } from './testUtils'
describe('compiler: transform scoped slots', () => {
test('basic', () => {
assert(
`<view><slot :item="item" :index="index"/></view>`,
`<view><slot/></view>`,
`(_ctx, _cache) => {
return { a: _r("default", { item: _ctx.item, index: _ctx.index }) }
}`
)
})
test('named slots', () => {
assert(
`<view><slot name="header" :item="item" :index="index"/></view>`,
`<view><slot name="header"/></view>`,
`(_ctx, _cache) => {
return { a: _r("header", { item: _ctx.item, index: _ctx.index }) }
}`
)
})
test('named slots + v-if', () => {
assert(
`<view><slot v-if="ok" name="header" :item="item" :index="index"/></view>`,
`<view><slot wx:if="{{a}}" name="header"/></view>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.ok }, _ctx.ok ? { b: _r("header", { item: _ctx.item, index: _ctx.index }) } : {})
}`
)
})
})
......@@ -4,14 +4,14 @@ describe('compiler: transform v-slot', () => {
test('default slot', () => {
assert(
`<custom><template v-slot/></custom>`,
`<custom vue-slots="{{['default']}}" class="v-r" v-i="2a9ec0b0-0"><view /></custom>`,
`<custom class="v-r" v-s="{{['default']}}" v-i="2a9ec0b0-0"><view /></custom>`,
`(_ctx, _cache) => {
return {}
}`
)
assert(
`<custom>test</custom>`,
`<custom vue-slots="{{['default']}}" class="v-r" v-i="2a9ec0b0-0">test</custom>`,
`<custom class="v-r" v-s="{{['default']}}" v-i="2a9ec0b0-0">test</custom>`,
`(_ctx, _cache) => {
return {}
}`
......@@ -20,19 +20,59 @@ describe('compiler: transform v-slot', () => {
test('named slots', () => {
assert(
`<custom><template v-slot:header/><template v-slot:default/><template v-slot:footer/></custom>`,
`<custom vue-slots="{{['header','default','footer']}}" class="v-r" v-i="2a9ec0b0-0"><view slot="header"/><view slot="default"/><view slot="footer"/></custom>`,
`<custom class="v-r" v-s="{{['header','default','footer']}}" v-i="2a9ec0b0-0"><view slot="header"/><view slot="default"/><view slot="footer"/></custom>`,
`(_ctx, _cache) => {
return {}
}`
)
})
// TODO 还未实现scoped slot
test('scoped slots', () => {
assert(
`<custom><template v-slot:default="slotProps"><view>{{ slotProps.item }}</view></template></custom>`,
`<custom vue-slots="{{['default']}}" class="v-r" v-i="2a9ec0b0-0"><view slot="default"><view>{{a}}</view></view></custom>`,
`<custom class="v-r" v-s="{{['default']}}" v-i="2a9ec0b0-0"><view slot="default"><block wx:for="{{a}}" wx:for-item="slotProps" wx:key="b"><view>{{slotProps.a}}</view></block></view></custom>`,
`(_ctx, _cache) => {
return { a: _w((slotProps, s0, i0) => { return { a: _t(slotProps.item), b: s0 }; }, { name: 'default', vueId: '2a9ec0b0-0' }) }
}`
)
})
test('scoped slots + scoped slots', () => {
assert(
`<custom><template v-slot:default="slotProps"><custom1><template v-slot:default="slotProps1">{{ slotProps.item }}{{ slotProps1.item }}</template></custom1></template></custom>`,
`<custom class="v-r" v-s="{{['default']}}" v-i="2a9ec0b0-0"><view slot="default"><block wx:for="{{a}}" wx:for-item="slotProps" wx:key="d"><custom1 class="v-r" v-s="{{['default']}}" v-i="{{slotProps.c}}"><view slot="default"><block wx:for="{{slotProps.a}}" wx:for-item="slotProps1" wx:key="b">{{slotProps.b}}{{slotProps1.a}}</block></view></custom1></block></view></custom>`,
`(_ctx, _cache) => {
return { a: _w((slotProps, s0, i0) => { return { a: _w((slotProps1, s1, i1) => { return { a: _t(slotProps1.item), b: s1 }; }, { name: 'default', vueId: '2a9ec0b0-1' + '-' + i0 + ',' + '2a9ec0b0-0' }), b: _t(slotProps.item), c: '2a9ec0b0-1' + '-' + i0 + ',' + '2a9ec0b0-0', d: s0 }; }, { name: 'default', vueId: '2a9ec0b0-0' }) }
}`
)
})
test('v-if + scoped slots', () => {
assert(
`<custom><template v-if="ok" v-slot:default="slotProps"><view>{{ slotProps.item }}</view></template></custom>`,
`<custom class="v-r" v-s="{{['default']}}" v-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="default"><block wx:for="{{b}}" wx:for-item="slotProps" wx:key="b"><view>{{slotProps.a}}</view></block></view></custom>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.ok }, _ctx.ok ? { b: _w((slotProps, s0, i0) => { return { a: _t(slotProps.item), b: s0 }; }, { name: 'default', vueId: '2a9ec0b0-0' }) } : {})
}`
)
})
test('v-for + scoped slots', () => {
assert(
`<custom v-for="item in items"><template v-slot:default="slotProps"><view>{{ slotProps.item }}</view></template></custom>`,
`<custom wx:for="{{a}}" wx:for-item="item" class="v-r-i-f" v-s="{{['default']}}" v-i="{{item.b}}"><view slot="default"><block wx:for="{{item.a}}" wx:for-item="slotProps" wx:key="b"><view>{{slotProps.a}}</view></block></view></custom>`,
`(_ctx, _cache) => {
return { a: _f(_ctx.items, (item, k0, i0) => { return { a: _w((slotProps, s1, i1) => { return { a: _t(slotProps.item), b: s1 }; }, { name: 'default', vueId: '2a9ec0b0-0' + '-' + i0 }), b: '2a9ec0b0-0' + '-' + i0 }; }) }
}`
)
})
test('v-for + v-for + scoped slots', () => {
assert(
`<view v-for="item in items"><custom v-for="item1 in item.list" :item="item1"><template v-slot:default="slotProps"><view>{{ slotProps.item }}</view></template></custom></view>`,
`<view wx:for="{{a}}" wx:for-item="item"><custom wx:for="{{item.a}}" wx:for-item="item1" class="v-r-i-f" v-s="{{['default']}}" item="{{item1.b}}" v-i="{{item1.c}}"><view slot="default"><block wx:for="{{item1.a}}" wx:for-item="slotProps" wx:key="b"><view>{{slotProps.a}}</view></block></view></custom></view>`,
`(_ctx, _cache) => {
return { a: _t(_ctx.slotProps.item), b: slotProps }
return { a: _f(_ctx.items, (item, k0, i0) => { return { a: _f(item.list, (item1, k1, i1) => { return { a: _w((slotProps, s2, i2) => { return { a: _t(slotProps.item), b: s2 }; }, { name: 'default', vueId: '2a9ec0b0-0' + '-' + i0 + '-' + i1 }), b: item1, c: '2a9ec0b0-0' + '-' + i0 + '-' + i1 }; }) }; }) }
}`
)
})
......
......@@ -2,8 +2,6 @@ import { isString } from '@vue/shared'
import { parseExpression } from '@babel/parser'
import {
identifier,
blockStatement,
callExpression,
objectProperty,
objectExpression,
spreadElement,
......@@ -12,14 +10,10 @@ import {
Expression,
SpreadElement,
ConditionalExpression,
arrowFunctionExpression,
Identifier,
returnStatement,
conditionalExpression,
NumericLiteral,
isNumericLiteral,
Pattern,
RestElement,
ArrowFunctionExpression,
stringLiteral,
StringLiteral,
......@@ -41,10 +35,9 @@ import {
locStub,
NodeTypes,
} from '@vue/compiler-core'
import { CodegenScope, CodegenVForScope, CodegenVIfScope } from './options'
import { CodegenScope, CodegenVIfScope } from './options'
import { TransformContext } from './transform'
import { genExpr } from './codegen'
import { V_FOR } from './runtimeHelpers'
export function createIdentifier(name: string) {
return identifier(name)
......@@ -91,22 +84,6 @@ export function createVIfSpreadElement(vIfScope: CodegenVIfScope) {
// return arrayExpression(elements)
// }
export function createVForCallExpression(
vForScope: CodegenVForScope,
context: TransformContext
) {
// let sourceExpr: Expression = vForScope.sourceExpr!
// if (isNumericLiteral(sourceExpr)) {
// sourceExpr = numericLiteralToArrayExpr((sourceExpr as NumericLiteral).value)
// }
return callExpression(identifier(context.helperString(V_FOR)), [
vForScope.sourceExpr!,
createVForArrowFunctionExpression(vForScope),
])
}
type FunctionParam = Identifier | Pattern | RestElement
export function parseExpr(
code: string | ExpressionNode,
context: TransformContext,
......@@ -141,32 +118,6 @@ export function parseParam(
return expr
}
function createVForArrowFunctionExpression({
valueExpr,
keyExpr,
indexExpr,
properties,
}: CodegenVForScope) {
const params: FunctionParam[] = []
if (valueExpr) {
params.push(valueExpr)
} else if (keyExpr || indexExpr) {
params.push(identifier('_'))
}
if (keyExpr) {
params.push(keyExpr)
} else if (indexExpr) {
params.push(identifier('__'))
}
if (indexExpr) {
params.push(indexExpr)
}
return arrowFunctionExpression(
params,
blockStatement([returnStatement(objectExpression(properties))])
)
}
export function isUndefined(expr: Expression) {
return isIdentifier(expr) && expr.name === 'undefined'
}
......
......@@ -14,6 +14,7 @@ import { transformOn } from './transforms/vOn'
import { transformElement } from './transforms/transformElement'
import { transformBind } from './transforms/vBind'
import { transformComponent } from './transforms/transformComponent'
import { transformSlot } from './transforms/vSlot'
export type TransformPreset = [
NodeTransform[],
......@@ -27,7 +28,8 @@ export function getBaseTransformPreset({
prefixIdentifiers: boolean
skipTransformIdentifier: boolean
}): TransformPreset {
const nodeTransforms = [transformIf, transformFor]
// order is important
const nodeTransforms = [transformIf, transformFor, transformSlot]
if (!skipTransformIdentifier) {
nodeTransforms.push(transformIdentifier)
}
......@@ -49,7 +51,6 @@ export function baseCompile(template: string, options: CompilerOptions = {}) {
prefixIdentifiers,
skipTransformIdentifier: options.skipTransformIdentifier === true,
})
options.hashId = genHashId(options)
if (options.filename) {
......
......@@ -5,6 +5,8 @@ export const V_FOR = Symbol(`vFor`)
export const EXTEND = Symbol(`extend`)
export const CAMELIZE = Symbol(`camelize`)
export const HYPHENATE = Symbol(`hyphenate`)
export const RENDER_SLOT = Symbol(`renderSlot`)
export const WITH_SCOPED_SLOT = Symbol(`withScopedSlot`)
export const STRINGIFY_STYLE = Symbol(`stringifyStyle`)
export const NORMALIZE_CLASS = Symbol(`normalizeClass`)
export const TO_DISPLAY_STRING = Symbol(`toDisplayString`)
......@@ -14,6 +16,8 @@ registerRuntimeHelpers({
[EXTEND]: 'e',
[CAMELIZE]: 'c',
[HYPHENATE]: 'h',
[RENDER_SLOT]: 'r',
[WITH_SCOPED_SLOT]: 'w',
[STRINGIFY_STYLE]: 's',
[NORMALIZE_CLASS]: 'n',
[TO_DISPLAY_STRING]: `t`,
......
......@@ -19,7 +19,7 @@ import { TemplateCodegenOptions } from '../options'
import { genExpr } from '../codegen'
import { isForElementNode, VForOptions } from '../transforms/vFor'
import { IfElementNode, isIfElementNode } from '../transforms/vIf'
import { createBindDirectiveNode } from '../ast'
import { findSlotName } from '../transforms/vSlot'
interface TemplateCodegenContext {
code: string
directive: string
......@@ -95,7 +95,7 @@ function genVElse({ push, directive }: TemplateCodegenContext) {
}
function genVFor(
{ sourceAlias, valueAlias, keyAlias }: VForOptions,
{ sourceAlias, valueAlias }: VForOptions,
node: ElementNode,
{ push, directive }: TemplateCodegenContext
) {
......@@ -112,6 +112,8 @@ function genVFor(
}
function genSlot(node: SlotOutletNode, context: TemplateCodegenContext) {
// 移除掉所有非name属性,即移除作用域插槽的绑定指令
node.props = node.props.filter((prop) => prop.name === 'name')
if (!node.children.length) {
return genElement(node, context)
}
......@@ -139,24 +141,11 @@ function genSlot(node: SlotOutletNode, context: TemplateCodegenContext) {
push(`</block>`)
}
function findSlotName(node: ElementNode) {
function genTemplate(node: TemplateNode, context: TemplateCodegenContext) {
const slotProp = node.props.find(
(prop) => prop.type === NodeTypes.DIRECTIVE && prop.name === 'slot'
) as DirectiveNode | undefined
if (slotProp) {
const { arg } = slotProp
if (!arg) {
return 'default'
}
if (arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic) {
return arg.content
}
}
}
function genTemplate(node: TemplateNode, context: TemplateCodegenContext) {
const slotName = findSlotName(node)
if (slotName) {
if (slotProp && findSlotName(slotProp)) {
/**
* 仅百度、字节支持使用 block 作为命名插槽根节点
* 此处为了统一仅默认替换为view
......@@ -173,23 +162,6 @@ function genTemplate(node: TemplateNode, context: TemplateCodegenContext) {
}
function genComponent(node: ComponentNode, context: TemplateCodegenContext) {
const slots = new Set<string>()
if (!node.children.length) {
return genElement(node, context)
}
node.children.forEach((child) => {
if (child.type === NodeTypes.ELEMENT) {
slots.add(findSlotName(child) || 'default')
} else if (child.type === NodeTypes.TEXT) {
slots.add('default')
}
})
node.props.unshift(
createBindDirectiveNode(
'vue-slots',
`[${[...slots].map((name) => `'${name}'`).join(',')}]`
)
)
return genElement(node, context)
}
......
......@@ -39,8 +39,6 @@ import {
CompilerError,
helperNameMap,
ExpressionNode,
ElementTypes,
isVSlot,
JSChildNode,
CacheExpression,
locStub,
......@@ -478,9 +476,9 @@ export function createStructuralDirectiveTransform(
const { props } = node
// structural directive transforms are not concerned with slots
// as they are handled separately in vSlot.ts
if (node.tagType === ElementTypes.TEMPLATE && props.some(isVSlot)) {
return
}
// if (node.tagType === ElementTypes.TEMPLATE && props.some(isVSlot)) {
// return
// }
const exitFns = []
for (let i = 0; i < props.length; i++) {
const prop = props[i]
......@@ -488,8 +486,8 @@ export function createStructuralDirectiveTransform(
// structural directives are removed to avoid infinite recursion
// also we remove them *before* applying so that it can further
// traverse itself in case it moves the node around
// props.splice(i, 1)
// i--
props.splice(i, 1)
i--
const onExit = fn(node, prop, context)
if (onExit) exitFns.push(onExit)
}
......
import {
ComponentNode,
ElementTypes,
isCoreComponent,
NodeTypes,
} from '@vue/compiler-core'
import { isComponentTag } from '@dcloudio/uni-shared'
import { ComponentNode } from '@vue/compiler-core'
import { isVForScope, NodeTransform, TransformContext } from '../transform'
import { createAttributeNode, createBindDirectiveNode } from '../ast'
import { addStaticClass } from './transformElement'
import {
ATTR_VUE_ID,
CLASS_VUE_REF,
CLASS_VUE_REF_IN_FOR,
isUserComponent,
} from './utils'
import { CodegenScope } from '../options'
import { isScopedSlotVFor } from './vSlot'
export const transformComponent: NodeTransform = (node, context) => {
const isComponent =
node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.COMPONENT &&
!isComponentTag(node.tag) &&
!isCoreComponent(node.tag) &&
!context.isBuiltInComponent(node.tag)
if (!isComponent) {
if (!isUserComponent(node, context)) {
return
}
addVueRef(node, context)
......@@ -60,9 +55,9 @@ function addVueId(node: ComponentNode, context: TransformContext) {
}
}
if (value.includes('+')) {
return node.props.push(createBindDirectiveNode('v-i', value))
return node.props.push(createBindDirectiveNode(ATTR_VUE_ID, value))
}
return node.props.push(createAttributeNode('v-i', value))
return node.props.push(createAttributeNode(ATTR_VUE_ID, value))
}
function addVueRef(node: ComponentNode, context: TransformContext) {
......@@ -70,6 +65,17 @@ function addVueRef(node: ComponentNode, context: TransformContext) {
node,
// vue-ref-in-for
// vue-ref
context.scopes.vFor ? 'v-r-i-f' : 'v-r'
isInVFor(context.currentScope) ? CLASS_VUE_REF_IN_FOR : CLASS_VUE_REF
)
}
function isInVFor(scope: CodegenScope) {
let parent: CodegenScope | null = scope
while (parent) {
if (isVForScope(parent) && !isScopedSlotVFor(parent)) {
return true
}
parent = parent.parent
}
return false
}
......@@ -191,8 +191,12 @@ function resolveSetupReference(name: string, context: TransformContext) {
}
}
function processProps(node: ElementNode, context: TransformContext) {
const { tag, props } = node
export function processProps(
node: ElementNode,
context: TransformContext,
props: ElementNode['props'] = node.props
) {
const { tag } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
for (let i = 0; i < props.length; i++) {
......@@ -292,5 +296,7 @@ function processVModel(node: ElementNode, context: TransformContext) {
i--
}
}
props.push(...dirs)
if (dirs.length) {
props.push(...dirs)
}
}
import {
createCompoundExpression,
DirectiveNode,
isSlotOutlet,
NodeTypes,
} from '@vue/compiler-core'
import { NodeTransform } from '../transform'
import { isForElementNode } from './vFor'
import { rewriteExpression } from './utils'
import { ATTR_VUE_SLOTS, rewriteExpression } from './utils'
import { isSelfKey, rewriteSelfKey } from './transformKey'
import {
findStaticClassIndex,
......@@ -19,9 +20,10 @@ import {
rewriteStyle,
} from './transformStyle'
import { TO_DISPLAY_STRING } from '../runtimeHelpers'
import { rewriteSlot } from './transformSlot'
export const transformIdentifier: NodeTransform = (node, context) => {
return () => {
return function transformIdentifier() {
if (node.type === NodeTypes.INTERPOLATION) {
node.content = rewriteExpression(
createCompoundExpression([
......@@ -31,6 +33,8 @@ export const transformIdentifier: NodeTransform = (node, context) => {
]),
context
)
} else if (isSlotOutlet(node)) {
rewriteSlot(node, context)
} else if (node.type === NodeTypes.ELEMENT) {
const vFor = isForElementNode(node) && node.vFor
const { props } = node
......@@ -82,13 +86,11 @@ export const transformIdentifier: NodeTransform = (node, context) => {
}
}
// vue-id
// const builtInProps = ['v-i']
const builtInProps = [ATTR_VUE_SLOTS]
function isBuiltIn(_dir: DirectiveNode) {
return false
// return (
// arg?.type === NodeTypes.SIMPLE_EXPRESSION &&
// builtInProps.includes(arg.content)
// )
function isBuiltIn({ arg }: DirectiveNode) {
return (
arg?.type === NodeTypes.SIMPLE_EXPRESSION &&
builtInProps.includes(arg.content)
)
}
import {
AttributeNode,
createCompilerError,
createCompoundExpression,
DirectiveNode,
ErrorCodes,
ExpressionNode,
isBindKey,
isStaticExp,
NodeTypes,
SimpleExpressionNode,
SlotOutletNode,
} from '@vue/compiler-core'
import { camelize } from '@vue/shared'
import { RENDER_SLOT } from '../runtimeHelpers'
import { genExpr } from '../codegen'
import { TransformContext } from '../transform'
import { processProps } from './transformElement'
import { rewriteExpression } from './utils'
export function rewriteSlot(node: SlotOutletNode, context: TransformContext) {
let slotName: string | ExpressionNode = `"default"`
let hasOtherDir = false
const nonNameProps: (AttributeNode | DirectiveNode)[] = []
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
if (p.type === NodeTypes.ATTRIBUTE) {
if (p.value) {
if (p.name === 'name') {
slotName = JSON.stringify(p.value.content)
} else {
p.name = camelize(p.name)
nonNameProps.push(p)
}
}
} else {
if (p.name !== 'bind') {
hasOtherDir = true
}
if (p.name === 'bind' && isBindKey(p.arg, 'name')) {
if (p.exp) slotName = p.exp
} else {
if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {
p.arg.content = camelize(p.arg.content)
}
nonNameProps.push(p)
}
}
}
if (hasOtherDir) {
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
node.loc
)
)
}
if (nonNameProps.length > 0) {
processProps(node, context, nonNameProps)
const properties: string[] = []
nonNameProps.forEach((prop) => {
if (prop.type === NodeTypes.DIRECTIVE && prop.name === 'bind') {
const property = transformProperty(prop, context)
property && properties.push(property)
}
})
if (properties.length) {
rewriteExpression(
createCompoundExpression([
context.helperString(RENDER_SLOT) + '(',
slotName,
',',
`{${properties.join(',')}}`,
')',
]),
context
)
}
}
}
function transformProperty(dir: DirectiveNode, context: TransformContext) {
if (!dir.arg || !dir.exp) {
return
}
const isStaticArg =
dir.arg.type === NodeTypes.SIMPLE_EXPRESSION && dir.arg.isStatic
if (isStaticArg) {
return `${(dir.arg as SimpleExpressionNode).content}:${genExpr(dir.exp)}`
}
return `[${genExpr(dir.arg)}||'']:${genExpr(dir.exp)}`
}
......@@ -10,11 +10,17 @@ import {
objectProperty,
SpreadElement,
} from '@babel/types'
import { isComponentTag } from '@dcloudio/uni-shared'
import {
ComponentNode,
createSimpleExpression,
ElementTypes,
ExpressionNode,
isCoreComponent,
NodeTypes,
RootNode,
SourceLocation,
TemplateChildNode,
} from '@vue/compiler-core'
import { walk, BaseNode } from 'estree-walker'
import { isUndefined, parseExpr } from '../ast'
......@@ -22,6 +28,25 @@ import { genBabelExpr, genExpr } from '../codegen'
import { CodegenScope } from '../options'
import { isVForScope, isVIfScope, TransformContext } from '../transform'
export const ATTR_VUE_ID = 'v-i'
export const ATTR_VUE_SLOTS = 'v-s'
export const CLASS_VUE_REF = 'v-r'
export const CLASS_VUE_REF_IN_FOR = 'v-r-i-f'
export const SCOPED_SLOT_IDENTIFIER = '__SCOPED_SLOT__'
export function isUserComponent(
node: RootNode | TemplateChildNode,
context: TransformContext
): node is ComponentNode {
return (
node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.COMPONENT &&
!isComponentTag(node.tag) &&
!isCoreComponent(node.tag) &&
!context.isBuiltInComponent(node.tag)
)
}
export function rewriteSpreadElement(
name: symbol,
expr: SpreadElement,
......@@ -75,11 +100,20 @@ export function parseExprWithRewriteClass(
) as Identifier | MemberExpression | undefined
}
export function rewriteExpression(
export function rewriteExpressionWithoutProperty(
node: ExpressionNode,
context: TransformContext,
babelNode?: Expression,
scope: CodegenScope = context.currentScope
) {
return rewriteExpression(node, context, babelNode, scope, false)
}
export function rewriteExpression(
node: ExpressionNode,
context: TransformContext,
babelNode?: Expression,
scope: CodegenScope = context.currentScope,
property: boolean = true
) {
if (node.type === NodeTypes.SIMPLE_EXPRESSION && node.isStatic) {
return node
......@@ -104,7 +138,9 @@ export function rewriteExpression(
scope = findReferencedScope(babelNode, scope)
const id = scope.id.next()
scope.properties.push(objectProperty(identifier(id), babelNode!))
if (property) {
scope.properties.push(objectProperty(identifier(id), babelNode!))
}
// 在v-for中包含的v-if块,所有变量需要补充当前v-for value前缀
if (isVIfScope(scope)) {
if (isVForScope(scope.parentScope)) {
......
......@@ -7,26 +7,39 @@ import {
getInnerRange,
SimpleExpressionNode,
SourceLocation,
createStructuralDirectiveTransform,
ElementTypes,
ElementNode,
NodeTypes,
isTemplateNode,
findProp,
ComponentNode,
} from '@vue/compiler-core'
import { createVForCallExpression, parseExpr, parseParam } from '../ast'
import { NodeTransform, TransformContext } from '../transform'
import { parseExpr, parseParam } from '../ast'
import {
createStructuralDirectiveTransform,
NodeTransform,
TransformContext,
} from '../transform'
import { processExpression } from './transformExpression'
import { genExpr } from '../codegen'
import {
arrowFunctionExpression,
blockStatement,
callExpression,
cloneNode,
Expression,
identifier,
Identifier,
isIdentifier,
objectExpression,
Pattern,
RestElement,
returnStatement,
} from '@babel/types'
import { rewriteExpression } from './utils'
import { CodegenVForScope } from '../options'
import { V_FOR } from '../runtimeHelpers'
import { createVSlotCallExpression, isScopedSlotVFor } from './vSlot'
export type VForOptions = Omit<ForParseResult, 'tagType'> & {
sourceExpr?: Expression
......@@ -50,8 +63,7 @@ export function isForElementNode(node: unknown): node is ForElementNode {
}
export const transformFor = createStructuralDirectiveTransform(
'for',
(node, dir, _context) => {
const context = _context as unknown as TransformContext
(node, dir, context) => {
if (!dir.exp) {
context.onError(
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc)
......@@ -89,10 +101,17 @@ export const transformFor = createStructuralDirectiveTransform(
const indexCode = genExpr(index)
const indexExpr = parseParam(indexCode, context, index)
const indexAlias = parseAlias(indexExpr, indexCode, 'i' + scopes.vFor)
// 先占位vFor,后续更新 cloneSourceExpr 为 CallExpression
const cloneSourceExpr = cloneNode(sourceExpr!, false)
const vForData: VForOptions = {
source,
sourceExpr,
sourceAlias: '',
sourceAlias: rewriteExpression(
source,
context,
cloneSourceExpr,
parentScope
).content,
value,
valueCode,
valueExpr,
......@@ -111,17 +130,12 @@ export const transformFor = createStructuralDirectiveTransform(
...vForData,
locals: findVForLocals(parseResult),
})
// 先占位vFor,后续更新 cloneSourceExpr 为 CallExpression
const cloneSourceExpr = cloneNode(sourceExpr!, false)
const vFor = {
...vForData,
sourceAlias: rewriteExpression(
source,
context,
cloneSourceExpr,
parentScope
).content,
}
const isScopedSlot = isScopedSlotVFor(vForScope)
;(node as ForElementNode).vFor = vFor
scopes.vFor++
......@@ -150,7 +164,14 @@ export const transformFor = createStructuralDirectiveTransform(
}
extend(
clearExpr(cloneSourceExpr),
createVForCallExpression(vForScope, context)
isScopedSlot
? createVSlotCallExpression(
(node as unknown as { slotComponent: ComponentNode })
.slotComponent,
vForScope,
context
)
: createVForCallExpression(vForScope, context)
)
popScope()
}
......@@ -316,3 +337,41 @@ function createParamsList(
.slice(0, i + 1)
.map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false))
}
function createVForCallExpression(
vForScope: CodegenVForScope,
context: TransformContext
) {
// let sourceExpr: Expression = vForScope.sourceExpr!
// if (isNumericLiteral(sourceExpr)) {
// sourceExpr = numericLiteralToArrayExpr((sourceExpr as NumericLiteral).value)
// }
return callExpression(identifier(context.helperString(V_FOR)), [
vForScope.sourceExpr!,
createVForArrowFunctionExpression(vForScope),
])
}
type FunctionParam = Identifier | Pattern | RestElement
export function createVForArrowFunctionExpression({
valueExpr,
keyExpr,
indexExpr,
properties,
}: CodegenVForScope) {
const params: FunctionParam[] = []
if (valueExpr) {
params.push(valueExpr)
}
if (keyExpr) {
params.push(keyExpr)
}
if (indexExpr) {
params.push(indexExpr)
}
return arrowFunctionExpression(
params,
blockStatement([returnStatement(objectExpression(properties))])
)
}
......@@ -9,7 +9,6 @@ import {
import {
createCompilerError,
createSimpleExpression,
createStructuralDirectiveTransform,
DirectiveNode,
ElementNode,
ErrorCodes,
......@@ -25,7 +24,12 @@ import {
parseExpr,
} from '../ast'
import { CodegenScope } from '../options'
import { NodeTransform, TransformContext, traverseNode } from '../transform'
import {
createStructuralDirectiveTransform,
NodeTransform,
TransformContext,
traverseNode,
} from '../transform'
import { processExpression } from './transformExpression'
import { rewriteExpression } from './utils'
interface IfOptions {
......@@ -42,8 +46,7 @@ export function isIfElementNode(node: unknown): node is IfElementNode {
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, _context) => {
const context = _context as unknown as TransformContext
(node, dir, context) => {
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
const { currentScope: parentScope, popScope } = context
const ifOptions: IfOptions = {
......
import {
binaryExpression,
BinaryExpression,
callExpression,
identifier,
objectExpression,
objectProperty,
stringLiteral,
} from '@babel/types'
import {
ComponentNode,
CompoundExpressionNode,
createCompilerError,
createSimpleExpression,
DirectiveNode,
ElementTypes,
ErrorCodes,
ExpressionNode,
findDir,
findProp,
isStaticExp,
isTemplateNode,
locStub,
NodeTypes,
TemplateChildNode,
TemplateNode,
} from '@vue/compiler-core'
import { WITH_SCOPED_SLOT } from '../runtimeHelpers'
import { createBindDirectiveNode, parseExpr } from '../ast'
import { genExpr } from '../codegen'
import { CodegenScope, CodegenVForScope } from '../options'
import { isVForScope, NodeTransform, TransformContext } from '../transform'
import {
ATTR_VUE_ID,
ATTR_VUE_SLOTS,
isUserComponent,
rewriteExpressionWithoutProperty,
SCOPED_SLOT_IDENTIFIER,
} from './utils'
import { createVForArrowFunctionExpression } from './vFor'
export const transformSlot: NodeTransform = (node, context) => {
if (!isUserComponent(node, context)) {
return
}
const { children } = node
const slots = new Set<string>()
const onComponentSlot = findDir(node, 'slot', true)
const implicitDefaultChildren: TemplateChildNode[] = []
for (let i = 0; i < children.length; i++) {
const slotElement = children[i]
let slotDir: DirectiveNode | undefined
if (
!isTemplateNode(slotElement) ||
!(slotDir = findDir(slotElement, 'slot', true))
) {
// not a <template v-slot>, skip.
if (slotElement.type !== NodeTypes.COMMENT) {
implicitDefaultChildren.push(slotElement)
}
continue
}
if (onComponentSlot) {
// already has on-component slot - this is incorrect usage.
context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc)
)
break
}
const slotName = findSlotName(slotDir)
if (!slotName) {
continue
}
slots.add(slotName)
const { exp } = slotDir
// non scoped slots
if (!exp) {
continue
}
// empty
if (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim()) {
continue
}
slots.add(slotName)
// 使用vFor来简单处理scoped slot作用域问题
slotElement.children = [
createVForTemplate(
slotElement,
{ name: slotName, value: genExpr(exp), slotComponent: node },
context
),
]
// v-slot="slotProps" => v-slot 避免 transformIdentifier 生成 slotProps 的变量声明
slotDir.exp = undefined
}
if (implicitDefaultChildren.length) {
slots.add('default')
}
if (slots.size) {
node.props.unshift(
createBindDirectiveNode(
ATTR_VUE_SLOTS,
`[${[...slots].map((name) => `'${name}'`).join(',')}]`
)
)
}
}
export function findSlotName(slotDir: DirectiveNode) {
if (!slotDir.arg) {
return 'default'
}
if (isStaticExp(slotDir.arg)) {
return slotDir.arg.content
}
}
export function isScopedSlotVFor({ source }: CodegenVForScope) {
if (source.type !== NodeTypes.COMPOUND_EXPRESSION) {
return false
}
const first = source.children[0] as ExpressionNode
return (
first.type === NodeTypes.SIMPLE_EXPRESSION &&
first.content.includes(SCOPED_SLOT_IDENTIFIER)
)
}
function findCurrentVForValueAlias(context: TransformContext) {
let scope: CodegenScope | null = context.currentScope
while (scope) {
if (isVForScope(scope)) {
return scope.valueAlias
}
scope = scope.parent
}
return ''
}
function createVForTemplate(
slotElement: TemplateNode,
{
name,
value,
slotComponent,
}: {
name: string
value: string
slotComponent: ComponentNode
},
context: TransformContext
) {
const key = 's' + context.scopes.vFor
const keyProp: DirectiveNode = createBindDirectiveNode('key', key)
const vForProp: DirectiveNode = {
type: NodeTypes.DIRECTIVE,
name: 'for',
loc: locStub,
modifiers: [],
arg: undefined,
exp: createSimpleExpression(
`(${value}, ${key}) in ${SCOPED_SLOT_IDENTIFIER}('${name}', ${
findCurrentVForValueAlias(context) || `''`
})`
),
}
return {
loc: slotElement.loc,
ns: 0,
tag: 'template',
type: NodeTypes.ELEMENT,
tagType: ElementTypes.TEMPLATE,
props: [vForProp, keyProp],
isSelfClosing: false,
codegenNode: undefined,
children: slotElement.children,
slotComponent,
} as TemplateNode
}
const slotNameRE = /\('(.*)',/
/**
* ('default','') => default
* @param source
* @returns
*/
function findCurrentSlotName(source: ExpressionNode) {
return stringLiteral(
((source as CompoundExpressionNode).children[1] as string).match(
slotNameRE
)![1]
)
}
function createPathBinaryExpr(scope: CodegenVForScope) {
return binaryExpression(
'+',
binaryExpression(
'+',
stringLiteral(parseVForPath(scope.sourceAlias) + '.'),
identifier(scope.indexAlias)
),
stringLiteral('.')
)
}
export function findCurrentPath(id: string, scope: CodegenScope) {
let parent = scope.parent
let binaryExpr: BinaryExpression | null = null
while (parent) {
if (isVForScope(parent)) {
if (!binaryExpr) {
binaryExpr = createPathBinaryExpr(parent)
} else {
binaryExpr = binaryExpression(
'+',
createPathBinaryExpr(parent),
binaryExpr
)
}
}
parent = parent.parent
}
return (
(binaryExpr && binaryExpression('+', binaryExpr, stringLiteral(id))) ||
stringLiteral(id)
)
}
function findCurrentVueIdExpr(node: ComponentNode, context: TransformContext) {
if (!node) {
return stringLiteral('')
}
const vueIdProp = findProp(node, ATTR_VUE_ID)!
if (vueIdProp.type === NodeTypes.ATTRIBUTE) {
return stringLiteral(vueIdProp.value!.content)
}
return parseExpr(genExpr(vueIdProp.exp!), context) || stringLiteral('')
}
/**
* 目前无用
* @param vForScope
* @param parentScope
* @param context
*/
export function rewriteScopedSlotVForScope(
vForScope: CodegenVForScope,
parentScope: CodegenScope,
context: TransformContext
) {
// 生成一个新的sourceAlias,用于scopedSlots
const { source, sourceExpr } = vForScope
vForScope.sourceAlias = rewriteExpressionWithoutProperty(
source,
context,
sourceExpr,
parentScope
).content
}
function parseVForPath(id: string) {
return id.includes('.') ? id.split('.')[1] : id
}
export function createVSlotCallExpression(
slotComponent: ComponentNode,
vForScope: CodegenVForScope,
context: TransformContext
) {
const { source /*, sourceAlias*/ } = vForScope
// const id = parseVForPath(sourceAlias)
return callExpression(identifier(context.helperString(WITH_SCOPED_SLOT)), [
createVForArrowFunctionExpression(vForScope),
objectExpression([
// 插槽名称,数据更新path,vueId
objectProperty(identifier('name'), findCurrentSlotName(source)),
// 暂不生成path
// objectProperty(identifier('path'), findCurrentPath(id, vForScope)),
objectProperty(
identifier('vueId'),
findCurrentVueIdExpr(slotComponent, context)
),
]),
])
}
......@@ -23,7 +23,6 @@ export {
initUnknownHooks,
} from './runtime/componentHooks'
export { initMocks, initComponentInstance } from './runtime/componentInstance'
export { handleEvent } from './runtime/componentEvents'
export { $createComponent, $destroyComponent } from './runtime/component'
export {
initRefs,
......
......@@ -6,7 +6,6 @@ import { initExtraOptions, initWxsCallMethods, initBehavior } from './util'
import { initProps } from './componentProps'
import { applyOptions } from './componentOptions'
import { handleEvent } from './componentEvents'
import { CreateComponentOptions } from './componentInstance'
import Component = WechatMiniprogram.Component
......@@ -102,7 +101,6 @@ export function parseComponent(
},
methods: {
__l: handleLink,
__e: handleEvent,
},
}
......
import {
extend,
hasOwn,
NOOP,
isArray,
isPlainObject,
isFunction,
} from '@vue/shared'
import { ComponentPublicInstance } from 'vue'
import { MPComponentInstance } from './component'
import { getTarget } from './util'
interface Event extends WechatMiniprogram.BaseEvent {
detail: Record<string, any>
stopPropagation: () => void
preventDefault: () => void
}
function getExtraValue(
instance: ComponentPublicInstance,
dataPathsArray: string[]
) {
let context: unknown = instance
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]
let vFor: any
if (Number.isInteger(dataPath)) {
vFor = dataPath
} else if (!dataPath) {
vFor = context
} else if (typeof dataPath === 'string' && dataPath) {
if (dataPath.indexOf('#s#') === 0) {
vFor = dataPath.substr(3)
} else {
vFor = getTarget(context, dataPath)
}
}
if (Number.isInteger(vFor)) {
context = value
} else if (!propPath) {
context = vFor[value]
} else {
if (isArray(vFor)) {
context = vFor.find((vForItem) => {
return getTarget(vForItem, propPath) === value
})
} else if (isPlainObject(vFor)) {
context = Object.keys(vFor).find((vForKey) => {
return getTarget(vFor[vForKey], propPath) === value
})
} else {
console.error('v-for 暂不支持循环数据:', vFor)
}
}
if (valuePath) {
context = getTarget(context, valuePath)
}
}
})
return context
}
function processEventExtra(
instance: ComponentPublicInstance,
extra: any[],
event: Event
) {
const extraObj: Record<string, any> = {}
if (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] = instance
} else {
if (dataPath === '$event') {
// $event
extraObj['$' + index] = event
} else if (dataPath === 'arguments') {
if (event.detail && event.detail.__args__) {
extraObj['$' + index] = event.detail.__args__
} else {
extraObj['$' + index] = [event]
}
} else if (dataPath.indexOf('$event.') === 0) {
// $event.target.value
extraObj['$' + index] = getTarget(
event,
dataPath.replace('$event.', '')
)
} else {
extraObj['$' + index] = getTarget(instance, dataPath)
}
}
} else {
extraObj['$' + index] = getExtraValue(instance, dataPath)
}
})
}
return extraObj
}
function getObjByArray(arr: any[]) {
const obj: Record<string, any> = {}
for (let i = 1; i < arr.length; i++) {
const element = arr[i]
obj[element[0]] = element[1]
}
return obj
}
function processEventArgs(
instance: ComponentPublicInstance,
event: Event,
args = [],
extra = [],
isCustom: boolean,
methodName: string
) {
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(instance, extra, event)
const ret: any[] = []
args.forEach((arg) => {
if (arg === '$event') {
if (methodName === '__set_model' && !isCustom) {
// input v-model value
ret.push((event.target as any).value)
} else {
if (isCustom && !isCustomMPEvent) {
ret.push(event.detail.__args__[0])
} else {
// wxcomponent 组件或内置组件
ret.push(event)
}
}
} else {
if (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
}
function wrapper(event: Event) {
event.stopPropagation = NOOP
event.preventDefault = NOOP
event.target = event.target || {}
if (!hasOwn(event, 'detail')) {
event.detail = {}
}
if (hasOwn(event, 'markerId')) {
event.detail = typeof event.detail === 'object' ? event.detail : {}
event.detail.markerId = (event as any).markerId
}
if (__PLATFORM__ === 'mp-baidu') {
// mp-baidu,checked=>value
if (
isPlainObject(event.detail) &&
hasOwn(event.detail, 'checked') &&
!hasOwn(event.detail, 'value')
) {
;(event.detail as any).value = (event.detail as any).checked
}
}
if (isPlainObject(event.detail)) {
event.target = extend({}, event.target, event.detail)
}
return event
}
const ONCE = '~'
const CUSTOM = '^'
function matchEventType(eventType: string, optType: string) {
return (
eventType === optType ||
(optType === 'regionchange' &&
(eventType === 'begin' || eventType === 'end'))
)
}
export function handleEvent(this: MPComponentInstance, event: Event) {
event = wrapper(event)
// [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]]
const dataset = (event.currentTarget || event.target).dataset
if (!dataset) {
return console.warn('事件信息不存在')
}
const eventOpts = (dataset.eventOpts ||
dataset['event-opts']) as unknown as any[] // 支付宝 web-view 组件 dataset 非驼峰
if (!eventOpts) {
return console.warn('事件信息不存在')
}
// [['handle',[1,2,a]],['handle1',[1,2,a]]]
const eventType = event.type
const ret: any[] = []
eventOpts.forEach((eventOpt: any[]) => {
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 && matchEventType(eventType, type)) {
eventsArray.forEach((eventArray: any[]) => {
const methodName = eventArray[0]
if (methodName) {
let handlerCtx = this.$vm!
if (
handlerCtx.$options.generic &&
handlerCtx.$parent &&
handlerCtx.$parent.$parent
) {
// mp-weixin,mp-toutiao 抽象节点模拟 scoped slots
handlerCtx = handlerCtx.$parent.$parent
}
if (methodName === '$emit') {
handlerCtx.$emit.apply(
handlerCtx,
processEventArgs(
this.$vm!,
event,
eventArray[1],
eventArray[2],
isCustom,
methodName
)
)
return
}
const handler = (handlerCtx as any)[methodName]
if (!isFunction(handler)) {
throw new Error(` _vm.${methodName} is not a function`)
}
if (isOnce) {
if (handler.once) {
return
}
handler.once = true
}
let params = processEventArgs(
this.$vm!,
event,
eventArray[1],
eventArray[2],
isCustom,
methodName
)
params = Array.isArray(params) ? params : []
// 参数尾部增加原始事件对象用于复杂表达式内获取额外数据
if (
/=\s*\S+\.eventParams\s*\|\|\s*\S+\[['"]event-params['"]\]/.test(
handler.toString()
)
) {
// eslint-disable-next-line no-sparse-arrays
params = params.concat([, , , , , , , , , , event])
}
ret.push(handler.apply(handlerCtx, params))
}
})
}
})
if (
eventType === 'input' &&
ret.length === 1 &&
typeof ret[0] !== 'undefined'
) {
return ret[0]
}
}
......@@ -7,11 +7,7 @@ import {
isObject,
isPlainObject,
} from '@vue/shared'
import {
ComponentPublicInstance,
ComponentInternalInstance,
onUnmounted,
} from 'vue'
import { ComponentPublicInstance, ComponentInternalInstance } from 'vue'
import { getEventChannel } from '../../api/protocols/navigateTo'
import { MPComponentInstance } from '../component'
import { getClass, getStyle, getValue } from './utils'
......@@ -170,16 +166,6 @@ export function initComponentInstance(
options: CreateComponentOptions
) {
initBaseInstance(instance, options)
if (
__PLATFORM__ === 'mp-weixin' ||
__PLATFORM__ === 'mp-qq' ||
__PLATFORM__ === 'mp-toutiao' ||
__PLATFORM__ === 'mp-kuaishou' ||
__PLATFORM__ === 'mp-alipay' ||
__PLATFORM__ === 'mp-baidu'
) {
initScopedSlotsParams(instance)
}
const ctx = (instance as any).ctx
MP_METHODS.forEach((method) => {
......@@ -238,58 +224,3 @@ function callHook(this: ComponentPublicInstance, name: string, args?: unknown) {
const hooks = (this.$ as any)[name]
return hooks && invokeArrayFns(hooks, args)
}
const center: Record<string, any> = {}
const parents: Record<string, ComponentPublicInstance> = {}
function initScopedSlotsParams(instance: ComponentInternalInstance) {
const ctx = (instance as any).ctx
ctx.$hasScopedSlotsParams = function (vueId: string) {
const has = center[vueId]
if (!has) {
parents[vueId] = this
onUnmounted(() => {
delete parents[vueId]
}, instance)
}
return has
}
ctx.$getScopedSlotsParams = function (
vueId: string,
name: string,
key: string
) {
const data = center[vueId]
if (data) {
const object = data[name] || {}
return key ? object[key] : object
} else {
parents[vueId] = this
onUnmounted(() => {
delete parents[vueId]
}, instance)
}
}
ctx.$setScopedSlotsParams = function (name: string, value: any) {
const vueIds = instance.attrs.vueId as string
if (vueIds) {
const vueId = vueIds.split(',')[0]
const object = (center[vueId] = center[vueId] || {})
object[name] = value
if (parents[vueId]) {
parents[vueId].$forceUpdate()
}
}
}
onUnmounted(function () {
const propsData = instance.attrs
const vueId = propsData && (propsData.vueId as string)
if (vueId) {
delete center[vueId]
delete parents[vueId]
}
}, instance)
}
import { ComponentPublicInstance } from 'vue'
import { extend, isObject, hyphenate } from '@vue/shared'
import { cache } from '@dcloudio/uni-shared'
import { getTarget } from '../util'
import { cache, getDataByPath } from '@dcloudio/uni-shared'
export function getValue(
this: ComponentPublicInstance,
dataPath: string,
target: Record<string, any>
) {
return getTarget(target || this, dataPath)
return getDataByPath(target || this, dataPath)
}
export function getClass(dynamicClass: unknown, staticClass: string) {
......
......@@ -48,7 +48,7 @@ function initDefaultProps(isBehavior: boolean = false) {
}
}
// 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
properties.vueSlots = {
properties.vS = {
type: null,
value: [],
observer: function (this: MPComponentInstance, newVal) {
......
......@@ -124,19 +124,3 @@ export function findVmByVueId(
}
}
}
export function getTarget(obj: any, path: string): unknown {
const parts = path.split('.')
let key: number | string = parts[0]
if (key.indexOf('__$n') === 0) {
//number index
key = parseInt(key.replace('__$n', ''))
}
if (!obj) {
obj = {}
}
if (parts.length === 1) {
return obj[key]
}
return getTarget(obj[key], parts.slice(1).join('.'))
}
import { isPlainObject, hasOwn, isArray, extend, hyphenate, isObject, toNumber, isFunction, NOOP, camelize } from '@vue/shared';
import { onUnmounted, injectHook, ref } from 'vue';
import { isPlainObject, extend, hyphenate, isObject, isArray, hasOwn, toNumber, isFunction, camelize } from '@vue/shared';
import { injectHook, ref } from 'vue';
const encode = encodeURIComponent;
function stringifyQuery(obj, encodeStr = encode) {
......@@ -21,6 +21,18 @@ function stringifyQuery(obj, encodeStr = encode) {
return res ? `?${res}` : '';
}
function getDataByPath(obj, path) {
const parts = path.split('.');
const key = parts[0];
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getDataByPath(obj[key], parts.slice(1).join('.'));
}
function cache(fn) {
const cache = Object.create(null);
return (str) => {
......@@ -129,102 +141,8 @@ function getEventChannel(id) {
return eventChannelStack.shift();
}
function initBehavior(options) {
return Behavior(options);
}
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.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs(instance, mpInstance) {
Object.defineProperty(instance, 'refs', {
get() {
const $refs = {};
selectAllComponents(mpInstance, '.v-r', $refs);
const forComponents = mpInstance.selectAllComponents('.v-r-i-f');
forComponents.forEach((component) => {
const ref = component.dataset.ref;
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;
}
}
}
function getTarget(obj, path) {
const parts = path.split('.');
let key = parts[0];
if (key.indexOf('__$n') === 0) {
//number index
key = parseInt(key.replace('__$n', ''));
}
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getTarget(obj[key], parts.slice(1).join('.'));
}
function getValue(dataPath, target) {
return getTarget(target || this, dataPath);
return getDataByPath(target || this, dataPath);
}
function getClass(dynamicClass, staticClass) {
return renderClass(staticClass, dynamicClass);
......@@ -420,9 +338,6 @@ function initBaseInstance(instance, options) {
}
function initComponentInstance(instance, options) {
initBaseInstance(instance, options);
{
initScopedSlotsParams(instance);
}
const ctx = instance.ctx;
MP_METHODS.forEach((method) => {
ctx[method] = function (...args) {
......@@ -469,53 +384,6 @@ function callHook(name, args) {
}
const hooks = this.$[name];
return hooks && invokeArrayFns(hooks, args);
}
const center = {};
const parents = {};
function initScopedSlotsParams(instance) {
const ctx = instance.ctx;
ctx.$hasScopedSlotsParams = function (vueId) {
const has = center[vueId];
if (!has) {
parents[vueId] = this;
onUnmounted(() => {
delete parents[vueId];
}, instance);
}
return has;
};
ctx.$getScopedSlotsParams = function (vueId, name, key) {
const data = center[vueId];
if (data) {
const object = data[name] || {};
return key ? object[key] : object;
}
else {
parents[vueId] = this;
onUnmounted(() => {
delete parents[vueId];
}, instance);
}
};
ctx.$setScopedSlotsParams = function (name, value) {
const vueIds = instance.attrs.vueId;
if (vueIds) {
const vueId = vueIds.split(',')[0];
const object = (center[vueId] = center[vueId] || {});
object[name] = value;
if (parents[vueId]) {
parents[vueId].$forceUpdate();
}
}
};
onUnmounted(function () {
const propsData = instance.attrs;
const vueId = propsData && propsData.vueId;
if (vueId) {
delete center[vueId];
delete parents[vueId];
}
}, instance);
}
const PAGE_HOOKS = [
......@@ -633,6 +501,85 @@ function initLocale(appVm) {
});
}
function initBehavior(options) {
return Behavior(options);
}
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.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs(instance, mpInstance) {
Object.defineProperty(instance, 'refs', {
get() {
const $refs = {};
selectAllComponents(mpInstance, '.v-r', $refs);
const forComponents = mpInstance.selectAllComponents('.v-r-i-f');
forComponents.forEach((component) => {
const ref = component.dataset.ref;
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 PROP_TYPES = [String, Number, Boolean, Object, Array, null];
function createObserver(name) {
return function observer(newVal) {
......@@ -656,7 +603,7 @@ function initDefaultProps(isBehavior = false) {
value: '',
};
// 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
properties.vueSlots = {
properties.vS = {
type: null,
value: [],
observer: function (newVal) {
......@@ -789,252 +736,6 @@ function applyOptions(componentOptions, vueOptions, initBehavior) {
componentOptions.behaviors = initBehaviors(vueOptions, initBehavior);
}
function getExtraValue(instance, dataPathsArray) {
let context = instance;
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];
let vFor;
if (Number.isInteger(dataPath)) {
vFor = dataPath;
}
else if (!dataPath) {
vFor = context;
}
else if (typeof dataPath === 'string' && dataPath) {
if (dataPath.indexOf('#s#') === 0) {
vFor = dataPath.substr(3);
}
else {
vFor = getTarget(context, dataPath);
}
}
if (Number.isInteger(vFor)) {
context = value;
}
else if (!propPath) {
context = vFor[value];
}
else {
if (isArray(vFor)) {
context = vFor.find((vForItem) => {
return getTarget(vForItem, propPath) === value;
});
}
else if (isPlainObject(vFor)) {
context = Object.keys(vFor).find((vForKey) => {
return getTarget(vFor[vForKey], propPath) === value;
});
}
else {
console.error('v-for 暂不支持循环数据:', vFor);
}
}
if (valuePath) {
context = getTarget(context, valuePath);
}
}
});
return context;
}
function processEventExtra(instance, extra, event) {
const extraObj = {};
if (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] = instance;
}
else {
if (dataPath === '$event') {
// $event
extraObj['$' + index] = event;
}
else if (dataPath === 'arguments') {
if (event.detail && event.detail.__args__) {
extraObj['$' + index] = event.detail.__args__;
}
else {
extraObj['$' + index] = [event];
}
}
else if (dataPath.indexOf('$event.') === 0) {
// $event.target.value
extraObj['$' + index] = getTarget(event, dataPath.replace('$event.', ''));
}
else {
extraObj['$' + index] = getTarget(instance, dataPath);
}
}
}
else {
extraObj['$' + index] = getExtraValue(instance, 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(instance, 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(instance, 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 (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;
}
function wrapper(event) {
event.stopPropagation = NOOP;
event.preventDefault = NOOP;
event.target = event.target || {};
if (!hasOwn(event, 'detail')) {
event.detail = {};
}
if (hasOwn(event, 'markerId')) {
event.detail = typeof event.detail === 'object' ? event.detail : {};
event.detail.markerId = event.markerId;
}
if (isPlainObject(event.detail)) {
event.target = extend({}, event.target, event.detail);
}
return event;
}
const ONCE = '~';
const CUSTOM = '^';
function matchEventType(eventType, optType) {
return (eventType === optType ||
(optType === 'regionchange' &&
(eventType === 'begin' || eventType === 'end')));
}
function handleEvent(event) {
event = wrapper(event);
// [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]]
const dataset = (event.currentTarget || event.target).dataset;
if (!dataset) {
return console.warn('事件信息不存在');
}
const eventOpts = (dataset.eventOpts ||
dataset['event-opts']); // 支付宝 web-view 组件 dataset 非驼峰
if (!eventOpts) {
return console.warn('事件信息不存在');
}
// [['handle',[1,2,a]],['handle1',[1,2,a]]]
const eventType = event.type;
const ret = [];
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 && matchEventType(eventType, type)) {
eventsArray.forEach((eventArray) => {
const methodName = eventArray[0];
if (methodName) {
let handlerCtx = this.$vm;
if (handlerCtx.$options.generic &&
handlerCtx.$parent &&
handlerCtx.$parent.$parent) {
// mp-weixin,mp-toutiao 抽象节点模拟 scoped slots
handlerCtx = handlerCtx.$parent.$parent;
}
if (methodName === '$emit') {
handlerCtx.$emit.apply(handlerCtx, processEventArgs(this.$vm, event, eventArray[1], eventArray[2], isCustom, methodName));
return;
}
const handler = handlerCtx[methodName];
if (!isFunction(handler)) {
throw new Error(` _vm.${methodName} is not a function`);
}
if (isOnce) {
if (handler.once) {
return;
}
handler.once = true;
}
let params = processEventArgs(this.$vm, event, eventArray[1], eventArray[2], isCustom, methodName);
params = Array.isArray(params) ? params : [];
// 参数尾部增加原始事件对象用于复杂表达式内获取额外数据
if (/=\s*\S+\.eventParams\s*\|\|\s*\S+\[['"]event-params['"]\]/.test(handler.toString())) {
// eslint-disable-next-line no-sparse-arrays
params = params.concat([, , , , , , , , , , event]);
}
ret.push(handler.apply(handlerCtx, params));
}
});
}
});
if (eventType === 'input' &&
ret.length === 1 &&
typeof ret[0] !== 'undefined') {
return ret[0];
}
}
function parseComponent(vueOptions, { parse, mocks, isPage, initRelation, handleLink, initLifetimes, }) {
vueOptions = vueOptions.default || vueOptions;
const options = {
......@@ -1060,7 +761,6 @@ function parseComponent(vueOptions, { parse, mocks, isPage, initRelation, handle
},
methods: {
__l: handleLink,
__e: handleEvent,
},
};
if (__VUE_OPTIONS_API__) {
......@@ -1175,7 +875,7 @@ function initLifetimes({ mocks, isPage, initRelation, vueOptions, }) {
}, {
mpType: isPage(mpInstance) ? 'page' : 'component',
mpInstance,
slots: properties.vueSlots,
slots: properties.vS,
parentComponent: relationOptions.parent && relationOptions.parent.$,
onBeforeSetup(instance, options) {
initRefs(instance, mpInstance);
......
import { isPlainObject, hasOwn, isArray, extend, hyphenate, isObject, toNumber, isFunction, NOOP, camelize } from '@vue/shared';
import { onUnmounted, injectHook, ref } from 'vue';
import { isPlainObject, extend, hyphenate, isObject, isArray, hasOwn, toNumber, isFunction, camelize } from '@vue/shared';
import { injectHook, ref } from 'vue';
const encode = encodeURIComponent;
function stringifyQuery(obj, encodeStr = encode) {
......@@ -21,6 +21,18 @@ function stringifyQuery(obj, encodeStr = encode) {
return res ? `?${res}` : '';
}
function getDataByPath(obj, path) {
const parts = path.split('.');
const key = parts[0];
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getDataByPath(obj[key], parts.slice(1).join('.'));
}
function cache(fn) {
const cache = Object.create(null);
return (str) => {
......@@ -129,102 +141,8 @@ function getEventChannel(id) {
return eventChannelStack.shift();
}
function initBehavior(options) {
return Behavior(options);
}
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.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs(instance, mpInstance) {
Object.defineProperty(instance, 'refs', {
get() {
const $refs = {};
selectAllComponents(mpInstance, '.v-r', $refs);
const forComponents = mpInstance.selectAllComponents('.v-r-i-f');
forComponents.forEach((component) => {
const ref = component.dataset.ref;
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;
}
}
}
function getTarget(obj, path) {
const parts = path.split('.');
let key = parts[0];
if (key.indexOf('__$n') === 0) {
//number index
key = parseInt(key.replace('__$n', ''));
}
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getTarget(obj[key], parts.slice(1).join('.'));
}
function getValue(dataPath, target) {
return getTarget(target || this, dataPath);
return getDataByPath(target || this, dataPath);
}
function getClass(dynamicClass, staticClass) {
return renderClass(staticClass, dynamicClass);
......@@ -420,9 +338,6 @@ function initBaseInstance(instance, options) {
}
function initComponentInstance(instance, options) {
initBaseInstance(instance, options);
{
initScopedSlotsParams(instance);
}
const ctx = instance.ctx;
MP_METHODS.forEach((method) => {
ctx[method] = function (...args) {
......@@ -469,53 +384,6 @@ function callHook(name, args) {
}
const hooks = this.$[name];
return hooks && invokeArrayFns(hooks, args);
}
const center = {};
const parents = {};
function initScopedSlotsParams(instance) {
const ctx = instance.ctx;
ctx.$hasScopedSlotsParams = function (vueId) {
const has = center[vueId];
if (!has) {
parents[vueId] = this;
onUnmounted(() => {
delete parents[vueId];
}, instance);
}
return has;
};
ctx.$getScopedSlotsParams = function (vueId, name, key) {
const data = center[vueId];
if (data) {
const object = data[name] || {};
return key ? object[key] : object;
}
else {
parents[vueId] = this;
onUnmounted(() => {
delete parents[vueId];
}, instance);
}
};
ctx.$setScopedSlotsParams = function (name, value) {
const vueIds = instance.attrs.vueId;
if (vueIds) {
const vueId = vueIds.split(',')[0];
const object = (center[vueId] = center[vueId] || {});
object[name] = value;
if (parents[vueId]) {
parents[vueId].$forceUpdate();
}
}
};
onUnmounted(function () {
const propsData = instance.attrs;
const vueId = propsData && propsData.vueId;
if (vueId) {
delete center[vueId];
delete parents[vueId];
}
}, instance);
}
const PAGE_HOOKS = [
......@@ -633,6 +501,85 @@ function initLocale(appVm) {
});
}
function initBehavior(options) {
return Behavior(options);
}
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.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs(instance, mpInstance) {
Object.defineProperty(instance, 'refs', {
get() {
const $refs = {};
selectAllComponents(mpInstance, '.v-r', $refs);
const forComponents = mpInstance.selectAllComponents('.v-r-i-f');
forComponents.forEach((component) => {
const ref = component.dataset.ref;
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 PROP_TYPES = [String, Number, Boolean, Object, Array, null];
function createObserver(name) {
return function observer(newVal) {
......@@ -656,7 +603,7 @@ function initDefaultProps(isBehavior = false) {
value: '',
};
// 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
properties.vueSlots = {
properties.vS = {
type: null,
value: [],
observer: function (newVal) {
......@@ -789,252 +736,6 @@ function applyOptions(componentOptions, vueOptions, initBehavior) {
componentOptions.behaviors = initBehaviors(vueOptions, initBehavior);
}
function getExtraValue(instance, dataPathsArray) {
let context = instance;
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];
let vFor;
if (Number.isInteger(dataPath)) {
vFor = dataPath;
}
else if (!dataPath) {
vFor = context;
}
else if (typeof dataPath === 'string' && dataPath) {
if (dataPath.indexOf('#s#') === 0) {
vFor = dataPath.substr(3);
}
else {
vFor = getTarget(context, dataPath);
}
}
if (Number.isInteger(vFor)) {
context = value;
}
else if (!propPath) {
context = vFor[value];
}
else {
if (isArray(vFor)) {
context = vFor.find((vForItem) => {
return getTarget(vForItem, propPath) === value;
});
}
else if (isPlainObject(vFor)) {
context = Object.keys(vFor).find((vForKey) => {
return getTarget(vFor[vForKey], propPath) === value;
});
}
else {
console.error('v-for 暂不支持循环数据:', vFor);
}
}
if (valuePath) {
context = getTarget(context, valuePath);
}
}
});
return context;
}
function processEventExtra(instance, extra, event) {
const extraObj = {};
if (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] = instance;
}
else {
if (dataPath === '$event') {
// $event
extraObj['$' + index] = event;
}
else if (dataPath === 'arguments') {
if (event.detail && event.detail.__args__) {
extraObj['$' + index] = event.detail.__args__;
}
else {
extraObj['$' + index] = [event];
}
}
else if (dataPath.indexOf('$event.') === 0) {
// $event.target.value
extraObj['$' + index] = getTarget(event, dataPath.replace('$event.', ''));
}
else {
extraObj['$' + index] = getTarget(instance, dataPath);
}
}
}
else {
extraObj['$' + index] = getExtraValue(instance, 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(instance, 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(instance, 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 (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;
}
function wrapper(event) {
event.stopPropagation = NOOP;
event.preventDefault = NOOP;
event.target = event.target || {};
if (!hasOwn(event, 'detail')) {
event.detail = {};
}
if (hasOwn(event, 'markerId')) {
event.detail = typeof event.detail === 'object' ? event.detail : {};
event.detail.markerId = event.markerId;
}
if (isPlainObject(event.detail)) {
event.target = extend({}, event.target, event.detail);
}
return event;
}
const ONCE = '~';
const CUSTOM = '^';
function matchEventType(eventType, optType) {
return (eventType === optType ||
(optType === 'regionchange' &&
(eventType === 'begin' || eventType === 'end')));
}
function handleEvent(event) {
event = wrapper(event);
// [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]]
const dataset = (event.currentTarget || event.target).dataset;
if (!dataset) {
return console.warn('事件信息不存在');
}
const eventOpts = (dataset.eventOpts ||
dataset['event-opts']); // 支付宝 web-view 组件 dataset 非驼峰
if (!eventOpts) {
return console.warn('事件信息不存在');
}
// [['handle',[1,2,a]],['handle1',[1,2,a]]]
const eventType = event.type;
const ret = [];
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 && matchEventType(eventType, type)) {
eventsArray.forEach((eventArray) => {
const methodName = eventArray[0];
if (methodName) {
let handlerCtx = this.$vm;
if (handlerCtx.$options.generic &&
handlerCtx.$parent &&
handlerCtx.$parent.$parent) {
// mp-weixin,mp-toutiao 抽象节点模拟 scoped slots
handlerCtx = handlerCtx.$parent.$parent;
}
if (methodName === '$emit') {
handlerCtx.$emit.apply(handlerCtx, processEventArgs(this.$vm, event, eventArray[1], eventArray[2], isCustom, methodName));
return;
}
const handler = handlerCtx[methodName];
if (!isFunction(handler)) {
throw new Error(` _vm.${methodName} is not a function`);
}
if (isOnce) {
if (handler.once) {
return;
}
handler.once = true;
}
let params = processEventArgs(this.$vm, event, eventArray[1], eventArray[2], isCustom, methodName);
params = Array.isArray(params) ? params : [];
// 参数尾部增加原始事件对象用于复杂表达式内获取额外数据
if (/=\s*\S+\.eventParams\s*\|\|\s*\S+\[['"]event-params['"]\]/.test(handler.toString())) {
// eslint-disable-next-line no-sparse-arrays
params = params.concat([, , , , , , , , , , event]);
}
ret.push(handler.apply(handlerCtx, params));
}
});
}
});
if (eventType === 'input' &&
ret.length === 1 &&
typeof ret[0] !== 'undefined') {
return ret[0];
}
}
function parseComponent(vueOptions, { parse, mocks, isPage, initRelation, handleLink, initLifetimes, }) {
vueOptions = vueOptions.default || vueOptions;
const options = {
......@@ -1060,7 +761,6 @@ function parseComponent(vueOptions, { parse, mocks, isPage, initRelation, handle
},
methods: {
__l: handleLink,
__e: handleEvent,
},
};
if (__VUE_OPTIONS_API__) {
......@@ -1175,7 +875,7 @@ function initLifetimes({ mocks, isPage, initRelation, vueOptions, }) {
}, {
mpType: isPage(mpInstance) ? 'page' : 'component',
mpInstance,
slots: properties.vueSlots,
slots: properties.vS,
parentComponent: relationOptions.parent && relationOptions.parent.$,
onBeforeSetup(instance, options) {
initRefs(instance, mpInstance);
......
import { isPlainObject, hasOwn, isArray, extend, hyphenate, isObject, toNumber, isFunction, NOOP, camelize } from '@vue/shared';
import { onUnmounted, injectHook, ref } from 'vue';
import { isPlainObject, extend, hyphenate, isObject, isArray, hasOwn, toNumber, isFunction, camelize } from '@vue/shared';
import { injectHook, ref } from 'vue';
const encode = encodeURIComponent;
function stringifyQuery(obj, encodeStr = encode) {
......@@ -21,6 +21,18 @@ function stringifyQuery(obj, encodeStr = encode) {
return res ? `?${res}` : '';
}
function getDataByPath(obj, path) {
const parts = path.split('.');
const key = parts[0];
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getDataByPath(obj[key], parts.slice(1).join('.'));
}
function cache(fn) {
const cache = Object.create(null);
return (str) => {
......@@ -129,102 +141,8 @@ function getEventChannel(id) {
return eventChannelStack.shift();
}
function initBehavior(options) {
return Behavior(options);
}
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.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs(instance, mpInstance) {
Object.defineProperty(instance, 'refs', {
get() {
const $refs = {};
selectAllComponents(mpInstance, '.v-r', $refs);
const forComponents = mpInstance.selectAllComponents('.v-r-i-f');
forComponents.forEach((component) => {
const ref = component.dataset.ref;
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;
}
}
}
function getTarget(obj, path) {
const parts = path.split('.');
let key = parts[0];
if (key.indexOf('__$n') === 0) {
//number index
key = parseInt(key.replace('__$n', ''));
}
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getTarget(obj[key], parts.slice(1).join('.'));
}
function getValue(dataPath, target) {
return getTarget(target || this, dataPath);
return getDataByPath(target || this, dataPath);
}
function getClass(dynamicClass, staticClass) {
return renderClass(staticClass, dynamicClass);
......@@ -420,9 +338,6 @@ function initBaseInstance(instance, options) {
}
function initComponentInstance(instance, options) {
initBaseInstance(instance, options);
{
initScopedSlotsParams(instance);
}
const ctx = instance.ctx;
MP_METHODS.forEach((method) => {
ctx[method] = function (...args) {
......@@ -469,53 +384,6 @@ function callHook(name, args) {
}
const hooks = this.$[name];
return hooks && invokeArrayFns(hooks, args);
}
const center = {};
const parents = {};
function initScopedSlotsParams(instance) {
const ctx = instance.ctx;
ctx.$hasScopedSlotsParams = function (vueId) {
const has = center[vueId];
if (!has) {
parents[vueId] = this;
onUnmounted(() => {
delete parents[vueId];
}, instance);
}
return has;
};
ctx.$getScopedSlotsParams = function (vueId, name, key) {
const data = center[vueId];
if (data) {
const object = data[name] || {};
return key ? object[key] : object;
}
else {
parents[vueId] = this;
onUnmounted(() => {
delete parents[vueId];
}, instance);
}
};
ctx.$setScopedSlotsParams = function (name, value) {
const vueIds = instance.attrs.vueId;
if (vueIds) {
const vueId = vueIds.split(',')[0];
const object = (center[vueId] = center[vueId] || {});
object[name] = value;
if (parents[vueId]) {
parents[vueId].$forceUpdate();
}
}
};
onUnmounted(function () {
const propsData = instance.attrs;
const vueId = propsData && propsData.vueId;
if (vueId) {
delete center[vueId];
delete parents[vueId];
}
}, instance);
}
const PAGE_HOOKS = [
......@@ -636,6 +504,85 @@ function initLocale(appVm) {
});
}
function initBehavior(options) {
return Behavior(options);
}
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.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs(instance, mpInstance) {
Object.defineProperty(instance, 'refs', {
get() {
const $refs = {};
selectAllComponents(mpInstance, '.v-r', $refs);
const forComponents = mpInstance.selectAllComponents('.v-r-i-f');
forComponents.forEach((component) => {
const ref = component.dataset.ref;
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 PROP_TYPES = [String, Number, Boolean, Object, Array, null];
function createObserver(name) {
return function observer(newVal) {
......@@ -665,7 +612,7 @@ function initDefaultProps(isBehavior = false) {
};
}
// 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
properties.vueSlots = {
properties.vS = {
type: null,
value: [],
observer: function (newVal) {
......@@ -798,252 +745,6 @@ function applyOptions(componentOptions, vueOptions, initBehavior) {
componentOptions.behaviors = initBehaviors(vueOptions, initBehavior);
}
function getExtraValue(instance, dataPathsArray) {
let context = instance;
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];
let vFor;
if (Number.isInteger(dataPath)) {
vFor = dataPath;
}
else if (!dataPath) {
vFor = context;
}
else if (typeof dataPath === 'string' && dataPath) {
if (dataPath.indexOf('#s#') === 0) {
vFor = dataPath.substr(3);
}
else {
vFor = getTarget(context, dataPath);
}
}
if (Number.isInteger(vFor)) {
context = value;
}
else if (!propPath) {
context = vFor[value];
}
else {
if (isArray(vFor)) {
context = vFor.find((vForItem) => {
return getTarget(vForItem, propPath) === value;
});
}
else if (isPlainObject(vFor)) {
context = Object.keys(vFor).find((vForKey) => {
return getTarget(vFor[vForKey], propPath) === value;
});
}
else {
console.error('v-for 暂不支持循环数据:', vFor);
}
}
if (valuePath) {
context = getTarget(context, valuePath);
}
}
});
return context;
}
function processEventExtra(instance, extra, event) {
const extraObj = {};
if (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] = instance;
}
else {
if (dataPath === '$event') {
// $event
extraObj['$' + index] = event;
}
else if (dataPath === 'arguments') {
if (event.detail && event.detail.__args__) {
extraObj['$' + index] = event.detail.__args__;
}
else {
extraObj['$' + index] = [event];
}
}
else if (dataPath.indexOf('$event.') === 0) {
// $event.target.value
extraObj['$' + index] = getTarget(event, dataPath.replace('$event.', ''));
}
else {
extraObj['$' + index] = getTarget(instance, dataPath);
}
}
}
else {
extraObj['$' + index] = getExtraValue(instance, 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(instance, 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(instance, 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 (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;
}
function wrapper(event) {
event.stopPropagation = NOOP;
event.preventDefault = NOOP;
event.target = event.target || {};
if (!hasOwn(event, 'detail')) {
event.detail = {};
}
if (hasOwn(event, 'markerId')) {
event.detail = typeof event.detail === 'object' ? event.detail : {};
event.detail.markerId = event.markerId;
}
if (isPlainObject(event.detail)) {
event.target = extend({}, event.target, event.detail);
}
return event;
}
const ONCE = '~';
const CUSTOM = '^';
function matchEventType(eventType, optType) {
return (eventType === optType ||
(optType === 'regionchange' &&
(eventType === 'begin' || eventType === 'end')));
}
function handleEvent(event) {
event = wrapper(event);
// [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]]
const dataset = (event.currentTarget || event.target).dataset;
if (!dataset) {
return console.warn('事件信息不存在');
}
const eventOpts = (dataset.eventOpts ||
dataset['event-opts']); // 支付宝 web-view 组件 dataset 非驼峰
if (!eventOpts) {
return console.warn('事件信息不存在');
}
// [['handle',[1,2,a]],['handle1',[1,2,a]]]
const eventType = event.type;
const ret = [];
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 && matchEventType(eventType, type)) {
eventsArray.forEach((eventArray) => {
const methodName = eventArray[0];
if (methodName) {
let handlerCtx = this.$vm;
if (handlerCtx.$options.generic &&
handlerCtx.$parent &&
handlerCtx.$parent.$parent) {
// mp-weixin,mp-toutiao 抽象节点模拟 scoped slots
handlerCtx = handlerCtx.$parent.$parent;
}
if (methodName === '$emit') {
handlerCtx.$emit.apply(handlerCtx, processEventArgs(this.$vm, event, eventArray[1], eventArray[2], isCustom, methodName));
return;
}
const handler = handlerCtx[methodName];
if (!isFunction(handler)) {
throw new Error(` _vm.${methodName} is not a function`);
}
if (isOnce) {
if (handler.once) {
return;
}
handler.once = true;
}
let params = processEventArgs(this.$vm, event, eventArray[1], eventArray[2], isCustom, methodName);
params = Array.isArray(params) ? params : [];
// 参数尾部增加原始事件对象用于复杂表达式内获取额外数据
if (/=\s*\S+\.eventParams\s*\|\|\s*\S+\[['"]event-params['"]\]/.test(handler.toString())) {
// eslint-disable-next-line no-sparse-arrays
params = params.concat([, , , , , , , , , , event]);
}
ret.push(handler.apply(handlerCtx, params));
}
});
}
});
if (eventType === 'input' &&
ret.length === 1 &&
typeof ret[0] !== 'undefined') {
return ret[0];
}
}
function parseComponent(vueOptions, { parse, mocks, isPage, initRelation, handleLink, initLifetimes, }) {
vueOptions = vueOptions.default || vueOptions;
const options = {
......@@ -1069,7 +770,6 @@ function parseComponent(vueOptions, { parse, mocks, isPage, initRelation, handle
},
methods: {
__l: handleLink,
__e: handleEvent,
},
};
if (__VUE_OPTIONS_API__) {
......@@ -1271,7 +971,7 @@ function initLifetimes$1({ mocks, isPage, initRelation, vueOptions, }) {
}, {
mpType,
mpInstance,
slots: properties.vueSlots,
slots: properties.vS,
parentComponent: relationOptions.parent && relationOptions.parent.$,
onBeforeSetup(instance, options) {
initRefs(instance, mpInstance);
......
......@@ -43,7 +43,7 @@ export function initLifetimes({
{
mpType,
mpInstance,
slots: properties.vueSlots,
slots: properties.vS, // vueSlots
parentComponent: relationOptions.parent && relationOptions.parent.$,
onBeforeSetup(
instance: ComponentInternalInstance,
......
import { initScopedSlotDataByPath } from '../src/helpers/withScopedSlot'
const tests: Record<string, Data> = {
a: {
a: [{ a: 1 }],
},
'a.b': {
a: {
b: [{ a: 1 }],
},
},
'a.0.b': {
a: [
{
b: [{ a: 1 }],
},
],
},
'a.1.b': {
a: createArrayData(1, { b: [{ a: 1 }] }),
},
'a.1.b.2.c.b': {
a: createArrayData(1, { b: createArrayData(2, { c: { b: [{ a: 1 }] } }) }),
},
}
function createArrayData(index: number, data: Data) {
const arr = []
arr[index] = data
return arr
}
describe('uni-mp-vue: withScopedSlot', () => {
Object.keys(tests).forEach((path) => {
test(path, () => {
const data = {}
initScopedSlotDataByPath(path, { a: 1 }, data)
expect(data).toMatchObject(tests[path])
})
})
})
......@@ -10,7 +10,7 @@
}
]
},
"external": ["@vue/shared"],
"external": ["@vue/shared", "@dcloudio/uni-shared"],
"replacements": {
"__PLATFORM__": "\"mp-weixin\""
}
......
import { extend, isSymbol, isObject, toRawType, def, hasChanged, isArray, isFunction, NOOP, remove, toHandlerKey, camelize, capitalize, isString, normalizeClass, normalizeStyle, isOn, isPromise, EMPTY_OBJ, isSet, isMap, isPlainObject, invokeArrayFns, hasOwn, NO, isIntegerKey, toNumber, hyphenate, isReservedProp, EMPTY_ARR, makeMap, toTypeString, stringifyStyle as stringifyStyle$1 } from '@vue/shared';
import { extend, isSymbol, isObject, toRawType, def, hasChanged, isArray, isFunction, NOOP, remove, toHandlerKey, camelize, capitalize, isString, normalizeClass, normalizeStyle, isOn, isPromise, EMPTY_OBJ, isSet, isMap, isPlainObject, toTypeString, invokeArrayFns, hasOwn, NO, isIntegerKey, toNumber, hyphenate, isReservedProp, EMPTY_ARR, makeMap, stringifyStyle as stringifyStyle$1 } from '@vue/shared';
export { camelize as c, camelize, extend as e, hyphenate as h, normalizeClass as n, normalizeClass, normalizeProps, normalizeStyle, toDisplayString as t, toDisplayString, toHandlerKey } from '@vue/shared';
// lifecycle
// App and Page
const ON_SHOW = 'onShow';
const ON_HIDE = 'onHide';
//App
const ON_LAUNCH = 'onLaunch';
const ON_ERROR = 'onError';
const ON_THEME_CHANGE = 'onThemeChange';
const ON_PAGE_NOT_FOUND = 'onPageNotFound';
const ON_UNHANDLE_REJECTION = 'onUnhandledRejection';
//Page
const ON_LOAD = 'onLoad';
const ON_READY = 'onReady';
const ON_UNLOAD = 'onUnload';
const ON_RESIZE = 'onResize';
const ON_BACK_PRESS = 'onBackPress';
const ON_PAGE_SCROLL = 'onPageScroll';
const ON_TAB_ITEM_TAP = 'onTabItemTap';
const ON_REACH_BOTTOM = 'onReachBottom';
const ON_PULL_DOWN_REFRESH = 'onPullDownRefresh';
const ON_SHARE_TIMELINE = 'onShareTimeline';
const ON_ADD_TO_FAVORITES = 'onAddToFavorites';
const ON_SHARE_APP_MESSAGE = 'onShareAppMessage';
// navigationBar
const ON_NAVIGATION_BAR_BUTTON_TAP = 'onNavigationBarButtonTap';
const ON_NAVIGATION_BAR_SEARCH_INPUT_CLICKED = 'onNavigationBarSearchInputClicked';
const ON_NAVIGATION_BAR_SEARCH_INPUT_CHANGED = 'onNavigationBarSearchInputChanged';
const ON_NAVIGATION_BAR_SEARCH_INPUT_CONFIRMED = 'onNavigationBarSearchInputConfirmed';
const ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED = 'onNavigationBarSearchInputFocusChanged';
const PAGE_HOOKS = [
ON_SHOW,
ON_HIDE,
ON_BACK_PRESS,
ON_PAGE_SCROLL,
ON_TAB_ITEM_TAP,
ON_REACH_BOTTOM,
ON_PULL_DOWN_REFRESH,
];
function isRootHook(name) {
return PAGE_HOOKS.indexOf(name) > -1;
}
const UniLifecycleHooks = [
ON_SHOW,
ON_HIDE,
ON_LAUNCH,
ON_ERROR,
ON_THEME_CHANGE,
ON_PAGE_NOT_FOUND,
ON_UNHANDLE_REJECTION,
ON_LOAD,
ON_READY,
ON_UNLOAD,
ON_RESIZE,
ON_BACK_PRESS,
ON_PAGE_SCROLL,
ON_TAB_ITEM_TAP,
ON_REACH_BOTTOM,
ON_PULL_DOWN_REFRESH,
ON_SHARE_TIMELINE,
ON_ADD_TO_FAVORITES,
ON_SHARE_APP_MESSAGE,
ON_NAVIGATION_BAR_BUTTON_TAP,
ON_NAVIGATION_BAR_SEARCH_INPUT_CLICKED,
ON_NAVIGATION_BAR_SEARCH_INPUT_CHANGED,
ON_NAVIGATION_BAR_SEARCH_INPUT_CONFIRMED,
ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED,
];
import { isRootHook, ON_ERROR, UniLifecycleHooks } from '@dcloudio/uni-shared';
function warn(msg, ...args) {
console.warn(`[Vue warn] ${msg}`, ...args);
......@@ -4547,7 +4479,7 @@ function getMPInstanceData(instance, keys) {
});
return ret;
}
function patch(instance, data) {
function patch(instance, data, oldData) {
if (!data) {
return;
}
......@@ -4563,7 +4495,7 @@ function patch(instance, data) {
const mpInstance = ctx.$scope;
const keys = Object.keys(data);
// data.__webviewId__ = mpInstance.data.__webviewId__
const diffData = diff(data, getMPInstanceData(mpInstance, keys));
const diffData = diff(data, oldData || getMPInstanceData(mpInstance, keys));
if (Object.keys(diffData).length) {
console.log('[' +
+new Date() +
......@@ -5031,10 +4963,27 @@ function createInvoker(initialValue, instance) {
invoker.value = initialValue;
return invoker;
}
function patchMPEvent(e) {
if (e.type && e.target) {
e.stopPropagation = () => { };
e.preventDefault = () => { };
function patchMPEvent(event) {
if (event.type && event.target) {
event.preventDefault = NOOP;
event.stopPropagation = NOOP;
event.stopImmediatePropagation = NOOP;
if (!hasOwn(event, 'detail')) {
event.detail = {};
}
if (hasOwn(event, 'markerId')) {
event.detail = typeof event.detail === 'object' ? event.detail : {};
event.detail.markerId = event.markerId;
}
// mp-baidu,checked=>value
if (isPlainObject(event.detail) &&
hasOwn(event.detail, 'checked') &&
!hasOwn(event.detail, 'value')) {
event.detail.value = event.detail.checked;
}
if (isPlainObject(event.detail)) {
event.target = extend({}, event.target, event.detail);
}
}
}
function patchStopImmediatePropagation(e, value) {
......@@ -5051,6 +5000,64 @@ function patchStopImmediatePropagation(e, value) {
}
}
function renderSlot(name, props = {}) {
const instance = getCurrentInstance();
const vueIds = instance.attrs.vI;
if (!vueIds) {
return;
}
const invoker = findScopedSlotInvoker(vueIds.split(',')[0], instance);
if (invoker) {
invoker(name, props);
}
else {
if (process.env.NODE_ENV !== 'production') {
console.error('scoped slot invoker not found', vueIds, name, props);
}
}
}
function findScopedSlotInvoker(vueId, instance) {
let parent = instance.parent;
while (parent) {
const invokers = parent.$ssi;
if (invokers && invokers[vueId]) {
return invokers[vueId];
}
parent = parent.parent;
}
}
//@ts-ignore
function withScopedSlot(fn, { name, path, vueId, }) {
fn.path = path;
const instance = getCurrentInstance();
const scopedSlots = (instance.$ssi ||
(instance.$ssi = {}));
const invoker = scopedSlots[vueId] ||
(scopedSlots[vueId] = createScopedSlotInvoker(instance));
if (!invoker.slots[name]) {
invoker.slots[name] = {
data: {},
fn,
};
}
else {
invoker.slots[name].fn = fn;
}
// 返回单元素数组,因为 scoped slot 被转换成了 for 循环
return [invoker.slots[name].data];
}
function createScopedSlotInvoker(instance) {
const invoker = (slotName, args) => {
const slot = invoker.slots[slotName];
slot.data = slot.fn(args, 0, 0);
// TODO 简单的 forceUpdate,理论上,可以仅对 scoped slot 部分数据 diff 更新
instance.proxy.$forceUpdate();
};
invoker.slots = {};
return invoker;
}
function stringifyStyle(value) {
if (isString(value)) {
return value;
......@@ -5068,4 +5075,4 @@ function createApp(rootComponent, rootProps = null) {
}
const createSSRApp = createApp;
export { EffectScope, ReactiveEffect, callWithAsyncErrorHandling, callWithErrorHandling, computed, createApp, createSSRApp, createVNode$1 as createVNode, createVueApp, customRef, defineComponent, defineEmits, defineExpose, defineProps, effect, effectScope, vFor as f, getCurrentInstance, getCurrentScope, inject, injectHook, isInSSRComponentSetup, isProxy, isReactive, isReadonly, isRef, logError, markRaw, mergeDefaults, mergeProps, nextTick, vOn as o, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onUnmounted, onUpdated, provide, proxyRefs, queuePostFlushCb, reactive, readonly, ref, resolveComponent, resolveDirective, resolveFilter, stringifyStyle as s, setupDevtoolsPlugin, shallowReactive, shallowReadonly, shallowRef, stop, toHandlers, toRaw, toRef, toRefs, triggerRef, unref, useAttrs, useSSRContext, useSlots, version, warn$1 as warn, watch, watchEffect, watchPostEffect, watchSyncEffect, withAsyncContext, withCtx, withDefaults, withDirectives, withModifiers, withScopeId };
export { EffectScope, ReactiveEffect, callWithAsyncErrorHandling, callWithErrorHandling, computed, createApp, createSSRApp, createVNode$1 as createVNode, createVueApp, customRef, defineComponent, defineEmits, defineExpose, defineProps, effect, effectScope, vFor as f, getCurrentInstance, getCurrentScope, inject, injectHook, isInSSRComponentSetup, isProxy, isReactive, isReadonly, isRef, logError, markRaw, mergeDefaults, mergeProps, nextTick, vOn as o, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onUnmounted, onUpdated, patch, provide, proxyRefs, queuePostFlushCb, renderSlot as r, reactive, readonly, ref, resolveComponent, resolveDirective, resolveFilter, stringifyStyle as s, setupDevtoolsPlugin, shallowReactive, shallowReadonly, shallowRef, stop, toHandlers, toRaw, toRef, toRefs, triggerRef, unref, useAttrs, useSSRContext, useSlots, version, withScopedSlot as w, warn$1 as warn, watch, watchEffect, watchPostEffect, watchSyncEffect, withAsyncContext, withCtx, withDefaults, withDirectives, withModifiers, withScopeId };
......@@ -4479,7 +4479,7 @@ function getMPInstanceData(instance, keys) {
});
return ret;
}
function patch(instance, data) {
function patch(instance, data, oldData) {
if (!data) {
return;
}
......@@ -4495,7 +4495,7 @@ function patch(instance, data) {
const mpInstance = ctx.$scope;
const keys = Object.keys(data);
// data.__webviewId__ = mpInstance.data.__webviewId__
const diffData = diff(data, getMPInstanceData(mpInstance, keys));
const diffData = diff(data, oldData || getMPInstanceData(mpInstance, keys));
if (Object.keys(diffData).length) {
console.log('[' +
+new Date() +
......@@ -4720,4 +4720,4 @@ function createVueApp(rootComponent, rootProps = null) {
function withModifiers() { }
function createVNode$1() { }
export { EffectScope, ReactiveEffect, callWithAsyncErrorHandling, callWithErrorHandling, computed, createVNode$1 as createVNode, createVueApp, customRef, defineComponent, defineEmits, defineExpose, defineProps, effect, effectScope, getCurrentInstance, getCurrentScope, inject, injectHook, isInSSRComponentSetup, isProxy, isReactive, isReadonly, isRef, logError, markRaw, mergeDefaults, mergeProps, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onUnmounted, onUpdated, provide, proxyRefs, queuePostFlushCb, reactive, readonly, ref, resolveComponent, resolveDirective, resolveFilter, shallowReactive, shallowReadonly, shallowRef, stop, toHandlers, toRaw, toRef, toRefs, triggerRef, unref, useAttrs, useSSRContext, useSlots, version, warn$1 as warn, watch, watchEffect, watchPostEffect, watchSyncEffect, withAsyncContext, withCtx, withDefaults, withDirectives, withModifiers, withScopeId };
export { EffectScope, ReactiveEffect, callWithAsyncErrorHandling, callWithErrorHandling, computed, createVNode$1 as createVNode, createVueApp, customRef, defineComponent, defineEmits, defineExpose, defineProps, effect, effectScope, getCurrentInstance, getCurrentScope, inject, injectHook, isInSSRComponentSetup, isProxy, isReactive, isReadonly, isRef, logError, markRaw, mergeDefaults, mergeProps, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onUnmounted, onUpdated, patch, provide, proxyRefs, queuePostFlushCb, reactive, readonly, ref, resolveComponent, resolveDirective, resolveFilter, shallowReactive, shallowReadonly, shallowRef, stop, toHandlers, toRaw, toRef, toRefs, triggerRef, unref, useAttrs, useSSRContext, useSlots, version, warn$1 as warn, watch, watchEffect, watchPostEffect, watchSyncEffect, withAsyncContext, withCtx, withDefaults, withDirectives, withModifiers, withScopeId };
export { vFor } from './vFor'
export { vOn } from './vOn'
export { renderSlot } from './renderSlot'
export { withScopedSlot } from './withScopedSlot'
export { stringifyStyle } from './style'
export { setupDevtoolsPlugin } from './devtools'
import type { ComponentInternalInstance } from 'vue'
import type { ScopedSlotInvokers } from './withScopedSlot'
import { getCurrentInstance } from 'vue'
export function renderSlot(name: string, props: Data = {}) {
const instance = getCurrentInstance() as ComponentInternalInstance
const vueIds = instance.attrs.vI as string
if (!vueIds) {
return
}
const invoker = findScopedSlotInvoker(vueIds.split(',')[0], instance)
if (invoker) {
invoker(name, props)
} else {
if (process.env.NODE_ENV !== 'production') {
console.error('scoped slot invoker not found', vueIds, name, props)
}
}
}
function findScopedSlotInvoker(
vueId: string,
instance: ComponentInternalInstance
) {
let parent = instance.parent
while (parent) {
const invokers = (parent as any).$ssi as ScopedSlotInvokers
if (invokers && invokers[vueId]) {
return invokers[vueId]
}
parent = parent.parent
}
}
import { isArray } from '@vue/shared'
import { extend, isArray, isPlainObject, hasOwn, NOOP } from '@vue/shared'
import {
callWithAsyncErrorHandling,
ComponentInternalInstance,
......@@ -8,7 +8,8 @@ import {
type EventValue = Function | Function[]
interface Invoker extends EventListener {
interface Invoker {
(evt: MPEvent): void
value: EventValue
}
......@@ -38,17 +39,20 @@ export function vOn(value: EventValue | undefined) {
return name
}
interface MPEvent extends Event {
detail: {
interface MPEvent extends WechatMiniprogram.BaseEvent {
detail: Record<string, any> & {
__args__?: unknown[]
}
preventDefault: () => void
stopPropagation: () => void
stopImmediatePropagation: () => void
}
function createInvoker(
initialValue: EventValue,
instance: ComponentInternalInstance | null
) {
const invoker: Invoker = (e: Event) => {
const invoker: Invoker = (e: MPEvent) => {
patchMPEvent(e)
let args: unknown[] = [e]
if ((e as MPEvent).detail && (e as MPEvent).detail.__args__) {
......@@ -65,15 +69,36 @@ function createInvoker(
return invoker
}
function patchMPEvent(e: Event) {
if (e.type && e.target) {
e.stopPropagation = () => {}
e.preventDefault = () => {}
function patchMPEvent(event: MPEvent) {
if (event.type && event.target) {
event.preventDefault = NOOP
event.stopPropagation = NOOP
event.stopImmediatePropagation = NOOP
if (!hasOwn(event, 'detail')) {
event.detail = {}
}
if (hasOwn(event, 'markerId')) {
event.detail = typeof event.detail === 'object' ? event.detail : {}
event.detail.markerId = (event as any).markerId
}
// mp-baidu,checked=>value
if (
isPlainObject(event.detail) &&
hasOwn(event.detail, 'checked') &&
!hasOwn(event.detail, 'value')
) {
event.detail.value = event.detail.checked
}
if (isPlainObject(event.detail)) {
event.target = extend({}, event.target, event.detail)
}
}
}
function patchStopImmediatePropagation(
e: Event,
e: MPEvent,
value: EventValue
): EventValue {
if (isArray(value)) {
......
import type { ComponentInternalInstance } from 'vue'
//@ts-ignore
import { patch, getCurrentInstance } from 'vue'
export interface ScopedSlotInvokers {
[vueId: string]: ScopedSlotInvoker
}
interface ScopedSlotFn {
(args: Data, key: number, index: number): Record<string, any>
path: string
}
interface ScopedSlotInvoker {
(slotName: string, args: Data): void
slots: {
[slotName: string]: {
fn: ScopedSlotFn
data: Data
}
}
}
export function withScopedSlot(
fn: ScopedSlotFn,
{
name,
path,
vueId,
}: {
name: string
path: string
vueId: string
}
) {
fn.path = path
const instance = getCurrentInstance() as ComponentInternalInstance
const scopedSlots = ((instance as any).$ssi ||
(((instance as any).$ssi as ScopedSlotInvokers) = {})) as ScopedSlotInvokers
const invoker =
scopedSlots[vueId] ||
(scopedSlots[vueId] = createScopedSlotInvoker(instance))
if (!invoker.slots[name]) {
invoker.slots[name] = {
data: {},
fn,
}
} else {
invoker.slots[name].fn = fn
}
// 返回单元素数组,因为 scoped slot 被转换成了 for 循环
return [invoker.slots[name].data]
}
function createScopedSlotInvoker(instance: ComponentInternalInstance) {
const invoker: ScopedSlotInvoker = (slotName: string, args: Data) => {
const slot = invoker.slots[slotName]
slot.data = slot.fn(args, 0, 0)
// TODO 简单的 forceUpdate,理论上,可以仅对 scoped slot 部分数据 diff 更新
instance.proxy!.$forceUpdate()
}
invoker.slots = {}
return invoker
}
const nubmerRE = /^\d+$/
function isInteger(str: unknown): str is number {
return nubmerRE.test(str as string)
}
/**
* 暂时没啥用,原本计划仅对 scoped slot 数据部分做 diff 更新,现在还是先简单的 forceUpdate,让宿主整体更新吧
* @param path
* @param scopedSlotData
* @param data
* @returns
*/
export function initScopedSlotDataByPath(
path: string,
scopedSlotData: Data,
data: Data | Data[]
): void {
const parts = path.split('.')
const len = parts.length
const key: string = parts[0]
if (len === 1) {
;(data as Data)[key] = [scopedSlotData]
return
}
const next: string = parts[1]
let nextData: Data | Data[]
if (isInteger(next)) {
// a.0 => {a:[]}
;(data as Data)[key] = nextData = []
} else {
;(data as Data)[key] = nextData = {}
}
return initScopedSlotDataByPath(
parts.slice(1).join('.'),
scopedSlotData,
nextData
)
}
import plugin from './plugin'
// @ts-ignore
import { createVueApp } from '../lib/vue.runtime.esm.js'
import { createVueApp } from 'vue'
export function createApp(rootComponent: unknown, rootProps = null) {
rootComponent && ((rootComponent as any).mpType = 'app')
return createVueApp(rootComponent, rootProps).use(plugin)
......@@ -9,6 +9,8 @@ export const createSSRApp = createApp
export {
vOn as o,
vFor as f,
renderSlot as r,
withScopedSlot as w,
stringifyStyle as s,
setupDevtoolsPlugin,
} from './helpers'
......@@ -20,4 +22,4 @@ export {
toDisplayString as t,
} from '@vue/shared'
// @ts-ignore
export * from '../lib/vue.runtime.esm.js'
export * from 'vue'
import { getCurrentInstance } from 'vue'
export function setState(name: string, value: unknown) {
const { __state } = getCurrentInstance()!
}
......@@ -42,7 +42,7 @@ export function initLifetimes({
{
mpType: isPage(mpInstance) ? 'page' : 'component',
mpInstance,
slots: properties.vueSlots,
slots: properties.vS, // vueSlots
parentComponent: relationOptions.parent && relationOptions.parent.$,
onBeforeSetup(
instance: ComponentInternalInstance,
......
......@@ -413,6 +413,18 @@ function formatAppLog(type, filename, ...args) {
res && console[type](res);
}
function getDataByPath(obj, path) {
const parts = path.split('.');
const key = parts[0];
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getDataByPath(obj[key], parts.slice(1).join('.'));
}
function plusReady(callback) {
if (typeof callback !== 'function') {
return;
......@@ -1229,6 +1241,7 @@ exports.formatAppLog = formatAppLog;
exports.formatDateTime = formatDateTime;
exports.formatLog = formatLog;
exports.getCustomDataset = getCustomDataset;
exports.getDataByPath = getDataByPath;
exports.getEnvLocale = getEnvLocale;
exports.getLen = getLen;
exports.getValueByDataPath = getValueByDataPath;
......
......@@ -181,6 +181,8 @@ export declare function formatLog(module: string, ...args: any[]): string;
export declare function getCustomDataset(el: HTMLElement | HTMLElementWithDataset): DOMStringMap & Record<string, any>;
export declare function getDataByPath(obj: Record<string | number, any>, path: string): unknown;
export declare function getEnvLocale(): string;
export declare function getLen(str?: string): number;
......
......@@ -409,6 +409,18 @@ function formatAppLog(type, filename, ...args) {
res && console[type](res);
}
function getDataByPath(obj, path) {
const parts = path.split('.');
const key = parts[0];
if (!obj) {
obj = {};
}
if (parts.length === 1) {
return obj[key];
}
return getDataByPath(obj[key], parts.slice(1).join('.'));
}
function plusReady(callback) {
if (typeof callback !== 'function') {
return;
......@@ -1118,4 +1130,4 @@ function getEnvLocale() {
return (lang && lang.replace(/[.:].*/, '')) || 'en';
}
export { ACTION_TYPE_ADD_EVENT, ACTION_TYPE_ADD_WXS_EVENT, ACTION_TYPE_CREATE, ACTION_TYPE_EVENT, ACTION_TYPE_INSERT, ACTION_TYPE_PAGE_CREATE, ACTION_TYPE_PAGE_CREATED, ACTION_TYPE_PAGE_SCROLL, ACTION_TYPE_REMOVE, ACTION_TYPE_REMOVE_ATTRIBUTE, ACTION_TYPE_REMOVE_EVENT, ACTION_TYPE_SET_ATTRIBUTE, ACTION_TYPE_SET_TEXT, ATTR_CHANGE_PREFIX, ATTR_CLASS, ATTR_INNER_HTML, ATTR_STYLE, ATTR_TEXT_CONTENT, ATTR_V_OWNER_ID, ATTR_V_RENDERJS, ATTR_V_SHOW, BACKGROUND_COLOR, BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, DATA_RE, EventChannel, EventModifierFlags, I18N_JSON_DELIMITERS, JSON_PROTOCOL, LINEFEED, NAVBAR_HEIGHT, NODE_TYPE_COMMENT, NODE_TYPE_ELEMENT, NODE_TYPE_PAGE, NODE_TYPE_TEXT, ON_ADD_TO_FAVORITES, ON_APP_ENTER_BACKGROUND, ON_APP_ENTER_FOREGROUND, ON_BACK_PRESS, ON_ERROR, ON_HIDE, ON_KEYBOARD_HEIGHT_CHANGE, ON_LAUNCH, ON_LOAD, ON_NAVIGATION_BAR_BUTTON_TAP, ON_NAVIGATION_BAR_SEARCH_INPUT_CHANGED, ON_NAVIGATION_BAR_SEARCH_INPUT_CLICKED, ON_NAVIGATION_BAR_SEARCH_INPUT_CONFIRMED, ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED, ON_PAGE_NOT_FOUND, ON_PAGE_SCROLL, ON_PULL_DOWN_REFRESH, ON_REACH_BOTTOM, ON_REACH_BOTTOM_DISTANCE, ON_READY, ON_RESIZE, ON_SHARE_APP_MESSAGE, ON_SHARE_TIMELINE, ON_SHOW, ON_TAB_ITEM_TAP, ON_THEME_CHANGE, ON_UNHANDLE_REJECTION, ON_UNLOAD, ON_WEB_INVOKE_APP_SERVICE, ON_WXS_INVOKE_CALL_METHOD, PLUS_RE, PRIMARY_COLOR, RENDERJS_MODULES, RESPONSIVE_MIN_WIDTH, SCHEME_RE, SELECTED_COLOR, TABBAR_HEIGHT, TAGS, UNI_SSR, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA, UNI_SSR_STORE, UNI_SSR_TITLE, UniBaseNode, UniCommentNode, UniElement, UniEvent, UniInputElement, UniLifecycleHooks, UniNode, UniTextAreaElement, UniTextNode, WEB_INVOKE_APPSERVICE, WXS_MODULES, WXS_PROTOCOL, addFont, cache, cacheStringFunction, callOptions, createRpx2Unit, createUniEvent, debounce, decode, decodedQuery, defaultMiniProgramRpx2Unit, defaultRpx2Unit, formatAppLog, formatDateTime, formatLog, getCustomDataset, getEnvLocale, getLen, getValueByDataPath, initCustomDataset, invokeArrayFns, isBuiltInComponent, isComponentTag, isCustomElement, isH5CustomElement, isH5NativeTag, isNativeTag, isRootHook, normalizeDataset, normalizeEventType, normalizeTarget, once, parseEventName, parseQuery, parseUrl, passive, plusReady, removeLeadingSlash, resolveOwnerEl, resolveOwnerVm, sanitise, scrollTo, stringifyQuery, updateElementStyle };
export { ACTION_TYPE_ADD_EVENT, ACTION_TYPE_ADD_WXS_EVENT, ACTION_TYPE_CREATE, ACTION_TYPE_EVENT, ACTION_TYPE_INSERT, ACTION_TYPE_PAGE_CREATE, ACTION_TYPE_PAGE_CREATED, ACTION_TYPE_PAGE_SCROLL, ACTION_TYPE_REMOVE, ACTION_TYPE_REMOVE_ATTRIBUTE, ACTION_TYPE_REMOVE_EVENT, ACTION_TYPE_SET_ATTRIBUTE, ACTION_TYPE_SET_TEXT, ATTR_CHANGE_PREFIX, ATTR_CLASS, ATTR_INNER_HTML, ATTR_STYLE, ATTR_TEXT_CONTENT, ATTR_V_OWNER_ID, ATTR_V_RENDERJS, ATTR_V_SHOW, BACKGROUND_COLOR, BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, DATA_RE, EventChannel, EventModifierFlags, I18N_JSON_DELIMITERS, JSON_PROTOCOL, LINEFEED, NAVBAR_HEIGHT, NODE_TYPE_COMMENT, NODE_TYPE_ELEMENT, NODE_TYPE_PAGE, NODE_TYPE_TEXT, ON_ADD_TO_FAVORITES, ON_APP_ENTER_BACKGROUND, ON_APP_ENTER_FOREGROUND, ON_BACK_PRESS, ON_ERROR, ON_HIDE, ON_KEYBOARD_HEIGHT_CHANGE, ON_LAUNCH, ON_LOAD, ON_NAVIGATION_BAR_BUTTON_TAP, ON_NAVIGATION_BAR_SEARCH_INPUT_CHANGED, ON_NAVIGATION_BAR_SEARCH_INPUT_CLICKED, ON_NAVIGATION_BAR_SEARCH_INPUT_CONFIRMED, ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED, ON_PAGE_NOT_FOUND, ON_PAGE_SCROLL, ON_PULL_DOWN_REFRESH, ON_REACH_BOTTOM, ON_REACH_BOTTOM_DISTANCE, ON_READY, ON_RESIZE, ON_SHARE_APP_MESSAGE, ON_SHARE_TIMELINE, ON_SHOW, ON_TAB_ITEM_TAP, ON_THEME_CHANGE, ON_UNHANDLE_REJECTION, ON_UNLOAD, ON_WEB_INVOKE_APP_SERVICE, ON_WXS_INVOKE_CALL_METHOD, PLUS_RE, PRIMARY_COLOR, RENDERJS_MODULES, RESPONSIVE_MIN_WIDTH, SCHEME_RE, SELECTED_COLOR, TABBAR_HEIGHT, TAGS, UNI_SSR, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA, UNI_SSR_STORE, UNI_SSR_TITLE, UniBaseNode, UniCommentNode, UniElement, UniEvent, UniInputElement, UniLifecycleHooks, UniNode, UniTextAreaElement, UniTextNode, WEB_INVOKE_APPSERVICE, WXS_MODULES, WXS_PROTOCOL, addFont, cache, cacheStringFunction, callOptions, createRpx2Unit, createUniEvent, debounce, decode, decodedQuery, defaultMiniProgramRpx2Unit, defaultRpx2Unit, formatAppLog, formatDateTime, formatLog, getCustomDataset, getDataByPath, getEnvLocale, getLen, getValueByDataPath, initCustomDataset, invokeArrayFns, isBuiltInComponent, isComponentTag, isCustomElement, isH5CustomElement, isH5NativeTag, isNativeTag, isRootHook, normalizeDataset, normalizeEventType, normalizeTarget, once, parseEventName, parseQuery, parseUrl, passive, plusReady, removeLeadingSlash, resolveOwnerEl, resolveOwnerVm, sanitise, scrollTo, stringifyQuery, updateElementStyle };
export function getDataByPath(
obj: Record<string | number, any>,
path: string
): unknown {
const parts = path.split('.')
const key: number | string = parts[0]
if (!obj) {
obj = {}
}
if (parts.length === 1) {
return obj[key]
}
return getDataByPath(obj[key], parts.slice(1).join('.'))
}
......@@ -3,6 +3,7 @@ export * from './log'
export * from './dom'
export * from './url'
export * from './hbx'
export * from './data'
export * from './plus'
export * from './tags'
export * from './vdom'
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册