yox
Version:
A lightweight mvvm framework
8,988 lines • 301 kB
JavaScript
/**
* yox.js v1.0.0-alpha.408
* (c) 2017-2023 musicode
* Released under the MIT License.
*/
const SYNTAX_IF = '#if';
const SYNTAX_ELSE = 'else';
const SYNTAX_ELSE_IF = 'else if';
const SYNTAX_EACH = '#each';
const SYNTAX_IMPORT = '>';
const SYNTAX_SPREAD = '...';
const SYNTAX_COMMENT = /^!(?:\s|--)/;
const TAG_SLOT = 'slot';
const TAG_PORTAL = 'portal';
const TAG_FRAGMENT = 'fragment';
const TAG_TEMPLATE = 'template';
const ATTR_TO = 'to';
const ATTR_KEY = 'key';
const ATTR_REF = 'ref';
const ATTR_SLOT = 'slot';
const ATTR_NAME = 'name';
const SLOT_DATA_PREFIX = '$slot_';
const SLOT_NAME_DEFAULT = 'children';
const VNODE_TYPE_TEXT = 1;
const VNODE_TYPE_COMMENT = 2;
const VNODE_TYPE_ELEMENT = 3;
const VNODE_TYPE_COMPONENT = 4;
const VNODE_TYPE_FRAGMENT = 5;
const VNODE_TYPE_PORTAL = 6;
const VNODE_TYPE_SLOT = 7;
const DIRECTIVE_ON = 'on';
const DIRECTIVE_LAZY = 'lazy';
const DIRECTIVE_MODEL = 'model';
const DIRECTIVE_EVENT = 'event';
const DIRECTIVE_TRANSITION = 'transition';
const DIRECTIVE_CUSTOM = 'o';
const MODIFER_NATIVE = 'native';
const MAGIC_VAR_SCOPE = '$scope';
const MAGIC_VAR_KEYPATH = '$keypath';
const MAGIC_VAR_LENGTH = '$length';
const MAGIC_VAR_EVENT = '$event';
const MAGIC_VAR_DATA = '$data';
const MODEL_PROP_DEFAULT = 'value';
const HOOK_BEFORE_CREATE = 'beforeCreate';
const HOOK_AFTER_CREATE = 'afterCreate';
const HOOK_BEFORE_RENDER = 'beforeRender';
const HOOK_AFTER_RENDER = 'afterRender';
const HOOK_BEFORE_MOUNT = 'beforeMount';
const HOOK_AFTER_MOUNT = 'afterMount';
const HOOK_BEFORE_UPDATE = 'beforeUpdate';
const HOOK_AFTER_UPDATE = 'afterUpdate';
const HOOK_BEFORE_DESTROY = 'beforeDestroy';
const HOOK_AFTER_DESTROY = 'afterDestroy';
const HOOK_BEFORE_PROPS_UPDATE = 'beforePropsUpdate';
/**
* 为了压缩,定义的常量
*/
const TRUE$1 = true;
const FALSE$1 = false;
const NULL$1 = null;
const UNDEFINED$1 = void 0;
const RAW_TRUE = 'true';
const RAW_FALSE = 'false';
const RAW_NULL = 'null';
const RAW_UNDEFINED = 'undefined';
const RAW_FILTER = 'filter';
const RAW_COMPONENT = 'component';
const RAW_DIRECTIVE = 'directive';
const RAW_TRANSITION = 'transition';
const RAW_THIS = 'this';
const RAW_FUNCTION = 'function';
const RAW_LENGTH = 'length';
const RAW_WILDCARD = '*';
const RAW_DOT = '.';
const RAW_SLASH = '/';
const RAW_DOLLAR = '$';
const KEYPATH_ROOT = '~';
const KEYPATH_PARENT = '..';
const KEYPATH_CURRENT = RAW_THIS;
const NODE_TYPE_ELEMENT = 1;
const NODE_TYPE_TEXT = 3;
const NODE_TYPE_COMMENT = 8;
/**
* Single instance for window in browser
*/
const WINDOW = typeof window !== RAW_UNDEFINED ? window : UNDEFINED$1;
/**
* Single instance for document in browser
*/
const DOCUMENT = typeof document !== RAW_UNDEFINED ? document : UNDEFINED$1;
/**
* tap 事件
*
* 非常有用的抽象事件,比如 pc 端是 click 事件,移动端是 touchend 事件
*
* 这样只需 on-tap="handler" 就可以完美兼容各端
*
* 框架未实现此事件,通过 Yox.dom.addSpecialEvent 提供给外部扩展
*
*/
const EVENT_TAP = 'tap';
/**
* 点击事件
*/
const EVENT_CLICK = 'click';
/**
* 输入事件
*/
const EVENT_INPUT = 'input';
/**
* 变化事件
*/
const EVENT_CHANGE = 'change';
/**
* 唯一内置的特殊事件:model
*/
const EVENT_MODEL = 'model';
/**
* Single instance for noop function
*/
const EMPTY_FUNCTION = function () {
/** yox */
};
/**
* 空对象,很多地方会用到,比如 `a || EMPTY_OBJECT` 确保是个对象
*/
const EMPTY_OBJECT = Object.freeze({});
/**
* 空数组
*/
const EMPTY_ARRAY = Object.freeze([]);
/**
* 空字符串
*/
const EMPTY_STRING = '';
/**
* 日志等级
*/
const LOG_LEVEL_DEBUG = 1;
const LOG_LEVEL_INFO = 2;
const LOG_LEVEL_WARN = 3;
const LOG_LEVEL_ERROR = 4;
const LOG_LEVEL_FATAL = 5;
/**
* 当前是否是源码调试,如果开启了代码压缩,empty function 里的注释会被干掉
* 源码模式默认选 INFO,因为 DEBUG 输出的日志太多,会导致性能急剧下降
*/
const LOG_LEVEL_DEFAULT = /yox/.test(EMPTY_FUNCTION.toString()) ? LOG_LEVEL_INFO : LOG_LEVEL_WARN;
/**
* 外部可配置的对象
*/
const PUBLIC_CONFIG = {
leftDelimiter: '{',
rightDelimiter: '}',
uglifyCompiled: FALSE$1,
minifyCompiled: FALSE$1,
logLevel: LOG_LEVEL_DEFAULT,
};
/**
* Check if value is a function.
*
* @param value
* @return
*/
function func(value) {
return typeof value === RAW_FUNCTION;
}
/**
* Check if value is an array.
*
* @param value
* @return
*/
function array$1(value) {
return Array.isArray(value);
}
/**
* Check if value is an object.
*
* @param value
* @return
*/
function object$1(value) {
// 低版本 IE 会把 null 当作 object
return value !== NULL$1 && typeof value === 'object';
}
/**
* Check if value is a string.
*
* @param value
* @return
*/
function string$1(value) {
return typeof value === 'string';
}
/**
* Check if value is a number.
*
* @param value
* @return
*/
function number(value) {
return typeof value === 'number' && !isNaN(value);
}
/**
* Check if value is boolean.
*
* @param value
* @return
*/
function boolean(value) {
return value === TRUE$1 || value === FALSE$1;
}
/**
* Check if value is numeric.
*
* @param value
* @return
*/
function numeric(value) {
return !isNaN(value - parseFloat(value));
}
var is = /*#__PURE__*/Object.freeze({
__proto__: null,
func: func,
array: array$1,
object: object$1,
string: string$1,
number: number,
boolean: boolean,
numeric: numeric
});
/**
* 任性地执行一个函数,不管它有没有、是不是
*
* @param fn 调用的函数
* @param context 执行函数时的 this 指向
* @param args 调用函数的参数,多参数时传入数组
* @return 调用函数的返回值
*/
function execute (fn, context, args) {
return array$1(args)
? fn.apply(context, args)
: context !== UNDEFINED$1
? fn.call(context, args)
: args !== UNDEFINED$1
? fn(args)
: fn();
}
class CustomEvent {
/**
* 构造函数
*
* 可以传事件名称,也可以传原生事件对象
*/
constructor(type, originalEvent) {
// 这里不设置命名空间
// 因为有没有命名空间取决于 Emitter 的构造函数有没有传 true
// CustomEvent 自己无法决定
this.type = type;
this.phase = CustomEvent.PHASE_CURRENT;
if (originalEvent) {
this.originalEvent = originalEvent;
}
}
static is(event) {
return event instanceof CustomEvent;
}
/**
* 阻止事件的默认行为
*/
preventDefault() {
const instance = this;
if (!instance.isPrevented) {
const { originalEvent } = instance;
if (originalEvent) {
originalEvent.preventDefault();
}
instance.isPrevented = TRUE$1;
}
return instance;
}
/**
* 停止事件广播
*/
stopPropagation() {
const instance = this;
if (!instance.isStoped) {
const { originalEvent } = instance;
if (originalEvent) {
originalEvent.stopPropagation();
}
instance.isStoped = TRUE$1;
}
return instance;
}
prevent() {
return this.preventDefault();
}
stop() {
return this.stopPropagation();
}
}
CustomEvent.PHASE_CURRENT = 0;
CustomEvent.PHASE_UPWARD = 1;
CustomEvent.PHASE_DOWNWARD = -1;
/**
* 遍历数组
*
* @param array
* @param callback 返回 false 可停止遍历
* @param reversed 是否逆序遍历
*/
function each$2(array, callback, reversed) {
const { length } = array;
if (length) {
if (reversed) {
for (let i = length - 1; i >= 0; i--) {
if (callback(array[i], i) === FALSE$1) {
break;
}
}
}
else {
for (let i = 0; i < length; i++) {
if (callback(array[i], i) === FALSE$1) {
break;
}
}
}
}
}
function nativePush(array, item) {
array[array.length] = item;
}
function nativeUnshift(array, item) {
array.unshift(item);
}
/**
* 添加
*
* @param array
* @param value
* @param action
*/
function addItem(array, value, action) {
if (array$1(value)) {
each$2(value, function (item) {
action(array, item);
});
}
else {
action(array, value);
}
}
/**
* 往后加
*
* @param array
* @param target
*/
function push(array, target) {
addItem(array, target, nativePush);
}
/**
* 往前加
*
* @param array
* @param target
*/
function unshift(array, target) {
addItem(array, target, nativeUnshift);
}
/**
* 数组项在数组中的位置
*
* @param array 数组
* @param target 数组项
* @param strict 是否全等判断,默认是全等
* @return 如果未找到,返回 -1
*/
function indexOf$1(array, target, strict) {
let result = -1;
each$2(array, function (item, index) {
if (strict === FALSE$1 ? item == target : item === target) {
result = index;
return FALSE$1;
}
});
return result;
}
/**
* 获取数组最后一项
*
* @param array 数组
* @return
*/
function last(array) {
const { length } = array;
if (length > 0) {
return array[length - 1];
}
}
/**
* 弹出数组最后一项
*
* 项目里用的太多,仅用于节省字符...
*
* @param array 数组
* @return 弹出的数组项
*/
function pop(array) {
const { length } = array;
if (length > 0) {
return array.pop();
}
}
/**
* 删除数组项
*
* @param array 数组
* @param item 待删除项
* @param strict 是否全等判断,默认是全等
* @return 删除的数量
*/
function remove$1(array, target, strict) {
let result = 0;
each$2(array, function (item, index) {
if (strict === FALSE$1 ? item == target : item === target) {
array.splice(index, 1);
result++;
}
}, TRUE$1);
return result;
}
/**
* 数组是否包含 item
*
* @param array 数组
* @param target 可能包含的数组项
* @param strict 是否全等判断,默认是全等
* @return
*/
function has$2(array, target, strict) {
return indexOf$1(array, target, strict) >= 0;
}
/**
* 把类数组转成数组
*
* @param array 类数组
* @return
*/
function toArray(array) {
return array$1(array)
? array
: execute(EMPTY_ARRAY.slice, array);
}
/**
* 把数组转成对象
*
* @param array 数组
* @param key 数组项包含的字段名称,如果数组项是基本类型,可不传
* @param value
* @return
*/
function toObject(array, key, value) {
let result = {};
each$2(array, function (item) {
result[key ? item[key] : item] = value || item;
});
return result;
}
/**
* 把数组合并成字符串
*
* @param array
* @param separator
* @return
*/
function join$1(array, separator) {
return array.join(separator);
}
/**
* 用于判断长度大于 0 的数组
*
* @param array
* @return
*/
function falsy$2(array) {
return !array$1(array) || !array.length;
}
var array = /*#__PURE__*/Object.freeze({
__proto__: null,
each: each$2,
push: push,
unshift: unshift,
indexOf: indexOf$1,
last: last,
pop: pop,
remove: remove$1,
has: has$2,
toArray: toArray,
toObject: toObject,
join: join$1,
falsy: falsy$2
});
function toString (target, defaultValue) {
return target != NULL$1 && target.toString
? target.toString()
: defaultValue !== UNDEFINED$1
? defaultValue
: EMPTY_STRING;
}
function isNative (target) {
return func(target)
&& toString(target).indexOf('[native code]') >= 0;
}
let createPureObject = function () {
const obj = Object.create(NULL$1);
return {
get(key) {
return obj[key];
},
set(key, value) {
obj[key] = value;
},
has(key) {
return key in obj;
},
keys() {
return Object.keys(obj);
}
};
};
// IE9+ 都已实现 Object.create
{
if (!isNative(Object.create)) {
createPureObject = function () {
const obj = {};
return {
get(key) {
return obj.hasOwnProperty(key)
? obj[key]
: UNDEFINED$1;
},
set(key, value) {
obj[key] = value;
},
has(key) {
return obj.hasOwnProperty(key);
},
keys() {
return Object.keys(obj);
}
};
};
}
}
/**
* 创建纯净对象
*
* @return 纯净对象
*/
var createPureObject$1 = createPureObject;
/**
* 缓存一个参数的函数调用结果
*
* @param fn 需要缓存的函数
* @return 带缓存功能的函数
*/
function createOneKeyCache(fn) {
const cache = createPureObject$1();
return function (key) {
const hit = cache.get(key);
if (hit !== UNDEFINED$1) {
return hit;
}
const value = fn(key);
cache.set(key, value);
return value;
};
}
/**
* 缓存两个参数的函数调用结果
*
* @param fn 需要缓存的函数
* @return 带缓存功能的函数
*/
function createTwoKeyCache(fn) {
const cache = createPureObject$1();
return function (key1, key2) {
let hit1 = cache.get(key1);
if (hit1) {
const hit2 = hit1.get(key2);
if (hit2) {
return hit2;
}
}
else {
hit1 = createPureObject$1();
cache.set(key1, hit1);
}
const value = fn(key1, key2);
hit1.set(key2, value);
return value;
};
}
const camelizePattern = /-([a-z])/gi, hyphenatePattern = /\B([A-Z])/g, capitalizePattern = /^[a-z]/;
/**
* 连字符转成驼峰
*
* @param str
* @return 驼峰格式的字符串
*/
const camelize = createOneKeyCache(function (str) {
return str.replace(camelizePattern, function (_, $1) {
return upper($1);
});
});
/**
* 驼峰转成连字符
*
* @param str
* @return 连字符格式的字符串
*/
const hyphenate = createOneKeyCache(function (str) {
return str.replace(hyphenatePattern, function (_, $1) {
return '-' + lower($1);
});
});
/**
* 首字母大写
*
* @param str
* @return
*/
const capitalize = createOneKeyCache(function (str) {
return str.replace(capitalizePattern, upper);
});
/**
* 重复字符串
*
* @param str
* @param count 重复次数
* @return
*/
function repeat(str, count) {
return join$1(new Array(count + 1), str);
}
/**
* 清除两侧空白符
*
* @param str
* @return 清除两侧空白符的字符串
*/
function trim(str) {
return falsy$1(str)
? EMPTY_STRING
: str.trim();
}
/**
* 截取字符串
*
* @param str
* @param start
* @param end
* @return
*/
function slice(str, start, end) {
return number(end)
? start === end
? EMPTY_STRING
: str.slice(start, end)
: str.slice(start);
}
/**
* 获取子串的起始位置
*
* @param str
* @param part
* @param start
* @return
*/
function indexOf(str, part, start) {
return str.indexOf(part, start !== UNDEFINED$1 ? start : 0);
}
/**
* 获取子串的起始位置
*
* @param str
* @param part
* @param end
* @return
*/
function lastIndexOf(str, part, end) {
return str.lastIndexOf(part, end !== UNDEFINED$1 ? end : str.length);
}
/**
* str 是否以 part 开头
*
* @param str
* @param part
* @return
*/
function startsWith(str, part) {
return indexOf(str, part) === 0;
}
/**
* str 是否以 part 结束
*
* @param str
* @param part
* @return
*/
function endsWith(str, part) {
const offset = str.length - part.length;
return offset >= 0 && lastIndexOf(str, part) === offset;
}
/**
* 获取某个位置的字符
*/
function charAt(str, index) {
return str.charAt(index || 0);
}
/**
* 获取某个位置的字符编码
*/
function codeAt(str, index) {
return str.charCodeAt(index || 0);
}
/**
* 大写格式
*/
function upper(str) {
return str.toUpperCase();
}
/**
* 小写格式
*/
function lower(str) {
return str.toLowerCase();
}
/**
* str 是否包含 part
*
* @param str
* @param part
* @return 是否包含
*/
function has$1(str, part) {
return indexOf(str, part) >= 0;
}
/**
* str 转成 value 为 true 的 map
*
* @param str
* @param separator
*/
function toMap$1(str, separator) {
const map = Object.create(NULL$1);
each$2(str.split(separator || ','), function (item) {
map[item] = TRUE$1;
});
return map;
}
/**
* 判断长度大于 0 的字符串
*
* @param str
* @return
*/
function falsy$1(str) {
return !string$1(str) || !str.length;
}
var string = /*#__PURE__*/Object.freeze({
__proto__: null,
camelize: camelize,
hyphenate: hyphenate,
capitalize: capitalize,
repeat: repeat,
trim: trim,
slice: slice,
indexOf: indexOf,
lastIndexOf: lastIndexOf,
startsWith: startsWith,
endsWith: endsWith,
charAt: charAt,
codeAt: codeAt,
upper: upper,
lower: lower,
has: has$1,
toMap: toMap$1,
falsy: falsy$1
});
const dotPattern = /\./g, asteriskPattern = /\*/g, doubleAsteriskPattern = /\*\*/g;
/**
* 判断 keypath 是否以 prefix 开头,如果是,返回匹配上的前缀长度,否则返回 -1
*
* @param keypath
* @param prefix
* @return
*/
const match = createTwoKeyCache(function (keypath, prefix) {
if (keypath === prefix) {
return prefix.length;
}
prefix += RAW_DOT;
return startsWith(keypath, prefix)
? prefix.length
: -1;
});
const getKeypathTokens = createOneKeyCache(function (keypath) {
return indexOf(keypath, RAW_DOT) < 0
? [keypath]
: keypath.split(RAW_DOT);
});
/**
* 遍历 keypath 的每个部分
*
* @param keypath
* @param callback 返回 false 可中断遍历
*/
function each$1(keypath, callback) {
const tokens = string$1(keypath) ? getKeypathTokens(keypath) : keypath;
for (let i = 0, lastIndex = tokens.length - 1; i <= lastIndex; i++) {
if (callback(tokens[i], i, lastIndex) === FALSE$1) {
break;
}
}
}
/**
* 遍历 keypath 的每个部分
*
* @param keypath1
* @param keypath2
*/
const join = createTwoKeyCache(function (keypath1, keypath2) {
return keypath1 && keypath2
? keypath1 + RAW_DOT + keypath2
: keypath1 || keypath2;
});
/**
* 是否模糊匹配
*
* @param keypath
*/
const isFuzzy = createOneKeyCache(function (keypath) {
return has$1(keypath, RAW_WILDCARD);
});
const getFuzzyPattern = createOneKeyCache(function (pattern) {
return new RegExp(`^${pattern
.replace(dotPattern, '\\.')
.replace(asteriskPattern, '(\\w+)')
.replace(doubleAsteriskPattern, '([\.\\w]+?)')}$`);
});
/**
* 模糊匹配 keypath
*
* @param keypath
* @param pattern
*/
const matchFuzzy = createTwoKeyCache(function (keypath, pattern) {
const result = keypath.match(getFuzzyPattern(pattern));
return result
? result[1]
: UNDEFINED$1;
});
/**
* 全局 value holder,避免频繁的创建临时对象
*/
const holder = {
value: UNDEFINED$1
};
/**
* 获取对象的 key 的数组
*
* @param object
* @return
*/
function keys(object) {
return Object.keys(object);
}
/**
* 遍历对象
*
* @param object
* @param callback 返回 false 可停止遍历
*/
function each(object, callback) {
for (let key in object) {
if (callback(object[key], key) === FALSE$1) {
break;
}
}
}
/**
* 扩展对象
*
* @return
*/
function extend(original, object) {
each(object, function (value, key) {
original[key] = value;
});
return original;
}
/**
* 合并对象
*
* @return
*/
function merge(object1, object2) {
return object1 && object2
? extend(extend({}, object1), object2)
: object1 || object2;
}
/**
* 拷贝对象
*
* @param object
* @param deep 是否需要深拷贝
* @return
*/
function copy(object, deep) {
let result = object;
if (array$1(object)) {
if (deep) {
result = [];
each$2(object, function (item, index) {
result[index] = copy(item, deep);
});
}
else {
result = object.slice();
}
}
else if (object$1(object)) {
result = {};
each(object, function (value, key) {
result[key] = deep ? copy(value, deep) : value;
});
}
return result;
}
function getCallback(value) {
// 如果是计算属性,取计算属性的值
return func(value.get)
? value.get()
: value;
}
/**
* 从对象中查找一个 keypath
*
* 返回值是空时,表示没找到值
*
* @param object
* @param keypath
* @return
*/
function get(object, keypath, callback) {
let result = object;
each$1(keypath, function (key, index, lastIndex) {
if (result != NULL$1) {
// 先直接取值
let value = result[key],
// 紧接着判断值是否存在
// 下面会处理计算属性的值,不能在它后面设置 hasValue
hasValue = value !== UNDEFINED$1;
// 为什么不用 hasValue 判断呢?
// 因为这里需要处理的 value 要么是函数,要么是对象,基础类型无需加工
if (value) {
// 如果数据中没有计算属性,也可以自定义
value = (callback || getCallback)(value);
}
if (index === lastIndex) {
if (hasValue) {
holder.value = value;
result = holder;
}
else {
result = UNDEFINED$1;
}
}
else {
result = value;
}
}
else {
result = UNDEFINED$1;
return FALSE$1;
}
});
return result;
}
/**
* 为对象设置一个键值对
*
* @param object
* @param keypath
* @param value
* @param autofill 是否自动填充不存在的对象,默认自动填充
*/
function set(object, keypath, value, autofill) {
let next = object;
each$1(keypath, function (key, index, lastIndex) {
if (index === lastIndex) {
next[key] = value;
}
else if (next[key]) {
next = next[key];
}
else if (autofill) {
next = next[key] = {};
}
else {
return FALSE$1;
}
});
}
/**
* 对象是否包含某个 key
*
* @param object
* @param key
* @return
*/
function has(object, key) {
// 不用 hasOwnProperty,性能差
return object[key] !== UNDEFINED$1;
}
/**
* 是否是空对象
*
* @param object
* @return
*/
function falsy(object) {
return !object$1(object)
|| array$1(object)
|| !keys(object).length;
}
var object = /*#__PURE__*/Object.freeze({
__proto__: null,
keys: keys,
each: each,
extend: extend,
merge: merge,
copy: copy,
get: get,
set: set,
has: has,
falsy: falsy
});
/**
* 外部可用这些常量
*/
const DEBUG = LOG_LEVEL_DEBUG;
const INFO = LOG_LEVEL_INFO;
const WARN = LOG_LEVEL_WARN;
const ERROR = LOG_LEVEL_ERROR;
const FATAL = LOG_LEVEL_FATAL;
/**
* 是否有原生的日志特性,没有必要单独实现
*/
const nativeConsole = typeof console !== RAW_UNDEFINED ? console : NULL$1,
/**
* console 样式前缀
* ie 和 edge 不支持 console.log 样式
*/
stylePrefix = WINDOW && /edge|msie|trident/i.test(WINDOW.navigator.userAgent)
? EMPTY_STRING
: '%c',
/**
* 日志打印函数
*/
printLog = nativeConsole
? stylePrefix
? function (tag, msg, style) {
nativeConsole.log(stylePrefix + tag, style, msg);
}
: function (tag, msg) {
nativeConsole.log(tag, msg);
}
: EMPTY_FUNCTION;
/**
* 全局调试开关
*/
function getLogLevel() {
const { logLevel } = PUBLIC_CONFIG;
if (logLevel >= DEBUG && logLevel <= FATAL) {
return logLevel;
}
return LOG_LEVEL_DEFAULT;
}
function getStyle(backgroundColor) {
return `background-color:${backgroundColor};border-radius:12px;color:#fff;font-size:10px;padding:3px 6px;`;
}
/**
* 打印 debug 日志
*
* @param msg
*/
function debug(msg, tag) {
if (getLogLevel() <= DEBUG) {
printLog(tag || 'Yox debug', msg, getStyle('#999'));
}
}
/**
* 打印 info 日志
*
* @param msg
*/
function info(msg, tag) {
if (getLogLevel() <= INFO) {
printLog(tag || 'Yox info', msg, getStyle('#2db7f5'));
}
}
/**
* 打印 warn 日志
*
* @param msg
*/
function warn(msg, tag) {
if (getLogLevel() <= WARN) {
printLog(tag || 'Yox warn', msg, getStyle('#f90'));
}
}
/**
* 打印 error 日志
*
* @param msg
*/
function error(msg, tag) {
if (getLogLevel() <= ERROR) {
printLog(tag || 'Yox error', msg, getStyle('#ed4014'));
}
}
/**
* 致命错误,中断程序
*
* @param msg
*/
function fatal(msg, tag) {
if (getLogLevel() <= FATAL) {
throw new Error(`[${tag || 'Yox fatal'}]: ${msg}`);
}
}
var logger = /*#__PURE__*/Object.freeze({
__proto__: null,
DEBUG: DEBUG,
INFO: INFO,
WARN: WARN,
ERROR: ERROR,
FATAL: FATAL,
debug: debug,
info: info,
warn: warn,
error: error,
fatal: fatal
});
class Emitter {
constructor(ns) {
this.ns = ns || FALSE$1;
this.listeners = {};
}
/**
* 发射事件
*
* @param type 事件名称或命名空间
* @param args 事件处理函数的参数列表
* @param filter 自定义过滤器
*/
fire(type, args, filter) {
let instance = this, event = string$1(type) ? instance.toEvent(type) : type, list = instance.listeners[event.type], isComplete = TRUE$1;
if (list) {
// 避免遍历过程中,数组发生变化,比如增删了
list = list.slice();
// 判断是否是发射事件
// 如果 args 的第一个参数是 CustomEvent 类型,表示发射事件
// 因为事件处理函数的参数列表是 (event, data)
const customEvent = args && CustomEvent.is(args[0])
? args[0]
: UNDEFINED$1;
// 这里不用 array.each,减少函数调用
for (let i = 0, length = list.length; i < length; i++) {
let options = list[i];
// 命名空间不匹配
if (!matchNamespace(event.ns, options)
// 在 fire 过程中被移除了
|| !has$2(list, options)
// 传了 filter,则用 filter 判断是否过滤此 options
|| (filter && !filter(event, args, options))) {
continue;
}
let result = execute(options.listener, options.ctx, args);
// 执行次数
options.num = options.num ? (options.num + 1) : 1;
// 注册的 listener 可以指定最大执行次数
if (options.num === options.max) {
instance.off(event.type, {
ns: event.ns,
listener: options.listener,
});
}
// 如果没有返回 false,而是调用了 customEvent.stop 也算是返回 false
if (customEvent) {
if (result === FALSE$1) {
customEvent.prevent().stop();
}
else if (customEvent.isStoped) {
result = FALSE$1;
}
}
if (result === FALSE$1) {
isComplete = FALSE$1;
break;
}
}
}
return isComplete;
}
/**
* 注册监听
*
* @param type
* @param listener
*/
on(type, listener) {
const instance = this, listeners = instance.listeners, options = func(listener)
? { listener: listener }
: listener;
if (object$1(options) && func(options.listener)) {
if (!string$1(options.ns)) {
const event = instance.toEvent(type);
options.ns = event.ns;
type = event.type;
}
push(listeners[type] || (listeners[type] = []), options);
}
}
/**
* 取消监听
*
* @param type
* @param listener
*/
off(type, listener) {
const instance = this, listeners = instance.listeners;
if (type) {
const filter = instance.toFilter(type, listener), each$1 = function (list, name) {
each$2(list, function (item, index) {
if (matchListener(filter.listener, item) && matchNamespace(filter.ns, item)) {
list.splice(index, 1);
}
}, TRUE$1);
if (!list.length) {
delete listeners[name];
}
};
if (filter.type) {
if (listeners[filter.type]) {
each$1(listeners[filter.type], filter.type);
}
}
// 按命名空间过滤,如 type 传入 .ns
else if (filter.ns) {
each(listeners, each$1);
}
}
else {
// 清空
instance.listeners = {};
}
}
/**
* 是否已监听某个事件
*
* @param type
* @param listener
*/
has(type, listener) {
let instance = this, listeners = instance.listeners, filter = instance.toFilter(type, listener), result = TRUE$1, each$1 = function (list) {
each$2(list, function (item) {
if (matchListener(filter.listener, item) && matchNamespace(filter.ns, item)) {
return result = FALSE$1;
}
});
return result;
};
if (filter.type) {
if (listeners[filter.type]) {
each$1(listeners[filter.type]);
}
}
else if (filter.ns) {
each(listeners, each$1);
}
return !result;
}
/**
* 把事件类型解析成命名空间格式
*
* @param type
*/
toEvent(type) {
// 这里 ns 必须为字符串
// 用于区分 event 对象是否已完成命名空间的解析
const event = {
type,
ns: EMPTY_STRING,
};
// 是否开启命名空间
if (this.ns) {
const index = indexOf(type, RAW_DOT);
if (index >= 0) {
event.type = slice(type, 0, index);
event.ns = slice(type, index + 1);
}
}
return event;
}
toFilter(type, listener) {
let filter;
if (listener) {
filter = func(listener)
? { listener: listener }
: listener;
}
else {
filter = {};
}
if (string$1(filter.ns)) {
filter.type = type;
}
else {
const event = this.toEvent(type);
filter.type = event.type;
filter.ns = event.ns;
}
return filter;
}
}
/**
* 判断 options 是否能匹配 listener
*
* @param listener
* @param options
*/
function matchListener(listener, options) {
return listener
? listener === options.listener
: TRUE$1;
}
/**
* 判断 options 是否能匹配命名空间
*
* 如果 namespace 和 options.ns 都不为空,则需完全匹配
*
* 如果他们两个其中任何一个为空,则不判断命名空间
*
* @param namespace
* @param options
*/
function matchNamespace(namespace, options) {
const { ns } = options;
return ns && namespace
? ns === namespace
: TRUE$1;
}
let nextTick;
// IE (10+) 和 node
if (typeof setImmediate === RAW_FUNCTION && isNative(setImmediate)) {
nextTick = setImmediate;
}
// 用 MessageChannel 去做 setImmediate 的 polyfill
// 原理是将新的 message 事件加入到原有的 dom events 之后
// 兼容性 IE10+ 和其他标准浏览器
if (typeof MessageChannel === RAW_FUNCTION && isNative(MessageChannel)) {
nextTick = function (fn) {
const channel = new MessageChannel();
channel.port1.onmessage = fn;
channel.port2.postMessage(1);
};
}
else {
nextTick = setTimeout;
}
var nextTick$1 = nextTick;
let shared;
class NextTask {
constructor(hooks) {
const instance = this;
instance.tasks = [];
instance.hooks = hooks || EMPTY_OBJECT;
}
/**
* 全局单例
*/
static shared() {
return shared || (shared = new NextTask());
}
/**
* 在队尾添加异步任务
*/
append(func, context) {
const instance = this, { tasks } = instance;
push(tasks, {
fn: func,
ctx: context
});
if (tasks.length === 1) {
nextTick$1(function () {
instance.run();
});
}
}
/**
* 在队首添加异步任务
*/
prepend(func, context) {
const instance = this, { tasks } = instance;
unshift(tasks, {
fn: func,
ctx: context
});
if (tasks.length === 1) {
nextTick$1(function () {
instance.run();
});
}
}
/**
* 清空异步队列
*/
clear() {
this.tasks.length = 0;
}
/**
* 立即执行异步任务,并清空队列
*/
run() {
const instance = this, { tasks, hooks } = instance, { length } = tasks;
if (length) {
instance.tasks = [];
if (hooks.beforeTask) {
hooks.beforeTask();
}
for (let i = 0; i < length; i++) {
execute(tasks[i].fn, tasks[i].ctx);
}
if (hooks.afterTask) {
hooks.afterTask();
}
}
}
}
/**
* 节流调用
*
* @param fn 需要节制调用的函数
* @param delay 调用的时间间隔,单位毫秒
* @param immediate 是否立即触发
* @return 节流函数
*/
function debounce (fn, delay, immediate) {
let timer;
return function () {
if (!timer) {
const args = toArray(arguments);
if (immediate) {
execute(fn, UNDEFINED$1, args);
}
timer = setTimeout(function () {
timer = UNDEFINED$1;
if (!immediate) {
execute(fn, UNDEFINED$1, args);
}
}, delay);
}
};
}
// vnode.data 内部使用的几个字段
const VNODE = '$vnode';
const LOADING = '$loading';
const LEAVING = '$leaving';
const MODEL_CONTROL = '$model_control';
const MODEL_DESTROY = '$model_destroy';
const EVENT_DESTROY = '$event_destroy';
const DIRECTIVE_HOOKS = '$directive_hooks';
const DIRECTIVE_UPDATING = '$directive_updating';
function addEvent$1(api, element, component, data, key, lazy, event) {
let { name, listener } = event;
if (lazy) {
const value = lazy[name] || lazy[EMPTY_STRING];
if (value === TRUE$1) {
name = EVENT_CHANGE;
}
else if (value > 0) {
listener = debounce(listener, value,
// 避免连续多次点击,主要用于提交表单场景
// 移动端的 tap 事件可自行在业务层打补丁实现
name === EVENT_CLICK || name === EVENT_TAP);
}
}
if (component) {
if (event.isNative) {
const target = component.$el;
api.on(target, name, listener);
return function () {
api.off(target, name, listener);
};
}
// event 有 ns 和 listener 两个字段,满足 ThisListenerOptions 的要求
component.on(name, event);
data[EVENT_DESTROY + key] = function () {
component.off(name, event);
delete data[EVENT_DESTROY + key];
};
}
else {
api.on(element, name, listener);
data[EVENT_DESTROY + key] = function () {
api.off(element, key, listener);
delete data[EVENT_DESTROY + key];
};
}
}
function afterCreate$5(api, vnode) {
const { events } = vnode;
if (events) {
const element = vnode.node, component = vnode.component, lazy = vnode.lazy, data = vnode.data;
for (let key in events) {
addEvent$1(api, element, component, data, key, lazy, events[key]);
}
}
}
function afterUpdate$4(api, vnode, oldVNode) {
const newEvents = vnode.events, oldEvents = oldVNode.events;
if (newEvents !== oldEvents) {
const element = vnode.node, component = vnode.component, lazy = vnode.lazy, data = vnode.data;
if (oldEvents) {
const newValue = newEvents || EMPTY_OBJECT;
for (let key in oldEvents) {
if (!newValue[key]) {
const destroy = data[EVENT_DESTROY + key];
if (destroy) {
destroy();
}
}
}
}
if (newEvents) {
const oldValue = oldEvents || EMPTY_OBJECT;
for (let key in newEvents) {
const event = newEvents[key], oldEvent = oldValue[key];
if (!oldEvent) {
addEvent$1(api, element, component, data, key, lazy, event);
}
else if (event.value !== oldEvent.value) {
const destroy = data[EVENT_DESTROY + key];
if (destroy) {
destroy();
}
addEvent$1(api, element, component, data, key, lazy, event);
}
else if (oldEvent.runtime && event.runtime) {
oldEvent.runtime.execute = event.runtime.execute;
event.runtime = oldEvent.runtime;
}
}
}
}
}
function beforeDestroy$3(api, vnode) {
const events = vnode.events, data = vnode.data;
if (events) {
for (let key in events) {
const destroy = data[EVENT_DESTROY + key];
if (destroy) {
destroy();
}
}
}
}
var eventHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate$5,
afterUpdate: afterUpdate$4,
beforeDestroy: beforeDestroy$3
});
function debounceIfNeeded(fn, lazy) {
// 应用 lazy
return lazy && lazy !== TRUE$1
? debounce(fn, lazy)
: fn;
}
const inputControl = {
set(node, value) {
node.value = toString(value);
},
sync(node, keypath, context) {
context.set(keypath, node.value);
},
}, radioControl = {
set(node, value) {
node.checked = node.value === toString(value);
},
sync(node, keypath, context) {
if (node.checked) {
context.set(keypath, node.value);
}
},
}, checkboxControl = {
set(node, value) {
node.checked = array$1(value)
? has$2(value, node.value, FALSE$1)
: !!value;
},
sync(node, keypath, context) {
const value = context.get(keypath);
if (array$1(value)) {
if (node.checked) {
context.append(keypath, node.value);
}
else {
context.removeAt(keypath, indexOf$1(value, node.value, FALSE$1));
}
}
else {
context.set(keypath, node.checked);
}
},
}, selectControl = {
set(node, value) {
const { multiple, options } = node;
for (let i = 0, length = options.length; i < length; i++) {
if (multiple) {
options[i].selected = has$2(value, options[i].value, FALSE$1);
}
else if (options[i].value == value) {
node.selectedIndex = i;
return;
}
}
if (!multiple) {
node.selectedIndex = -1;
}
},
sync(node, keypath, context) {
const { multiple, options } = node;
if (multiple) {
const values = [];
for (let i = 0, length = options.length; i < length; i++) {
if (options[i].selected) {
values.push(options[i].value);
}
}
context.set(keypath, values);
}
else {
context.set(keypath, options[node.selectedIndex].value);
}
},
};
function addModel(api, element, component, data, vnode) {
let { context, model, lazy, nativeAttrs } = vnode, { keypath, value } = model, lazyValue = lazy && (lazy[DIRECTIVE_MODEL] || lazy[EMPTY_STRING]);
if (component) {
let viewBinding = component.$model, viewSyncing = debounceIfNeeded(function (newValue) {
context.set(keypath, newValue);
}, lazyValue);
component.watch(viewBinding, viewSyncing);
data[MODEL_DESTROY] = function () {
component.unwatch(viewBinding, viewSyncing);
delete data[MODEL_DESTROY];
};
}
else {
let control = vnode.tag === 'select'
? selectControl
: inputControl,
// checkbox,radio,select 监听的是 change 事件
eventName = EVENT_CHANGE;
if (control === inputControl) {
const type = nativeAttrs && nativeAttrs.type;
if (type === 'radio') {
control = radioControl;
}
else if (type === 'checkbox') {
control = checkboxControl;
}
// 如果是输入框,则切换成 model 事件
// model 事件是个 yox-dom 实现的特殊事件
// 不会在输入法组合文字过程中得到触发事件
else if (lazyValue !== TRUE$1) {
eventName = EVENT_MODEL;
}
}
const sync = debounceIfNeeded(function () {
control.sync(element, keypath, context);
}, lazyValue);
api.on(element, eventName, sync);
control.set(element, value);
data[MODEL_CONTROL] = control;
data[MODEL_DESTROY] = function () {
api.off(element, eventName, sync);
delete data[MODEL_DESTROY];
delete data[MODEL_CONTROL];
};
}
}
function afterCreate$4(api, vnode) {
const model = vnode.model;
if (model) {
addModel(api, vnode.node, vnode.component, vnode.data, vnode);
}
}
function afterUpdate$3(api, vnode, oldVNode) {
const data = vnode.data, newModel = vnode.model, oldModel = oldVNode.model;
if (newModel) {
const element = vnode.node, component = vnode.component;
if (!oldModel) {
addModel(api, element, component, data, vnode);
}
else if (newModel.keypath !== oldModel.keypath) {
data[MODEL_DESTROY]();
addModel(api, element, component, data, vnode);
}
else {
if (component) {
component.set(component.$model, newModel.value);
}
else {
const control = data[MODEL_CONTROL];
if (control) {
control.set(element, newModel.value);
}
}
}
}
else if (oldModel) {
data[MODEL_DESTROY]();
}
}
function beforeDestroy$2(api, vnode) {
const data = vnode.data, destroy = data[MODEL_DESTROY];
if (destroy) {
destroy();
}
}
var modelHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate$4,
afterUpdate: afterUpdate$3,
beforeDestroy: beforeDestroy$2
});
function afterCreate$3(api, vnode) {
const { nativeAttrs } = vnode;
if (nativeAttrs) {
const element = vnode.node;
for (let name in nativeAttrs) {
api.setAttr(element, name, nativeAttrs[name]);
}
}
}
function afterUpdate$2(api, vnode, oldVNode) {
const newNativeAttrs = vnode.nativeAttrs, oldNativeAttrs = oldVNode.nativeAttrs;
if (newNativeAttrs !== oldNativeAttrs) {
const element = vnode.node;
if (newNativeAttrs) {
const oldValue = oldNativeAttrs || EMPTY_OBJECT;
for (let name in newNativeAttrs) {
if (oldValue[name] === UNDEFINED$1
|| newNativeAttrs[name] !== oldValue[name]) {
api.setAttr(element, name, newNativeAttrs[name]);
}
}
}
if (oldNativeAttrs) {
const newValue = newNativeAttrs || EMPTY_OBJECT;
for (let name in oldNativeAttrs) {
if (newValue[name] === UNDEFINED$1) {
api.removeAttr(element, name);
}
}
}
}
}
var nativeAttrHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate$3,
afterUpdate: afterUpdate$2
});
function afterCreate$2(api, vnode) {
const { nativeStyles } = vnode;
if (nativeStyles) {
const elementStyle = vnode.node.style;
for (let name in nativeStyles) {
api.setStyle(elementStyle, name, nativeStyles[name]);
}
}
}
function afterUpdate$1(api, vnode, oldVNode) {
const newNativeStyles = vnode.nativeStyles, oldNativeStyles = oldVNode.nativeStyles;
if (newNativeStyles !== oldNativeStyles) {
const elementStyle = vnode.node.style;
if (newNativeStyles) {
const oldValue = oldNativeStyles || EMPTY_OBJECT;
for (let name in newNativeStyles) {
if (oldValue[name] === UNDEFINED$1
|| newNativeStyles[name] !== oldValue[name]) {
api.setStyle(elementStyle, name, newNativeStyles[name]);
}
}
}
if (oldNativeStyles) {
const newValue = newNativeStyles || EMPTY_OBJECT;
for (let name in oldNativeStyles) {
if (newValue[name] === UNDEFINED$1) {
api.removeStyle(elementStyle, name);
}
}
}
}
}
var nativeStyleHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate$2,
afterUpdate: afterUpdate$1
});
function callDirectiveCreate(data, vnode, directive) {
data[DIRECTIVE_HOOKS + directive.name] = directive.create(vnode.component || vnode.node, directive, vnode);
}
function callDirectiveHook(data, vnode, directive, hookName) {
const hooks = data[DIRECTIVE_HOOKS + directive.name], hook = hooks && hooks[hookName];
if (hook) {
hook(directive, vnode);
}
}
function genetateDirectiveHook(hookName) {
return function (api, vnode) {
const { directives } = vnode;
if (directives) {
const data = vnode.data;
for (let name in directives) {
callDirectiveHook(data, vnode, directives[name], hookName);
}
}
};
}
function afterCreate$1(api, vnode) {
const { directives } = vnode;
if (directives) {
const data = vnode.data;
for (let name in directives) {
callDirectiveCreate(data, vnode, directives[name]);
}
}
}
function beforeUpdate$1(api, vnode, oldVNode) {
const newDirectives = vnode.directives, oldDirectives = oldVNode.directives, data = vnode.data;
// 先触发 beforeDestroy 比较符合直觉
if (oldDirectives) {
const newValue = newDirectives || EMPTY_OBJECT;
for (let name in oldDirectives) {
if (newValue[name] === UNDEFINED$1) {
callDirectiveHook(data, vnode, oldDirectives[name], 'beforeDestroy');
}
}
}
if (newDirectives) {
const oldValue = oldDirectives || EMPTY_OBJECT, updatingDirectives = [];
for (let name in newDirectives) {
const directive = newDirectives[name];
if (oldValue[name] === UNDEFINED$1) {
callDirectiveCreate(data, vnode, directive);
}
else if (directive.value !== oldValue[name].value) {
callDirectiveHook(data, vnode, directive, 'beforeUpdate');
updatingDirectives.push(directive);
}
}
data[DIRECTIVE_UPDATING] = updatingDirectives;
}
}
function afterUpdate(api, vnode, oldVNode) {
const { data } = vnode;
if (data) {
const directives = data[DIRECTIVE_UPDATING];
if (directives) {
for (let i = 0, length = directives.length; i < length; i++) {
callDirectiveHook(data, vnode, directives[i], 'afterUpdate');
}
data[DIRECTIVE_UPDATING] = UNDEFINED$1;
}
}
}
const afterMount = genetateDirectiveHook('afterMount');
const beforeDestroy$1 = genetateDirectiveHook('beforeDestroy');
var directiveHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate$1,
beforeUpdate: beforeUpdate$1,
afterUpdate: afterUpdate,
afterMount: afterMount,
beforeDestroy: beforeDestroy$1
});
function afterCreate(api, vnode) {
const ref = vnode.ref;
if (ref) {
const context = vnode.context;
let $refs = context.$refs;
if (!$refs) {
$refs = context.$refs = {};
}
$refs[ref] = vnode.component || vnode.node;
}
}
// 删除 ref 的时候,要确保是相同的节点
// 因为模板中可能出现同一个 ref 名字,出现在不同的地方,
// 这样就可能出现一种特殊情况,即前面刚创建了 ref1,后面又把这个这个新创建的 ref1 删除了
function beforeUpdate(api, vnode, oldVNode) {
const newRef = vnode.ref, oldRef = oldVNode.ref;
if (newRef || oldRef) {
const context = vnode.context, node = vnode.component || vnode.node;
let $refs = context.$refs;
if (newRef) {
if (!oldRef) {
if (!$refs) {
$refs = context.$refs = {};
}
$refs[newRef] = node;
}
else if (newRef !== oldRef) {
if ($refs) {
if ($refs[newRef] === node) {
delete $refs[newRef];
}
}
else {
$refs = context.$refs = {};
}
$refs[newRef] = node;
}
}
else if ($refs && oldRef && $refs[oldRef] === node) {
delete $refs[oldRef];
}
}
}
function beforeDestroy(api, vnode) {
const { ref } = vnode;
if (ref) {
const { $refs } = vnode.context, node = vnode.component || vnode.node;
if ($refs && $refs[ref] === node) {
delete $refs[ref];
}
}
}
var refHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate,
beforeUpdate: beforeUpdate,
beforeDestroy: beforeDestroy
});
function getFragmentHostNode(api, vnode) {
if (vnode.type === VNODE_TYPE_FRAGMENT
|| vnode.type === VNODE_TYPE_SLOT) {
const child = vnode.children[0];
return child
? getFragmentHostNode(api, child)
: api.createComment(EMPTY_STRING);
}
return vnode.node;
}
function insertNodeNatively(api, parentNode, node, referenceNode) {
if (referenceNode) {
api.before(parentNode, node, referenceNode);
}
else {
api.append(parentNode, node);
}
}
function textVNodeUpdateOperator(api, vnode, oldVNode) {
const node = oldVNode.node;
vnode.node = node;
vnode.parentNode = oldVNode.parentNode;
if (vnode.text !== oldVNode.text) {
api.setNodeText(node, vnode.text);
}
}
function elementVNodeEnterOperator(vnode) {
if (vnode.data) {
enterVNode(vnode, vnode.node);
}
}
function elementVNodeLeaveOperator(vnode, done) {
if (vnode.data
&& leaveVNode(vnode, vnode.node, done)) {
return;
}
done();
}
function vnodeInsertOperator(api, parentNode, vnode, before) {
// 这里不调用 insertNodeNatively,避免判断两次
if (before) {
api.before(parentNode, vnode.node, before.node);
}
else {
api.append(parentNode, vnode.node);
}
}
function vnodeRemoveOperator(api, vnode) {
api.remove(vnode.parentNode, vnode.node);
}
function vnodeLeaveOperator(vnode, done) {
done();
}
function vnodeCreateChildrenOperator(api, vnode) {
const children = vnode.children;
for (let i = 0, length = children.length; i < length; i++) {
createVNode(api, children[i]);
}
}
function vnodeUpdateChildrenOperator(api, parentNode, vnode, oldVNode) {
updateChildren(api, parentNode, vnode.children, oldVNode.children);
}
function vnodeDestroyChildrenOperator(api, vnode) {
const children = vnode.children;
for (let i = 0, length = children.length; i < length; i++) {
destroyVNode(api, children[i]);
}
}
function vnodeInsertChildrenOperator(api, parentNode, vnode, before) {
const children = vnode.children;
for (let i = 0, length = children.length; i < length; i++) {
insertVNode(api, parentNode, children[i], before);
}
}
function vnodeRemoveChildrenOperator(api, vnode) {
const children = vnode.children;
for (let i = 0, length = children.length; i < length; i++) {
removeVNode(api, children[i]);
}
}
const textVNodeOperator = {
create(api, vnode) {
vnode.node = api.createText(vnode.text);
},
update: textVNodeUpdateOperator,
destroy: EMPTY_FUNCTION,
insert: vnodeInsertOperator,
remove: vnodeRemoveOperator,
enter: EMPTY_FUNCTION,
leave: vnodeLeaveOperator,
};
const commentVNodeOperator = {
create(api, vnode) {
vnode.node = api.createComment(vnode.text);
},
update: textVNodeUpdateOperator,
destroy: EMPTY_FUNCTION,
insert: vnodeInsertOperator,
remove: vnodeRemoveOperator,
enter: EMPTY_FUNCTION,
leave: vnodeLeaveOperator,
};
const vnodeHooksList = [
nativeAttrHook,
nativeStyleHook,
refHook,
eventHook,
modelHook,
directiveHook,
];
const vnodeHooksLength = vnodeHooksList.length;
function callVNodeHooks(name, args) {
for (let i = 0; i < vnodeHooksLength; i++) {
const hook = vnodeHooksList[i][name];
if (hook) {
hook.apply(UNDEFINED$1, args);
}
}
}
const elementVNodeOperator = {
create(api, vnode) {
const node = vnode.node = api.createElement(vnode.tag, vnode.isSvg);
if (vnode.children) {
addVNodes(api, node, vnode.children);
}
else if (vnode.text) {
api.setElementText(node, vnode.text);
}
else if (vnode.html) {
api.setHtml(node, vnode.html);
}
if (!vnode.isPure) {
vnode.data = {};
}
callVNodeHooks('afterCreate', [api, vnode]);
},
update(api, vnode, oldVNode) {
const node = oldVNode.node;
vnode.node = node;
vnode.parentNode = oldVNode.parentNode;
vnode.data = oldVNode.data;
if (!vnode.isPure && oldVNode.isPure) {
vnode.data = {};
}
callVNodeHooks('beforeUpdate', [api, vnode, oldVNode]);
const { text, html, children } = vnode, oldText = oldVNode.text, oldHtml = oldVNode.html, oldChildren = oldVNode.children;
if (string$1(text)) {
if (oldChildren) {
removeVNodes(api, oldChildren);
}
if (text !== oldText) {
api.setElementText(node, text);
}
}
else if (string$1(html)) {
if (oldChildren) {
removeVNodes(api, oldChildren);
}
if (html !== oldHtml) {
api.setHtml(node, html);
}
}
else if (children) {
// 两个都有需要 diff
if (oldChildren) {
if (children !== oldChildren) {
updateChildren(api, node, children, oldChildren);
}
}
// 有新的没旧的 - 新增节点
else {
if (oldText || oldHtml) {
api.setElementText(node, EMPTY_STRING);
}
addVNodes(api, node, children);
}
}
// 有旧的没新的 - 删除节点
else if (oldChildren) {
removeVNodes(api, oldChildren);
}
// 有旧的 text 没有新的 text
else if (oldText || oldHtml) {
api.setElementText(node, EMPTY_STRING);
}
callVNodeHooks('afterUpdate', [api, vnode, oldVNode]);
},
destroy(api, vnode) {
if (vnode.isPure) {
return;
}
callVNodeHooks('beforeDestroy', [api, vnode]);
const { children } = vnode;
if (children) {
for (let i = 0, length = children.length; i < length; i++) {
destroyVNode(api, children[i]);
}
}
},
insert: vnodeInsertOperator,
remove: vnodeRemoveOperator,
enter: elementVNodeEnterOperator,
leave: elementVNodeLeaveOperator,
};
const componentVNodeOperator = {
create(api, vnode) {
const data = vnode.data = {};
let componentOptions = UNDEFINED$1;
// 动态组件,tag 可能为空
if (vnode.tag) {
vnode.context.loadComponent(vnode.tag, function (options) {
if (has(data, LOADING)) {
// 异步组件
if (data[LOADING]) {
// 尝试使用最新的 vnode
if (data[VNODE]) {
vnode = data[VNODE];
// 用完就删掉
delete data[VNODE];
}
createComponent(api, vnode, options);
vnode.operator.enter(vnode);
}
}
// 同步组件
else {
componentOptions = options;
}
});
}
// 不论是同步还是异步组件,都需要一个占位元素
vnode.node = api.createComment(RAW_COMPONENT);
if (componentOptions) {
createComponent(api, vnode, componentOptions);
}
else {
data[LOADING] = TRUE$1;
}
},
update(api, vnode, oldVNode) {
const data = oldVNode.data;
vnode.data = data;
vnode.node = oldVNode.node;
vnode.parentNode = oldVNode.parentNode;
vnode.component = oldVNode.component;
// 组件正在异步加载,更新为最新的 vnode
// 当异步加载完成时才能用上最新的 vnode
if (data[LOADING]) {
data[VNODE] = vnode;
return;
}
callVNodeHooks('beforeUpdate', [api, vnode, oldVNode]);
const { component, slots } = vnode;
if (component) {
let nextProps = vnode.props;
if (slots) {
nextProps = extend(nextProps || {}, slots);
}
if (nextProps) {
component.forceUpdate(nextProps);
}
}
callVNodeHooks('afterUpdate', [api, vnode, oldVNode]);
},
destroy(api, vnode) {
const { component } = vnode;
if (component) {
callVNodeHooks('beforeDestroy', [api, vnode]);
component.destroy();
// 移除时,组件可能已经发生过变化,即 shadow 不是创建时那个对象了
vnode.shadow = component.$vnode;
vnode.component = UNDEFINED$1;
}
else {
vnode.data[LOADING] = FALSE$1;
}
},
insert(api, parentNode, vnode, before) {
const { shadow } = vnode;
if (shadow) {
shadow.operator.insert(api, parentNode, shadow, before);
shadow.parentNode = parentNode;
}
else {
vnodeInsertOperator(api, parentNode, vnode, before);
}
},
remove(api, vnode) {
const { shadow } = vnode;
if (shadow) {
shadow.operator.remove(api, shadow);
shadow.parentNode = UNDEFINED$1;
}
else {
vnodeRemoveOperator(api, vnode);
}
},
enter(vnode) {
const { shadow } = vnode;
if (shadow) {
if (vnode.transition) {
enterVNode(vnode, shadow.node);
}
else {
shadow.operator.enter(shadow);
}
}
},
leave(vnode, done) {
const { shadow } = vnode;
if (shadow) {
if (vnode.transition) {
if (leaveVNode(vnode, shadow.node, done)) {
return;
}
}
else {
shadow.operator.leave(shadow, done);
return;
}
}
done();
},
};
const fragmentVNodeOperator = {
create(api, vnode) {
vnodeCreateChildrenOperator(api, vnode);
vnode.node = getFragmentHostNode(api, vnode);
},
update(api, vnode, oldVNode) {
const { parentNode } = oldVNode;
vnode.node = oldVNode.node;
vnode.parentNode = parentNode;
vnodeUpdateChildrenOperator(api, parentNode, vnode, oldVNode);
},
destroy: vnodeDestroyChildrenOperator,
insert: vnodeInsertChildrenOperator,
remove: vnodeRemoveChildrenOperator,
enter: EMPTY_FUNCTION,
leave: vnodeLeaveOperator,
};
const portalVNodeOperator = {
create(api, vnode) {
let target = UNDEFINED$1;
if (vnode.to) {
target = api.find(vnode.to);
}
// 用 body 元素兜底
if (!target) {
target = api.getBodyElement();
}
vnode.target = target;
// 用注释占用节点在模板里的位置
// 这样删除或替换节点,才有找到它应该在的位置
vnode.node = api.createComment(EMPTY_STRING);
const children = vnode.children;
for (let i = 0, length = children.length; i < length; i++) {
createVNode(api, children[i]);
insertVNode(api, target, children[i]);
}
},
update(api, vnode, oldVNode) {
const { target } = oldVNode;
vnode.node = oldVNode.node;
vnode.parentNode = oldVNode.parentNode;
vnode.target = target;
vnodeUpdateChildrenOperator(api, target, vnode, oldVNode);
},
destroy(api, vnode) {
const children = vnode.children;
for (let i = 0, length = children.length; i < length; i++) {
destroyVNode(api, children[i]);
removeVNode(api, children[i]);
}
},
insert: vnodeInsertOperator,
remove: vnodeRemoveOperator,
enter: EMPTY_FUNCTION,
leave: vnodeLeaveOperator,
};
const slotVNodeOperator = {
create(api, vnode) {
vnodeCreateChildrenOperator(api, vnode);
vnode.data = {};
vnode.node = getFragmentHostNode(api, vnode);
callVNodeHooks('afterCreate', [api, vnode]);
},
update(api, vnode, oldVNode) {
const { parentNode } = oldVNode;
vnode.node = oldVNode.node;
vnode.parentNode = parentNode;
vnode.data = oldVNode.data;
callVNodeHooks('beforeUpdate', [api, vnode, oldVNode]);
vnodeUpdateChildrenOperator(api, parentNode, vnode, oldVNode);
callVNodeHooks('afterUpdate', [api, vnode, oldVNode]);
},
destroy(api, vnode) {
callVNodeHooks('beforeDestroy', [api, vnode]);
vnodeDestroyChildrenOperator(api, vnode);
},
insert: vnodeInsertChildrenOperator,
remove: vnodeRemoveChildrenOperator,
enter: elementVNodeEnterOperator,
leave: elementVNodeLeaveOperator,
};
function isPatchable(vnode, oldVNode) {
return vnode.type === oldVNode.type
&& vnode.tag === oldVNode.tag
&& vnode.key === oldVNode.key;
}
function createKeyToIndex(vnodes, startIndex, endIndex) {
let result, vnode, key;
while (startIndex <= endIndex) {
vnode = vnodes[startIndex];
if (vnode && (key = vnode.key)) {
if (!result) {
result = {};
}
result[key] = startIndex;
}
startIndex++;
}
return result || EMPTY_OBJECT;
}
function createComponent(api, vnode, options) {
const data = vnode.data, child = (vnode.parent || vnode.context).createComponent(options, vnode);
vnode.component = child;
vnode.shadow = child.$vnode;
data[LOADING] = FALSE$1;
callVNodeHooks('afterCreate', [api, vnode]);
return child;
}
function createVNode(api, vnode) {
if (!vnode.node) {
vnode.operator.create(api, vnode);
}
}
function addVNodes(api, parentNode, vnodes, startIndex, endIndex, before) {
let vnode, start = startIndex || 0, end = endIndex !== UNDEFINED$1 ? endIndex : vnodes.length - 1;
while (start <= end) {
vnode = vnodes[start];
createVNode(api, vnode);
insertVNode(api, parentNode, vnode, before);
start++;
}
}
function insertVNode(api, parentNode, vnode, before) {
const { operator } = vnode;
operator.insert(api, parentNode, vnode, before);
vnode.parentNode = parentNode;
callVNodeHooks('afterMount', [api, vnode]);
operator.enter(vnode);
}
function removeVNodes(api, vnodes, startIndex, endIndex) {
let vnode, start = startIndex || 0, end = endIndex !== UNDEFINED$1 ? endIndex : vnodes.length - 1;
while (start <= end) {
vnode = vnodes[start];
if (vnode) {
destroyVNode(api, vnode);
removeVNode(api, vnode);
}
start++;
}
}
function destroyVNode(api, vnode) {
vnode.operator.destroy(api, vnode);
}
function removeVNode(api, vnode) {
const { operator } = vnode;
operator.leave(vnode, function () {
operator.remove(api, vnode);
vnode.parentNode = UNDEFINED$1;
});
}
function enterVNode(vnode, node) {
const { context, transition } = vnode, data = vnode.data, leaving = data[LEAVING];
if (leaving) {
leaving();
}
if (transition) {
const { enter } = transition;
if (enter) {
enter.call(context, node);
}
}
}
function leaveVNode(vnode, node, done) {
const { context, transition } = vnode, data = vnode.data, leaving = data[LEAVING];
if (leaving) {
leaving();
}
if (transition) {
const { leave } = transition;
if (leave) {
leave.call(context, node, data[LEAVING] = function () {
if (data[LEAVING]) {
done();
data[LEAVING] = UNDEFINED$1;
}
});
return TRUE$1;
}
}
}
function updateChildren(api, parentNode, children, oldChildren) {
let startIndex = 0, endIndex = children.length - 1, startVNode = children[startIndex], endVNode = children[endIndex], oldStartIndex = 0, oldEndIndex = oldChildren.length - 1, oldStartVNode = oldChildren[oldStartIndex], oldEndVNode = oldChildren[oldEndIndex], oldKeyToIndex, oldIndex;
while (oldStartIndex <= oldEndIndex && startIndex <= endIndex) {
// 下面有设为 UNDEFINED 的逻辑
if (!startVNode) {
startVNode = children[++startIndex];
}
else if (!endVNode) {
endVNode = children[--endIndex];
}
else if (!oldStartVNode) {
oldStartVNode = oldChildren[++oldStartIndex];
}
else if (!oldEndVNode) {
oldEndVNode = oldChildren[--oldEndIndex];
}
// 从头到尾比较,位置相同且值得 patch
else if (isPatchable(startVNode, oldStartVNode)) {
updateVNode(api, startVNode, oldStartVNode);
startVNode = children[++startIndex];
oldStartVNode = oldChildren[++oldStartIndex];
}
// 从尾到头比较,位置相同且值得 patch
else if (isPatchable(endVNode, oldEndVNode)) {
updateVNode(api, endVNode, oldEndVNode);
endVNode = children[--endIndex];
oldEndVNode = oldChildren[--oldEndIndex];
}
// 比较完两侧的节点,剩下就是 位置发生改变的节点 和 全新的节点
// 当 endVNode 和 oldStartVNode 值得 patch
// 说明元素被移到右边了
else if (isPatchable(endVNode, oldStartVNode)) {
updateVNode(api, endVNode, oldStartVNode);
insertNodeNatively(api, parentNode, oldStartVNode.node, api.next(oldEndVNode.node));
endVNode = children[--endIndex];
oldStartVNode = oldChildren[++oldStartIndex];
}
// 当 oldEndVNode 和 startVNode 值得 patch
// 说明元素被移到左边了
else if (isPatchable(startVNode, oldEndVNode)) {
updateVNode(api, startVNode, oldEndVNode);
insertNodeNatively(api, parentNode, oldEndVNode.node, oldStartVNode.node);
startVNode = children[++startIndex];
oldEndVNode = oldChildren[--oldEndIndex];
}
// 尝试同级元素的 key
else {
if (!oldKeyToIndex) {
oldKeyToIndex = createKeyToIndex(oldChildren, oldStartIndex, oldEndIndex);
}
// 新节点之前的位置
oldIndex = startVNode.key
? oldKeyToIndex[startVNode.key]
: UNDEFINED$1;
// 移动元素
if (oldIndex !== UNDEFINED$1) {
patch(api, startVNode, oldChildren[oldIndex]);
oldChildren[oldIndex] = UNDEFINED$1;
}
// 新元素
else {
createVNode(api, startVNode);
}
insertVNode(api, parentNode, startVNode, oldStartVNode);
startVNode = children[++startIndex];
}
}
if (oldStartIndex > oldEndIndex) {
addVNodes(api, parentNode, children, startIndex, endIndex, children[endIndex + 1]);
}
else if (startIndex > endIndex) {
removeVNodes(api, oldChildren, oldStartIndex, oldEndIndex);
}
}
function updateVNode(api, vnode, oldVNode) {
if (vnode !== oldVNode) {
vnode.operator.update(api, vnode, oldVNode);
}
}
function patch(api, vnode, oldVNode) {
if (vnode === oldVNode) {
return;
}
// 如果不能 patch,则删除重建
if (!isPatchable(vnode, oldVNode)) {
// 同步加载的组件,初始化时不会传入占位节点
// 它内部会自动生成一个注释节点,当它的根 vnode 和注释节点对比时,必然无法 patch
// 于是走进此分支,为新组件创建一个 DOM 节点,然后继续 createComponent 后面的流程
const parentNode = oldVNode.parentNode;
createVNode(api, vnode);
if (parentNode) {
insertVNode(api, parentNode, vnode, oldVNode);
destroyVNode(api, oldVNode);
removeVNode(api, oldVNode);
}
return;
}
updateVNode(api, vnode, oldVNode);
}
function create(api, node, context) {
const vnode = {
context,
node,
parentNode: api.parent(node),
};
switch (node.nodeType) {
case NODE_TYPE_ELEMENT:
vnode.data = {};
vnode.tag = api.tag(node);
vnode.type = VNODE_TYPE_ELEMENT;
vnode.operator = elementVNodeOperator;
break;
case NODE_TYPE_TEXT:
vnode.isPure = TRUE$1;
vnode.text = node.nodeValue;
vnode.type = VNODE_TYPE_TEXT;
vnode.operator = textVNodeOperator;
break;
case NODE_TYPE_COMMENT:
vnode.isPure = TRUE$1;
vnode.text = node.nodeValue;
vnode.type = VNODE_TYPE_COMMENT;
vnode.operator = commentVNodeOperator;
break;
}
return vnode;
}
function destroy(api, vnode, isRemove) {
destroyVNode(api, vnode);
if (isRemove) {
removeVNode(api, vnode);
}
}
function clone(vnode) {
const { children } = vnode;
return {
type: vnode.type,
data: vnode.data,
node: vnode.node,
parentNode: vnode.parentNode,
target: vnode.target,
shadow: vnode.shadow,
parent: vnode.parent,
component: vnode.component,
context: vnode.context,
operator: vnode.operator,
tag: vnode.tag,
isSvg: vnode.isSvg,
isStatic: vnode.isStatic,
isPure: vnode.isPure,
slots: vnode.slots,
props: vnode.props,
nativeAttrs: vnode.nativeAttrs,
nativeStyles: vnode.nativeStyles,
directives: vnode.directives,
events: vnode.events,
lazy: vnode.lazy,
transition: vnode.transition,
model: vnode.model,
to: vnode.to,
ref: vnode.ref,
key: vnode.key,
text: vnode.text,
html: vnode.html,
children: children
? children.map(clone)
: children,
};
}
/**
* 元素 节点
*/
const ELEMENT = 1;
/**
* 属性 节点
*/
const ATTRIBUTE = 2;
/**
* 指令 节点
*/
const DIRECTIVE = 3;
/**
* 样式 节点
*/
const STYLE = 4;
/**
* 文本 节点
*/
const TEXT = 5;
/**
* if 节点
*/
const IF = 6;
/**
* else if 节点
*/
const ELSE_IF = 7;
/**
* else 节点
*/
const ELSE = 8;
/**
* each 节点
*/
const EACH = 9;
/**
* import 节点
*/
const IMPORT = 10;
/**
* 表达式 节点
*/
const EXPRESSION = 11;
/**
* 延展操作 节点
*/
const SPREAD = 12;
// 特殊标签
const specialTags = {};
// 特殊属性
const specialAttrs = {};
// 名称 -> 类型的映射
const name2Type = {};
// 标签名 -> vnode 类型的映射
const specialTag2VNodeType = {};
specialTags[TAG_SLOT] =
specialTags[TAG_PORTAL] =
specialTags[TAG_FRAGMENT] =
specialTags[TAG_TEMPLATE] =
specialAttrs[ATTR_KEY] =
specialAttrs[ATTR_REF] =
specialAttrs[TAG_SLOT] = TRUE$1;
name2Type['if'] = IF;
name2Type['each'] = EACH;
specialTag2VNodeType[TAG_FRAGMENT] = VNODE_TYPE_FRAGMENT;
specialTag2VNodeType[TAG_PORTAL] = VNODE_TYPE_PORTAL;
specialTag2VNodeType[TAG_SLOT] = VNODE_TYPE_SLOT;
function isSpecialAttr(element, attr) {
return specialAttrs[attr.name]
|| element.tag === TAG_PORTAL && attr.name === ATTR_TO
|| element.tag === TAG_SLOT && attr.name === ATTR_NAME;
}
function parseStyleString(source, callback) {
const parts = source.split(';');
for (let i = 0, len = parts.length; i < len; i++) {
const item = parts[i];
const index = item.indexOf(':');
if (index > 0) {
const key = trim(item.substring(0, index));
const value = trim(item.substring(index + 1));
if (key && value) {
callback(camelize(key), value);
}
}
}
}
function createAttribute$1(name, ns) {
return {
type: ATTRIBUTE,
isStatic: TRUE$1,
name,
ns,
};
}
function createDirective(name, ns, modifier) {
return {
type: DIRECTIVE,
ns,
name,
modifier,
};
}
function createStyle() {
return {
type: STYLE,
isStatic: TRUE$1,
};
}
function createEach(from, to, equal, index) {
return {
type: EACH,
from,
to,
equal: equal || UNDEFINED$1,
index,
isVirtual: TRUE$1,
};
}
function createElement$2(tag, dynamicTag, isSvg, isStyle, isComponent) {
const isSlot = tag === TAG_SLOT, isVirtual = !isComponent && !isSlot && !!specialTags[tag], isNative = !isComponent && !isSlot && !isVirtual;
return {
type: ELEMENT,
tag,
dynamicTag,
isSvg,
isStyle,
isStatic: isNative,
isNative,
isVirtual,
isComponent,
};
}
function createElse() {
return {
type: ELSE,
isVirtual: TRUE$1,
};
}
function createElseIf(expr) {
return {
type: ELSE_IF,
expr,
isVirtual: TRUE$1,
};
}
function createExpression(expr, safe) {
return {
type: EXPRESSION,
expr,
safe,
isLeaf: TRUE$1,
isStatic: expr.isStatic,
};
}
function createIf(expr) {
return {
type: IF,
expr,
isVirtual: TRUE$1,
};
}
function createImport(expr) {
return {
type: IMPORT,
expr,
isLeaf: TRUE$1,
};
}
function createSpread(expr) {
return {
type: SPREAD,
expr,
isLeaf: TRUE$1,
};
}
function createText$1(text) {
return {
type: TEXT,
text,
isStatic: TRUE$1,
isLeaf: TRUE$1,
};
}
const // 首字母大写,或中间包含 -
componentNamePattern = /^[A-Z]|-/,
// HTML 实体(中间最多 6 位,没见过更长的)
htmlEntityPattern = /&[#\w\d]{2,6};/,
// 常见的自闭合标签
selfClosingTagNames = toMap$1('area,base,embed,track,source,param,input,col,img,br,hr') ,
// 常见的 svg 标签
svgTagNames = toMap$1('svg,g,defs,desc,metadata,symbol,use,image,path,rect,circle,line,ellipse,polyline,polygon,text,tspan,tref,textpath,marker,pattern,clippath,mask,filter,cursor,view,animate,font,font-face,glyph,missing-glyph,animateColor,animateMotion,animateTransform,textPath,foreignObject') ,
// 常见的数字类型的属性(width,height,cellpadding,cellspacing 支持百分比,因此不计入数字类型)
numberAttributeNames = toMap$1('min,minlength,max,maxlength,step,size,rows,cols,tabindex,colspan,rowspan,frameborder') ,
// 常见的布尔类型的属性
booleanAttributeNames = toMap$1('disabled,checked,required,multiple,readonly,autofocus,autoplay,reversed,selected,controls,default,loop,muted,novalidate,draggable,contenteditable,hidden,spellcheck,allowfullscreen') ;
function isSelfClosing(tagName) {
return selfClosingTagNames[tagName] !== UNDEFINED$1;
}
function createAttribute(element, name, ns) {
// 组件用驼峰格式
if (element.isComponent) {
return createAttribute$1(camelize(name), ns);
}
// 原生 dom 属性
if (name === 'style') {
return createStyle();
}
const attribute = createAttribute$1(name, ns);
if (isBooleanNativeAttribute(name)) {
// 默认为 true 的布尔属性只有以下两种情况
attribute.defaultValue = name === 'spellcheck'
|| (element.tag === 'img' && name === 'draggable');
}
return attribute;
}
function getAttributeDefaultValue(element, name, defaultValue) {
// 比如 <Dog isLive>
if (element.isComponent) {
return TRUE$1;
}
// 无视 <input min> 无效写法
if (isNumberNativeAttribute(name)) {
return UNDEFINED$1;
}
// 布尔类型取决于 defaultValue
if (isBooleanNativeAttribute(name)) {
return formatBooleanNativeAttributeValue(name, TRUE$1, defaultValue);
}
// 字符串类型返回空字符串
return EMPTY_STRING;
}
function formatNativeAttributeValue(name, value, defaultValue) {
if (isNumberNativeAttribute(name)) {
return formatNumberNativeAttributeValue(name, value);
}
else if (isBooleanNativeAttribute(name)) {
return formatBooleanNativeAttributeValue(name, value, defaultValue);
}
// 字符串类型的属性,保持原样即可
return value;
}
function isNumberNativeAttribute(name) {
return numberAttributeNames[name];
}
function isBooleanNativeAttribute(name) {
return booleanAttributeNames[name];
}
function formatNumberNativeAttributeValue(name, value) {
return toString(value);
}
function formatBooleanNativeAttributeValue(name, value, defaultValue) {
// 布尔类型的属性,只有值为 true 或 属性名 才表示 true
const isTrue = value === TRUE$1 || value === RAW_TRUE || value === name;
return isTrue === defaultValue
? UNDEFINED$1
: (isTrue ? RAW_TRUE : RAW_FALSE);
}
function createElement$1(staticTag, dynamicTag) {
let isSvg = FALSE$1, isStyle = FALSE$1, isComponent = FALSE$1;
if (dynamicTag) {
isComponent = TRUE$1;
}
else {
isSvg = svgTagNames[staticTag] !== UNDEFINED$1;
// 是 svg 就不可能是组件
// 加这个判断的原因是,svg 某些标签含有 连字符 和 大写字母,比较蛋疼
if (!isSvg && componentNamePattern.test(staticTag)) {
isComponent = TRUE$1;
}
else if (staticTag === 'style') {
isStyle = TRUE$1;
}
}
return createElement$2(staticTag, dynamicTag, isSvg, isStyle, isComponent);
}
function compatElement(element) {
let { attrs } = element, hasType = FALSE$1;
if (attrs) {
each$2(attrs, function (attr) {
const name = attr.type === ATTRIBUTE
? attr.name
: UNDEFINED$1;
if (name === 'type') {
hasType = TRUE$1;
}
});
}
// 补全 style 标签的 type
// style 如果没有 type 则加一个 type="text/css"
// 因为低版本 IE 没这个属性,没法正常渲染样式
if (element.isStyle && !hasType) {
const attr = createAttribute$1('type');
attr.value = 'text/css';
push(element.attrs || (element.attrs = []), attr);
}
}
function setElementText$1(element, text) {
if (string$1(text)) {
if (htmlEntityPattern.test(text)) {
element.html = text;
}
else {
element.text = text;
}
}
else {
element.text = text;
}
return TRUE$1;
}
function setElementHtml(element, expr) {
element.html = expr;
return TRUE$1;
}
function isDef (target) {
return target !== UNDEFINED$1;
}
/**
* 字面量
*/
const LITERAL = 1;
/**
* 标识符
*/
const IDENTIFIER = 2;
/**
* 对象属性或数组下标
*/
const MEMBER = 3;
/**
* 一元表达式,如 - a
*/
const UNARY = 4;
/**
* 二元表达式,如 a + b
*/
const BINARY = 5;
/**
* 三元表达式,如 a ? b : c
*/
const TERNARY = 6;
/**
* 数组表达式,如 [ 1, 2, 3 ]
*/
const ARRAY = 7;
/**
* 对象表达式,如 { name: 'yox' }
*/
const OBJECT = 8;
/**
* 函数调用表达式,如 a()
*/
const CALL = 9;
const CODE_EOF = 0; //
const CODE_DOT = 46; // .
const CODE_COMMA = 44; // ,
const CODE_SLASH = 47; // /
const CODE_BACKSLASH = 92; // \
const CODE_SQUOTE = 39; // '
const CODE_DQUOTE = 34; // "
const CODE_OPAREN = 40; // (
const CODE_CPAREN = 41; // )
const CODE_OBRACK = 91; // [
const CODE_CBRACK = 93; // ]
const CODE_OBRACE = 123; // {
const CODE_CBRACE = 125; // }
const CODE_QUESTION = 63; // ?
const CODE_COLON = 58; // :
const CODE_PLUS = 43; // +
const CODE_MINUS = 45; // -
const CODE_MULTIPLY = 42; // *
const CODE_DIVIDE = 47; // /
const CODE_MODULO = 37; // %
const CODE_WAVE = 126; // ~
const CODE_AND = 38; // &
const CODE_OR = 124; // |
const CODE_XOR = 94; // ^
const CODE_NOT = 33; // !
const CODE_LESS = 60; // <
const CODE_EQUAL = 61; // =
const CODE_GREAT = 62; // >
const CODE_AT = 64; // @
/**
* 区分关键字和普通变量
* 举个例子:a === true
* 从解析器的角度来说,a 和 true 是一样的 token
*/
const keywordLiterals = {};
keywordLiterals[RAW_TRUE] = TRUE$1;
keywordLiterals[RAW_FALSE] = FALSE$1;
keywordLiterals[RAW_NULL] = NULL$1;
keywordLiterals[RAW_UNDEFINED] = UNDEFINED$1;
/**
* 是否是空白符,用下面的代码在浏览器测试一下
*
* ```
* for (var i = 0; i < 200; i++) {
* console.log(i, String.fromCharCode(i))
* }
* ```
*
* 从 0 到 32 全是空白符,100 往上分布比较散且较少用,唯一需要注意的是 160
*
* 160 表示 non-breaking space
* http://www.adamkoch.com/2009/07/25/white-space-and-character-160/
*/
function isWhitespace(code) {
return (code > 0 && code < 33) || code === 160;
}
/**
* 是否是数字
*/
function isDigit(code) {
return code > 47 && code < 58; // 0...9
}
/**
* 是否是数字
*/
function isNumber(code) {
return isDigit(code) || code === CODE_DOT;
}
/**
* 是否是插槽变量,@name 表示引用 name 所指定的插槽
*/
function isSlotIdentifierStart(code) {
return code === CODE_AT;
}
/**
* 变量开始字符必须是 字母、下划线、$
*/
function isIdentifierStart(code) {
return code === 36 // $
|| code === 95 // _
|| (code > 96 && code < 123) // a...z
|| (code > 64 && code < 91); // A...Z
}
/**
* 变量剩余的字符必须是 字母、下划线、$、数字
*/
function isIdentifierPart(code) {
return isIdentifierStart(code) || isDigit(code);
}
function createArray(nodes, raw) {
return {
type: ARRAY,
raw,
nodes,
isStatic: isStaticNodes(nodes),
};
}
function createBinary(left, operator, right, raw) {
return {
type: BINARY,
raw,
left,
operator,
right,
isStatic: isStaticNodes([left, right]),
};
}
function createCall(name, args, raw) {
return {
type: CALL,
raw,
name,
args,
};
}
function createIdentifier(raw, name, isProp) {
let root = FALSE$1, lookup = TRUE$1, offset = 0;
if (name === KEYPATH_CURRENT) {
name = EMPTY_STRING;
lookup = FALSE$1;
}
else if (name === KEYPATH_PARENT) {
name = EMPTY_STRING;
lookup = FALSE$1;
offset = 1;
}
else if (name === KEYPATH_ROOT) {
name = EMPTY_STRING;
root = TRUE$1;
lookup = FALSE$1;
}
else {
name = replaceSlotIdentifierIfNeeded(name);
}
// 对象属性需要区分 a.b 和 a[b]
// 如果不借用 Literal 无法实现这个判断
// 同理,如果用了这种方式,就无法区分 a.b 和 a['b'],但是无所谓,这两种表示法本就一个意思
return isProp
? createLiteral(name, raw)
: createIdentifierInner(raw, name, root, lookup, offset);
}
function createLiteral(value, raw) {
return {
type: LITERAL,
raw,
value,
isStatic: TRUE$1,
};
}
function createObject(keys, values, raw) {
return {
type: OBJECT,
raw,
keys,
values,
isStatic: isStaticNodes(values),
};
}
function createTernary(test, yes, no, raw) {
return {
type: TERNARY,
raw,
test,
yes,
no,
isStatic: isStaticNodes([test, yes, no]),
};
}
function createUnary(operator, node, raw) {
return {
type: UNARY,
raw,
operator,
node,
isStatic: node.isStatic,
};
}
/**
* 通过判断 nodes 来决定是否需要创建 Member
*
* 创建 Member 至少需要 nodes 有两个节点
*/
function createMemberIfNeeded(raw, nodes) {
// 第一个节点要特殊处理
let firstNode = nodes.shift(),
// 是否直接从顶层查找
root = FALSE$1,
// 是否向上查找
lookup = TRUE$1,
// 偏移量,默认从当前 context 开始查找
offset = 0;
// 表示传入的 nodes 至少有两个节点(弹出了一个)
if (nodes.length > 0) {
// 处理剩下的 nodes
// 这里要做两手准备:
// 1. 如果全是 literal 节点,则编译时 join
// 2. 如果不全是 literal 节点,则运行时 join
// 是否全是 Literal 节点
let isLiteral = TRUE$1,
// 静态节点
staticNodes = [],
// 对于 this.a.b[c] 这样的
// 要还原静态部分 this.a.b 的 raw
// 虽然 raw 没什么大用吧,谁让我是洁癖呢
staticRaw = EMPTY_STRING,
// 动态节点
dynamicNodes = [];
each$2(nodes, function (node) {
if (isLiteral) {
if (node.type === LITERAL) {
const literalNode = node;
if (literalNode.raw === KEYPATH_PARENT) {
offset += 1;
staticRaw = staticRaw
? staticRaw + RAW_SLASH + KEYPATH_PARENT
: KEYPATH_PARENT;
return;
}
if (literalNode.raw !== KEYPATH_CURRENT) {
const value = toString(literalNode.value);
push(staticNodes, value);
if (staticRaw) {
staticRaw += endsWith(staticRaw, KEYPATH_PARENT)
? RAW_SLASH
: RAW_DOT;
}
staticRaw += value;
}
}
else {
isLiteral = FALSE$1;
}
}
if (!isLiteral) {
push(dynamicNodes, node);
}
});
// lookup 要求第一位元素是 Identifier,且它的 lookup 是 true 才为 true
// 其他情况都为 false,如 "11".length 第一位元素是 Literal,不存在向上寻找的需求
// 优化 1:计算 keypath
//
// 计算 keypath 的唯一方式是,第一位元素是 Identifier,后面都是 Literal
// 否则就表示中间包含动态元素,这会导致无法计算静态路径
// 如 a.b.c 可以算出 static keypath,而 a[b].c 则不行,因为 b 是动态的
// 优化 2:计算 offset 并智能转成 Identifier
//
// 比如 xx 这样的表达式,应优化成 offset = 2,并转成 Identifier
// 处理第一个节点
if (firstNode.type === IDENTIFIER) {
const identifierNode = firstNode;
root = identifierNode.root;
lookup = identifierNode.lookup;
offset += identifierNode.offset;
let firstName = identifierNode.name;
// 不是 KEYPATH_THIS 或 KEYPATH_PARENT 或 KEYPATH_ROOT
if (firstName) {
firstName = replaceSlotIdentifierIfNeeded(firstName, identifierNode);
unshift(staticNodes, firstName);
}
// 转成 Identifier
firstName = join$1(staticNodes, RAW_DOT);
// a.b.c
if (isLiteral) {
firstNode = createIdentifierInner(raw, firstName, root, lookup, offset, staticNodes);
}
// a[b]
// this.a[b]
else {
// 当 isLiteral 为 false 时
// 需要为 lead 节点创建合适的 raw
let firstRaw = identifierNode.raw;
if (staticRaw) {
// 确定 firstNode 和后续静态节点的连接字符
let separator = RAW_DOT;
if (firstRaw === KEYPATH_ROOT
|| firstRaw === KEYPATH_PARENT) {
separator = RAW_SLASH;
}
firstRaw += separator + staticRaw;
}
firstNode = createMemberInner(raw, createIdentifierInner(firstRaw, firstName, root, lookup, offset, staticNodes), UNDEFINED$1, dynamicNodes, root, lookup, offset);
}
}
else {
// 例子:
// "xxx".length
// format().a.b
if (isLiteral) {
firstNode = createMemberInner(raw, firstNode, join$1(staticNodes, RAW_DOT), UNDEFINED$1, root, lookup, offset);
}
// 例子:
// "xxx"[length]
// format()[a]
else {
firstNode = createMemberInner(raw, firstNode, UNDEFINED$1, dynamicNodes, root, lookup, offset);
}
}
}
return firstNode;
}
function createIdentifierInner(raw, name, root, lookup, offset, literals) {
return {
type: IDENTIFIER,
raw,
name,
root,
lookup,
offset,
literals: literals && literals.length > 1 ? literals : UNDEFINED$1,
};
}
function createMemberInner(raw, lead, keypath, nodes, root, lookup, offset) {
return {
type: MEMBER,
raw,
lead,
keypath,
nodes,
root,
lookup,
offset,
isStatic: lead.isStatic && (!nodes || isStaticNodes(nodes)),
};
}
function replaceSlotIdentifierIfNeeded(name, identifierNode) {
// 如果是插槽变量,则进行替换
if (isSlotIdentifierStart(codeAt(name, 0))) {
name = SLOT_DATA_PREFIX + slice(name, 1);
if (identifierNode) {
identifierNode.name = name;
}
}
return name;
}
function isStaticNodes(nodes) {
let isStatic = TRUE$1;
each$2(nodes, function (node) {
if (!node.isStatic) {
return isStatic = FALSE$1;
}
});
return isStatic;
}
const unary = {
'+': TRUE$1,
'-': TRUE$1,
'~': TRUE$1,
'!': TRUE$1,
'!!': TRUE$1,
};
// 参考 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table
const binary = {
'*': 15,
'/': 15,
'%': 15,
'+': 14,
'-': 14,
'<<': 13,
'>>': 13,
'>>>': 13,
'<': 12,
'<=': 12,
'>': 12,
'>=': 12,
'==': 11,
'!=': 11,
'===': 11,
'!==': 11,
'&': 10,
'^': 9,
'|': 8,
'&&': 7,
'||': 6,
};
class Parser {
constructor(content) {
const instance = this;
instance.index = -1;
instance.end = content.length;
instance.code = CODE_EOF;
instance.content = content;
instance.go();
}
/**
* 移动一个字符
*/
go(step) {
let instance = this, { index, end } = instance;
index += step || 1;
if (index >= 0 && index < end) {
instance.code = instance.codeAt(index);
instance.index = index;
}
else {
instance.code = CODE_EOF;
instance.index = index < 0 ? -1 : end;
}
}
/**
* 跳过空白符
*/
skip(step) {
const instance = this, reversed = step && step < 0;
// 如果表达式是 " xyz ",到达结尾后,如果希望 skip(-1) 回到最后一个非空白符
// 必须先判断最后一个字符是空白符,否则碰到 "xyz" 这样结尾不是空白符的,其实不应该回退
if (instance.code === CODE_EOF) {
const oldIndex = instance.index;
instance.go(step);
// 如果跳一位之后不是空白符,还原,然后返回
if (!isWhitespace(instance.code)) {
instance.go(oldIndex - instance.index);
return;
}
}
// 逆向时,只有位置真的发生过变化才需要在停止时正向移动一位
// 比如 (a) 如果调用 skip 前位于 ),调用 skip(-1) ,结果应该是原地不动
// 为了解决这个问题,应该首先判断当前是不是空白符,如果不是,直接返回
else if (!isWhitespace(instance.code)) {
return;
}
// 如果是正向的,停在第一个非空白符左侧
// 如果是逆向的,停在第一个非空白符右侧
while (TRUE$1) {
if (isWhitespace(instance.code)) {
instance.go(step);
}
else {
if (reversed) {
instance.go();
}
break;
}
}
}
/**
* 判断当前字符
*/
is(code) {
return this.code === code;
}
/**
* 截取一段字符串
*/
pick(startIndex, endIndex) {
return slice(this.content, startIndex, isDef(endIndex) ? endIndex : this.index);
}
/**
* 读取 index 位置的 char code
*
* @param index
*/
codeAt(index) {
return codeAt(this.content, index);
}
/**
* 尝试解析下一个 token
*/
scanToken() {
const instance = this, { code, index } = instance;
let isSlotIdentifier = FALSE$1;
if (isSlotIdentifierStart(code)) {
isSlotIdentifier = TRUE$1;
instance.go();
}
// 因为上面可能前进了一步,因此这里用 instance.code
if (isIdentifierStart(instance.code)) {
return instance.scanTail(index, [
instance.scanIdentifier(index, isSlotIdentifier)
]);
}
// @后面是个标识符才行,否则回退
else if (isSlotIdentifier) {
instance.go(-1);
}
if (isDigit(code)) {
return instance.scanNumber(index);
}
switch (code) {
case CODE_EOF:
return;
// 'x' "x"
case CODE_SQUOTE:
case CODE_DQUOTE:
return instance.scanTail(index, [
instance.scanString(index, code)
]);
// .1 ./ ../
case CODE_DOT:
instance.go();
return isDigit(instance.code)
? instance.scanNumber(index)
: instance.scanPath(index);
// ~/a
case CODE_WAVE:
// 因为 ~ 可以是一元运算符,因此必须判断后面紧跟 / 才是路径
if (instance.codeAt(index + 1) === CODE_SLASH) {
return instance.scanPath(index);
}
break;
// (xx)
case CODE_OPAREN:
instance.go();
return instance.scanTernary(CODE_CPAREN);
// [xx, xx]
case CODE_OBRACK:
return instance.scanTail(index, [
createArray(instance.scanTuple(index, CODE_CBRACK), instance.pick(index))
]);
// { a: 'x', b: 'x' }
case CODE_OBRACE:
return instance.scanObject(index);
}
// 因为 scanOperator 会导致 index 发生变化,只能放在最后尝试
const operator = instance.scanOperator(index);
if (operator && unary[operator]) {
const node = instance.scanTernary();
if (node) {
if (node.type === LITERAL) {
const value = node.value;
if (number(value)) {
// 类似 ' -1 ' 这样的右侧有空格,需要撤回来
instance.skip(-1);
return createLiteral(-value, instance.pick(index));
}
}
// 类似 ' -a ' 这样的右侧有空格,需要撤回来
instance.skip(-1);
return createUnary(operator, node, instance.pick(index));
}
}
}
/**
* 扫描数字
*
* 支持整数和小数
*
* @param startIndex
* @return
*/
scanNumber(startIndex) {
const instance = this;
while (isNumber(instance.code)) {
instance.go();
}
const raw = instance.pick(startIndex);
// 尝试转型,如果转型失败,则确定是个错误的数字
if (numeric(raw)) {
return createLiteral(+raw, raw);
}
}
/**
* 扫描字符串
*
* 支持反斜线转义引号
*
* @param startIndex
* @param endCode
*/
scanString(startIndex, endCode) {
const instance = this;
loop: while (TRUE$1) {
// 这句有两个作用:
// 1. 跳过开始的引号
// 2. 驱动 index 前进
instance.go();
switch (instance.code) {
// \" \'
case CODE_BACKSLASH:
instance.go();
break;
case endCode:
instance.go();
break loop;
case CODE_EOF:
break loop;
}
}
// new Function 处理字符转义
const raw = instance.pick(startIndex);
return createLiteral(new Function(`return ${raw}`)(), raw);
}
/**
* 扫描对象字面量
*
* @param startIndex
*/
scanObject(startIndex) {
let instance = this, keys = [], values = [], isKey = TRUE$1, node;
// 跳过 {
instance.go();
loop: while (TRUE$1) {
switch (instance.code) {
case CODE_CBRACE:
instance.go();
break loop;
case CODE_EOF:
break loop;
// :
case CODE_COLON:
instance.go();
isKey = FALSE$1;
break;
// ,
case CODE_COMMA:
instance.go();
isKey = TRUE$1;
break;
default:
// 解析 key 的时候,node 可以为空,如 { } 或 { name: 'xx', }
// 解析 value 的时候,node 不能为空
node = instance.scanTernary();
if (isKey) {
if (node) {
// 处理 { key : value } key 后面的空格
instance.skip();
if (node.type === IDENTIFIER) {
push(keys, node.name);
}
else if (node.type === LITERAL) {
push(keys, node.value);
}
else {
break loop;
}
}
}
else if (node) {
// 处理 { key : value } value 后面的空格
instance.skip();
push(values, node);
}
// 类似这样 { key: }
else {
break loop;
}
}
}
return createObject(keys, values, instance.pick(startIndex));
}
/**
* 扫描元组,即 `a, b, c` 这种格式,可以是参数列表,也可以是数组
*
* @param startIndex
* @param endCode 元组的结束字符编码
*/
scanTuple(startIndex, endCode) {
let instance = this, nodes = [], node;
// 跳过开始字符,如 [ 和 (
instance.go();
loop: while (TRUE$1) {
switch (instance.code) {
case endCode:
instance.go();
break loop;
case CODE_EOF:
break loop;
case CODE_COMMA:
instance.go();
break;
default:
// 1. ( )
// 2. (1, 2, )
// 这三个例子都会出现 scanTernary 为空的情况
// 但是不用报错
node = instance.scanTernary();
if (node) {
// 为了解决 1 , 2 , 3 这样的写法
// 当解析出值后,先跳过后面的空格
instance.skip();
push(nodes, node);
}
}
}
return nodes;
}
/**
* 扫描路径,如 `./` 和 `../` 和 `/a`
*
* 路径必须位于开头,如 ./../ 或 ,不存在 a/../b/../c 这样的情况,因为路径是用来切换或指定 context 的
*
* @param startIndex
* @param prevNode
*/
scanPath(startIndex) {
let instance = this, nodes = [], name;
// 进入此函数时,已确定前一个 code 是 helper.CODE_DOT
// 此时只需判断接下来是 ./ 还是 / 就行了
while (TRUE$1) {
name = KEYPATH_CURRENT;
// ../
if (instance.is(CODE_DOT)) {
instance.go();
name = KEYPATH_PARENT;
}
// ~/a
else if (instance.is(CODE_WAVE)) {
instance.go();
name = KEYPATH_ROOT;
}
push(nodes, createIdentifier(name, name, nodes.length > 0));
// 如果以 / 结尾,则命中 ./ 或 ../
if (instance.is(CODE_SLASH)) {
instance.go();
const { index, code } = instance;
let isSlotIdentifier = FALSE$1;
if (isSlotIdentifierStart(code)) {
isSlotIdentifier = TRUE$1;
instance.go();
}
// 因为上面可能前进了一步,因此这里用 instance.code
if (isIdentifierStart(instance.code)) {
push(nodes, instance.scanIdentifier(index, isSlotIdentifier, TRUE$1));
return instance.scanTail(startIndex, nodes);
}
else {
// @后面是个标识符才行,否则回退
if (isSlotIdentifier) {
instance.go(-1);
}
if (instance.is(CODE_DOT)) {
// 先跳过第一个 .
instance.go();
// 继续循环
}
else {
break;
}
}
}
// 类似 . 或 ..,可能就是想读取层级对象
// 此处不用关心后面跟的具体是什么字符,那是其他函数的事情,就算报错也让别的函数去报
// 此处也不用关心延展操作符,即 ...object,因为表达式引擎管不了这事,它没法把对象变成 attr1=value1 attr2=value2 的格式
// 这应该是模板引擎该做的事
else {
break;
}
}
}
/**
* 扫描变量
*/
scanTail(startIndex, nodes) {
let instance = this, node;
/**
* 标识符后面紧着的字符,可以是 ( . [,此外还存在各种组合,感受一下:
*
* a.b.c().length
* a[b].c()()
* a[b][c]()[d](e, f, g).length
* [].length
*/
loop: while (TRUE$1) {
switch (instance.code) {
// a(x)
case CODE_OPAREN:
nodes = [
createCall(createMemberIfNeeded(instance.pick(startIndex), nodes), instance.scanTuple(instance.index, CODE_CPAREN), instance.pick(startIndex))
];
break;
// a.x
case CODE_DOT:
instance.go();
// 接下来的字符,可能是数字,也可能是标识符,如果不是就报错
if (isIdentifierPart(instance.code)) {
// 无需识别关键字
push(nodes, instance.scanIdentifier(instance.index, FALSE$1, TRUE$1));
break;
}
else {
break loop;
}
// a[]
case CODE_OBRACK:
// 过掉 [
instance.go();
node = instance.scanTernary(CODE_CBRACK);
if (node) {
push(nodes, node);
break;
}
else {
break loop;
}
default:
break loop;
}
}
return createMemberIfNeeded(instance.pick(startIndex), nodes);
}
/**
* 扫描标识符
*
* @param startIndex
* @param isProp 是否是对象的属性
* @return
*/
scanIdentifier(startIndex, isSlotIdentifier, isProp) {
const instance = this;
// 标识符的第一个字符在外面已经判断过,肯定符合要求
// 因此这里先前进一步
do {
instance.go();
} while (isIdentifierPart(instance.code));
const raw = instance.pick(startIndex);
// 插槽变量,@ 后面必须有其他字符
if (isSlotIdentifier && raw.length === 1) {
instance.fatal(startIndex, 'A slot identifier must be followed by its name.');
}
return !isProp && raw in keywordLiterals
? createLiteral(keywordLiterals[raw], raw)
: createIdentifier(raw, raw, isProp);
}
/**
* 扫描运算符
*
* @param startIndex
*/
scanOperator(startIndex) {
const instance = this;
switch (instance.code) {
// /、%、~、^
case CODE_DIVIDE:
case CODE_MODULO:
case CODE_WAVE:
case CODE_XOR:
instance.go();
break;
// *
case CODE_MULTIPLY:
instance.go();
break;
// +
case CODE_PLUS:
instance.go();
break;
// -
case CODE_MINUS:
instance.go();
break;
// !、!!、!=、!==
case CODE_NOT:
instance.go();
if (instance.is(CODE_NOT)) {
instance.go();
}
else if (instance.is(CODE_EQUAL)) {
instance.go();
if (instance.is(CODE_EQUAL)) {
instance.go();
}
}
break;
// &、&&
case CODE_AND:
instance.go();
if (instance.is(CODE_AND)) {
instance.go();
}
break;
// |、||
case CODE_OR:
instance.go();
if (instance.is(CODE_OR)) {
instance.go();
}
break;
// ==、===
case CODE_EQUAL:
instance.go();
if (instance.is(CODE_EQUAL)) {
instance.go();
if (instance.is(CODE_EQUAL)) {
instance.go();
}
}
break;
// <、<=、<<
case CODE_LESS:
instance.go();
if (instance.is(CODE_EQUAL)
|| instance.is(CODE_LESS)) {
instance.go();
}
break;
// >、>=、>>、>>>
case CODE_GREAT:
instance.go();
if (instance.is(CODE_EQUAL)) {
instance.go();
}
else if (instance.is(CODE_GREAT)) {
instance.go();
if (instance.is(CODE_GREAT)) {
instance.go();
}
}
break;
}
if (instance.index > startIndex) {
return instance.pick(startIndex);
}
}
/**
* 扫描二元运算
*/
scanBinary(startIndex) {
// 二元运算,如 a + b * c / d,这里涉及运算符的优先级
// 算法参考 https://en.wikipedia.org/wiki/Shunting-yard_algorithm
let instance = this,
// 格式为 [ index1, node1, index2, node2, ... ]
output = [], token, index, operator, operatorPrecedence, lastOperator, lastOperatorPrecedence;
while (TRUE$1) {
instance.skip();
push(output, instance.index);
token = instance.scanToken();
if (token) {
push(output, token);
push(output, instance.index);
instance.skip();
operator = instance.scanOperator(instance.index);
// 必须是二元运算符,一元不行
if (operator && (operatorPrecedence = binary[operator])) {
while (TRUE$1) {
// 比较前一个运算符
index = output.length - 4;
// 如果前一个运算符的优先级 >= 现在这个,则新建 Binary
// 如 a + b * c / d,当从左到右读取到 / 时,发现和前一个 * 优先级相同,则把 b * c 取出用于创建 Binary
if ((lastOperator = output[index])
&& (lastOperatorPrecedence = binary[lastOperator])
&& lastOperatorPrecedence >= operatorPrecedence) {
output.splice(index - 2, 5, createBinary(output[index - 2], lastOperator, output[index + 2], instance.pick(output[index - 3], output[index + 3])));
}
else {
break;
}
}
push(output, operator);
continue;
}
else {
operator = UNDEFINED$1;
}
}
// 没匹配到 token 或 operator 则跳出循环
break;
}
// 类似 a + b * c 这种走到这会有 11 个
// 此时需要从后往前遍历,因为确定后面的优先级肯定大于前面的
while (TRUE$1) {
// 最少的情况是 a + b,它有 7 个元素
if (output.length >= 7) {
index = output.length - 4;
output.splice(index - 2, 5, createBinary(output[index - 2], output[index], output[index + 2], instance.pick(output[index - 3], output[index + 3])));
}
else {
return output[1];
}
}
}
/**
* 扫描三元运算
*
* @param endCode
*/
scanTernary(endCode) {
/**
* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
*
* ?: 运算符的优先级几乎是最低的,比它低的只有四种: 赋值、yield、延展、逗号
* 我们不支持这四种,因此可认为 ?: 优先级最低
*/
const instance = this;
instance.skip();
let index = instance.index, test = instance.scanBinary(index), yes, no;
if (instance.is(CODE_QUESTION)) {
// 跳过 ?
instance.go();
yes = instance.scanTernary();
if (instance.is(CODE_COLON)) {
// 跳过 :
instance.go();
no = instance.scanTernary();
}
if (test && yes && no) {
// 类似 ' a ? 1 : 0 ' 这样的右侧有空格,需要撤回来
instance.skip(-1);
test = createTernary(test, yes, no, instance.pick(index));
}
}
// 过掉结束字符
if (isDef(endCode)) {
instance.skip();
if (instance.is(endCode)) {
instance.go();
}
}
return test;
}
fatal(start, message) {
}
}
const compile$1 = createOneKeyCache(function (content) {
const parser = new Parser(content);
return parser.scanTernary(CODE_EOF);
});
// 当前不位于 block 之间
const BLOCK_MODE_NONE = 1,
// {{ x }}
BLOCK_MODE_SAFE = 2,
// {{{ x }}}
BLOCK_MODE_UNSAFE = 3,
// 缓存编译正则
patternCache = {},
// 指令分隔符,如 on-click 和 lazy-click
directiveSeparator = '-',
// on-
directiveOnSeparator = DIRECTIVE_ON + directiveSeparator,
// lazy-
directiveLazySeparator = DIRECTIVE_LAZY + directiveSeparator,
// o-
directiveCustomSeparator = DIRECTIVE_CUSTOM + directiveSeparator,
// 解析 each 的 index
eachIndexPattern = /\s*:\s*([_$a-z]+)$/i,
// 换行符
// 比较神奇是,有时候你明明看不到换行符,却真的存在一个,那就是 \r
breaklinePattern = /^\s*[\n\r]\s*|\s*[\n\r]\s*$/g,
// 区间遍历
rangePattern = /\s*(=>|->)\s*/,
// 标签
// 动态标签支持路径表达式,比如 $this.name、$../name、$~/name
tagPattern = /<(\/)?([a-z][-a-z0-9]*|\$[^\s]*)/i,
// 开始注释
openCommentCode = '<!--',
// 结束注释
endCommentCode = '-->',
// 属性的 name
// 支持 on-click.namespace="" 或 on-get-out="" 或 xml:xx=""
attributePattern = /^\s*([-$.:\w]+)(?:=(['"]))?/,
// 自闭合标签
selfClosingTagPattern = /^\s*(\/)?>/;
/**
* 截取前缀之后的字符串
*/
function slicePrefix(str, prefix) {
return trim(slice(str, prefix.length));
}
function toTextNode(node) {
if (node.safe
&& node.expr.type === LITERAL) {
return createText$1(toString(node.expr.value));
}
}
function compareMatchIndex(index1, index2) {
if (index1 >= 0 && index2 >= 0) {
return index1 < index2 ? -1 : 1;
}
else if (index1 >= 0) {
return -1;
}
else if (index2 >= 0) {
return 1;
}
return 0;
}
function walkCommentString(stack, code, push$1, pop$1) {
while (TRUE$1) {
const openIndex = code.text.indexOf(openCommentCode, code.position), closeIndex = code.text.indexOf(endCommentCode, code.position);
switch (compareMatchIndex(openIndex, closeIndex)) {
case -1:
code.position = openIndex + 1;
const range = {
startIndex: openIndex
};
push(stack, range);
if (push$1) {
push$1(code, range);
}
break;
case 1:
code.position = closeIndex + 1;
// 如果 stack.length 为 0,却匹配到了一个 -->
// 这种情况不用处理,因为可能就是纯字符串,或者 <!-- 被插值语法隔断了
const { length } = stack;
if (length > 0) {
const range = pop(stack);
range.endIndex = closeIndex + endCommentCode.length;
const isEmpty = length === 1;
if (pop$1) {
const result = pop$1(code, range, isEmpty);
if (result === FALSE$1) {
return;
}
}
if (isEmpty) {
return;
}
}
break;
case 0:
return;
}
}
}
function removeCommentNode(children) {
// 类似 <!-- xx {{name}} yy {{age}} zz --> 这样的注释里包含插值
// 按照目前的解析逻辑,是根据定界符进行模板分拆
// 一旦出现插值,children 长度必然大于 1
const stack = [];
// children 是否发生 splice 操作
let hasSplice = FALSE$1;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.type === TEXT) {
const textNode = child;
walkCommentString(stack, {
text: textNode.text,
position: 0,
}, function (_, commentRange) {
commentRange.index = i;
commentRange.text = slice(textNode.text, 0, commentRange.startIndex);
}, function (commentPosition, commentRange) {
const openIndex = commentRange.index, closeIndex = i, openText = commentRange.text, closeText = slice(textNode.text, commentRange.endIndex);
if (openIndex === closeIndex) {
commentPosition.text = textNode.text = openText + closeText;
commentPosition.position = commentRange.startIndex;
return;
}
let startIndex = openIndex, endIndex = closeIndex;
if (openText) {
children[openIndex].text = openText;
startIndex++;
}
if (closeText) {
children[closeIndex].text = closeText;
endIndex--;
}
const deleteCount = endIndex - startIndex + 1;
if (deleteCount > 0) {
hasSplice = TRUE$1;
children.splice(startIndex, deleteCount);
i = startIndex - 1;
}
return FALSE$1;
});
}
}
if (hasSplice) {
// 合并相邻的静态文本节点
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.type === TEXT) {
const nextChild = children[i + 1];
if (nextChild && nextChild.type === TEXT) {
child.text += nextChild.text;
children.splice(i + 1, 1);
}
}
}
}
}
function compile(content) {
// 左安全定界符
let leftSafeDelimiter = repeat(PUBLIC_CONFIG.leftDelimiter, 2),
// 右安全定界符
rightSafeDelimiter = repeat(PUBLIC_CONFIG.rightDelimiter, 2), leftUnsafeFlag = PUBLIC_CONFIG.leftDelimiter, rightUnsafeFlag = PUBLIC_CONFIG.rightDelimiter, nodeList = [], nodeStack = [], commentStack,
// 持有 if 节点,方便 if/elseif/else 出栈时,获取到 if 节点
ifList = [],
// 持有 if/elseif/else 节点
ifStack = [],
// 持有 each 节点,方便 each/else 出栈时,获取到 each 节点
eachList = [],
// 持有 each/else 节点
eachStack = [], currentElement, currentAttribute, length = content.length,
// 当前处理的位置
index = 0,
// 下一段开始的位置
nextIndex = 0,
// 开始定界符的位置,表示的是 {{ 的右侧位置
openBlockIndex = 0,
// 结束定界符的位置,表示的是 }} 的左侧位置
closeBlockIndex = 0,
// 当前正在处理或即将处理的 block 类型
blockMode = BLOCK_MODE_NONE,
// mustache 注释可能出现嵌套插值的情况
blockStack = [], indexList = [], code, attributeStartQuote, /**
* 常见的两种情况:
*
* <div>
* <input>1
* </div>
*
* <div>
* <input>
* </div>
*/
popSelfClosingElementIfNeeded = function (popingTagName) {
const lastNode = last(nodeStack);
if (lastNode && lastNode.type === ELEMENT) {
const lastElement = lastNode;
if (lastElement.tag !== popingTagName
&& isSelfClosing(lastElement.tag)) {
popStack(lastElement.type, lastElement.tag);
}
}
}, popStack = function (type, tagName) {
const node = pop(nodeStack);
const branchNode = node, isElement = type === ELEMENT, isAttribute = type === ATTRIBUTE, isStyle = type === STYLE, isDirective = type === DIRECTIVE, parentBranchNode = last(nodeStack);
let { children } = branchNode;
// 先处理 children.length 大于 1 的情况,因为这里会有一些优化,导致最后的 children.length 不一定大于 0
if (children && children.length > 1) {
// 元素层级
if (!currentElement) {
removeCommentNode(children);
if (!children.length) {
children = branchNode.children = UNDEFINED$1;
}
}
}
// 除了符合 isSpecialAttr() 定义的特殊属性,attrs 里的任何节点都不能单独拎出来赋给 element
// 因为 attrs 可能存在 if,所以每个 attr 最终都不一定会存在
if (children) {
// 优化单个子节点
// 减少运行时的负担
const onlyChild = children.length === 1 && children[0];
if (onlyChild) {
switch (onlyChild.type) {
case TEXT:
if (isElement) {
processElementSingleText(branchNode, onlyChild);
}
else if (currentElement) {
if (isAttribute) {
processAttributeSingleText(currentElement, branchNode, onlyChild);
}
else if (isStyle) {
processStyleSingleText(currentElement, branchNode, onlyChild);
}
else if (isDirective) {
processDirectiveSingleText(currentElement, branchNode, onlyChild);
}
}
break;
case EXPRESSION:
if (isElement) {
processElementSingleExpression(branchNode, onlyChild);
}
else if (currentElement && (isAttribute || isStyle || isDirective)) {
processAttributeSingleExpression(currentElement, branchNode, onlyChild);
}
break;
}
}
}
// 0 个子节点
else if (currentElement) {
if (isAttribute) {
processAttributeEmptyChildren(currentElement, branchNode);
}
else if (isStyle) {
processStyleEmptyChildren(currentElement, branchNode);
}
else if (isDirective) {
processDirectiveEmptyChildren(currentElement, branchNode);
}
}
if (branchNode.isVirtual
&& !branchNode.children) {
replaceChild(branchNode);
}
if (isElement) {
checkElement(branchNode);
}
else if (currentElement) {
if (isAttribute) {
checkAttribute(currentElement, branchNode);
}
}
// 弹出过程可能会修改 branchNode.isStatic,因此这段放在最后执行
// 当 branchNode 出栈时,它的 isStatic 就彻底固定下来,不会再变了
// 这时如果它不是静态节点,则父节点也不是静态节点
if (parentBranchNode
&& parentBranchNode.isStatic
&& !branchNode.isStatic) {
parentBranchNode.isStatic = FALSE$1;
}
return branchNode;
}, processElementSingleText = function (element, child) {
// 需要在这特殊处理的是 html 实体
// 但这只是 WEB 平台的特殊逻辑,所以丢给 platform 处理
if (element.isNative
&& setElementText$1(element, child.text)) {
element.children = UNDEFINED$1;
}
}, processElementSingleExpression = function (element, child) {
if (element.isNative) {
if (child.safe && setElementText$1(element, child.expr)
|| !child.safe && setElementHtml(element, child.expr)) {
element.children = UNDEFINED$1;
}
}
}, processStyleEmptyChildren = function (element, style) {
// 如果不写值,直接忽略
replaceChild(style);
}, processStyleSingleText = function (element, style, child) {
if (child.text) {
style.value = child.text;
style.children = UNDEFINED$1;
}
else {
// 如果是 style="",直接忽略
replaceChild(style);
}
}, processAttributeEmptyChildren = function (element, attr) {
if (isSpecialAttr(element, attr)) ;
else {
attr.value = getAttributeDefaultValue(element, attr.name, attr.defaultValue);
}
}, processAttributeSingleText = function (element, attr, child) {
const { text } = child;
if (element.isNative) {
attr.value = formatNativeAttributeValue(attr.name, text, attr.defaultValue);
}
else {
attr.value = text;
}
attr.children = UNDEFINED$1;
}, processAttributeSingleExpression = function (element, attr, child) {
const { expr } = child;
if (expr.type === LITERAL) {
let value = expr.value;
if (element.isNative && attr.type === ATTRIBUTE) {
value = formatNativeAttributeValue(attr.name, value, attr.defaultValue);
}
attr.value = value;
}
else {
attr.expr = expr;
}
attr.children = UNDEFINED$1;
}, processDirectiveEmptyChildren = function (element, directive) {
directive.value = TRUE$1;
}, processDirectiveSingleText = function (element, directive, child) {
let { ns } = directive, { text } = child,
// 自定义指令运行不合法的表达式
isCustom = ns === DIRECTIVE_CUSTOM,
// 指令的值是纯文本,可以预编译表达式,提升性能
expr, error;
try {
expr = compile$1(text);
}
catch (e) {
error = e;
}
if (expr) {
directive.expr = expr;
directive.value = expr.type === LITERAL
? expr.value
: text;
}
else {
// 自定义指令支持错误的表达式
// 反正是自定义的规则,爱怎么写就怎么写
if (!isCustom) {
throw error;
}
directive.value = text;
}
directive.children = UNDEFINED$1;
}, checkCondition = function (condition) {
// 这里会去掉没有子节点的空分支
let currentNode = condition, nodeList = [], hasNext = FALSE$1, hasChildren = FALSE$1;
// 转成数组,方便下一步从后往前遍历
while (TRUE$1) {
push(nodeList, currentNode);
if (currentNode.next) {
currentNode = currentNode.next;
}
else {
break;
}
}
each$2(nodeList, function (node) {
// 当前分支有子节点
if (node.children) {
// 从后往前遍历第一次发现非空分支
// 此时,可以删掉后面的空分支
if (!hasNext && node.next) {
delete node.next;
}
hasChildren = hasNext = TRUE$1;
}
}, TRUE$1);
// 所有分支都没有子节点,删掉整个 if
if (!hasChildren) {
replaceChild(condition);
}
}, checkElement = function (element) {
const { tag, slot } = element, isTemplate = tag === TAG_TEMPLATE, isFragment = tag === TAG_FRAGMENT, isPortal = tag === TAG_PORTAL;
// 没有子节点,则意味着这个元素没任何意义
if ((isTemplate || isFragment || isPortal) && !element.children) {
replaceChild(element);
}
// 处理浏览器兼容问题
else if (element.isNative) {
compatElement(element);
}
}, checkAttribute = function (element, attr) {
const { name, value } = attr, isSlot = name === ATTR_SLOT;
if (isSpecialAttr(element, attr)) {
const isStringValueRequired = isSlot;
element[name] = isStringValueRequired ? value : attr;
replaceChild(attr);
if (attr.isStatic) {
attr.isStatic = FALSE$1;
}
}
}, replaceChild = function (oldNode, newNode) {
let currentBranch = last(nodeStack), isAttr, list, index;
if (currentBranch) {
isAttr = currentElement && currentElement === currentBranch;
list = isAttr
? currentBranch.attrs
: currentBranch.children;
}
else {
list = nodeList;
}
if (list) {
index = indexOf$1(list, oldNode);
if (index >= 0) {
if (newNode) {
list[index] = newNode;
}
else {
list.splice(index, 1);
if (currentBranch && !list.length) {
if (isAttr) {
delete currentBranch.attrs;
}
else {
currentBranch.children = UNDEFINED$1;
}
}
}
}
}
}, addChild = function (node) {
/**
* <div>
* <input>
* <div></div>
* </div>
*
* <div>
* <input>xxx
* </div>
*/
if (!currentElement) {
popSelfClosingElementIfNeeded();
}
let type = node.type, currentBranch = last(nodeStack), lastIfBranch = UNDEFINED$1, lastElseIfBranch = UNDEFINED$1, lastEachBranch = UNDEFINED$1;
if (type === ELSE_IF) {
const lastNode = last(ifStack);
if (lastNode) {
// lastNode 只能是 if 或 else if 节点
if (lastNode.type === IF) {
lastIfBranch = lastNode;
}
else if (lastNode.type === ELSE_IF) {
lastElseIfBranch = lastNode;
}
// 上一个节点是 else,又加了一个 else if
else ;
}
}
else if (type === ELSE) {
const lastIfNode = last(ifStack), lastEachNode = last(eachStack);
if (lastIfNode && currentBranch === lastIfNode) {
// lastIfNode 只能是 if 或 else if 节点
if (lastIfNode.type === IF) {
lastIfBranch = lastIfNode;
}
else if (lastIfNode.type === ELSE_IF) {
lastElseIfBranch = lastIfNode;
}
// 上一个节点是 else,又加了一个 else
else ;
}
else if (lastEachNode && currentBranch === lastEachNode) {
// lastEachNode 只能是 each 节点
if (lastEachNode.type === EACH) {
lastEachBranch = lastEachNode;
}
}
else ;
}
else {
if (currentBranch) {
// 这里不能写 currentElement && !currentAttribute,举个例子
//
// <div id="x" {{#if}} name="xx" alt="xx" {{/if}}
//
// 当 name 属性结束后,条件满足,但此时已不是元素属性层级了
if (currentElement && currentBranch.type === ELEMENT) {
// node 没法转型,一堆可能的类型怎么转啊...
push(currentElement.attrs || (currentElement.attrs = []), node);
}
else {
const children = currentBranch.children || (currentBranch.children = []), lastChild = last(children);
// 如果表达式是安全插值的字面量,可以优化成字符串
if (type === EXPRESSION
// 在元素的子节点中,则直接转成字符串
&& (!currentElement
// 在元素的属性中,如果同级节点大于 0 个(即至少存在一个),则可以转成字符串
|| (currentAttribute && children.length > 0))) {
const textNode = toTextNode(node);
if (textNode) {
node = textNode;
type = textNode.type;
}
}
// 连续添加文本节点,则直接合并
if (lastChild
&& type === TEXT) {
// 合并两个文本节点
if (lastChild.type === TEXT) {
lastChild.text += node.text;
return;
}
// 前一个是字面量的表达式,也可以合并节点
// 比如 attr="{{true}}1",先插入了一个 true 字面量表达式,然后再插入一个文本时,可以合并
if (lastChild.type === EXPRESSION) {
const textNode = toTextNode(lastChild);
if (textNode) {
children[children.length - 1] = textNode;
textNode.text += node.text;
return;
}
}
}
push(children, node);
}
}
else {
push(nodeList, node);
}
}
if (type === IF) {
push(ifList, node);
push(ifStack, node);
}
else if (type === EACH) {
push(eachList, node);
push(eachStack, node);
}
else if (lastIfBranch) {
lastIfBranch.next = node;
ifStack[ifStack.length - 1] = node;
popStack(lastIfBranch.type);
}
else if (lastElseIfBranch) {
lastElseIfBranch.next = node;
ifStack[ifStack.length - 1] = node;
popStack(lastElseIfBranch.type);
}
else if (lastEachBranch) {
lastEachBranch.next = node;
eachStack[eachStack.length - 1] = node;
popStack(lastEachBranch.type);
}
if (node.isLeaf) {
// 当前树枝节点如果是静态的,一旦加入了一个非静态子节点,改变当前树枝节点的 isStatic
// 这里不处理树枝节点的进栈,因为当树枝节点出栈时,还有一次处理机会,那时它的 isStatic 已确定下来,不会再变
if (currentBranch) {
if (currentBranch.isStatic && !node.isStatic) {
currentBranch.isStatic = FALSE$1;
}
}
}
else {
push(nodeStack, node);
}
}, addTextChild = function (text) {
// [注意]
// 这里不能随便删掉
// 因为收集组件的子节点会受影响,举个例子:
// <Component>
//
// </Component>
// 按现在的逻辑,这样的组件是没有子节点的,因为在这里过滤掉了,因此该组件没有 slot
// 如果这里放开了,组件就会有一个 slot
// trim 文本开始和结束位置的换行符
text = text.replace(breaklinePattern, EMPTY_STRING);
if (text) {
addChild(createText$1(text));
}
}, htmlParsers = [
function (content) {
if (!commentStack && !currentElement) {
const match = content.match(tagPattern);
// 必须以 <tag 开头才能继续
// 如果 <tag 前面有别的字符,会走进第四个 parser
if (match && match.index === 0) {
let tag = match[2];
// 结束标签
if (match[1] === RAW_SLASH) {
/**
* 处理可能存在的自闭合元素,如下
*
* <div>
* <input>
* </div>
*/
popSelfClosingElementIfNeeded(tag);
// 等到 > 字符才算真正的结束
currentElement = popStack(ELEMENT);
}
// 开始标签
else {
let dynamicTag;
// 如果以 $ 开头,表示动态组件
if (charAt(tag) === RAW_DOLLAR) {
// 编译成表达式
tag = slice(tag, 1);
dynamicTag = compile$1(tag);
}
const node = createElement$1(tag, dynamicTag);
addChild(node);
currentElement = node;
}
return match[0];
}
}
},
// 处理标签的 > 或 />,不论开始还是结束标签
function (content) {
if (commentStack) {
return;
}
const match = content.match(selfClosingTagPattern);
if (match) {
// 处理开始标签的 > 或 />
// 处理结束标签的 >
if (currentElement && !currentAttribute) {
// 自闭合标签
if (match[1] === RAW_SLASH) {
popStack(currentElement.type, currentElement.tag);
}
currentElement = UNDEFINED$1;
return match[0];
}
// 如果只是写了一个 > 字符
// 比如 <div>></div>
// 则交给其他 parser 处理
}
},
// 处理 attribute directive 的 name 部分
function (content) {
// 当前在 element 层级
if (!commentStack && currentElement && !currentAttribute) {
const match = content.match(attributePattern);
if (match) {
let node, name = match[1];
if (name === DIRECTIVE_MODEL || name === DIRECTIVE_TRANSITION) {
node = createDirective(EMPTY_STRING, name);
}
// 这里要用 on- 判断前缀,否则 on 太容易重名了
else if (startsWith(name, directiveOnSeparator)) {
const event = slicePrefix(name, directiveOnSeparator);
const parts = camelize(event).split(RAW_DOT);
node = createDirective(parts[0], DIRECTIVE_EVENT, parts[1]);
}
// 当一个元素绑定了多个事件时,可分别指定每个事件的 lazy
// 当只有一个事件时,可简写成 lazy
// <div on-click="xx" lazy-click
else if (name === DIRECTIVE_LAZY) {
node = createDirective(EMPTY_STRING, DIRECTIVE_LAZY);
}
else if (startsWith(name, directiveLazySeparator)) {
const lazy = slicePrefix(name, directiveLazySeparator);
node = createDirective(camelize(lazy), DIRECTIVE_LAZY);
}
// 自定义指令
else if (startsWith(name, directiveCustomSeparator)) {
const custom = slicePrefix(name, directiveCustomSeparator);
const parts = camelize(custom).split(RAW_DOT);
node = createDirective(parts[0], DIRECTIVE_CUSTOM, parts[1]);
}
else {
// 处理类似 xml:name="value" 的命名空间
const parts = name.split(':');
node = parts.length === 2
? createAttribute(currentElement, parts[1], parts[0])
: createAttribute(currentElement, name);
}
addChild(node);
// 这里先记下,下一个 handler 要匹配结束引号
attributeStartQuote = match[2];
// 有属性值才需要设置 currentAttribute,便于后续收集属性值
if (attributeStartQuote) {
currentAttribute = node;
}
else {
popStack(node.type);
}
return match[0];
}
}
},
function (content) {
let text = UNDEFINED$1, match;
// 处理 attribute directive 的 value 部分
if (!commentStack && currentAttribute && attributeStartQuote) {
match = content.match(patternCache[attributeStartQuote] || (patternCache[attributeStartQuote] = new RegExp(attributeStartQuote)));
// 有结束引号
if (match) {
text = slice(content, 0, match.index);
addTextChild(text);
// 收集 value 到此结束
// 此时如果一个值都没收集到,需设置一个空字符串
// 否则无法区分 <div a b=""> 中的 a 和 b
if (!currentAttribute.children) {
addChild(createText$1(EMPTY_STRING));
}
text += attributeStartQuote;
popStack(currentAttribute.type);
currentAttribute = UNDEFINED$1;
}
// 没有结束引号,整段匹配
// 比如 <div name="1{{a}}2"> 中的 1
else {
text = content;
addTextChild(text);
}
}
// 如果不加判断,类似 <div {{...obj}}> 这样写,会把空格当做一个属性
// 收集文本只有两处:属性值、元素内容
// 属性值通过上面的 if 处理过了,这里只需要处理元素内容
else if (!currentElement) {
let childText = EMPTY_STRING;
// 如果当前位置不在注释中,则先判断是否有标签
if (!commentStack) {
match = content.match(tagPattern);
if (match) {
const tagIndex = match.index, commentIndex = content.indexOf(openCommentCode);
// tag 在 comment 前面
if (compareMatchIndex(tagIndex, commentIndex) < 0) {
text = slice(content, 0, tagIndex);
childText = text;
}
}
}
if (!isDef(text)) {
let startIndex = -1, endIndex = -1;
const oldCommentStack = commentStack, newCommentStack = commentStack || [];
// 重置 commentStack
commentStack = UNDEFINED$1;
walkCommentString(newCommentStack, {
text: content,
position: 0,
}, function (_, commentRange) {
if (startIndex < 0) {
startIndex = commentRange.startIndex;
text = content;
childText = content;
commentStack = newCommentStack;
}
}, function (_, commentRange, isEmpty) {
if (isEmpty) {
commentStack = UNDEFINED$1;
endIndex = commentRange.endIndex;
// 注释是完整的
if (startIndex >= 0) {
if (oldCommentStack) {
text = slice(content, 0, endIndex);
childText = text;
}
else {
text = slice(content, 0, endIndex);
childText = slice(content, 0, startIndex);
}
}
// 只匹配到注释结束符
else {
text = slice(content, 0, endIndex);
childText = text;
}
}
});
}
if (!isDef(text)) {
// 普通文本
text = content;
childText = content;
}
addTextChild(childText);
}
else {
text = content;
}
return text;
},
], blockParsers = [
// {{#each xx:index}}
function (source) {
if (startsWith(source, SYNTAX_EACH)) {
source = trim(slicePrefix(source, SYNTAX_EACH));
let literal = source, index = UNDEFINED$1, match = source.match(eachIndexPattern);
if (match) {
index = match[1];
literal = slice(source, 0, -1 * match[0].length);
}
match = literal.match(rangePattern);
if (match) {
const parts = literal.split(rangePattern), from = compile$1(parts[0]), to = compile$1(parts[2]);
if (from && to) {
return createEach(from, to, match[1] === '=>', index);
}
}
else {
const expr = compile$1(literal);
if (expr) {
return createEach(expr, UNDEFINED$1, FALSE$1, index);
}
}
}
},
// {{#import name}}
function (source) {
if (startsWith(source, SYNTAX_IMPORT)) {
source = slicePrefix(source, SYNTAX_IMPORT);
const expr = compile$1(source);
return createImport(expr);
}
},
// {{#if expr}}
function (source) {
if (startsWith(source, SYNTAX_IF)) {
source = slicePrefix(source, SYNTAX_IF);
const expr = compile$1(source);
return createIf(expr);
}
},
// {{else if expr}}
function (source) {
if (startsWith(source, SYNTAX_ELSE_IF)) {
source = slicePrefix(source, SYNTAX_ELSE_IF);
const expr = compile$1(source);
return createElseIf(expr);
}
},
// {{else}}
function (source) {
if (startsWith(source, SYNTAX_ELSE)) {
source = slicePrefix(source, SYNTAX_ELSE);
return createElse();
}
},
// {{...obj}}
function (source) {
if (startsWith(source, SYNTAX_SPREAD)) {
source = slicePrefix(source, SYNTAX_SPREAD);
const expr = compile$1(source);
if (currentElement && currentElement.isComponent) {
return createSpread(expr);
}
}
},
// {{expr}}
function (source) {
if (!SYNTAX_COMMENT.test(source)) {
source = trim(source);
const expr = compile$1(source);
return createExpression(expr, blockMode === BLOCK_MODE_SAFE);
}
},
], parseHtml = function (code) {
while (code) {
each$2(htmlParsers, function (parse) {
const match = parse(code);
if (match) {
code = slice(code, match.length);
return FALSE$1;
}
});
}
}, parseBlock = function (code) {
if (charAt(code) === RAW_SLASH) {
/**
* 处理可能存在的自闭合元素,如下
*
* {{#if xx}}
* <input>
* {{/if}}
*/
popSelfClosingElementIfNeeded();
const name = slice(code, 1);
let type = name2Type[name], ifNode = UNDEFINED$1, eachNode = UNDEFINED$1;
if (type === IF) {
const node = pop(ifStack);
if (node) {
type = node.type;
ifNode = pop(ifList);
}
}
else if (type === EACH) {
const node = pop(eachStack);
if (node) {
type = node.type;
eachNode = pop(eachList);
}
}
popStack(type);
if (ifNode) {
checkCondition(ifNode);
}
else if (eachNode) {
checkCondition(eachNode);
}
}
else {
// 开始下一个 block 或表达式
each$2(blockParsers, function (parse) {
const node = parse(code);
if (node) {
addChild(node);
return FALSE$1;
}
});
}
}, closeBlock = function () {
// 确定开始和结束定界符能否配对成功,即 {{ 对 }},{{{ 对 }}}
// 这里不能动 openBlockIndex 和 closeBlockIndex,因为等下要用他俩 slice
index = closeBlockIndex + rightSafeDelimiter.length;
// 这里要用 <=,因为很可能到头了
if (index <= length) {
if (index < length && charAt(content, index) === rightUnsafeFlag) {
if (blockMode === BLOCK_MODE_UNSAFE) {
nextIndex = index + 1;
}
}
else {
if (blockMode === BLOCK_MODE_SAFE) {
nextIndex = index;
}
}
pop(blockStack);
// 结束定界符左侧的位置
addIndex(closeBlockIndex);
// 此时 nextIndex 位于结束定界符的右侧
}
else {
// 到头了
return TRUE$1;
}
}, addIndex = function (index) {
if (!blockStack.length) {
push(indexList, index);
}
};
// 因为存在 mustache 注释内包含插值的情况
// 这里把流程设计为先标记切片的位置,标记过程中丢弃无效的 block
// 最后处理有效的 block
while (TRUE$1) {
// 当前内容位置
addIndex(nextIndex);
// 寻找下一个开始定界符和结束定界符
openBlockIndex = indexOf(content, leftSafeDelimiter, nextIndex);
closeBlockIndex = indexOf(content, rightSafeDelimiter, nextIndex);
// 如果是连续的结束定界符,比如 {{!{{xx}} }}
// 需要调用 closeBlock
if (closeBlockIndex >= nextIndex
&& (openBlockIndex < 0 || closeBlockIndex < openBlockIndex)) {
if (closeBlock()) {
break;
}
}
// 解析下一个 block
else if (openBlockIndex >= nextIndex) {
// 当前为安全插值模式
blockMode = BLOCK_MODE_SAFE;
// 开始定界符左侧的位置
addIndex(openBlockIndex);
// 跳过开始定界符
openBlockIndex += leftSafeDelimiter.length;
// 开始定界符后面总得有内容吧
if (openBlockIndex < length) {
// 判断是否为危险插值模式
if (charAt(content, openBlockIndex) === leftUnsafeFlag) {
blockMode = BLOCK_MODE_UNSAFE;
openBlockIndex++;
}
// 开始定界符右侧的位置
addIndex(openBlockIndex);
// block 模式
addIndex(blockMode);
// 打开一个 block 就入栈一个
push(blockStack, TRUE$1);
if (openBlockIndex < length) {
// 结束定界符左侧的位置
closeBlockIndex = indexOf(content, rightSafeDelimiter, openBlockIndex);
if (closeBlockIndex >= openBlockIndex) {
nextIndex = indexOf(content, leftSafeDelimiter, openBlockIndex);
// 判断结束定界符是否能匹配开始定界符
// 因为支持 mustache 注释,而注释又能嵌套,如 {{! {{xx}} {{! {{xx}} }} }}
// 当 {{ 和 }} 中间还有 {{ 时,则表示无法匹配,需要靠下一次循环再次解析
if (nextIndex < 0 || closeBlockIndex < nextIndex) {
if (closeBlock()) {
break;
}
}
}
}
}
}
else {
break;
}
}
// 开始处理有效 block 之前,重置 blockMode
blockMode = BLOCK_MODE_NONE;
for (let i = 0, length = indexList.length; i < length; i += 5) {
// 每个单元有 5 个 index
// [当前内容位置,下一个开始定界符的左侧, 下一个开始定界符的右侧, block 模式, 下一个结束定界符的左侧]
index = indexList[i];
// 开始定界符左侧的位置
openBlockIndex = indexList[i + 1];
// 如果 openBlockIndex 存在,则后续 3 个 index 都存在
if (isDef(openBlockIndex)) {
parseHtml(slice(content, index, openBlockIndex));
// 开始定界符右侧的位置
openBlockIndex = indexList[i + 2];
blockMode = indexList[i + 3];
// 结束定界符左侧的位置
closeBlockIndex = indexList[i + 4];
code = trim(slice(content, openBlockIndex, closeBlockIndex));
// 不用处理 {{ }} 和 {{{ }}} 这种空 block
if (code) {
parseBlock(code);
}
}
else {
blockMode = BLOCK_MODE_NONE;
parseHtml(slice(content, index));
}
}
if (nodeStack.length) {
/**
* 处理可能存在的自闭合元素,如下
*
* <input>
*/
popSelfClosingElementIfNeeded();
}
if (nodeList.length > 0) {
removeCommentNode(nodeList);
}
return nodeList;
}
// 下面这些值需要根据外部配置才能确定
let isUglify$1 = UNDEFINED$1, isMinify = UNDEFINED$1,
// 保留字,避免 IE 出现 { class: 'xx' } 报错
reservedWords = toMap$1('abstract,goto,native,static,enum,implements,package,super,byte,export,import,private,protected,public,synchronized,char,extends,int,throws,class,final,interface,transient,yield,let,const,float,double,boolean,long,short,volatile,default'), varId = 0, varMap = {}, varCache = {}, VAR_PREFIX = EMPTY_STRING, TEMP = EMPTY_STRING, UNDEFINED = EMPTY_STRING, NULL = EMPTY_STRING, TRUE = EMPTY_STRING, FALSE = EMPTY_STRING, SPACE = EMPTY_STRING, INDENT = EMPTY_STRING, BREAK_LINE = EMPTY_STRING;
class Primitive {
constructor(value) {
this.value = value;
}
toString() {
const { value } = this;
return value === TRUE$1
? TRUE
: value === FALSE$1
? FALSE
: value === NULL$1
? NULL
: value === UNDEFINED$1
? UNDEFINED
: string$1(value)
? toStringLiteral(value)
: `${value}`;
}
}
class Tuple {
constructor(left, right, separator, breakLine, offset, items) {
this.left = left;
this.right = right;
this.separator = separator;
this.breakLine = breakLine;
this.offset = offset;
this.items = items || [];
}
unshift(value) {
unshift(this.items, value);
}
push(value) {
push(this.items, value);
}
toString(tabSize) {
let { left, right, separator, breakLine, offset, items } = this, { length } = items;
if (!length) {
return `${left}${right}`;
}
const currentTabSize = tabSize || 0, nextTabSize = currentTabSize + offset, currentIndentSize = repeat(INDENT, currentTabSize), nextIndentSize = repeat(INDENT, nextTabSize), result = items.map(function (item) {
return item.toString(nextTabSize);
});
if (left && breakLine) {
left += BREAK_LINE + nextIndentSize;
}
if (right && breakLine) {
right = BREAK_LINE + currentIndentSize + right;
}
return `${left}${join$1(result, breakLine
? separator + BREAK_LINE + nextIndentSize
: separator + SPACE)}${right}`;
}
}
class Statement {
constructor(items) {
this.items = items || [];
}
add(value) {
push(this.items, value);
}
toString(tabSize) {
const { items } = this;
if (items.length === 1) {
return items[0].toString(tabSize);
}
return new Tuple('(', ')', ',', TRUE$1, 1, items).toString(tabSize);
}
}
class Map {
constructor(fields) {
this.fields = {};
if (fields) {
const instance = this;
each(fields, function (value, key) {
instance.set(key, value);
});
}
}
set(name, value) {
if (value instanceof Primitive
&& value.value === UNDEFINED$1) {
return;
}
this.fields[name] = value;
}
isNotEmpty() {
return keys(this.fields).length > 0;
}
toString(tabSize) {
const { fields } = this,
// 按字典排序显得比较有规律
items = keys(fields).sort().map(function (key) {
return {
toString(tabSize) {
return toObjectPair(key, fields[key].toString(tabSize));
}
};
});
return toTuple('{', '}', ',', TRUE$1, 1, items).toString(tabSize);
}
}
class Call {
constructor(name, args) {
this.name = name;
this.args = args;
}
toString(tabSize) {
const { name, args } = this, newArgs = args ? trimArgs(args) : [];
return newArgs.length
? `${name.toString(tabSize)}${toTuple('(', ')', ',', TRUE$1, 1, newArgs).toString(tabSize)}`
: `${name.toString(tabSize)}()`;
}
}
class Precedence {
constructor(value) {
this.value = value;
}
toString(tabSize) {
return `(${this.value.toString(tabSize)})`;
}
}
class StringBuffer {
append(text) {
const { buffer } = this;
if (buffer) {
this.buffer = toBinary(buffer instanceof Ternary
? toPrecedence(buffer)
: buffer, '+', text instanceof Ternary
? toPrecedence(text)
: text);
}
else {
this.buffer = text;
}
}
toString(tabSize) {
return this.buffer.toString(tabSize);
}
}
class Unary {
constructor(operator, value) {
this.operator = operator;
this.value = value;
}
toString(tabSize) {
return `${this.operator}${this.value.toString(tabSize)}`;
}
}
class Binary {
constructor(left, operator, right) {
this.left = left;
this.operator = operator;
this.right = right;
}
toString(tabSize) {
return `${this.left.toString(tabSize)}${SPACE}${this.operator}${SPACE}${this.right.toString(tabSize)}`;
}
}
class Ternary {
constructor(test, yes, no) {
this.test = test;
this.yes = yes;
this.no = no;
}
toString(tabSize) {
return `${this.test.toString(tabSize)}${SPACE}?${SPACE}${this.yes.toString(tabSize)}${SPACE}:${SPACE}${this.no.toString(tabSize)}`;
}
}
class AnonymousFunction {
constructor(args, body, returnValue) {
this.args = args;
this.body = body;
this.returnValue = returnValue;
}
toString(tabSize) {
const { args, body, returnValue } = this, currentTabSize = tabSize || 0, nextTabSize = currentTabSize + 1, currentIndentSize = repeat(INDENT, currentTabSize), nextIndentSize = repeat(INDENT, nextTabSize), tuple = args ? toTuple(EMPTY_STRING, EMPTY_STRING, ',', FALSE$1, 1, args).toString(currentTabSize) : EMPTY_STRING, code = [];
if (body) {
push(code, body.toString(nextTabSize) + (returnValue ? ';' : EMPTY_STRING));
}
if (returnValue) {
push(code, `return ${returnValue.toString(nextTabSize)}`);
}
return `${RAW_FUNCTION}${SPACE}(${tuple})${SPACE}{${BREAK_LINE}${nextIndentSize}${join$1(code, BREAK_LINE + nextIndentSize)}${BREAK_LINE}${currentIndentSize}}`;
}
}
class Member {
constructor(base, props) {
this.base = base;
this.props = props;
}
toString(tabSize) {
const { base, props } = this;
let result = base.toString(tabSize);
each$2(props, function (prop) {
if (prop instanceof Primitive) {
if (numeric(prop.value)) {
result += `[${SPACE}${prop.value}${SPACE}]`;
}
else {
result += '.' + prop.value;
}
}
else {
result += `[${SPACE}${prop.toString(tabSize)}${SPACE}]`;
}
});
return result;
}
}
class Assign {
constructor(name, value, isDeclaration) {
this.name = name;
this.value = value;
this.isDeclaration = isDeclaration;
}
toString(tabSize) {
const { name, value, isDeclaration } = this;
const statement = `${name.toString(tabSize)}${SPACE}=${SPACE}${value.toString(tabSize)}`;
return isDeclaration
? `var ${statement}`
: statement;
}
}
class Push {
constructor(array, item) {
this.array = array;
this.item = item;
}
toString(tabSize) {
const { array, item } = this;
return toCall(toMember(array, [
toPrimitive('push')
]), [
item
]).toString(tabSize);
}
}
class Typeof {
constructor(value, type) {
this.value = value;
this.type = type;
}
toString(tabSize) {
const { value, type } = this;
return `typeof ${value.toString(tabSize)}${SPACE}===${SPACE}${toStringLiteral(type)}`;
}
}
function toPrimitive(value) {
return new Primitive(value);
}
function toTuple(left, right, separator, breakLine, offset, items) {
return new Tuple(left, right, separator, breakLine, offset, items);
}
function toStatement(items) {
return new Statement(items);
}
function toList(items, join) {
let result = toTuple('[', ']', ',', TRUE$1, 1, items);
if (string$1(join)) {
return {
toString(tabSize) {
return `${result.toString(tabSize)}.join(${toPrimitive(join).toString()})`;
}
};
}
return result;
}
function toMap(fields) {
return new Map(fields);
}
function toCall(name, args) {
return new Call(name, args);
}
function toPrecedence(value) {
return new Precedence(value);
}
function toStringBuffer() {
return new StringBuffer();
}
function toUnary(operator, value) {
return new Unary(operator, value);
}
function toBinary(left, operator, right) {
return new Binary(left, operator, right);
}
function toTernary(test, yes, no) {
return new Ternary(test, yes, no);
}
function toAnonymousFunction(args, body, returnValue) {
return new AnonymousFunction(args, body, returnValue);
}
function toMember(base, props) {
return new Member(base, props);
}
function toAssign(name, value, isDeclaration) {
return new Assign(name, value, isDeclaration);
}
function toPush(array, item) {
return new Push(array, item);
}
function toTypeof(value, type) {
return new Typeof(value, type);
}
function getTempName() {
return TEMP;
}
/**
* 目的是 保证调用参数顺序稳定,减少运行时判断
*
* [a, undefined, undefined] => [a]
* [a, undefined, b, undefined] => [a, undefined, b]
*/
function trimArgs(list) {
let args = [], removable = TRUE$1;
each$2(list, function (arg) {
const isDef = arg instanceof Primitive
? arg.value !== UNDEFINED$1
: TRUE$1;
if (isDef) {
removable = FALSE$1;
unshift(args, arg);
}
else if (!removable) {
unshift(args, toPrimitive(UNDEFINED$1));
}
}, TRUE$1);
return args;
}
function toStringLiteral(value) {
value = value
.replace(/\\?'/g, "\\'")
.replace(/\\?"/g, '\\"')
// 换行符会导致字符串语法错误
.replace(/\n\s*/g, '\\n');
return `"${value}"`;
}
function toObjectPair(key, value) {
if (!/^[\w$]+$/.test(key) || reservedWords[key]) {
key = toStringLiteral(key);
}
return `${key}:${SPACE}${value}`;
}
function toVarPair(key, value) {
return value !== UNDEFINED$1
? `${key}${SPACE}=${SPACE}${value}`
: key;
}
function init$1() {
if (isUglify$1 !== PUBLIC_CONFIG.uglifyCompiled) {
isUglify$1 = PUBLIC_CONFIG.uglifyCompiled;
VAR_PREFIX = isUglify$1 ? '$' : 'var';
}
if (isMinify !== PUBLIC_CONFIG.minifyCompiled) {
isMinify = PUBLIC_CONFIG.minifyCompiled;
if (isMinify) {
SPACE = INDENT = BREAK_LINE = EMPTY_STRING;
}
else {
SPACE = ' ';
INDENT = ' ';
BREAK_LINE = '\n';
}
}
varId = 0;
varMap = {};
varCache = {};
TEMP = addVar();
UNDEFINED = addVar('void 0');
NULL = addVar('null');
TRUE = addVar('!0');
FALSE = addVar('!1');
}
function addVar(value, cache) {
const hash = value ? value.toString() : UNDEFINED$1;
if (cache && hash && varCache[hash]) {
return varCache[hash];
}
const key = VAR_PREFIX + (varId++);
varMap[key] = value;
if (cache && hash) {
varCache[hash] = key;
}
return key;
}
function parse(keypath) {
return keypath.split(RAW_DOT)
.filter(function (item) {
return item.length > 0;
})
.map(toPrimitive);
}
function generate$2(args, code) {
const varList = [];
each(varMap, function (value, key) {
push(varList, {
toString(tabSize) {
return toVarPair(key, value
? value.toString(tabSize)
: UNDEFINED$1);
}
});
});
const result = toAnonymousFunction(UNDEFINED$1, toTuple('var ', EMPTY_STRING, ',', FALSE$1, 0, varList), toAnonymousFunction(args, code));
return `(${result.toString()})()`;
}
/**
* 是否需要给 node 加括号,以提升运算优先级
* 这里不去比较运算符的优先级,而是根据节点类型判断
* 因为在 compile 环节会根据运算符优先级或括号,创建不同的节点,节点本身已经是优先级的体现了
*
* @param node
*/
function needOperatorPrecedence(node) {
return node.type === TERNARY || node.type === BINARY;
}
function generate$1(node, transformIdentifier, generateIdentifier, generateValue, generateCall, holder, parentNode) {
let value, hasHolder = FALSE$1, generateNode = function (node, parentNode) {
return generate$1(node, transformIdentifier, generateIdentifier, generateValue, generateCall, FALSE$1, // 如果是内部临时值,不需要 holder
parentNode);
}, generateNodes = function (nodes, parentNode) {
return nodes.map(function (node) {
return generateNode(node, parentNode);
});
};
switch (node.type) {
case LITERAL:
const literalNode = node;
value = toPrimitive(literalNode.value);
break;
case UNARY:
const unaryNode = node;
value = toUnary(unaryNode.operator, generateNode(unaryNode.node));
break;
case BINARY:
let binaryNode = node, left = generateNode(binaryNode.left), right = generateNode(binaryNode.right);
value = toBinary(needOperatorPrecedence(binaryNode.left)
? toPrecedence(left)
: left, binaryNode.operator, needOperatorPrecedence(binaryNode.right)
? toPrecedence(right)
: right);
break;
case TERNARY:
const ternaryNode = node, test = generateNode(ternaryNode.test), yes = generateNode(ternaryNode.yes), no = generateNode(ternaryNode.no);
value = toTernary(test, yes, no);
break;
case ARRAY:
const arrayNode = node;
value = toList(generateNodes(arrayNode.nodes, parentNode));
break;
case OBJECT:
const objectNode = node, newObject = toMap();
each$2(objectNode.keys, function (key, index) {
const value = objectNode.values[index];
newObject.set(key, generateNode(value));
});
value = newObject;
break;
case IDENTIFIER:
hasHolder = TRUE$1;
const identifierNode = node;
value = transformIdentifier(identifierNode)
|| generateIdentifier(identifierNode, identifierNode.name ? parse(identifierNode.name) : [], identifierNode.name, holder, parentNode);
break;
case MEMBER:
hasHolder = TRUE$1;
const memberNode = node;
if (memberNode.isStatic) {
// 原样输出
value = memberNode.raw;
}
else if (memberNode.lead.type === IDENTIFIER) {
// 只能是 a[b] 的形式,因为 a.b 已经在解析时转换成 Identifier 了
const leadNode = memberNode.lead, leadValue = transformIdentifier(leadNode), memberNodes = generateNodes(memberNode.nodes || []);
if (leadValue) {
value = generateValue(leadValue, memberNodes, holder);
}
else {
if (leadNode.name) {
// a.b.c[d] 这里要把 a.b.c 拆开
each$2(parse(leadNode.name), function (node) {
memberNodes.unshift(node);
}, TRUE$1);
}
value = generateIdentifier(memberNode, memberNodes, UNDEFINED$1, holder, parentNode);
}
}
else if (memberNode.nodes) {
// "xx"[length]
// format()[a][b]
value = generateValue(generateNode(memberNode.lead), generateNodes(memberNode.nodes || []), holder);
}
else {
// "xx".length
// format().a.b
value = generateValue(generateNode(memberNode.lead), parse(memberNode.keypath), holder);
}
break;
default:
hasHolder = TRUE$1;
const callNode = node;
value = generateCall(callNode, generateNode(callNode.name, callNode), callNode.args.length
? generateNodes(callNode.args)
: UNDEFINED$1, holder);
break;
}
if (!holder || hasHolder) {
return value;
}
return toMap({
value,
});
}
// 是否正在收集虚拟节点
const vnodeStack = [TRUE$1],
// 是否正在处理 attribute
attributeStack = [],
// 是否正在处理特殊 each,包括 遍历 range 和 遍历数组字面量和对象字面量
eachStack = [],
// 是否正在处理 slot
slotStack = [],
// 是否正在收集动态 child
dynamicChildrenStack = [TRUE$1],
// 收集属性值
attributeValueStack = [],
// vnode 类型
vnodeTypeStack = [], magicVariables = [MAGIC_VAR_KEYPATH, MAGIC_VAR_LENGTH, MAGIC_VAR_EVENT, MAGIC_VAR_DATA], nodeGenerator = {}, FIELD_NATIVE_ATTRIBUTES = 'nativeAttrs', FIELD_NATIVE_STYLES = 'nativeStyles', FIELD_PROPERTIES = 'props', FIELD_DIRECTIVES = 'directives', FIELD_EVENTS = 'events', FIELD_MODEL = 'model', FIELD_LAZY = 'lazy', FIELD_TRANSITION = 'transition', FIELD_CHILDREN = 'children', FIELD_SLOTS = 'slots', FIELD_OPERATOR = 'operator', PRIMITIVE_UNDEFINED = toPrimitive(UNDEFINED$1), PRIMITIVE_TRUE = toPrimitive(TRUE$1);
// 下面这些值需要根据外部配置才能确定
let isUglify = UNDEFINED$1, currentTextVNode = UNDEFINED$1, RENDER_STYLE_STRING = EMPTY_STRING, RENDER_STYLE_EXPR = EMPTY_STRING, RENDER_TRANSITION = EMPTY_STRING, RENDER_MODEL = EMPTY_STRING, RENDER_EVENT_METHOD = EMPTY_STRING, RENDER_EVENT_NAME = EMPTY_STRING, RENDER_DIRECTIVE = EMPTY_STRING, RENDER_SPREAD = EMPTY_STRING, RENDER_EACH = EMPTY_STRING, RENDER_RANGE = EMPTY_STRING, RENDER_SLOT = EMPTY_STRING, APPEND_VNODE_PROPERTY = EMPTY_STRING, FORMAT_NATIVE_ATTRIBUTE_NUMBER_VALUE = EMPTY_STRING, FORMAT_NATIVE_ATTRIBUTE_BOOLEAN_VALUE = EMPTY_STRING, LOOKUP_KEYPATH = EMPTY_STRING, LOOKUP_PROP = EMPTY_STRING, READ_KEYPATH = EMPTY_STRING, SET_VALUE_HOLDER = EMPTY_STRING, TO_STRING = EMPTY_STRING, OPERATOR_TEXT_VNODE = EMPTY_STRING, OPERATOR_COMMENT_VNODE = EMPTY_STRING, OPERATOR_ELEMENT_VNODE = EMPTY_STRING, OPERATOR_COMPONENT_VNODE = EMPTY_STRING, OPERATOR_FRAGMENT_VNODE = EMPTY_STRING, OPERATOR_PORTAL_VNODE = EMPTY_STRING, OPERATOR_SLOT_VNODE = EMPTY_STRING, ARG_INSTANCE = EMPTY_STRING, ARG_LOGGER = EMPTY_STRING, ARG_FILTERS = EMPTY_STRING, ARG_GLOBAL_FILTERS = EMPTY_STRING, ARG_DIRECTIVES = EMPTY_STRING, ARG_GLOBAL_DIRECTIVES = EMPTY_STRING, ARG_TRANSITIONS = EMPTY_STRING, ARG_GLOBAL_TRANSITIONS = EMPTY_STRING, ARG_STACK = EMPTY_STRING, ARG_PARENT = EMPTY_STRING, ARG_VNODE = EMPTY_STRING, ARG_CHILDREN = EMPTY_STRING, ARG_SCOPE = EMPTY_STRING, ARG_KEYPATH = EMPTY_STRING, ARG_LENGTH = EMPTY_STRING, ARG_EVENT = EMPTY_STRING, ARG_DATA = EMPTY_STRING;
function init() {
if (isUglify === PUBLIC_CONFIG.uglifyCompiled) {
return;
}
if (PUBLIC_CONFIG.uglifyCompiled) {
RENDER_STYLE_STRING = '_a';
RENDER_STYLE_EXPR = '_b';
RENDER_TRANSITION = '_c';
RENDER_MODEL = '_d';
RENDER_EVENT_METHOD = '_e';
RENDER_EVENT_NAME = '_f';
RENDER_DIRECTIVE = '_g';
RENDER_SPREAD = '_h';
RENDER_EACH = '_i';
RENDER_RANGE = '_j';
RENDER_SLOT = '_k';
APPEND_VNODE_PROPERTY = '_l';
FORMAT_NATIVE_ATTRIBUTE_NUMBER_VALUE = '_m';
FORMAT_NATIVE_ATTRIBUTE_BOOLEAN_VALUE = '_n';
LOOKUP_KEYPATH = '_o';
LOOKUP_PROP = '_p';
READ_KEYPATH = '_q';
SET_VALUE_HOLDER = '_r';
TO_STRING = '_s';
OPERATOR_TEXT_VNODE = '_t';
OPERATOR_COMMENT_VNODE = '_u';
OPERATOR_ELEMENT_VNODE = '_v';
OPERATOR_COMPONENT_VNODE = '_w';
OPERATOR_FRAGMENT_VNODE = '_x';
OPERATOR_PORTAL_VNODE = '_y';
OPERATOR_SLOT_VNODE = '_z';
ARG_INSTANCE = '_A';
ARG_LOGGER = '_B';
ARG_FILTERS = '_C';
ARG_GLOBAL_FILTERS = '_D';
ARG_DIRECTIVES = '_E';
ARG_GLOBAL_DIRECTIVES = '_F';
ARG_TRANSITIONS = '_G';
ARG_GLOBAL_TRANSITIONS = '_H';
ARG_STACK = '_I';
ARG_PARENT = '_J';
ARG_VNODE = '_K';
ARG_CHILDREN = '_L';
ARG_SCOPE = '_M';
ARG_KEYPATH = '_N';
ARG_LENGTH = '_O';
ARG_EVENT = '_P';
ARG_DATA = '_Q';
}
else {
RENDER_STYLE_STRING = 'renderStyleStyle';
RENDER_STYLE_EXPR = 'renderStyleExpr';
RENDER_TRANSITION = 'renderTransition';
RENDER_MODEL = 'renderModel';
RENDER_EVENT_METHOD = 'renderEventMethod';
RENDER_EVENT_NAME = 'renderEventName';
RENDER_DIRECTIVE = 'renderDirective';
RENDER_SPREAD = 'renderSpread';
RENDER_EACH = 'renderEach';
RENDER_RANGE = 'renderRange';
RENDER_SLOT = 'renderSlot';
APPEND_VNODE_PROPERTY = 'appendVNodeProperty';
FORMAT_NATIVE_ATTRIBUTE_NUMBER_VALUE = 'formatNativeAttributeNumberValue';
FORMAT_NATIVE_ATTRIBUTE_BOOLEAN_VALUE = 'formatNativeAttributeBooleanValue';
LOOKUP_KEYPATH = 'lookupKeypath';
LOOKUP_PROP = 'lookupProp';
READ_KEYPATH = 'readKeypath';
SET_VALUE_HOLDER = 'setValueHolder';
TO_STRING = 'toString';
OPERATOR_TEXT_VNODE = 'textVNodeOperator';
OPERATOR_COMMENT_VNODE = 'commentVNodeOperator';
OPERATOR_ELEMENT_VNODE = 'elementVNodeOperator';
OPERATOR_COMPONENT_VNODE = 'componentVNodeOperator';
OPERATOR_FRAGMENT_VNODE = 'fragmentVNodeOperator';
OPERATOR_PORTAL_VNODE = 'portalVNodeOperator';
OPERATOR_SLOT_VNODE = 'slotVNodeOperator';
ARG_INSTANCE = 'instance';
ARG_LOGGER = 'logger';
ARG_FILTERS = 'filters';
ARG_GLOBAL_FILTERS = 'globalFilters';
ARG_DIRECTIVES = 'directives';
ARG_GLOBAL_DIRECTIVES = 'globalDirectives';
ARG_TRANSITIONS = 'transition';
ARG_GLOBAL_TRANSITIONS = 'globalTransitions';
ARG_STACK = 'stack';
ARG_PARENT = 'parent';
ARG_VNODE = 'vnode';
ARG_CHILDREN = 'children';
ARG_SCOPE = MAGIC_VAR_SCOPE;
ARG_KEYPATH = MAGIC_VAR_KEYPATH;
ARG_LENGTH = MAGIC_VAR_LENGTH;
ARG_EVENT = MAGIC_VAR_EVENT;
ARG_DATA = MAGIC_VAR_DATA;
}
isUglify = PUBLIC_CONFIG.uglifyCompiled;
}
class CommentVNode {
constructor(text) {
this.text = text;
}
toString(tabSize) {
return toMap({
type: toPrimitive(VNODE_TYPE_COMMENT),
isPure: PRIMITIVE_TRUE,
operator: OPERATOR_COMMENT_VNODE,
text: this.text,
}).toString(tabSize);
}
}
class TextVNode {
constructor(text) {
this.buffer = toStringBuffer();
this.append(text);
}
append(text) {
this.buffer.append(text);
}
toString(tabSize) {
return toMap({
type: toPrimitive(VNODE_TYPE_TEXT),
isPure: PRIMITIVE_TRUE,
operator: OPERATOR_TEXT_VNODE,
text: this.buffer,
}).toString(tabSize);
}
}
function replaceMagicVariable(name) {
if (has$2(magicVariables, name)) {
switch (name) {
case MAGIC_VAR_KEYPATH:
return ARG_KEYPATH;
case MAGIC_VAR_LENGTH:
return ARG_LENGTH;
case MAGIC_VAR_EVENT:
return ARG_EVENT;
case MAGIC_VAR_DATA:
return ARG_DATA;
default:
return name;
}
}
}
function transformExpressionIdentifier(node) {
const { name, root, lookup, offset, literals } = node;
if (literals) {
const variable = replaceMagicVariable(literals[0]);
if (isDef(variable)) {
const result = copy(literals);
result[0] = variable;
return join$1(result, RAW_DOT);
}
}
else {
const variable = replaceMagicVariable(name);
if (isDef(variable)) {
return variable;
}
}
// this 仅在 each 中有意义
// 这里把 this 转成 $scope,方便直接读取
// 避免不必要的查找,提升性能
if (last(eachStack)
&& root === FALSE$1
&& lookup === FALSE$1
&& offset === 0) {
if (name === EMPTY_STRING) {
return ARG_SCOPE;
}
return toMember(ARG_SCOPE, parse(name));
}
}
function generateHolderIfNeeded(node, holder) {
return holder
? node
: toMember(node, [
toPrimitive('value')
]);
}
function generateExpressionIndex(root, offset) {
if (root) {
return toPrimitive(0);
}
return toBinary(toMember(ARG_STACK, [
toPrimitive(RAW_LENGTH)
]), '-', toPrimitive(1 + (offset ? offset : 0)));
}
function generateExpressionIdentifier(node, nodes, keypath, holder, parentNode) {
const { root, lookup, offset } = node, { length } = nodes, index = generateExpressionIndex(root, offset);
let filter = PRIMITIVE_UNDEFINED;
// 函数调用
if (parentNode
&& parentNode.type === CALL
// 调用过滤器肯定无需指定路径
&& lookup
// 过滤器名称是简单的标识符,可支持多级属性,如 lodash.toUpper
&& keypath
&& length > 0) {
if (length > 1) {
filter = toMember(ARG_GLOBAL_FILTERS, nodes);
}
else {
filter = generateSelfAndGlobalReader(ARG_FILTERS, ARG_GLOBAL_FILTERS, keypath);
}
}
let result = toCall(LOOKUP_KEYPATH, [
ARG_STACK,
index,
string$1(keypath)
? toPrimitive(keypath)
: toList(nodes, RAW_DOT),
toList(nodes),
lookup
? PRIMITIVE_TRUE
: PRIMITIVE_UNDEFINED,
filter
]);
// 如果是读取一级属性的场景,比如 this.x,这里可以优化成 scope.x
// 如果是读取多级属性的场景,比如 this.x.y,这里不做优化,因为 x 可能为空,导致整个表达式报错
// 处理一级属性
if (keypath && length === 1) {
// this.name
if (!root && !offset && !lookup) {
// setValueHolder(
// value,
// stack[index].getKeypath(name)
// )
result = toCall(SET_VALUE_HOLDER, [
toMember(ARG_SCOPE, nodes),
toCall(toMember(toMember(ARG_STACK, [
index
]), [
toPrimitive('getKeypath')
]), [
toPrimitive(keypath),
])
]);
}
// 未指定路径,如 name
else if (!root && !offset) {
result = toCall(LOOKUP_PROP, [
ARG_STACK,
index,
toPrimitive(keypath),
filter
]);
}
// 指定了路径,如 ~/name 或 ../name
else {
const statement = toStatement(), varName = getTempName();
// temp = stack[index]
statement.add(toAssign(varName, toMember(ARG_STACK, [
index
])));
// setValueHolder(
// temp.getScope()[name],
// temp.getKeypath(name)
// )
statement.add(toCall(SET_VALUE_HOLDER, [
toMember(toCall(toMember(varName, [
toPrimitive('getScope')
])), [
toPrimitive(keypath),
]),
toCall(toMember(varName, [
toPrimitive('getKeypath')
]), [
toPrimitive(keypath),
]),
]));
result = statement;
}
}
// 处理属性为空串,如 this、../this、~/this 之类的
else if (!keypath && !length) {
const statement = toStatement(), varName = getTempName();
// temp = stack[index]
statement.add(toAssign(varName, toMember(ARG_STACK, [
index
])));
// setValueHolder(temp.getScope(), temp.keypath)
statement.add(toCall(SET_VALUE_HOLDER, [
toCall(toMember(varName, [
toPrimitive('getScope')
])),
toMember(varName, [
toPrimitive('keypath')
])
]));
result = statement;
}
return generateHolderIfNeeded(result, holder);
}
function generateExpressionValue(value, keys, holder) {
let result;
switch (keys.length) {
case 0:
result = toCall(SET_VALUE_HOLDER, [
value,
]);
break;
case 1:
result = toCall(SET_VALUE_HOLDER, [
toMember(value, keys)
]);
break;
default:
result = toCall(READ_KEYPATH, [
value,
toList(keys)
]);
break;
}
return generateHolderIfNeeded(result, holder);
}
function generateExpressionCall(node, fn, args, holder) {
const statement = toStatement(), varName = getTempName();
// temp = fn
statement.add(toAssign(varName, fn));
// temp()
statement.add(toTernary(toTypeof(varName, 'function'), toCall(varName, args), toCall(toMember(ARG_LOGGER, [
toPrimitive('fatal')
]), [
toPrimitive(`[${node.raw}] is not a function.`)
])));
return generateHolderIfNeeded(toCall(SET_VALUE_HOLDER, [
statement
]), holder);
}
function generateExpression(expr, holder) {
const result = generate$1(expr, transformExpressionIdentifier, generateExpressionIdentifier, generateExpressionValue, generateExpressionCall, holder);
return expr.isStatic
? addVar(result, TRUE$1)
: result;
}
function createAttributeValue(nodes) {
const attributeValue = toStringBuffer();
push(attributeValueStack, attributeValue);
each$2(nodes, function (node) {
nodeGenerator[node.type](node);
});
pop(attributeValueStack);
return attributeValue;
}
function generateAttributeValue(attr) {
if (isDef(attr.value)) {
return toPrimitive(attr.value);
}
// 只有一个表达式时,保持原始类型
if (attr.expr) {
return generateExpression(attr.expr);
}
// 多个值拼接时,要求是字符串
if (attr.children) {
// 常见的应用场景是序列化 HTML 元素属性值,处理值时要求字符串,在处理属性名这个级别,不要求字符串
// compiler 会把原始字符串编译成 value
// compiler 会把单个插值编译成 expr
// 因此走到这里,一定是多个插值或是单个特殊插值(比如 If)
return createAttributeValue(attr.children);
}
return PRIMITIVE_UNDEFINED;
}
function mapNodes(nodes) {
currentTextVNode = UNDEFINED$1;
const result = [];
each$2(nodes, function (node) {
const item = nodeGenerator[node.type](node);
if (item instanceof Primitive
&& item.value === UNDEFINED$1) {
return;
}
push(result, item);
});
currentTextVNode = UNDEFINED$1;
return result;
}
function generateNodesToTuple(nodes) {
return toTuple(EMPTY_STRING, EMPTY_STRING, ';', TRUE$1, 0, mapNodes(nodes));
}
function generateNodesToList(nodes) {
return toList(mapNodes(nodes));
}
function generateNodesToChildren(nodes, args) {
const tuple = generateNodesToTuple(nodes);
// 用 ARG_CHILDREN 收集所有的节点
tuple.unshift(toAssign(ARG_CHILDREN, toList(), TRUE$1));
return toAnonymousFunction(args, tuple, toTernary(toMember(ARG_CHILDREN, [
toPrimitive(RAW_LENGTH)
]), ARG_CHILDREN, toPrimitive(UNDEFINED$1)));
}
function appendDynamicChildVNode(vnode) {
currentTextVNode = vnode instanceof TextVNode
? vnode
: UNDEFINED$1;
return toPush(ARG_CHILDREN, vnode);
}
function generateSelfAndGlobalReader(self, global, name) {
return toBinary(toBinary(self, '&&', toMember(self, [
toPrimitive(name)
])), '||', toMember(global, [
toPrimitive(name)
]));
}
function generateVNode(vnode) {
return last(dynamicChildrenStack)
? appendDynamicChildVNode(vnode)
: vnode;
}
function generateCommentVNode() {
return generateVNode(new CommentVNode(toPrimitive(EMPTY_STRING)));
}
function generateTextVNode(text) {
if (currentTextVNode) {
currentTextVNode.append(text);
return PRIMITIVE_UNDEFINED;
}
return generateVNode(new TextVNode(text));
}
function generateComponentSlots(children) {
const result = toMap(), slots = {}, addSlot = function (name, nodes) {
if (!falsy$2(nodes)) {
name = SLOT_DATA_PREFIX + name;
push(slots[name] || (slots[name] = []), nodes);
}
};
push(slotStack, TRUE$1);
each$2(children, function (child) {
// 找到具名 slot
if (child.type === ELEMENT) {
const element = child;
if (element.slot) {
addSlot(element.slot, element.tag === TAG_TEMPLATE
? element.children
: [element]);
return;
}
}
// 匿名 slot,名称统一为 children
// 这个步骤不能放在 compiler,因为除了 element,还会有其他节点,比如文本节点
addSlot(SLOT_NAME_DEFAULT, [child]);
});
each(slots, function (children, name) {
result.set(name, generateNodesToChildren(children, [
ARG_PARENT
]));
});
pop(slotStack);
if (result.isNotEmpty()) {
return result;
}
}
function parseAttrs(attrs, vnodeType) {
let nativeAttributeList = [], propertyList = [], style = UNDEFINED$1, lazyList = [], transition = UNDEFINED$1, model = UNDEFINED$1,
// 最后收集事件指令、自定义指令、动态属性
eventList = [], customDirectiveList = [], otherList = [];
for (let i = 0, len = attrs.length; i < len; i++) {
const attr = attrs[i];
if (attr.type === ATTRIBUTE) {
const attributeNode = attr;
if (vnodeType === VNODE_TYPE_COMPONENT || vnodeType === VNODE_TYPE_SLOT) {
push(propertyList, attributeNode);
}
else {
push(nativeAttributeList, attributeNode);
}
}
else if (attr.type === STYLE) {
style = attr;
}
else if (attr.type === DIRECTIVE) {
const directiveNode = attr;
switch (directiveNode.ns) {
case DIRECTIVE_LAZY:
push(lazyList, directiveNode);
break;
case DIRECTIVE_TRANSITION:
transition = directiveNode;
break;
case DIRECTIVE_MODEL:
model = directiveNode;
break;
case DIRECTIVE_EVENT:
push(eventList, directiveNode);
break;
default:
push(customDirectiveList, directiveNode);
}
}
else {
push(otherList, attr);
}
}
return {
nativeAttributeList,
propertyList,
style,
lazyList,
transition,
model,
eventList,
customDirectiveList,
otherList,
};
}
function sortAttrs(attrs, vnodeType) {
const { nativeAttributeList, propertyList, style, lazyList, transition, model, eventList, customDirectiveList, otherList, } = parseAttrs(attrs, vnodeType);
const result = [];
push(result, nativeAttributeList);
push(result, propertyList);
if (style) {
push(result, style);
}
push(result, lazyList);
if (transition) {
push(result, transition);
}
if (model) {
push(result, model);
}
push(result, eventList);
push(result, customDirectiveList);
push(result, otherList);
return result;
}
function generateNativeAttributeValue(node) {
const { name } = node;
let value = generateAttributeValue(node);
if (!(value instanceof Primitive)) {
if (isNumberNativeAttribute(name)) {
value = toCall(FORMAT_NATIVE_ATTRIBUTE_NUMBER_VALUE, [
toPrimitive(name),
value
]);
}
else if (isBooleanNativeAttribute(name)) {
value = toCall(FORMAT_NATIVE_ATTRIBUTE_BOOLEAN_VALUE, [
toPrimitive(name),
value,
toPrimitive(node.defaultValue)
]);
}
}
return value;
}
function parseChildren(children, forceDynamic) {
let dynamicChildren = UNDEFINED$1, staticChildren = UNDEFINED$1, isDynamic = forceDynamic || FALSE$1;
if (!isDynamic) {
each$2(children, function (node) {
if (!node.isStatic) {
isDynamic = TRUE$1;
return FALSE$1;
}
});
}
push(dynamicChildrenStack, isDynamic);
if (isDynamic) {
dynamicChildren = toCall(generateNodesToChildren(children));
}
else {
staticChildren = generateNodesToList(children);
}
pop(dynamicChildrenStack);
return {
dynamicChildren,
staticChildren,
};
}
nodeGenerator[ELEMENT] = function (node) {
let { tag, dynamicTag, isComponent, to, ref, key, html, text, attrs, children } = node, vnodeType = isComponent
? VNODE_TYPE_COMPONENT
: (specialTag2VNodeType[tag] || VNODE_TYPE_ELEMENT), vnode = toMap({
context: ARG_INSTANCE,
type: toPrimitive(vnodeType),
tag: dynamicTag
? generateExpression(dynamicTag)
: toPrimitive(tag)
}), isFragment = vnodeType === VNODE_TYPE_FRAGMENT, isPortal = vnodeType === VNODE_TYPE_PORTAL, isSlot = vnodeType === VNODE_TYPE_SLOT, outputAttrs = UNDEFINED$1, outputChildren = UNDEFINED$1, outputSlots = UNDEFINED$1, renderSlot = UNDEFINED$1;
// 先序列化 children,再序列化 attrs,原因需要举两个例子:
// 例子1:
// <div on-click="output(this)"></div> 如果 this 序列化成 $scope,如果外部修改了 this,因为模板没有计入此依赖,不会刷新,因此 item 是旧的
// 这个例子要求即使是动态执行的代码,也不能简单的直接序列化成 $scope
// 例子2:
// <div on-click="output(this)">{{this}}</div>,如果第一个 this 转成 $scope,第二个正常读取数据,这样肯定没问题
// 但问题是,你不知道有没有第二个 this,因此这里反过来,先序列化非动态部分,即 children,再序列化可能动态的部分,即 attrs
// 这样序列化动态部分的时候,就知道是否可以转成 $scope
// 后来发现,即使这样实现也不行,因为模板里存在各种可能的 if 或三元运算,导致依赖的捕捉充满不确定,因此这里我们不再考虑把 this 转成 $scope
push(vnodeStack, TRUE$1);
push(attributeStack, FALSE$1);
push(vnodeTypeStack, vnodeType);
if (children) {
if (isComponent) {
outputSlots = generateComponentSlots(children);
}
else {
const { dynamicChildren, staticChildren } = parseChildren(children);
if (dynamicChildren) {
outputChildren = dynamicChildren;
}
else if (staticChildren) {
if (isSlot) {
outputChildren = staticChildren;
}
else {
vnode.set(FIELD_CHILDREN, staticChildren);
}
}
}
}
// 开始序列化 attrs,原则也是先序列化非动态部分,再序列化动态部分,即指令留在最后序列化
vnodeStack[vnodeStack.length - 1] = FALSE$1;
attributeStack[attributeStack.length - 1] = TRUE$1;
// 在 vnodeStack 为 false 时取值
if (to) {
vnode.set('to', generateAttributeValue(to));
}
if (ref) {
vnode.set('ref', generateAttributeValue(ref));
}
if (key) {
vnode.set('key', generateAttributeValue(key));
}
if (html) {
vnode.set('html', string$1(html)
? toPrimitive(html)
: toCall(TO_STRING, [
generateExpression(html)
]));
}
if (text) {
vnode.set('text', string$1(text)
? toPrimitive(text)
: toCall(TO_STRING, [
generateExpression(text)
]));
}
if (attrs) {
const { nativeAttributeList, propertyList, style, lazyList, transition, model, eventList, customDirectiveList, otherList, } = parseAttrs(attrs, vnodeType), hasDynamicAttrs = otherList.length > 0;
if (nativeAttributeList.length) {
let nativeAttributes = toMap(), isDynamic = hasDynamicAttrs;
each$2(nativeAttributeList, function (node) {
if (!node.isStatic) {
isDynamic = TRUE$1;
}
nativeAttributes.set(node.name, generateNativeAttributeValue(node));
});
vnode.set(FIELD_NATIVE_ATTRIBUTES, isDynamic
? nativeAttributes
: addVar(nativeAttributes, TRUE$1));
}
if (propertyList.length) {
const properties = toMap();
each$2(propertyList, function (node) {
properties.set(node.name, generateAttributeValue(node));
});
vnode.set(FIELD_PROPERTIES, properties);
}
if (style) {
vnode.set(FIELD_NATIVE_STYLES, getStyleValue(style));
}
if (lazyList.length) {
const lazy = toMap();
each$2(lazyList, function (node) {
lazy.set(node.name, getLazyValue(node));
});
// 编译器保证了 lazy 的值是静态的
vnode.set(FIELD_LAZY, addVar(lazy, TRUE$1));
}
if (transition) {
vnode.set(FIELD_TRANSITION, getTransitionValue(transition));
}
if (model) {
vnode.set(FIELD_MODEL, getModelValue(model));
}
if (eventList.length) {
const events = toMap();
each$2(eventList, function (node) {
const info = getEventInfo(node);
events.set(getDirectiveKey(node), toCall(info.name, info.args));
});
vnode.set(FIELD_EVENTS, events);
}
if (customDirectiveList.length) {
const directives = toMap();
each$2(customDirectiveList, function (node) {
directives.set(getDirectiveKey(node), toCall(RENDER_DIRECTIVE, getDirectiveArgs(node)));
});
vnode.set(FIELD_DIRECTIVES, directives);
}
if (otherList.length) {
outputAttrs = toAnonymousFunction([
ARG_VNODE
], generateNodesToTuple(otherList), ARG_VNODE);
}
}
if (isSlot) {
const nameAttr = node.name;
let outputName = toPrimitive(SLOT_DATA_PREFIX + SLOT_NAME_DEFAULT);
if (nameAttr) {
// 如果 name 是字面量,直接拼出结果
outputName = isDef(nameAttr.value)
? toPrimitive(SLOT_DATA_PREFIX + nameAttr.value)
: toBinary(toPrimitive(SLOT_DATA_PREFIX), '+', toPrecedence(generateAttributeValue(nameAttr)));
}
renderSlot = toCall(RENDER_SLOT, [
outputName,
last(slotStack)
? ARG_PARENT
: PRIMITIVE_UNDEFINED
]);
}
pop(vnodeStack);
pop(attributeStack);
pop(vnodeTypeStack);
if (vnodeType === VNODE_TYPE_ELEMENT) {
vnode.set(FIELD_OPERATOR, OPERATOR_ELEMENT_VNODE);
}
else if (isFragment) {
vnode.set(FIELD_OPERATOR, OPERATOR_FRAGMENT_VNODE);
}
else if (isPortal) {
vnode.set(FIELD_OPERATOR, OPERATOR_PORTAL_VNODE);
}
else if (isComponent) {
vnode.set(FIELD_OPERATOR, OPERATOR_COMPONENT_VNODE);
if (last(slotStack)) {
vnode.set('parent', ARG_PARENT);
}
}
else if (isSlot) {
vnode.set(FIELD_OPERATOR, OPERATOR_SLOT_VNODE);
// 如果 renderSlot 没内容,则取 slot 元素的 children 作为内容
// <slot>
// default
// </slot>
outputChildren = outputChildren
? toBinary(renderSlot, '||', outputChildren)
: renderSlot;
}
if (node.isSvg) {
vnode.set('isSvg', PRIMITIVE_TRUE);
}
if (node.isStatic) {
vnode.set('isStatic', PRIMITIVE_TRUE);
vnode.set('isPure', PRIMITIVE_TRUE);
}
if (outputChildren) {
vnode.set(FIELD_CHILDREN, outputChildren);
}
if (outputSlots) {
vnode.set(FIELD_SLOTS, outputSlots);
}
const result = outputAttrs
? toCall(outputAttrs, [
vnode,
])
: vnode;
if (isFragment || isPortal || isSlot) {
const statement = toStatement(), varName = getTempName();
// temp = vnode
statement.add(toAssign(varName, result));
// temp.children && temp.children.length && children.push(temp)
statement.add(toBinary(toBinary(toMember(varName, [
toPrimitive(FIELD_CHILDREN)
]), '&&', toMember(varName, [
toPrimitive(FIELD_CHILDREN),
toPrimitive(RAW_LENGTH)
])), '&&', toPush(ARG_CHILDREN, varName)));
return statement;
}
return generateVNode(result);
};
nodeGenerator[ATTRIBUTE] = function (node) {
const lastVNodeType = last(vnodeTypeStack);
return toCall(APPEND_VNODE_PROPERTY, [
ARG_VNODE,
toPrimitive((lastVNodeType === VNODE_TYPE_COMPONENT || lastVNodeType === VNODE_TYPE_SLOT)
? FIELD_PROPERTIES
: FIELD_NATIVE_ATTRIBUTES),
toPrimitive(node.name),
generateNativeAttributeValue(node),
]);
};
nodeGenerator[STYLE] = function (node) {
return toAssign(toMember(ARG_VNODE, [
toPrimitive(FIELD_NATIVE_STYLES)
]), getStyleValue(node));
};
function getStyleValue(node) {
if (isDef(node.value)) {
const styles = toMap();
parseStyleString(node.value, function (key, value) {
styles.set(key, toPrimitive(value));
});
return addVar(styles, TRUE$1);
}
if (node.expr) {
if (node.expr.type === OBJECT) {
return generateExpression(node.expr);
}
return toCall(RENDER_STYLE_EXPR, [
generateExpression(node.expr)
]);
}
// 多值拼接,compiler 保证了 children 必然有值
return toCall(RENDER_STYLE_STRING, [
createAttributeValue(node.children)
]);
}
function getLazyValue(node) {
return toPrimitive(node.value);
}
function getTransitionValue(node) {
return toCall(RENDER_TRANSITION, [
toPrimitive(node.value),
generateSelfAndGlobalReader(ARG_TRANSITIONS, ARG_GLOBAL_TRANSITIONS, node.value)
]);
}
function getModelValue(node) {
return toCall(RENDER_MODEL, [
generateExpression(node.expr, TRUE$1)
]);
}
function addEventBooleanInfo(args, node) {
// isComponent
push(args, PRIMITIVE_UNDEFINED);
// isNative
push(args, PRIMITIVE_UNDEFINED);
if (last(vnodeTypeStack) === VNODE_TYPE_COMPONENT) {
if (node.modifier === MODIFER_NATIVE) {
// isNative
args[args.length - 1] = PRIMITIVE_TRUE;
}
else {
// isComponent
args[args.length - 2] = PRIMITIVE_TRUE;
}
}
}
function getEventInfo(node) {
const args = [];
// key
push(args, toPrimitive(getDirectiveKey(node)));
// value
push(args, toPrimitive(node.value));
// from
push(args, toPrimitive(node.name));
// fromNs
push(args,
// 组件事件要用 component.on(type, options) 进行监听
// 为了保证 options.ns 是字符串类型,这里需确保 fromNs 是字符串
toPrimitive(node.modifier || EMPTY_STRING));
// 事件的 expr 必须是表达式
const expr = node.expr, { raw } = expr;
if (expr.type === CALL) {
const callNode = expr;
// compiler 保证了函数调用的 name 是标识符
// method
push(args, toPrimitive(callNode.name.name));
// 为了实现运行时动态收集参数,这里序列化成函数
if (!falsy$2(callNode.args)) {
// runtime
push(args, toMap({
execute: toAnonymousFunction([
ARG_EVENT,
ARG_DATA,
], UNDEFINED$1, toList(callNode.args.map(function (arg) {
return generateExpression(arg);
})))
}));
}
else {
// runtime
push(args, PRIMITIVE_UNDEFINED);
}
addEventBooleanInfo(args, node);
return {
name: RENDER_EVENT_METHOD,
args,
};
}
const parts = raw.split(RAW_DOT);
// to
push(args, toPrimitive(parts[0]));
// toNs
push(args, toPrimitive(parts[1]));
addEventBooleanInfo(args, node);
return {
name: RENDER_EVENT_NAME,
args,
};
}
function getDirectiveKey(node) {
return join(node.name, node.modifier || EMPTY_STRING);
}
function getDirectiveArgs(node) {
const args = [];
// key
push(args, toPrimitive(getDirectiveKey(node)));
// name
push(args, toPrimitive(node.name));
// modifier
push(args, toPrimitive(node.modifier));
// value
push(args, node.expr
? generateExpression(node.expr)
: toPrimitive(node.value));
// create
push(args, generateSelfAndGlobalReader(ARG_DIRECTIVES, ARG_GLOBAL_DIRECTIVES, node.name));
return args;
}
nodeGenerator[DIRECTIVE] = function (node) {
switch (node.ns) {
case DIRECTIVE_LAZY:
return toCall(APPEND_VNODE_PROPERTY, [
ARG_VNODE,
toPrimitive(FIELD_LAZY),
toPrimitive(node.name),
getLazyValue(node),
]);
// <div transition="name">
case DIRECTIVE_TRANSITION:
return toAssign(toMember(ARG_VNODE, [
toPrimitive(FIELD_TRANSITION)
]), getTransitionValue(node));
// <input model="id">
case DIRECTIVE_MODEL:
return toAssign(toMember(ARG_VNODE, [
toPrimitive(FIELD_MODEL)
]), getModelValue(node));
// <div on-click="name">
case DIRECTIVE_EVENT:
const info = getEventInfo(node);
return toCall(APPEND_VNODE_PROPERTY, [
ARG_VNODE,
toPrimitive(FIELD_EVENTS),
toPrimitive(getDirectiveKey(node)),
toCall(info.name, info.args),
]);
default:
return toCall(APPEND_VNODE_PROPERTY, [
ARG_VNODE,
toPrimitive(FIELD_DIRECTIVES),
toPrimitive(getDirectiveKey(node)),
toCall(RENDER_DIRECTIVE, getDirectiveArgs(node)),
]);
}
};
nodeGenerator[SPREAD] = function (node) {
return toCall(RENDER_SPREAD, [
ARG_VNODE,
toPrimitive(FIELD_PROPERTIES),
generateExpression(node.expr)
]);
};
nodeGenerator[TEXT] = function (node) {
const text = toPrimitive(node.text);
if (last(vnodeStack)) {
return generateTextVNode(text);
}
const attributeValue = last(attributeValueStack);
if (attributeValue) {
attributeValue.append(text);
return PRIMITIVE_UNDEFINED;
}
return text;
};
nodeGenerator[EXPRESSION] = function (node) {
const value = generateExpression(node.expr);
if (last(vnodeStack)) {
return generateTextVNode(toCall(TO_STRING, [
value
]));
}
const attributeValue = last(attributeValueStack);
if (attributeValue) {
attributeValue.append(toCall(TO_STRING, [
value
]));
return PRIMITIVE_UNDEFINED;
}
return value;
};
function getBranchDefaultValue() {
return last(vnodeStack)
? generateCommentVNode()
: last(attributeValueStack)
? toPrimitive(EMPTY_STRING)
: PRIMITIVE_UNDEFINED;
}
function getBranchValue(children) {
if (children) {
if (last(attributeStack)) {
children = sortAttrs(children, last(vnodeTypeStack));
}
if (last(attributeValueStack)) {
return createAttributeValue(children);
}
return toStatement(mapNodes(children));
}
}
nodeGenerator[IF] = function (node) {
const { next } = node, attributeValue = last(attributeValueStack), defaultValue = getBranchDefaultValue(), result = toTernary(generateExpression(node.expr), getBranchValue(node.children) || defaultValue, next
? nodeGenerator[next.type](next)
: defaultValue);
if (attributeValue) {
attributeValue.append(result);
return PRIMITIVE_UNDEFINED;
}
return result;
};
nodeGenerator[ELSE_IF] = function (node) {
const { next } = node, defaultValue = getBranchDefaultValue();
return toTernary(generateExpression(node.expr), getBranchValue(node.children) || defaultValue, next
? nodeGenerator[next.type](next)
: defaultValue);
};
nodeGenerator[ELSE] = function (node) {
return getBranchValue(node.children) || getBranchDefaultValue();
};
nodeGenerator[EACH] = function (node) {
const { index, from, to, equal, next } = node, isSpecial = to || from.type === ARRAY || from.type === OBJECT;
const args = [
ARG_STACK,
ARG_SCOPE,
ARG_KEYPATH,
ARG_LENGTH,
];
if (index) {
push(args, index);
push(magicVariables, index);
}
// 如果是特殊的 each,包括 遍历 range 和 遍历数组字面量和对象字面量
// 在这种 each 中引用 this 无需追踪依赖,因此可直接认为 this 已用过,这样生成代码时,会直接引用局部变量,提高执行效率
push(eachStack, isSpecial);
// compiler 保证了 children 一定有值
const renderChildren = toAnonymousFunction(args, generateNodesToTuple(node.children));
if (index) {
pop(magicVariables);
}
pop(eachStack);
// compiler 保证了 children 一定有值
const renderElse = next
? toAnonymousFunction(UNDEFINED$1, generateNodesToTuple(next.children))
: PRIMITIVE_UNDEFINED;
// 遍历区间
if (to) {
return toCall(RENDER_RANGE, [
generateExpression(from),
generateExpression(to),
toPrimitive(equal),
renderChildren,
renderElse,
]);
}
// 遍历数组和对象
return toCall(RENDER_EACH, [
generateExpression(from, TRUE$1),
renderChildren,
renderElse,
]);
};
nodeGenerator[IMPORT] = function (node) {
const varName = getTempName(), statement = toStatement();
// temp = vnode
statement.add(toAssign(varName, generateExpression(node.expr)));
statement.add(toTernary(varName, generateVNode(varName), PRIMITIVE_UNDEFINED));
return statement;
};
function generate(node) {
init();
init$1();
return generate$2([
RENDER_STYLE_STRING,
RENDER_STYLE_EXPR,
RENDER_TRANSITION,
RENDER_MODEL,
RENDER_EVENT_METHOD,
RENDER_EVENT_NAME,
RENDER_DIRECTIVE,
RENDER_SPREAD,
RENDER_EACH,
RENDER_RANGE,
RENDER_SLOT,
APPEND_VNODE_PROPERTY,
FORMAT_NATIVE_ATTRIBUTE_NUMBER_VALUE,
FORMAT_NATIVE_ATTRIBUTE_BOOLEAN_VALUE,
LOOKUP_KEYPATH,
LOOKUP_PROP,
READ_KEYPATH,
SET_VALUE_HOLDER,
TO_STRING,
OPERATOR_TEXT_VNODE,
OPERATOR_COMMENT_VNODE,
OPERATOR_ELEMENT_VNODE,
OPERATOR_COMPONENT_VNODE,
OPERATOR_FRAGMENT_VNODE,
OPERATOR_PORTAL_VNODE,
OPERATOR_SLOT_VNODE,
ARG_INSTANCE,
ARG_LOGGER,
ARG_FILTERS,
ARG_GLOBAL_FILTERS,
ARG_DIRECTIVES,
ARG_GLOBAL_DIRECTIVES,
ARG_TRANSITIONS,
ARG_GLOBAL_TRANSITIONS,
ARG_STACK,
ARG_SCOPE,
ARG_KEYPATH,
ARG_CHILDREN,
], nodeGenerator[node.type](node));
}
const STATUS_INIT = 1;
const STATUS_FRESH = 2;
const STATUS_DIRTY = 3;
function runGetter(instance) {
const { input, getter } = instance;
instance.value = input
? getter.apply(UNDEFINED$1, input)
: getter();
}
function runOutput(instance) {
const { value, output } = instance;
return output
? output(value)
: value;
}
class Deps {
constructor() {
this.map = {};
this.list = [];
}
add(observer, dep) {
const deps = this.map[observer.id] || (this.map[observer.id] = {});
if (!deps[dep]) {
deps[dep] = observer;
this.list.push([
observer, dep
]);
}
}
watch(watcher) {
const { list } = this;
if (list) {
for (let i = 0, length = list.length; i < length; i++) {
list[i][0].watch(list[i][1], watcher);
}
}
}
unwatch(watcher) {
const { list } = this;
if (list) {
for (let i = 0, length = list.length; i < length; i++) {
list[i][0].unwatch(list[i][1], watcher);
}
}
}
}
/**
* 计算属性
*
* 可配置 cache、deps, get、set 等
*/
class Computed {
constructor(keypath, cache, sync, input, output, getter, setter, onChange) {
const instance = this;
instance.status = STATUS_INIT;
instance.keypath = keypath;
instance.cache = cache;
instance.input = input;
instance.output = output;
instance.setter = setter;
instance.getter = getter;
instance.onChange = onChange;
instance.watcherOptions = {
sync,
watcher() {
instance.refresh();
}
};
}
/**
* 读取计算属性的值
*/
get() {
const instance = this, { status, watcherOptions } = instance;
// 禁用缓存
if (!instance.cache) {
runGetter(instance);
}
// 减少取值频率,尤其是处理复杂的计算规则
else if (status !== STATUS_FRESH) {
// 如果写死了依赖,则不需要收集依赖
if (instance.staticDeps) {
runGetter(instance);
}
// 自动收集依赖
else {
let { dynamicDeps } = instance;
// 清空上次收集的依赖
if (dynamicDeps) {
dynamicDeps.unwatch(watcherOptions.watcher);
}
instance.dynamicDeps = UNDEFINED$1;
const lastComputed = Computed.current;
// 开始收集新的依赖
Computed.current = instance;
runGetter(instance);
// 取值完成,恢复原值
Computed.current = lastComputed;
dynamicDeps = instance.dynamicDeps;
if (dynamicDeps) {
dynamicDeps.watch(watcherOptions);
}
}
}
if (status !== STATUS_FRESH) {
instance.status = STATUS_FRESH;
}
return runOutput(instance);
}
set(value) {
const { setter } = this;
if (setter) {
setter(value);
}
else if (func(value)) {
this.getter = value;
this.refresh();
}
}
refresh() {
const oldValue = this.value;
this.status = STATUS_DIRTY;
const newValue = this.get();
if (newValue !== oldValue) {
this.onChange(this.keypath, newValue, oldValue);
}
}
addStaticDeps(observer, deps) {
const staticDeps = this.staticDeps || (this.staticDeps = new Deps());
for (let i = 0, length = deps.length; i < length; i++) {
staticDeps.add(observer, deps[i]);
}
staticDeps.watch(this.watcherOptions);
}
addDynamicDep(observer, dep) {
// 动态依赖不能在这直接 watch
// 只有当计算属性的依赖全部收集完了,才能监听该计算属性的所有依赖
// 这样可保证依赖最少的计算属性最先执行 watch,当依赖变化时,它也会最早触发 refresh
const deps = this.dynamicDeps || (this.dynamicDeps = new Deps());
deps.add(observer, dep);
}
}
class Context {
constructor(keypath, scopeValue, scopeKey) {
this.keypath = keypath;
this.scopeValue = scopeValue;
this.scopeKey = scopeKey;
}
getScope() {
const { scopeValue, scopeKey } = this;
return scopeKey !== UNDEFINED$1 ? scopeValue[scopeKey] : scopeValue;
}
getKeypath(name) {
const { keypath } = this;
return keypath ? keypath + RAW_DOT + name : name;
}
}
function render(instance, template, rootScope, filters, globalFilters, directives, globalDirectives, transitions, globalTransitions, addDependency) {
let rootKeypath = EMPTY_STRING, contextStack = [
new Context(rootKeypath, rootScope)
],
// 模板渲染过程收集的 vnode
children = [], appendVNodeProperty = function (vnode, key, name, value) {
if (vnode[key]) {
vnode[key][name] = value;
}
else {
const map = {};
map[name] = value;
vnode[key] = map;
}
}, renderStyleString = function (value) {
const styles = {};
parseStyleString(value, function (key, value) {
styles[key] = value;
});
return styles;
}, renderStyleExpr = function (value) {
if (array$1(value)) {
const styles = {};
for (let i = 0, len = value.length; i < len; i++) {
const item = renderStyleExpr(value[i]);
if (item) {
for (let key in item) {
styles[key] = item[key];
}
}
}
return styles;
}
if (object$1(value)) {
return value;
}
if (string$1(value)) {
return renderStyleString(value);
}
}, renderTransition = function (name, transition) {
return transition;
},
// holder 是全局共用的,这里要浅拷贝一次
renderModel = function (holder) {
return {
keypath: holder.keypath,
value: holder.value,
};
}, createEventNameListener = function (type, ns, isComponent) {
return function (event, data, isNative) {
// 监听组件事件不用处理父组件传下来的事件
if (isComponent && event.phase === CustomEvent.PHASE_DOWNWARD) {
return;
}
if (type !== event.type || ns !== event.ns) {
event = new CustomEvent(type, isNative
? event.originalEvent
: event);
event.ns = ns;
}
instance.fire(event, data);
};
}, createEventMethodListener = function (method, runtime, isComponent) {
return function (event, data) {
// 监听组件事件不用处理父组件传下来的事件
if (isComponent && event.phase === CustomEvent.PHASE_DOWNWARD) {
return;
}
const result = callMethod(method, runtime
? runtime.execute(event, data)
: (data ? [event, data] : [event]));
if (result === FALSE$1) {
event.prevent().stop();
}
};
}, renderEventMethod = function (key, value, name, ns, method, runtime, isComponent, isNative) {
return {
key,
value,
name,
ns,
isNative,
runtime,
listener: createEventMethodListener(method, runtime, isComponent),
};
}, renderEventName = function (key, value, name, ns, to, toNs, isComponent, isNative) {
return {
key,
value,
name,
ns,
isNative,
listener: createEventNameListener(to, toNs, isComponent),
};
}, renderDirective = function (key, name, modifier, value, create) {
return {
ns: DIRECTIVE_CUSTOM,
name,
value,
modifier,
create,
};
}, callMethod = function (name, args) {
const method = instance[name];
if (args && args.length > 0) {
return execute(method, instance, args);
}
return instance[name]();
}, renderSpread = function (vnode, key, value) {
if (object$1(value)) {
for (let name in value) {
appendVNodeProperty(vnode, key, name, value[name]);
}
}
}, renderEach = function (holder, renderChildren, renderElse) {
let { keypath, value } = holder, length = 0, needKeypath = !!keypath, oldScopeStack = contextStack, currentKeypath = last(contextStack).keypath;
if (array$1(value)) {
length = value.length;
for (let i = 0; i < length; i++) {
if (needKeypath) {
currentKeypath = keypath + RAW_DOT + i;
// slice + push 比直接 concat 快多了
contextStack = oldScopeStack.slice();
contextStack.push(new Context(currentKeypath, value, i));
}
renderChildren(contextStack, value[i], currentKeypath, length, i);
}
}
else if (object$1(value)) {
const keys$1 = keys(value);
length = keys$1.length;
for (let i = 0; i < length; i++) {
const key = keys$1[i];
if (needKeypath) {
// 这里 key 虽然可能为空,但也必须直接拼接
// 因为不拼接就变成了原来的 keypath,这样更是错的,
// 只能在使用上尽量避免 key 为空的用法
currentKeypath = keypath + RAW_DOT + key;
// slice + push 比直接 concat 快多了
contextStack = oldScopeStack.slice();
contextStack.push(new Context(currentKeypath, value, key));
}
renderChildren(contextStack, value[key], currentKeypath, length, key);
}
}
if (contextStack !== oldScopeStack) {
contextStack = oldScopeStack;
}
if (renderElse && length === 0) {
renderElse();
}
}, renderRange = function (from, to, equal, renderChildren, renderElse) {
let count = 0, length = 0, currentKeypath = last(contextStack).keypath;
if (from < to) {
length = to - from;
if (equal) {
for (let i = from; i <= to; i++) {
renderChildren(contextStack, i, currentKeypath, length, count++);
}
}
else {
for (let i = from; i < to; i++) {
renderChildren(contextStack, i, currentKeypath, length, count++);
}
}
}
else {
length = from - to;
if (equal) {
for (let i = from; i >= to; i--) {
renderChildren(contextStack, i, currentKeypath, length, count++);
}
}
else {
for (let i = from; i > to; i--) {
renderChildren(contextStack, i, currentKeypath, length, count++);
}
}
}
if (renderElse && length === 0) {
renderElse();
}
}, renderSlot = function (name, parent) {
addDependency(name);
const target = rootScope[name];
if (target) {
if (target instanceof Computed) {
// 如果 slot 透传好几层组件,最里面的那个组件调用 renderSlot 时,会把自己传入 parent 参数
// 那么在它之上的每一层组件,都应该调用原始的渲染函数 getter,而不是调用经过封装的 get
return parent
? target.getter(parent)
: target.get();
}
return target;
}
}, lookupKeypath = function (stack, index, keypathStr, keypathList, lookup, filter) {
let defaultResult;
while (index >= 0) {
const item = stack[index], currentKeypath = item.getKeypath(keypathStr), result = get(item.getScope(), keypathList);
const valueHolder = setValueHolder(result ? result.value : UNDEFINED$1, currentKeypath);
if (result) {
return valueHolder;
}
if (!defaultResult) {
defaultResult = valueHolder;
}
if (lookup && index > 0) {
index--;
}
else {
break;
}
}
return filter
? setValueHolder(filter)
: defaultResult;
}, lookupProp = function (stack, index, prop, filter) {
return lookupKeypath(stack, index, prop, [prop], TRUE$1, filter);
}, readKeypath = function (value, keypath) {
const result = get(value, keypath);
return setValueHolder(result ? result.value : UNDEFINED$1);
}, setValueHolder = function (value, keypath) {
if (value && func(value.get)) {
value = value.get();
}
holder.keypath = keypath;
holder.value = value;
if (keypath !== UNDEFINED$1) {
addDependency(keypath);
}
return holder;
}, renderTemplate = function (render, scope, keypath, children) {
render(renderStyleString, renderStyleExpr, renderTransition, renderModel, renderEventMethod, renderEventName, renderDirective, renderSpread, renderEach, renderRange, renderSlot, appendVNodeProperty, formatNumberNativeAttributeValue, formatBooleanNativeAttributeValue, lookupKeypath, lookupProp, readKeypath, setValueHolder, toString, textVNodeOperator, commentVNodeOperator, elementVNodeOperator, componentVNodeOperator, fragmentVNodeOperator, portalVNodeOperator, slotVNodeOperator, instance, logger, filters, globalFilters, directives, globalDirectives, transitions, globalTransitions, contextStack, scope, keypath, children);
};
renderTemplate(template, rootScope, rootKeypath, children);
return children[0];
}
let guid$1 = 0,
// 这里先写 IE9 支持的接口
// 文本或注释节点设置内容的属性
textContent = 'textContent',
// 元素节点设置 text 的属性
innerText = textContent,
// 元素节点设置 html 的属性
innerHTML = 'innerHTML', cssFloat = 'cssFloat', createEvent = function (event, node) {
return event;
}, findElement = function (selector) {
const node = DOCUMENT.querySelector(selector);
if (node) {
return node;
}
}, addEventListener = function (node, type, listener) {
node.addEventListener(type, listener, FALSE$1);
}, removeEventListener = function (node, type, listener) {
node.removeEventListener(type, listener, FALSE$1);
},
// IE9 不支持 classList
addElementClass = function (node, className) {
node.classList.add(className);
}, removeElementClass = function (node, className) {
node.classList.remove(className);
};
{
if (DOCUMENT) {
// 此时 document.body 不一定有值,比如 script 放在 head 里
let testElement = DOCUMENT.documentElement;
if (!(cssFloat in testElement.style)) {
cssFloat = 'styleFloat';
}
if (!testElement.classList) {
addElementClass = function (node, className) {
const classes = node.className.split(CHAR_WHITESPACE);
if (!has$2(classes, className)) {
push(classes, className);
node.className = join$1(classes, CHAR_WHITESPACE);
}
};
removeElementClass = function (node, className) {
const classes = node.className.split(CHAR_WHITESPACE);
if (remove$1(classes, className)) {
node.className = join$1(classes, CHAR_WHITESPACE);
}
};
}
// 为 IE9 以下浏览器打补丁
{
if (!testElement.addEventListener) {
const PROPERTY_CHANGE = 'propertychange', isBoxElement = function (node) {
return node.tagName === 'INPUT'
&& (node.type === 'radio' || node.type === 'checkbox');
};
class IEEvent {
constructor(event, element) {
extend(this, event);
this.currentTarget = element;
this.target = event.srcElement || element;
this.originalEvent = event;
}
preventDefault() {
this.originalEvent.returnValue = FALSE$1;
}
stopPropagation() {
this.originalEvent.cancelBubble = TRUE$1;
}
}
// 兼容 IE678
textContent = 'nodeValue';
innerText = 'innerText';
createEvent = function (event, element) {
return new IEEvent(event, element);
};
findElement = function (selector) {
// 去掉 #
if (codeAt(selector, 0) === 35) {
selector = slice(selector, 1);
}
const node = DOCUMENT.getElementById(selector);
if (node) {
return node;
}
};
addEventListener = function (node, type, listener) {
if (type === EVENT_INPUT) {
addEventListener(node, PROPERTY_CHANGE,
// 借用 EMITTER,反正只是内部临时用一下...
listener[EVENT] = function (event) {
if (event.propertyName === 'value') {
listener(new CustomEvent(EVENT_INPUT, createEvent(event, node)));
}
});
}
else if (type === EVENT_CHANGE && isBoxElement(node)) {
addEventListener(node, EVENT_CLICK, listener[EVENT] = function (event) {
listener(new CustomEvent(EVENT_CHANGE, createEvent(event, node)));
});
}
else {
node.attachEvent(`on${type}`, listener);
}
};
removeEventListener = function (node, type, listener) {
if (type === EVENT_INPUT) {
removeEventListener(node, PROPERTY_CHANGE, listener[EVENT]);
delete listener[EVENT];
}
else if (type === EVENT_CHANGE && isBoxElement(node)) {
removeEventListener(node, EVENT_CLICK, listener[EVENT]);
delete listener[EVENT];
}
else {
node.detachEvent(`on${type}`, listener);
}
};
}
}
testElement = UNDEFINED$1;
}
}
const CHAR_WHITESPACE = ' ',
/**
* 绑定在 HTML 元素上的事件发射器
*/
EVENT = '$event',
/**
* 低版本 IE 上 style 标签的专有属性
*/
STYLE_SHEET = 'styleSheet',
/**
* 跟输入事件配套使用的事件
*/
COMPOSITION_START = 'compositionstart',
/**
* 跟输入事件配套使用的事件
*/
COMPOSITION_END = 'compositionend', domain = 'http://www.w3.org/', namespaces = {
svg: domain + '2000/svg',
// xml: domain + 'XML/1998/namespace',
// xlink: domain + '1999/xlink',
}, nativeListenerCount = {}, nativeListeners = {}, customListeners = {}, specialEvents = {};
specialEvents[EVENT_MODEL] = {
on(node, listener) {
let locked = FALSE$1;
on(node, COMPOSITION_START, listener[COMPOSITION_START] = function () {
locked = TRUE$1;
});
on(node, COMPOSITION_END, listener[COMPOSITION_END] = function (event) {
locked = FALSE$1;
listener(event);
});
addEventListener(node, EVENT_INPUT, listener[EVENT_INPUT] = function (event) {
if (!locked) {
listener(event);
}
});
},
off(node, listener) {
off(node, COMPOSITION_START, listener[COMPOSITION_START]);
off(node, COMPOSITION_END, listener[COMPOSITION_END]);
removeEventListener(node, EVENT_INPUT, listener[EVENT_INPUT]);
listener[COMPOSITION_START] =
listener[COMPOSITION_END] =
listener[EVENT_INPUT] = UNDEFINED$1;
}
};
function getBodyElement() {
return DOCUMENT.body;
}
function createElement(tag, isSvg) {
return isSvg
? DOCUMENT.createElementNS(namespaces.svg, tag)
: DOCUMENT.createElement(tag);
}
function createText(text) {
return DOCUMENT.createTextNode(text);
}
function createComment(text) {
return DOCUMENT.createComment(text);
}
function getAttr(node, name) {
const value = node.getAttribute(name);
if (value != NULL$1) {
return value;
}
}
function setAttr(node, name, value) {
if (value === UNDEFINED$1) {
node.removeAttribute(name);
}
else {
node.setAttribute(name, value);
}
}
function removeAttr(node, name) {
node.removeAttribute(name);
}
// 这里不传 HTMLElement 是因为外面会在循环里调用,频繁读取 node.style 挺浪费性能的
function setStyle(style, name, value) {
if (value == NULL$1) {
style[name] = EMPTY_STRING;
return;
}
style[name === 'float' ? cssFloat : name] = value;
}
// 这里不传 HTMLElement 是因为外面会在循环里调用,频繁读取 node.style 挺浪费性能的
function removeStyle(style, name) {
style[name] = EMPTY_STRING;
}
function before(parentNode, node, beforeNode) {
parentNode.insertBefore(node, beforeNode);
}
function append(parentNode, node) {
parentNode.appendChild(node);
}
function replace(parentNode, node, oldNode) {
parentNode.replaceChild(node, oldNode);
}
function remove(parentNode, node) {
parentNode.removeChild(node);
}
function parent(node) {
const { parentNode } = node;
if (parentNode) {
return parentNode;
}
}
function next(node) {
const { nextSibling } = node;
if (nextSibling) {
return nextSibling;
}
}
const find = findElement;
function tag(node) {
if (node.nodeType === NODE_TYPE_ELEMENT) {
return lower(node.tagName);
}
}
function getNodeText(node) {
return node[textContent];
}
function setNodeText(node, text) {
node[textContent] = text;
}
function getElementText(node) {
{
if (tag(node) === 'style' && has(node, STYLE_SHEET)) {
return node[STYLE_SHEET].cssText;
}
}
return node[innerText];
}
function setElementText(node, text) {
{
if (tag(node) === 'style' && has(node, STYLE_SHEET)) {
node[STYLE_SHEET].cssText = text;
return;
}
}
node[innerText] = text;
}
function getHtml(node) {
{
if (tag(node) === 'style' && has(node, STYLE_SHEET)) {
return node[STYLE_SHEET].cssText;
}
}
return node[innerHTML];
}
function setHtml(node, html) {
{
if (tag(node) === 'style' && has(node, STYLE_SHEET)) {
node[STYLE_SHEET].cssText = html;
return;
}
}
node[innerHTML] = html;
}
const addClass = addElementClass;
const removeClass = removeElementClass;
function on(node, type, listener) {
const nativeKey = node[EVENT] || (node[EVENT] = ++guid$1), nativeListenerMap = nativeListeners[nativeKey] || (nativeListeners[nativeKey] = {}), customListenerMap = customListeners[nativeKey] || (customListeners[nativeKey] = {}), customListenerList = customListenerMap[type] || (customListenerMap[type] = []);
// 一个元素,相同的事件,只注册一个 native listener
if (!nativeListenerMap[type]) {
// 特殊事件
const special = specialEvents[type],
// 唯一的原生监听器
nativeListener = function (event) {
let customEvent;
if (CustomEvent.is(event)) {
customEvent = event;
if (customEvent.type !== type) {
customEvent.type = type;
}
}
else {
customEvent = new CustomEvent(type, createEvent(event, node));
}
// 避免遍历过程中,数组发生变化,比如增删了
const listenerList = customListenerList.slice();
for (let i = 0, length = listenerList.length; i < length; i++) {
listenerList[i](customEvent, UNDEFINED$1, TRUE$1);
}
};
nativeListenerMap[type] = nativeListener;
if (nativeListenerCount[nativeKey]) {
nativeListenerCount[nativeKey]++;
}
else {
nativeListenerCount[nativeKey] = 1;
}
if (special) {
special.on(node, nativeListener);
}
else {
addEventListener(node, type, nativeListener);
}
}
customListenerList.push(listener);
}
function off(node, type, listener) {
let nativeKey = node[EVENT], nativeListenerMap = nativeListeners[nativeKey], customListenerMap = customListeners[nativeKey], customListenerList = customListenerMap && customListenerMap[type];
if (customListenerList) {
remove$1(customListenerList, listener);
if (!customListenerList.length) {
customListenerList = UNDEFINED$1;
delete customListenerMap[type];
}
}
// 如果注册的 type 事件都解绑了,则去掉原生监听器
if (nativeListenerMap && nativeListenerMap[type] && !customListenerList) {
const special = specialEvents[type], nativeListener = nativeListenerMap[type];
if (special) {
special.off(node, nativeListener);
}
else {
removeEventListener(node, type, nativeListener);
}
delete nativeListenerMap[type];
if (nativeListenerCount[nativeKey]) {
nativeListenerCount[nativeKey]--;
}
}
if (!nativeListenerCount[nativeKey]) {
node[EVENT] = UNDEFINED$1;
delete nativeListeners[nativeKey];
delete customListeners[nativeKey];
}
}
function addSpecialEvent(type, hooks) {
specialEvents[type] = hooks;
}
var domApi = /*#__PURE__*/Object.freeze({
__proto__: null,
getBodyElement: getBodyElement,
createElement: createElement,
createText: createText,
createComment: createComment,
getAttr: getAttr,
setAttr: setAttr,
removeAttr: removeAttr,
setStyle: setStyle,
removeStyle: removeStyle,
before: before,
append: append,
replace: replace,
remove: remove,
parent: parent,
next: next,
find: find,
tag: tag,
getNodeText: getNodeText,
setNodeText: setNodeText,
getElementText: getElementText,
setElementText: setElementText,
getHtml: getHtml,
setHtml: setHtml,
addClass: addClass,
removeClass: removeClass,
on: on,
off: off,
addSpecialEvent: addSpecialEvent
});
function toNumber (target, defaultValue) {
return numeric(target)
? +target
: defaultValue !== UNDEFINED$1
? defaultValue
: 0;
}
function readValue (source, keypath) {
if (source == NULL$1 || keypath === EMPTY_STRING) {
return source;
}
const result = get(source, keypath);
if (result) {
return result.value;
}
}
/**
* 对比新旧字符串
*
* @param newValue
* @param oldValue
* @param callback
*/
function diffString (newValue, oldValue, callback) {
const newIsString = string$1(newValue), oldIsString = string$1(oldValue);
if (newIsString || oldIsString) {
callback(RAW_LENGTH, newIsString ? newValue.length : UNDEFINED$1, oldIsString ? oldValue.length : UNDEFINED$1);
return TRUE$1;
}
}
/**
* 对比新旧数组
*
* @param newValue
* @param oldValue
* @param callback
*/
function diffArray (newValue, oldValue, callback) {
const newIsArray = array$1(newValue), oldIsArray = array$1(oldValue);
if (newIsArray || oldIsArray) {
const newLength = newIsArray ? newValue.length : UNDEFINED$1, oldLength = oldIsArray ? oldValue.length : UNDEFINED$1;
callback(RAW_LENGTH, newLength, oldLength);
for (let i = 0, length = Math.max(newLength || 0, oldLength || 0); i < length; i++) {
callback(
// 把 number 转成 string
EMPTY_STRING + i, newIsArray ? newValue[i] : UNDEFINED$1, oldIsArray ? oldValue[i] : UNDEFINED$1);
}
return TRUE$1;
}
}
/**
* 对比新旧对象
*
* @param newValue
* @param oldValue
* @param callback
*/
function diffObject (newValue, oldValue, callback) {
const newIsObject = object$1(newValue), oldIsObject = object$1(oldValue);
if (newIsObject || oldIsObject) {
const diffed = createPureObject$1(), newObject = newIsObject ? newValue : EMPTY_OBJECT, oldObject = oldIsObject ? oldValue : EMPTY_OBJECT;
if (newIsObject) {
for (let key in newObject) {
const value = newObject[key];
if (value !== oldObject[key]) {
// 保证遍历 oldObject 时不会再次触发
diffed.set(key, TRUE$1);
callback(key, value, oldObject[key]);
}
}
}
if (oldIsObject) {
for (let key in oldObject) {
const value = oldObject[key];
if (diffed.get(key) === UNDEFINED$1 && value !== newObject[key]) {
callback(key, newObject[key], value);
}
}
}
}
}
/**
* 递归对比
*/
function diffRecursion(keypath, newValue, oldValue, fuzzyKeypaths, fuzzyKeypathLength, callback) {
const diff = function (subKey, subNewValue, subOldValue) {
if (subNewValue !== subOldValue) {
const newKeypath = join(keypath, subKey);
for (let i = 0; i < fuzzyKeypathLength; i++) {
const fuzzyKeypath = fuzzyKeypaths[i];
if (matchFuzzy(newKeypath, fuzzyKeypath) !== UNDEFINED$1) {
callback(fuzzyKeypath, newKeypath, subNewValue, subOldValue);
}
}
diffRecursion(newKeypath, subNewValue, subOldValue, fuzzyKeypaths, fuzzyKeypathLength, callback);
}
};
diffString(newValue, oldValue, diff)
|| diffArray(newValue, oldValue, diff)
|| diffObject(newValue, oldValue, diff);
}
function diffWatcher (keypath, newValue, oldValue, watcher, isRecursive, callback) {
let fuzzyKeypaths;
// 遍历监听的 keypath,如果未被监听,则无需触发任何事件
for (const watchKeypath in watcher) {
// 模糊监听,如 users.*.name
if (isFuzzy(watchKeypath)) {
// 如果当前修改的是 users.0 整个对象
// users.0 和 users.*.name 无法匹配
// 此时要知道设置 users.0 到底会不会改变 users.*.name 需要靠递归了
// 如果匹配,则无需递归
if (matchFuzzy(keypath, watchKeypath) !== UNDEFINED$1) {
callback(watchKeypath, keypath, newValue, oldValue);
}
else if (isRecursive) {
if (fuzzyKeypaths) {
fuzzyKeypaths.push(watchKeypath);
}
else {
fuzzyKeypaths = [watchKeypath];
}
}
}
// 不是模糊匹配,直接通过前缀匹配
else {
// 比如监听的是 users.0.name,此时修改 users.0,则直接读出子属性值,判断是否相等
const length = match(watchKeypath, keypath);
if (length >= 0) {
const subKeypath = slice(watchKeypath, length), subNewValue = readValue(newValue, subKeypath), subOldValue = readValue(oldValue, subKeypath);
if (subNewValue !== subOldValue) {
callback(watchKeypath, watchKeypath, subNewValue, subOldValue);
}
}
}
}
// 存在模糊匹配的需求
// 必须对数据进行递归
// 性能确实会慢一些,但是很好用啊,几乎可以监听所有的数据
if (fuzzyKeypaths) {
diffRecursion(keypath, newValue, oldValue, fuzzyKeypaths, fuzzyKeypaths.length, callback);
}
}
// 避免频繁创建对象
const optionsHolder = {
watcher: EMPTY_FUNCTION,
};
/**
* 格式化 watch options
*
* @param options
*/
function formatWatcherOptions (options, immediate) {
const isWatcher = func(options);
if (isWatcher) {
optionsHolder.watcher = options;
optionsHolder.immediate = immediate === TRUE$1;
return optionsHolder;
}
return options;
}
let guid = 0;
/**
* 观察者有两种观察模式:
*
* 1. 同步监听
* 2. 异步监听
*
* 对于`计算属性`这种需要实时变化的对象,即它的依赖变了,它需要立即跟着变,否则会出现不一致的问题
* 这种属于同步监听
*
* 对于外部调用 observer.watch('keypath', listener),属于异步监听,它只关心是否变了,而不关心是否是立即触发的
*/
class Observer {
constructor(data, context, nextTask) {
const instance = this;
instance.id = guid++;
instance.data = data || {};
instance.context = context || instance;
instance.nextTask = nextTask || new NextTask();
instance.syncEmitter = new Emitter();
instance.asyncEmitter = new Emitter();
instance.asyncOldValues = {};
instance.asyncKeypaths = {};
instance.onComputedChange = function (keypath, newValue, oldValue) {
instance.diff(keypath, newValue, oldValue);
};
}
/**
* 获取数据
*
* @param keypath
* @param defaultValue
* @param depIgnore
* @return
*/
get(keypath, defaultValue, depIgnore) {
const instance = this, { data } = instance, currentComputed = Computed.current;
// 传入 '' 获取整个 data
if (keypath === EMPTY_STRING) {
return data;
}
// 调用 get 时,外面想要获取依赖必须设置是谁在收集依赖
// 如果没设置,则跳过依赖收集
if (currentComputed && !depIgnore) {
currentComputed.addDynamicDep(instance, keypath);
}
const result = get(data, keypath);
return result
? result.value
: defaultValue;
}
/**
* 更新数据
*
* @param keypath
* @param value
*/
set(keypath, value) {
const instance = this, { data } = instance, setValue = function (keypath, newValue) {
const oldValue = instance.get(keypath);
if (newValue === oldValue) {
return;
}
let next;
each$1(keypath, function (key, index, lastIndex) {
if (index === 0) {
const item = data[key];
if (item && item instanceof Computed) {
if (lastIndex === 0) {
item.set(newValue);
}
else {
// 这里 next 可能为空
next = item.get();
}
}
else {
if (lastIndex === 0) {
data[key] = newValue;
}
else {
next = data[key] || (data[key] = {});
}
}
return;
}
if (next) {
if (index === lastIndex) {
next[key] = newValue;
}
else {
next = next[key] || (next[key] = {});
}
}
});
instance.diff(keypath, newValue, oldValue);
};
if (string$1(keypath)) {
setValue(keypath, value);
}
else if (object$1(keypath)) {
for (let key in keypath) {
setValue(key, keypath[key]);
}
}
}
/**
* 同步调用的 diff,用于触发 syncEmitter,以及唤醒 asyncEmitter
*
* @param keypath
* @param newValue
* @param oldValue
*/
diff(keypath, newValue, oldValue) {
let instance = this, { syncEmitter, asyncEmitter, asyncOldValues, asyncKeypaths } = instance,
/**
* 我们认为 $ 开头的变量是不可递归的
* 比如浏览器中常见的 $0 表示当前选中元素
* DOM 元素是不能递归的
*/
isRecursive = codeAt(keypath) !== 36;
diffWatcher(keypath, newValue, oldValue, syncEmitter.listeners, isRecursive, function (watchKeypath, keypath, newValue, oldValue) {
syncEmitter.fire({
type: watchKeypath,
ns: EMPTY_STRING,
}, [
newValue,
oldValue,
keypath,
]);
});
/**
* 此处有坑,举个例子
*
* observer.watch('a', function () {})
*
* observer.set('a', 1)
*
* observer.watch('a', function () {})
*
* 这里,第一个 watcher 应该触发,但第二个不应该,因为它绑定监听时,值已经是最新的了
*/
diffWatcher(keypath, newValue, oldValue, asyncEmitter.listeners, isRecursive, function (watchKeypath, keypath, newValue, oldValue) {
// 这里是为了解决上面说的坑
const options = asyncEmitter.listeners[watchKeypath];
for (let i = 0, length = options.length; i < length; i++) {
options[i].count++;
}
if (!asyncKeypaths[keypath]) {
asyncOldValues[keypath] = oldValue;
asyncKeypaths[keypath] = {};
}
asyncKeypaths[keypath][watchKeypath] = TRUE$1;
if (!instance.pending) {
instance.pending = TRUE$1;
instance.nextTask.append(function () {
if (instance.pending) {
instance.diffAsync();
}
});
}
});
}
/**
* 异步触发的 diff
*/
diffAsync() {
const instance = this, { asyncEmitter, asyncOldValues, asyncKeypaths } = instance;
instance.pending = UNDEFINED$1;
instance.asyncOldValues = {};
instance.asyncKeypaths = {};
for (let keypath in asyncOldValues) {
const args = [
instance.get(keypath),
asyncOldValues[keypath],
keypath,
], keypaths = asyncKeypaths[keypath], hasChange = args[0] !== args[1], filterWatcher = function (event, args, options) {
// 前面递增了 count
// 这里要递减 count
// count > 0 表示前面标记了该监听器需要响应此次变化
if (options.count) {
// 采用计数器的原因是,同一个 options 可能执行多次
// 比如监听 user.*,如果同批次修改了 user.name 和 user.age
// 这个监听器会调用多次,如果第一次执行就把 count 干掉了,第二次就无法执行了
options.count--;
// 新旧值不相等才能触发监听器
return hasChange;
}
};
for (let watchKeypath in keypaths) {
asyncEmitter.fire({
type: watchKeypath,
ns: EMPTY_STRING,
}, args, filterWatcher);
}
}
}
/**
* 添加计算属性
*
* @param keypath
* @param options
*/
addComputed(keypath, options) {
let instance = this, context = instance.context, cache = TRUE$1, sync = TRUE$1, deps, input, getter, setter, output;
// 这里用 bind 方法转换一下调用的 this
// 还有一个好处,它比 call(context) 速度稍快一些
if (func(options)) {
getter = options.bind(context);
}
else if (object$1(options)) {
const computedOptions = options;
if (boolean(computedOptions.cache)) {
cache = computedOptions.cache;
}
if (boolean(computedOptions.sync)) {
sync = computedOptions.sync;
}
if (array$1(computedOptions.deps)) {
deps = computedOptions.deps;
}
// 参数列表必须是长度大于 0 的数组
if (!falsy$2(computedOptions.input)) {
input = computedOptions.input;
}
if (func(computedOptions.output)) {
output = computedOptions.output;
}
if (func(computedOptions.get)) {
getter = computedOptions.get.bind(context);
}
if (func(computedOptions.set)) {
setter = computedOptions.set.bind(context);
}
}
if (getter) {
const computed = new Computed(keypath, cache, sync, input, output, getter, setter, instance.onComputedChange);
if (cache && deps) {
computed.addStaticDeps(instance, deps);
}
return instance.data[keypath] = computed;
}
}
/**
* 移除计算属性
*
* @param keypath
*/
removeComputed(keypath) {
delete this.data[keypath];
}
/**
* 监听数据变化
*
* @param keypath
* @param watcher
* @param immediate
*/
watch(keypath, watcher, immediate) {
const instance = this, { context, syncEmitter, asyncEmitter } = instance, addWatcher = function (keypath, options) {
const emitter = options.sync ? syncEmitter : asyncEmitter,
// formatWatcherOptions 保证了 options.watcher 一定存在
listener = {
ns: EMPTY_STRING,
listener: options.watcher,
ctx: context,
count: 0,
};
if (options.once) {
listener.max = 1;
}
emitter.on(keypath, listener);
if (options.immediate) {
options.watcher.call(context, instance.get(keypath), UNDEFINED$1, keypath);
}
};
if (string$1(keypath)) {
addWatcher(keypath, formatWatcherOptions(watcher, immediate));
}
else {
for (let key in keypath) {
addWatcher(key, formatWatcherOptions(keypath[key]));
}
}
}
/**
* 取消监听数据变化
*
* @param keypath
* @param watcher
*/
unwatch(keypath, watcher) {
this.syncEmitter.off(keypath, watcher);
this.asyncEmitter.off(keypath, watcher);
}
/**
* 取反 keypath 对应的数据
*
* 不管 keypath 对应的数据是什么类型,操作后都是布尔型
*
* @param keypath
* @return 取反后的布尔值
*/
toggle(keypath) {
const value = !this.get(keypath);
this.set(keypath, value);
return value;
}
/**
* 递增 keypath 对应的数据
*
* 注意,最好是整型的加法,如果涉及浮点型,不保证计算正确
*
* @param keypath 值必须能转型成数字,如果不能,则默认从 0 开始递增
* @param step 步进值,默认是 1
* @param max 可以递增到的最大值,默认不限制
*/
increase(keypath, step, max) {
const value = toNumber(this.get(keypath), 0) + (step || 1);
if (!number(max) || value <= max) {
this.set(keypath, value);
return value;
}
}
/**
* 递减 keypath 对应的数据
*
* 注意,最好是整型的减法,如果涉及浮点型,不保证计算正确
*
* @param keypath 值必须能转型成数字,如果不能,则默认从 0 开始递减
* @param step 步进值,默认是 1
* @param min 可以递减到的最小值,默认不限制
*/
decrease(keypath, step, min) {
const value = toNumber(this.get(keypath), 0) - (step || 1);
if (!number(min) || value >= min) {
this.set(keypath, value);
return value;
}
}
/**
* 在数组指定位置插入元素
*
* @param keypath
* @param item
* @param index
*/
insert(keypath, item, index) {
let list = this.get(keypath);
list = array$1(list) ? list.slice() : [];
const { length } = list;
if (index === TRUE$1 || index === length) {
list.push(item);
}
else if (index === FALSE$1 || index === 0) {
list.unshift(item);
}
else if (index > 0 && index < length) {
list.splice(index, 0, item);
}
else {
return;
}
this.set(keypath, list);
return TRUE$1;
}
/**
* 在数组尾部添加元素
*
* @param keypath
* @param item
*/
append(keypath, item) {
return this.insert(keypath, item, TRUE$1);
}
/**
* 在数组首部添加元素
*
* @param keypath
* @param item
*/
prepend(keypath, item) {
return this.insert(keypath, item, FALSE$1);
}
/**
* 通过索引移除数组中的元素
*
* @param keypath
* @param index
*/
removeAt(keypath, index) {
let list = this.get(keypath);
if (array$1(list)
&& index >= 0
&& index < list.length) {
list = list.slice();
list.splice(index, 1);
this.set(keypath, list);
return TRUE$1;
}
}
/**
* 直接移除数组中的元素
*
* @param keypath
* @param item
*/
remove(keypath, item) {
let list = this.get(keypath);
if (array$1(list)) {
list = list.slice();
if (remove$1(list, item)) {
this.set(keypath, list);
return TRUE$1;
}
}
}
/**
* 拷贝任意数据,支持深拷贝
*
* @param data
* @param deep
*/
copy(data, deep) {
return copy(data, deep);
}
/**
* 销毁
*/
destroy() {
const instance = this;
instance.syncEmitter.off();
instance.asyncEmitter.off();
instance.nextTask.clear();
instance.data = {};
}
}
class LifeCycle {
constructor() {
this.$emitter = new Emitter();
}
fire(component, type, data) {
this.$emitter.fire(type, [
component,
data,
]);
}
on(type, listener) {
this.$emitter.on(type, listener);
return this;
}
off(type, listener) {
this.$emitter.off(type, listener);
return this;
}
}
const globalDirectives = {}, globalTransitions = {}, globalComponents = {}, globalFilters = {}, selectorPattern = /^[#.][-\w+]+$/, lifeCycle = new LifeCycle(), compileTemplate = createOneKeyCache(function (template) {
const nodes = compile(template);
return generate(nodes[0]);
}), templateComputed = '$$template', templateComputedWatcher = {
watcher(vnode) {
this.update(vnode, this.$vnode);
},
sync: TRUE$1,
}, outputSlot = function (vnodes) {
return vnodes
? vnodes.map(clone)
: vnodes;
};
class Yox {
constructor(options) {
const instance = this, $options = options || EMPTY_OBJECT;
// 为了冒泡 HOOK_BEFORE_CREATE 事件,必须第一时间创建 emitter
// 监听各种事件
// 支持命名空间
instance.$emitter = new Emitter(TRUE$1);
if ($options.events) {
instance.on($options.events);
}
{
// 当前组件的直接父组件
if ($options.parent) {
instance.$parent = $options.parent;
}
// 建立好父子连接后,立即触发钩子
const beforeCreateHook = $options[HOOK_BEFORE_CREATE];
if (beforeCreateHook) {
beforeCreateHook.call(instance, $options);
}
lifeCycle.fire(instance, HOOK_BEFORE_CREATE, {
options: $options,
});
}
let { data, props, vnode, propTypes, computed, methods, watchers, extensions, } = $options;
instance.$options = $options;
if (extensions) {
extend(instance, extensions);
}
// 数据源,默认值仅在创建组件时启用
const source = props ? copy(props) : {};
{
if (propTypes) {
each(propTypes, function (rule, key) {
let value = source[key];
if (value === UNDEFINED$1) {
value = rule.value;
if (value !== UNDEFINED$1) {
source[key] = rule.type === RAW_FUNCTION
? value
: func(value)
? value()
: value;
}
}
});
}
}
// 先放 props
// 当 data 是函数时,可以通过 this.get() 获取到外部数据
const observer = instance.$observer = new Observer(source, instance, instance.$nextTask = new NextTask());
if (computed) {
each(computed, function (options, keypath) {
observer.addComputed(keypath, options);
});
}
const extend$1 = func(data) ? data.call(instance, options) : data;
if (object$1(extend$1)) {
each(extend$1, function (value, key) {
source[key] = value;
});
}
if (methods) {
each(methods, function (method, name) {
instance[name] = method;
});
}
{
let placeholder = UNDEFINED$1, { el, root, model, context, replace, template, transitions, components, directives, filters, slots, } = $options;
if (model) {
instance.$model = model;
}
// 检查 template
if (string$1(template)) {
// 传了选择器,则取对应元素的 html
if (selectorPattern.test(template)) {
placeholder = find(template);
if (placeholder) {
template = getHtml(placeholder);
placeholder = UNDEFINED$1;
}
}
}
// 检查 el
if (el) {
if (string$1(el)) {
const selector = el;
if (selectorPattern.test(selector)) {
placeholder = find(selector);
}
}
else {
placeholder = el;
}
if (!replace) {
append(placeholder, placeholder = createComment(EMPTY_STRING));
}
}
// 根组件
if (root) {
instance.$root = root;
}
// 当前组件是被哪个组件渲染出来的
// 因为有 slot 机制,$context 不一定等于 $parent
if (context) {
instance.$context = context;
}
setOptionsSmartly(instance, RAW_TRANSITION, transitions);
setOptionsSmartly(instance, RAW_COMPONENT, components);
setOptionsSmartly(instance, RAW_DIRECTIVE, directives);
setOptionsSmartly(instance, RAW_FILTER, filters);
if (template) {
if (watchers) {
observer.watch(watchers);
}
if (slots) {
for (let name in slots) {
observer.addComputed(name, {
get: slots[name],
input: [instance],
output: outputSlot,
});
}
}
observer.addComputed(templateComputed, {
get: instance.render,
sync: FALSE$1,
});
observer.watch(templateComputed, templateComputedWatcher);
{
const afterCreateHook = $options[HOOK_AFTER_CREATE];
if (afterCreateHook) {
afterCreateHook.call(instance);
}
lifeCycle.fire(instance, HOOK_AFTER_CREATE);
}
// 编译模板
// 在开发阶段,template 是原始的 html 模板
// 在产品阶段,template 是编译后的渲染函数
// 当然,具体是什么需要外部自己控制
instance.$template = string$1(template)
? Yox.compile(template)
: template;
if (!vnode) {
vnode = create(domApi, placeholder, instance);
}
instance.update(instance.get(templateComputed), vnode);
return;
}
}
if (watchers) {
observer.watch(watchers);
}
{
const afterCreateHook = $options[HOOK_AFTER_CREATE];
if (afterCreateHook) {
afterCreateHook.call(instance);
}
lifeCycle.fire(instance, HOOK_AFTER_CREATE);
}
}
/**
* 定义组件对象
*/
static define(options) {
return options;
}
/**
* 安装插件
*
* 插件必须暴露 install 方法
*/
static use(plugin) {
plugin.install(Yox);
}
/**
* 因为组件采用的是异步更新机制,为了在更新之后进行一些操作,可使用 nextTick
*/
static nextTick(task, context) {
NextTask.shared().append(task, context);
}
/**
* 编译模板,暴露出来是为了打包阶段的模板预编译
*/
static compile(template, stringify) {
{
// 需要编译的都是模板源文件,一旦经过预编译,就成了 render 函数
if (func(template)) {
return template;
}
template = compileTemplate(template);
return stringify
? template
: new Function(`return ${template}`)();
}
}
/**
* 注册全局指令
*/
static directive(name, directive) {
{
if (string$1(name) && !directive) {
return getResource(globalDirectives, name);
}
{
setResourceSmartly(globalDirectives, name, directive);
}
}
}
/**
* 注册全局过渡动画
*/
static transition(name, transition) {
{
if (string$1(name) && !transition) {
return getResource(globalTransitions, name);
}
{
setResourceSmartly(globalTransitions, name, transition);
}
}
}
/**
* 注册全局组件
*/
static component(name, component) {
{
if (string$1(name) && !component) {
return getResource(globalComponents, name);
}
{
setResourceSmartly(globalComponents, name, component);
}
}
}
/**
* 注册全局过滤器
*/
static filter(name, filter) {
{
if (string$1(name) && !filter) {
return getResource(globalFilters, name);
}
{
setResourceSmartly(globalFilters, name, filter);
}
}
}
/**
* 注册全局方法
*/
static method(name, method) {
if (string$1(name) && !method) {
return YoxPrototype[name];
}
{
setResourceSmartly(YoxPrototype, name, method);
}
}
/**
* 取值
*/
get(keypath, defaultValue) {
return this.$observer.get(keypath, defaultValue);
}
/**
* 设值
*/
set(keypath, value) {
// 组件经常有各种异步改值,为了避免组件销毁后依然调用 set
// 这里判断一下,至于其他方法的异步调用就算了,业务自己控制吧
const { $observer } = this;
if ($observer) {
$observer.set(keypath, value);
}
}
/**
* 监听事件,支持链式调用
*/
on(type, listener) {
addEventSmartly(this, type, listener);
return this;
}
/**
* 监听一次事件,支持链式调用
*/
once(type, listener) {
addEventSmartly(this, type, listener, TRUE$1);
return this;
}
/**
* 取消监听事件,支持链式调用
*/
off(type, listener) {
this.$emitter.off(type, listener);
return this;
}
/**
* 发射事件
*/
fire(type, data, downward) {
// 外部为了使用方便,fire(type) 或 fire(type, data) 就行了
// 内部为了保持格式统一
// 需要转成 Event,这样还能知道 target 是哪个组件
const instance = this, { $emitter, $parent, $children } = instance;
// 生成事件对象
let event;
if (CustomEvent.is(type)) {
event = type;
}
else if (string$1(type)) {
event = new CustomEvent(type);
}
else {
const emitterEvent = type;
event = new CustomEvent(emitterEvent.type);
event.ns = emitterEvent.ns;
}
// 先解析出命名空间,避免每次 fire 都要解析
if (event.ns === UNDEFINED$1) {
const emitterEvent = $emitter.toEvent(event.type);
event.type = emitterEvent.type;
event.ns = emitterEvent.ns;
}
// 告诉外部是谁发出的事件
if (!event.target) {
event.target = instance;
}
// 事件参数列表
let args = [event],
// 事件是否正常结束(未被停止冒泡)
isComplete;
// 比如 fire('name', true) 直接向下发事件
if (object$1(data)) {
push(args, data);
}
else if (data === TRUE$1) {
downward = TRUE$1;
}
// 向上发事件会经过自己
// 如果向下发事件再经过自己,就产生了一次重叠
// 这是没有必要的,而且会导致向下发事件时,外部能接收到该事件,但我们的本意只是想让子组件接收到事件
isComplete = downward && event.target === instance
? TRUE$1
: $emitter.fire(event, args);
if (isComplete) {
if (downward) {
if ($children) {
event.phase = CustomEvent.PHASE_DOWNWARD;
each$2($children, function (child) {
return isComplete = child.fire(event, data, TRUE$1);
});
}
}
else if ($parent) {
event.phase = CustomEvent.PHASE_UPWARD;
isComplete = $parent.fire(event, data);
}
}
return isComplete;
}
/**
* 监听数据变化,支持链式调用
*/
watch(keypath, watcher, immediate) {
this.$observer.watch(keypath, watcher, immediate);
return this;
}
/**
* 取消监听数据变化,支持链式调用
*/
unwatch(keypath, watcher) {
this.$observer.unwatch(keypath, watcher);
return this;
}
/**
* 加载组件,组件可以是同步或异步,最后会调用 callback
*
* @param name 组件名称
* @param callback 组件加载成功后的回调
*/
loadComponent(name, callback) {
{
if (!loadComponent(this.$components, name, callback)) {
{
loadComponent(globalComponents, name, callback);
}
}
}
}
/**
* 创建子组件
*
* @param options 组件配置
* @param vnode 虚拟节点
*/
createComponent(options, vnode) {
{
const instance = this;
options = copy(options);
options.root = instance.$root || instance;
options.parent = instance;
options.context = vnode.context;
options.vnode = vnode;
options.replace = TRUE$1;
let { props, slots, model } = vnode;
if (model) {
if (!props) {
props = {};
}
const key = options.model || MODEL_PROP_DEFAULT;
props[key] = model.value;
options.model = key;
}
if (props) {
options.props = props;
}
if (slots) {
options.slots = slots;
}
const child = new Yox(options);
push(instance.$children || (instance.$children = []), child);
const node = child.$el;
if (node) {
vnode.node = node;
}
return child;
}
}
/**
* 注册当前组件级别的指令
*/
directive(name, directive) {
{
const instance = this, { $directives } = instance;
if (string$1(name) && !directive) {
return getResource($directives, name, Yox.directive);
}
{
setResourceSmartly($directives || (instance.$directives = {}), name, directive);
}
}
}
/**
* 注册当前组件级别的过渡动画
*/
transition(name, transition) {
{
const instance = this, { $transitions } = instance;
if (string$1(name) && !transition) {
return getResource($transitions, name, Yox.transition);
}
{
setResourceSmartly($transitions || (instance.$transitions = {}), name, transition);
}
}
}
/**
* 注册当前组件级别的组件
*/
component(name, component) {
{
const instance = this, { $components } = instance;
if (string$1(name) && !component) {
return getResource($components, name, Yox.component);
}
{
setResourceSmartly($components || (instance.$components = {}), name, component);
}
}
}
/**
* 注册当前组件级别的过滤器
*/
filter(name, filter) {
{
const instance = this, { $filters } = instance;
if (string$1(name) && !filter) {
return getResource($filters, name, Yox.filter);
}
{
setResourceSmartly($filters || (instance.$filters = {}), name, filter);
}
}
}
/**
* 对于某些特殊场景,修改了数据,但是模板的依赖中并没有这一项
* 而你非常确定需要更新模板,强制刷新正是你需要的
*/
forceUpdate(props) {
{
const instance = this, { $options, $vnode, $nextTask } = instance;
if ($vnode) {
if (props) {
const beforePropsUpdateHook = $options[HOOK_BEFORE_PROPS_UPDATE];
if (beforePropsUpdateHook) {
beforePropsUpdateHook.call(instance, props);
}
instance.set(props);
}
// 当前可能正在进行下一轮更新
$nextTask.run();
// 没有更新模板,强制刷新
if (!props && $vnode === instance.$vnode) {
instance.update(instance.get(templateComputed), $vnode);
}
}
}
}
/**
* 把模板抽象语法树渲染成 virtual dom
*/
render() {
{
const instance = this, { $options, $observer } = instance, { data } = $observer, beforeRenderHook = $options[HOOK_BEFORE_RENDER], afterRenderHook = $options[HOOK_AFTER_RENDER];
if (beforeRenderHook) {
beforeRenderHook.call(instance, data);
}
lifeCycle.fire(instance, HOOK_BEFORE_RENDER, {
props: data,
});
const result = render(instance, instance.$template, data, instance.$filters, globalFilters, instance.$directives, globalDirectives, instance.$transitions, globalTransitions, function (keypath) {
// 事件、指令触发时调用方法,Computed.current 为空
// 其他情况不为空
const { current } = Computed;
if (current) {
current.addDynamicDep($observer, keypath);
}
});
if (afterRenderHook) {
afterRenderHook.call(instance);
}
lifeCycle.fire(instance, HOOK_AFTER_RENDER);
return result;
}
}
/**
* 更新 virtual dom
*
* @param vnode
* @param oldVNode
*/
update(vnode, oldVNode) {
{
let instance = this, { $vnode, $options } = instance, afterHookName;
if ($vnode) {
const beforeUpdateHook = $options[HOOK_BEFORE_UPDATE];
if (beforeUpdateHook) {
beforeUpdateHook.call(instance);
}
lifeCycle.fire(instance, HOOK_BEFORE_UPDATE);
patch(domApi, vnode, oldVNode);
afterHookName = HOOK_AFTER_UPDATE;
}
else {
const beforeMountHook = $options[HOOK_BEFORE_MOUNT];
if (beforeMountHook) {
beforeMountHook.call(instance);
}
lifeCycle.fire(instance, HOOK_BEFORE_MOUNT);
patch(domApi, vnode, oldVNode);
instance.$el = vnode.node;
afterHookName = HOOK_AFTER_MOUNT;
}
instance.$vnode = vnode;
// 跟 nextTask 保持一个节奏
// 这样可以预留一些优化的余地
Yox.nextTick(function () {
if (instance.$vnode) {
const afterHook = $options[afterHookName];
if (afterHook) {
afterHook.call(instance);
}
lifeCycle.fire(instance, afterHookName);
}
});
}
}
/**
* 校验组件参数
*
* @param props
*/
checkProp(key, value) {
}
/**
* 销毁组件
*/
destroy() {
const instance = this, { $parent, $options, $emitter, $observer } = instance;
{
const beforeDestroyHook = $options[HOOK_BEFORE_DESTROY];
if (beforeDestroyHook) {
beforeDestroyHook.call(instance);
}
lifeCycle.fire(instance, HOOK_BEFORE_DESTROY);
if ($parent && $parent.$children) {
remove$1($parent.$children, instance);
}
const { $vnode } = instance;
if ($vnode) {
destroy(domApi, $vnode, !$parent);
}
}
$observer.destroy();
{
const afterDestroyHook = $options[HOOK_AFTER_DESTROY];
if (afterDestroyHook) {
afterDestroyHook.call(instance);
}
lifeCycle.fire(instance, HOOK_AFTER_DESTROY);
}
// 发完 after destroy 事件再解绑所有事件
$emitter.off();
instance.$el = UNDEFINED$1;
}
/**
* 因为组件采用的是异步更新机制,为了在更新之后进行一些操作,可使用 nextTick
*/
nextTick(task) {
this.$nextTask.append(task, this);
}
/**
* 取反 keypath 对应的数据
*
* 不管 keypath 对应的数据是什么类型,操作后都是布尔型
*/
toggle(keypath) {
return this.$observer.toggle(keypath);
}
/**
* 递增 keypath 对应的数据
*
* 注意,最好是整型的加法,如果涉及浮点型,不保证计算正确
*
* @param keypath 值必须能转型成数字,如果不能,则默认从 0 开始递增
* @param step 步进值,默认是 1
* @param max 可以递增到的最大值,默认不限制
*/
increase(keypath, step, max) {
return this.$observer.increase(keypath, step, max);
}
/**
* 递减 keypath 对应的数据
*
* 注意,最好是整型的减法,如果涉及浮点型,不保证计算正确
*
* @param keypath 值必须能转型成数字,如果不能,则默认从 0 开始递减
* @param step 步进值,默认是 1
* @param min 可以递减到的最小值,默认不限制
*/
decrease(keypath, step, min) {
return this.$observer.decrease(keypath, step, min);
}
/**
* 在数组指定位置插入元素
*
* @param keypath
* @param item
* @param index
*/
insert(keypath, item, index) {
return this.$observer.insert(keypath, item, index);
}
/**
* 在数组尾部添加元素
*
* @param keypath
* @param item
*/
append(keypath, item) {
return this.$observer.append(keypath, item);
}
/**
* 在数组首部添加元素
*
* @param keypath
* @param item
*/
prepend(keypath, item) {
return this.$observer.prepend(keypath, item);
}
/**
* 通过索引移除数组中的元素
*
* @param keypath
* @param index
*/
removeAt(keypath, index) {
return this.$observer.removeAt(keypath, index);
}
/**
* 直接移除数组中的元素
*
* @param keypath
* @param item
*/
remove(keypath, item) {
return this.$observer.remove(keypath, item);
}
/**
* 拷贝任意数据,支持深拷贝
*
* @param data
* @param deep
*/
copy(data, deep) {
return this.$observer.copy(data, deep);
}
}
/**
* core 版本
*/
Yox.version = "1.0.0-alpha.408";
/**
* 方便外部共用的通用逻辑,特别是写插件,减少重复代码
*/
Yox.is = is;
Yox.dom = domApi;
Yox.array = array;
Yox.object = object;
Yox.string = string;
Yox.logger = logger;
Yox.Event = CustomEvent;
Yox.Emitter = Emitter;
Yox.lifeCycle = lifeCycle;
/**
* 外部可配置的对象
*/
Yox.config = PUBLIC_CONFIG;
const YoxPrototype = Yox.prototype;
// 内置方法,外部不可覆盖
toObject(keys(YoxPrototype));
function loadComponent(registry, name, callback) {
if (registry && registry[name]) {
const component = registry[name];
// 注册的是异步加载函数
if (func(component)) {
registry[name] = [callback];
const componentCallback = function (result) {
const queue = registry[name], options = result['default'] || result;
registry[name] = options;
each$2(queue, function (callback) {
callback(options);
});
}, promise = component(componentCallback);
if (promise) {
promise.then(componentCallback);
}
}
// 正在加载中
else if (array$1(component)) {
push(component, callback);
}
// 不是异步加载函数,直接同步返回
else {
callback(component);
}
return TRUE$1;
}
}
function getResource(registry, name, lookup) {
if (registry && registry[name]) {
return registry[name];
}
else if (lookup) {
return lookup(name);
}
}
function setResourceItem(registry, name, value, options) {
if (options && options.format) {
value = options.format(value);
}
registry[name] = value;
}
function setResourceSmartly(registry, name, value, options) {
if (string$1(name)) {
setResourceItem(registry, name, value, options);
}
else {
each(name, function (value, key) {
setResourceItem(registry, key, value, options);
});
}
}
function setOptionsSmartly(instance, key, value) {
if (func(value)) {
instance[key](value.call(instance));
}
else if (object$1(value)) {
instance[key](value);
}
}
function addEvent(instance, options, once) {
instance.$emitter.on(options.type, {
listener: options.listener,
ns: options.ns,
max: once ? 1 : -1,
ctx: instance,
});
}
function addEventSmartly(instance, type, listener, once) {
const { $emitter } = instance;
if (string$1(type)) {
addEvent(instance, $emitter.toFilter(type, listener), once);
}
else if (array$1(type)) {
each$2(type, function (filter) {
addEvent(instance, filter, once);
});
}
else {
each(type, function (value, key) {
addEvent(instance, $emitter.toFilter(key, value), once);
});
}
}
export default Yox;
//# sourceMappingURL=yox.esm.js.map