You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gym/vuedw.js

4216 lines
158 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 缓存样式文本解析器将CSS字符串转换为样式对象
var parseStyleText = cached(function (cssText) {
var res = {}; // 存储解析结果的空对象
var listDelimiter = /;(?![^(]*\))/g; // 分号分割(排除括号内的分号)
var propertyDelimiter = /:(.+)/; // 冒号分隔属性名和值
cssText.split(listDelimiter).forEach(function (item) { // 拆分样式声明
if (item) { // 过滤空项
var tmp = item.split(propertyDelimiter); // 分割属性键值
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim()); // 存入结果对象
}
});
return res // 返回解析后的样式对象
});
// 合并静态样式和动态样式数据
function normalizeStyleData (data) {
var style = normalizeStyleBinding(data.style); // 标准化动态样式
return data.staticStyle // 合并静态样式(编译时处理)
? extend(data.staticStyle, style)
: style
}
// 标准化样式绑定值(支持数组/字符串/对象格式)
function normalizeStyleBinding (bindingStyle) {
if (Array.isArray(bindingStyle)) { // 数组转为对象合并
return toObject(bindingStyle)
}
if (typeof bindingStyle === 'string') { // 字符串解析为对象
return parseStyleText(bindingStyle)
}
return bindingStyle // 直接返回对象
}
// 获取层级样式(父组件样式覆盖子组件)
function getStyle (vnode, checkChild) {
var res = {}; // 结果对象
var styleData; // 临时存储样式数据
if (checkChild) { // 检查子组件链
var childNode = vnode;
while (childNode.componentInstance) { // 遍历子组件实例
childNode = childNode.componentInstance._vnode; // 获取子组件虚拟节点
if (childNode && childNode.data && (styleData = normalizeStyleData(childNode.data))) {
extend(res, styleData); // 合并子组件样式
}
}
}
if ((styleData = normalizeStyleData(vnode.data))) { // 当前节点样式
extend(res, styleData);
}
var parentNode = vnode;
while ((parentNode = parentNode.parent)) { // 遍历父节点链
if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) {
extend(res, styleData); // 合并父组件样式
}
}
return res // 返回最终合并结果
}
// 匹配CSS自定义属性--开头)
var cssVarRE = /^--/;
// 匹配!important结尾
var importantRE = /\s*!important$/;
// 设置元素样式属性(处理多种情况)
var setProp = function (el, name, val) {
if (cssVarRE.test(name)) { // CSS变量处理
el.style.setProperty(name, val);
} else if (importantRE.test(val)) { // 含!important处理
el.style.setProperty(hyphenate(name), val.replace(importantRE, ''), 'important');
} else { // 常规属性处理
var normalizedName = normalize(name); // 标准化属性名
if (Array.isArray(val)) { // 数组值处理(自动前缀备选)
for (var i = 0, len = val.length; i < len; i++) { // 遍历数组设置属性
el.style[normalizedName] = val[i];
}
} else { // 单值处理
el.style[normalizedName] = val;
}
}
};
// 浏览器厂商前缀列表
var vendorNames = ['Webkit', 'Moz', 'ms'];
// 缓存空样式对象用于属性检测
var emptyStyle;
// 标准化样式属性名(自动添加前缀)
var normalize = cached(function (prop) {
emptyStyle = emptyStyle || document.createElement('div').style; // 创建临时元素
prop = camelize(prop); // 驼峰化属性名
if (prop !== 'filter' && (prop in emptyStyle)) { // 原生支持检测
return prop
}
var capName = prop.charAt(0).toUpperCase() + prop.slice(1); // 首字母大写
for (var i = 0; i < vendorNames.length; i++) { // 遍历前缀列表
var name = vendorNames[i] + capName; // 生成带前缀属性名
if (name in emptyStyle)) { // 检测是否支持
return name // 返回带前缀属性名
}
}
});
// 样式更新函数(对比新旧样式差异)
function updateStyle (oldVnode, vnode) {
var data = vnode.data; // 新节点数据
var oldData = oldVnode.data; // 旧节点数据
// 无样式变化直接返回
if (isUndef(data.staticStyle) && isUndef(data.style) &&
isUndef(oldData.staticStyle) && isUndef(oldData.style)
) {
return
}
var cur, name; // 临时变量
var el = vnode.elm; // 实际DOM元素
var oldStaticStyle = oldData.staticStyle; // 旧静态样式
var oldStyleBinding = oldData.normalizedStyle || oldData.style || {}; // 旧动态样式
var oldStyle = oldStaticStyle || oldStyleBinding; // 合并旧样式
var style = normalizeStyleBinding(vnode.data.style) || {}; // 标准化新样式
// 存储标准化后的样式用于下次对比
vnode.data.normalizedStyle = isDef(style.__ob__)
? extend({}, style) // 响应式数据需要克隆
: style;
var newStyle = getStyle(vnode, true); // 获取层级合并后的新样式
// 移除旧样式中不存在于新样式的属性
for (name in oldStyle) {
if (isUndef(newStyle[name])) {
setProp(el, name, '');
}
}
// 设置/更新变化的样式属性
for (name in newStyle) {
cur = newStyle[name];
if (cur !== oldStyle[name]) { // 值发生变化时更新
setProp(el, name, cur == null ? '' : cur); // null转为空字符串
}
}
}
// 导出样式模块(创建和更新时调用)
var style = {
create: updateStyle,
update: updateStyle
};
// 匹配空白字符的正则
var whitespaceRE = /\s+/;
// 添加类名兼容SVG元素
function addClass (el, cls) {
if (!cls || !(cls = cls.trim())) { // 空值处理
return
}
if (el.classList) { // 支持classList
if (cls.indexOf(' ') > -1) { // 多类名分割处理
cls.split(whitespaceRE).forEach(function (c) { return el.classList.add(c); });
} else {
el.classList.add(cls);
}
} else { // 兼容旧浏览器
var cur = " " + (el.getAttribute('class') || '') + " "; // 当前类名字符串
if (cur.indexOf(' ' + cls + ' ') < 0) { // 不存在时添加
el.setAttribute('class', (cur + cls).trim());
}
}
}
// 移除类名兼容SVG元素
function removeClass (el, cls) {
if (!cls || !(cls = cls.trim())) { // 空值处理
return
}
if (el.classList) { // 支持classList
if (cls.indexOf(' ') > -1) { // 多类名分割处理
cls.split(whitespaceRE).forEach(function (c) { return el.classList.remove(c); });
} else {
el.classList.remove(cls);
}
if (!el.classList.length) { // 无类名时移除属性
el.removeAttribute('class');
}
} else { // 兼容旧浏览器
var cur = " " + (el.getAttribute('class') || '') + " ";
var tar = ' ' + cls + ' ';
while (cur.indexOf(tar) >= 0) { // 循环替换目标类名
cur = cur.replace(tar, ' ');
}
cur = cur.trim(); // 清理空白
if (cur) {
el.setAttribute('class', cur);
} else {
el.removeAttribute('class');
}
}
}
// 解析过渡配置并标准化格式
function resolveTransition (def$$1) {
if (!def$$1) { // 无配置直接返回
return
}
/* istanbul ignore else */
if (typeof def$$1 === 'object') { // 对象类型配置
var res = {};
if (def$$1.css !== false) { // 允许CSS过渡
extend(res, autoCssTransition(def$$1.name || 'v')); // 合并自动生成的CSS类
}
extend(res, def$$1); // 合并用户自定义配置
return res
} else if (typeof def$$1 === 'string') { // 字符串类型配置
return autoCssTransition(def$$1) // 生成对应CSS类配置
}
}
// 缓存自动生成的CSS过渡类配置
var autoCssTransition = cached(function (name) {
return {
enterClass: (name + "-enter"), // 进入开始类
enterToClass: (name + "-enter-to"), // 进入结束类
enterActiveClass: (name + "-enter-active"), // 进入激活类
leaveClass: (name + "-leave"), // 离开开始类
leaveToClass: (name + "-leave-to"), // 离开结束类
leaveActiveClass: (name + "-leave-active") // 离开激活类
}
});
// 浏览器环境检测排除IE9
var hasTransition = inBrowser && !isIE9;
var TRANSITION = 'transition'; // CSS过渡类型
var ANIMATION = 'animation'; // CSS动画类型
// 过渡属性/事件检测
var transitionProp = 'transition'; // 过渡属性名
var transitionEndEvent = 'transitionend'; // 过渡结束事件
var animationProp = 'animation'; // 动画属性名
var animationEndEvent = 'animationend'; // 动画结束事件
if (hasTransition) { // 处理浏览器前缀
/* istanbul ignore if */
if (window.ontransitionend === undefined && // 检测Webkit前缀
window.onwebkittransitionend !== undefined
) {
transitionProp = 'WebkitTransition'; // Webkit过渡属性
transitionEndEvent = 'webkitTransitionEnd'; // Webkit过渡结束事件
}
if (window.onanimationend === undefined && // 检测Webkit动画
window.onwebkitanimationend !== undefined
) {
animationProp = 'WebkitAnimation'; // Webkit动画属性
animationEndEvent = 'webkitAnimationEnd'; // Webkit动画结束事件
}
}
// 跨浏览器requestAnimationFrame实现
var raf = inBrowser
? window.requestAnimationFrame // 原生支持
? window.requestAnimationFrame.bind(window)
: setTimeout // 降级方案
: /* istanbul ignore next */ function (fn) { return fn(); }; // 非浏览器环境直接执行
// 下一帧执行函数双raf保证在样式变更后执行
function nextFrame (fn) {
raf(function () {
raf(fn);
});
}
// 添加过渡类并记录
function addTransitionClass (el, cls) {
var transitionClasses = el._transitionClasses || (el._transitionClasses = []); // 初始化类存储
if (transitionClasses.indexOf(cls) < 0) { // 避免重复添加
transitionClasses.push(cls); // 记录类名
addClass(el, cls); // 添加类到元素
}
}
// 移除过渡类并清理记录
function removeTransitionClass (el, cls) {
if (el._transitionClasses) { // 存在记录时
remove(el._transitionClasses, cls); // 从记录中移除
}
removeClass(el, cls); // 从元素移除类
}
// 等待过渡结束执行回调
function whenTransitionEnds (
el,
expectedType,
cb
) {
var ref = getTransitionInfo(el, expectedType); // 获取过渡信息
var type = ref.type; // 过渡类型
var timeout = ref.timeout; // 超时时间
var propCount = ref.propCount; // 属性数量
if (!type) { return cb() } // 无过渡类型立即回调
var event = type === TRANSITION ? transitionEndEvent : animationEndEvent; // 确定事件类型
var ended = 0; // 结束计数器
var end = function () { // 结束处理函数
el.removeEventListener(event, onEnd); // 移除监听
cb(); // 执行回调
};
var onEnd = function (e) { // 事件回调
if (e.target === el) { // 确认目标元素
if (++ended >= propCount) { // 所有属性过渡完成
end();
}
}
};
setTimeout(function () { // 超时兜底
if (ended < propCount) {
end();
}
}, timeout + 1); // 增加1ms防止边界情况
el.addEventListener(event, onEnd); // 监听结束事件
}
// 匹配transform属性的正则
var transformRE = /\b(transform|all)(,|$)/;
// 获取元素过渡信息
function getTransitionInfo (el, expectedType) {
var styles = window.getComputedStyle(el); // 获取计算样式
var transitionDelays = (styles[transitionProp + 'Delay'] || '').split(', '); // 过渡延迟
var transitionDurations = (styles[transitionProp + 'Duration'] || '').split(', '); // 过渡持续时间
var transitionTimeout = getTimeout(transitionDelays, transitionDurations); // 计算总时间
var animationDelays = (styles[animationProp + 'Delay'] || '').split(', '); // 动画延迟
var animationDurations = (styles[animationProp + 'Duration'] || '').split(', '); // 动画持续时间
var animationTimeout = getTimeout(animationDelays, animationDurations); // 计算总时间
var type; // 过渡类型
var timeout = 0; // 总超时
var propCount = 0; // 属性数量
/* istanbul ignore if */
if (expectedType === TRANSITION) { // 预期为过渡类型
if (transitionTimeout > 0) {
type = TRANSITION;
timeout = transitionTimeout;
propCount = transitionDurations.length;
}
} else if (expectedType === ANIMATION) { // 预期为动画类型
if (animationTimeout > 0) {
type = ANIMATION;
timeout = animationTimeout;
propCount = animationDurations.length;
}
} else { // 自动判断类型
timeout = Math.max(transitionTimeout, animationTimeout);
type = timeout > 0
? transitionTimeout > animationTimeout
? TRANSITION
: ANIMATION
: null;
propCount = type
? type === TRANSITION
? transitionDurations.length
: animationDurations.length
: 0;
}
var hasTransform = // 检测是否包含transform属性
type === TRANSITION &&
transformRE.test(styles[transitionProp + 'Property']);
return { // 返回过渡信息对象
type: type,
timeout: timeout,
propCount: propCount,
hasTransform: hasTransform
}
}
// 计算最大超时时间
function getTimeout (delays, durations) {
/* istanbul ignore next */ // 填充延迟数组保证长度匹配
while (delays.length < durations.length) {
delays = delays.concat(delays);
}
return Math.max.apply(null, durations.map(function (d, i) { // 计算每个属性的总时间
return toMs(d) + toMs(delays[i])
}))
}
// 转换时间为毫秒(处理逗号小数格式)
function toMs (s) {
return Number(s.slice(0, -1).replace(',', '.')) * 1000 // 替换逗号为点
}
/* 进入过渡处理 */
function enter (vnode, toggleDisplay) {
var el = vnode.elm; // 获取DOM元素
// 立即调用旧的离开回调
if (isDef(el._leaveCb)) {
el._leaveCb.cancelled = true; // 标记取消
el._leaveCb(); // 执行回调
}
var data = resolveTransition(vnode.data.transition); // 解析过渡配置
if (isUndef(data)) { // 无有效配置直接返回
return
}
/* istanbul ignore if */
if (isDef(el._enterCb) || el.nodeType !== 1) { // 已存在进入回调或非元素节点
return
}
// 解构过渡配置项
var css = data.css;
var type = data.type;
var enterClass = data.enterClass;
var enterToClass = data.enterToClass;
var enterActiveClass = data.enterActiveClass;
var appearClass = data.appearClass;
var appearToClass = data.appearToClass;
var appearActiveClass = data.appearActiveClass;
var beforeEnter = data.beforeEnter;
var enter = data.enter;
var afterEnter = data.afterEnter;
var enterCancelled = data.enterCancelled;
var beforeAppear = data.beforeAppear;
var appear = data.appear;
var afterAppear = data.afterAppear;
var appearCancelled = data.appearCancelled;
var duration = data.duration;
// 获取上下文实例
var context = activeInstance;
var transitionNode = activeInstance.$vnode;
while (transitionNode && transitionNode.parent) { // 查找最近的transition父组件
context = transitionNode.context;
transitionNode = transitionNode.parent;
}
var isAppear = !context._isMounted || !vnode.isRootInsert; // 判断是否初次渲染
if (isAppear && !appear && appear !== '') { // 初次渲染但未配置appear
return
}
// 确定使用的类名
var startClass = isAppear && appearClass
? appearClass
: enterClass;
var activeClass = isAppear && appearActiveClass
? appearActiveClass
: enterActiveClass;
var toClass = isAppear && appearToClass
? appearToClass
: enterToClass;
// 确定使用的钩子函数
var beforeEnterHook = isAppear
? (beforeAppear || beforeEnter)
: beforeEnter;
var enterHook = isAppear
? (typeof appear === 'function' ? appear : enter)
: enter;
var afterEnterHook = isAppear
? (afterAppear || afterEnter)
: afterEnter;
var enterCancelledHook = isAppear
? (appearCancelled || enterCancelled)
: enterCancelled;
// 处理显式指定的持续时间
var explicitEnterDuration = toNumber(
isObject(duration)
? duration.enter
: duration
);
if (explicitEnterDuration != null) { // 验证持续时间有效性
checkDuration(explicitEnterDuration, 'enter', vnode);
}
var expectsCSS = css !== false && !isIE9; // 是否期望CSS过渡
var userWantsControl = getHookArgumentsLength(enterHook); // 用户是否控制过渡
var cb = el._enterCb = once(function () { // 单次执行回调
if (expectsCSS) { // 清理CSS类
removeTransitionClass(el, toClass);
removeTransitionClass(el, activeClass);
}
if (cb.cancelled) { // 过渡取消处理
if (expectsCSS) {
removeTransitionClass(el, startClass);
}
enterCancelledHook && enterCancelledHook(el);
} else { // 正常结束处理
afterEnterHook && afterEnterHook(el);
}
el._enterCb = null; // 清理回调引用
});
if (!vnode.data.show) { // 非v-show控制的元素
// 注入insert钩子处理待定离开元素
mergeVNodeHook(vnode, 'insert', function () {
var parent = el.parentNode;
var pendingNode = parent && parent._pending && parent._pending[vnode.key];
if (pendingNode && // 清理旧节点的离开回调
pendingNode.tag === vnode.tag &&
pendingNode.elm._leaveCb
) {
pendingNode.elm._leaveCb();
}
enterHook && enterHook(el, cb); // 执行进入钩子
});
}
// 开始进入过渡
beforeEnterHook && beforeEnterHook(el); // 执行前置钩子
if (expectsCSS) { // CSS过渡处理
addTransitionClass(el, startClass); // 添加起始类
addTransitionClass(el, activeClass); // 添加激活类
nextFrame(function () { // 下一帧处理
removeTransitionClass(el, startClass); // 移除起始类
if (!cb.cancelled) {
addTransitionClass(el, toClass); // 添加目标类
if (!userWantsControl) { // 用户未自定义控制
if (isValidDuration(explicitEnterDuration)) { // 有效持续时间
setTimeout(cb, explicitEnterDuration); // 定时器结束
} else {
whenTransitionEnds(el, type, cb); // 监听过渡结束
}
}
}
});
}
if (vnode.data.show) { // v-show控制的元素
toggleDisplay && toggleDisplay(); // 切换显示状态
enterHook && enterHook(el, cb); // 执行进入钩子
}
if (!expectsCSS && !userWantsControl) { // 无CSS过渡且用户未控制
cb(); // 立即回调
}
}
// 处理元素离开过渡
function leave (vnode, rm) {
var el = vnode.elm; // 获取DOM元素
// 立即调用可能的进入回调
if (isDef(el._enterCb)) {
el._enterCb.cancelled = true; // 标记进入回调已取消
el._enterCb(); // 执行进入回调清理
}
var data = resolveTransition(vnode.data.transition); // 解析过渡配置
if (isUndef(data) || el.nodeType !== 1) { // 无效配置或非元素节点
return rm() // 直接执行移除回调
}
/* istanbul ignore if */
if (isDef(el._leaveCb)) { // 已存在离开回调
return // 防止重复执行
}
// 解构过渡配置项
var css = data.css;
var type = data.type;
var leaveClass = data.leaveClass;
var leaveToClass = data.leaveToClass;
var leaveActiveClass = data.leaveActiveClass;
var beforeLeave = data.beforeLeave;
var leave = data.leave;
var afterLeave = data.afterLeave;
var leaveCancelled = data.leaveCancelled;
var delayLeave = data.delayLeave; // 延迟离开函数
var duration = data.duration;
var expectsCSS = css !== false && !isIE9; // 是否使用CSS过渡
var userWantsControl = getHookArgumentsLength(leave); // 用户是否控制离开过程
// 处理显式离开持续时间
var explicitLeaveDuration = toNumber(
isObject(duration)
? duration.leave
: duration
);
if (isDef(explicitLeaveDuration)) { // 验证持续时间有效性
checkDuration(explicitLeaveDuration, 'leave', vnode);
}
// 创建一次性离开回调
var cb = el._leaveCb = once(function () {
if (el.parentNode && el.parentNode._pending) { // 清理父节点待处理记录
el.parentNode._pending[vnode.key] = null;
}
if (expectsCSS) { // 清理CSS类
removeTransitionClass(el, leaveToClass);
removeTransitionClass(el, leaveActiveClass);
}
if (cb.cancelled) { // 过渡取消处理
if (expectsCSS) {
removeTransitionClass(el, leaveClass);
}
leaveCancelled && leaveCancelled(el); // 调用取消钩子
} else { // 正常结束
rm(); // 执行移除回调
afterLeave && afterLeave(el); // 调用结束钩子
}
el._leaveCb = null; // 清除回调引用
});
if (delayLeave) { // 存在延迟离开函数
delayLeave(performLeave); // 延迟执行离开
} else {
performLeave(); // 立即执行离开
}
// 实际执行离开动画的函数
function performLeave () {
if (cb.cancelled) { // 已取消则返回
return
}
// 记录待移除元素
if (!vnode.data.show && el.parentNode) {
(el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode;
}
beforeLeave && beforeLeave(el); // 执行前置钩子
if (expectsCSS) { // CSS过渡处理
addTransitionClass(el, leaveClass); // 添加离开起始类
addTransitionClass(el, leaveActiveClass); // 添加离开激活类
nextFrame(function () { // 下一帧处理
removeTransitionClass(el, leaveClass); // 移除起始类
if (!cb.cancelled) {
addTransitionClass(el, leaveToClass); // 添加离开目标类
if (!userWantsControl) { // 用户未自定义控制
if (isValidDuration(explicitLeaveDuration)) { // 有效持续时间
setTimeout(cb, explicitLeaveDuration); // 定时触发回调
} else {
whenTransitionEnds(el, type, cb); // 监听过渡结束
}
}
}
});
}
leave && leave(el, cb); // 执行用户提供的离开钩子
if (!expectsCSS && !userWantsControl) { // 无CSS且用户未控制
cb(); // 直接执行回调
}
}
}
// 开发环境校验过渡持续时间有效性
function checkDuration (val, name, vnode) {
if (typeof val !== 'number') { // 非数字类型警告
warn(
"<transition> 显式 " + name + " 持续时间无效,应为数字 - 得到 " + (JSON.stringify(val)),
vnode.context
);
} else if (isNaN(val)) { // NaN值警告
warn(
"<transition> 显式 " + name + " 持续时间为 NaN可能表达式错误",
vnode.context
);
}
}
// 验证是否为有效持续时间
function isValidDuration (val) {
return typeof val === 'number' && !isNaN(val) // 数字且非NaN
}
/**
* 标准化过渡钩子的参数长度(处理合并钩子/组件方法/普通函数)
*/
function getHookArgumentsLength (fn) {
if (isUndef(fn)) return false // 无钩子直接返回
var invokerFns = fn.fns; // 处理合并的钩子数组
if (isDef(invokerFns)) {
return getHookArgumentsLength( // 递归检测第一个真实钩子
Array.isArray(invokerFns) ? invokerFns[0] : invokerFns
)
} else {
return (fn._length || fn.length) > 1 // 检测参数长度是否大于1
}
}
// 进入过渡的封装函数用于create/activate生命周期
function _enter (_, vnode) {
if (vnode.data.show !== true) { // 非v-show控制的元素
enter(vnode); // 执行进入过渡
}
}
// 浏览器环境的过渡处理模块
var transition = inBrowser ? {
create: _enter, // 元素创建时触发进入
activate: _enter, // 组件激活时触发进入
remove: function remove$$1 (vnode, rm) { // 元素移除时
if (vnode.data.show !== true) { // 非v-show元素
leave(vnode, rm); // 执行离开过渡
} else {
rm(); // 直接移除
}
}
} : {}; // 非浏览器环境空对象
// 平台相关模块列表(按顺序应用)
var platformModules = [
attrs, // 属性处理模块
klass, // class类名处理模块
events, // 事件处理模块
domProps, // DOM属性处理模块
style, // 样式处理模块
transition // 过渡处理模块
];
// 合并基础模块和平台模块
var modules = platformModules.concat(baseModules);
// 创建虚拟DOM patch函数
var patch = createPatchFunction({
nodeOps: nodeOps, // DOM节点操作方法
modules: modules // 使用的模块列表
});
/* IE9兼容处理 */
if (isIE9) {
// 监听selectionchange事件解决oninput不触发的问题
document.addEventListener('selectionchange', function () {
var el = document.activeElement; // 当前焦点元素
if (el && el.vmodel) { // 带有vmodel标记的元素
trigger(el, 'input'); // 手动触发input事件
}
});
}
// v-model指令实现
var directive = {
inserted: function inserted (el, binding, vnode, oldVnode) {
if (vnode.tag === 'select') { // select元素处理
if (oldVnode.elm && !oldVnode.elm._vOptions) { // 旧节点无选项缓存
mergeVNodeHook(vnode, 'postpatch', function () { // 合并postpatch钩子
directive.componentUpdated(el, binding, vnode); // 更新后设置选中
});
} else {
setSelected(el, binding, vnode.context); // 直接设置选中
}
el._vOptions = [].map.call(el.options, getValue); // 缓存选项值
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) { // 文本输入类元素
el._vModifiers = binding.modifiers; // 存储修饰符
if (!binding.modifiers.lazy) { // 非lazy模式
el.addEventListener('compositionstart', onCompositionStart); // 输入法开始
el.addEventListener('compositionend', onCompositionEnd); // 输入法结束
el.addEventListener('change', onCompositionEnd); // 处理Safari兼容
if (isIE9) el.vmodel = true; // IE9特殊标记
}
}
},
componentUpdated: function componentUpdated (el, binding, vnode) {
if (vnode.tag === 'select') { // select元素更新后
setSelected(el, binding, vnode.context); // 设置选中状态
var prevOptions = el._vOptions; // 旧选项值
var curOptions = el._vOptions = [].map.call(el.options, getValue); // 新选项值
if (curOptions.some(function (o, i) { return !looseEqual(o, prevOptions[i]); })) { // 选项变化检测
var needReset = el.multiple // 多选模式检测
? binding.value.some(hasNoMatchingOption)
: binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions);
if (needReset) trigger(el, 'change'); // 触发change事件
}
}
}
};
// 设置select元素选中状态
function setSelected (el, binding, vm) {
actuallySetSelected(el, binding, vm); // 实际设置
/* IE/Edge兼容处理 */
if (isIE || isEdge) setTimeout(() => actuallySetSelected(el, binding, vm), 0);
}
// 实际设置选中逻辑
function actuallySetSelected (el, binding, vm) {
var value = binding.value; // 当前绑定值
var isMultiple = el.multiple; // 是否多选
if (isMultiple && !Array.isArray(value)) { // 多选但值非数组
warn(
`<select multiple v-model="${binding.expression}"> 需要数组类型值`,
vm
);
return
}
var selected, option;
for (var i = 0; i < el.options.length; i++) { // 遍历选项
option = el.options[i];
if (isMultiple) { // 多选处理
selected = looseIndexOf(value, getValue(option)) > -1; // 是否在值数组中
if (option.selected !== selected) option.selected = selected; // 更新选中状态
} else { // 单选处理
if (looseEqual(getValue(option), value)) { // 值匹配
if (el.selectedIndex !== i) el.selectedIndex = i; // 设置选中索引
return
}
}
}
if (!isMultiple) el.selectedIndex = -1; // 未匹配时取消选中
}
// 检测值是否无匹配选项
function hasNoMatchingOption (value, options) {
return options.every(o => !looseEqual(o, value)) // 全部不匹配返回true
}
// 获取选项值优先使用_value
function getValue (option) {
return '_value' in option ? option._value : option.value
}
// 输入法开始组合输入
function onCompositionStart (e) {
e.target.composing = true; // 标记组合输入状态
}
// 输入法结束组合输入
function onCompositionEnd (e) {
if (!e.target.composing) return // 非组合输入直接返回
e.target.composing = false; // 清除标记
trigger(e.target, 'input'); // 触发input事件
}
// 触发原生事件
function trigger (el, type) {
var e = document.createEvent('HTMLEvents'); // 创建事件
e.initEvent(type, true, true); // 初始化
el.dispatchEvent(e); // 派发事件
}
// 递归查找真实过渡节点(跳过无过渡的组件)
function locateNode (vnode) {
return (vnode.componentInstance && (!vnode.data || !vnode.data.transition))
? locateNode(vnode.componentInstance._vnode) // 递归组件实例
: vnode // 返回找到的节点
}
// v-show指令实现
var show = {
bind: function (el, { value }, vnode) { // 初始绑定
vnode = locateNode(vnode); // 查找过渡节点
var transition = vnode.data && vnode.data.transition; // 过渡配置
el.__vOriginalDisplay = el.style.display === 'none' ? '' : el.style.display; // 保存原始display
if (value && transition) { // 显示且有过渡
vnode.data.show = true; // 标记显示状态
enter(vnode, () => el.style.display = el.__vOriginalDisplay); // 执行进入过渡
} else {
el.style.display = value ? el.__vOriginalDisplay : 'none'; // 直接设置显示
}
},
update: function (el, { value, oldValue }, vnode) { // 更新
if (!!value === !!oldValue) return // 值无变化跳过
vnode = locateNode(vnode);
var transition = vnode.data && vnode.data.transition;
if (transition) {
vnode.data.show = true; // 更新显示标记
value
? enter(vnode, () => el.style.display = el.__vOriginalDisplay) // 进入过渡
: leave(vnode, () => el.style.display = 'none') // 离开过渡
} else {
el.style.display = value ? el.__vOriginalDisplay : 'none'; // 直接切换
}
},
unbind: function (el, binding, vnode, oldVnode, isDestroy) { // 解绑
if (!isDestroy) { // 非销毁阶段
el.style.display = el.__vOriginalDisplay; // 恢复原始display
}
}
};
// 导出平台指令v-model和v-show
var platformDirectives = {
model: directive,
show: show
};
// 定义过渡组件支持的属性列表
var transitionProps = {
name: String, // 过渡名称
appear: Boolean, // 是否在初始渲染时使用过渡
css: Boolean, // 是否使用 CSS 过渡类
mode: String, // 过渡模式in-out/out-in
type: String, // 过渡类型transition/animation
enterClass: String, // 进入开始类名
leaveClass: String, // 离开开始类名
enterToClass: String, // 进入结束类名
leaveToClass: String, // 离开结束类名
enterActiveClass: String, // 进入激活类名
leaveActiveClass: String, // 离开激活类名
appearClass: String, // 初始渲染开始类名
appearActiveClass: String, // 初始渲染激活类名
appearToClass: String, // 初始渲染结束类名
duration: [Number, String, Object] // 过渡持续时间
};
// 递归获取真实子组件(跳过抽象组件)
function getRealChild (vnode) {
var compOptions = vnode && vnode.componentOptions;
if (compOptions && compOptions.Ctor.options.abstract) { // 如果是抽象组件
return getRealChild(getFirstComponentChild(compOptions.children)) // 递归查找
} else {
return vnode // 返回真实子节点
}
}
// 从组件实例提取过渡数据
function extractTransitionData (comp) {
var data = {};
var options = comp.$options;
// 复制 props 数据
for (var key in options.propsData) {
data[key] = comp[key];
}
// 复制父级监听器
var listeners = options._parentListeners;
for (var key$1 in listeners) {
data[camelize(key$1)] = listeners[key$1]; // 驼峰化事件名
}
return data
}
// 生成占位符元素(处理 keep-alive
function placeholder (h, rawChild) {
if (/\d-keep-alive$/.test(rawChild.tag)) { // 匹配 keep-alive 标签
return h('keep-alive', { // 创建新的 keep-alive 元素
props: rawChild.componentOptions.propsData // 继承原始属性
})
}
}
// 检查父级是否包含过渡
function hasParentTransition (vnode) {
while ((vnode = vnode.parent)) { // 遍历父级节点
if (vnode.data.transition) {
return true
}
}
}
// 判断是否是相同子节点
function isSameChild (child, oldChild) {
return oldChild.key === child.key && oldChild.tag === child.tag // 比较 key 和 tag
}
// 过滤非文本节点的辅助函数
var isNotTextNode = function (c) { return c.tag || isAsyncPlaceholder(c); };
// 检测是否是 v-show 指令
var isVShowDirective = function (d) { return d.name === 'show'; };
// 过渡组件定义
var Transition = {
name: 'transition',
props: transitionProps,
abstract: true, // 标记为抽象组件
render: function render (h) {
var this$1 = this;
var children = this.$slots.default; // 获取默认插槽内容
if (!children) {
return
}
// 过滤掉文本节点(可能存在空白)
children = children.filter(isNotTextNode);
/* istanbul ignore if */
if (!children.length) { // 没有有效子节点时返回
return
}
// 多个子节点警告
if (children.length > 1) {
warn(
'<transition> 只能用于单个元素,列表请用 <transition-group>',
this.$parent
);
}
var mode = this.mode;
// 验证过渡模式有效性
if (mode && mode !== 'in-out' && mode !== 'out-in') {
warn(
'无效的过渡模式: ' + mode,
this.$parent
);
}
var rawChild = children[0]; // 获取第一个子节点
// 如果父级有过渡则直接返回
if (hasParentTransition(this.$vnode)) {
return rawChild
}
// 获取真实子节点(跳过抽象组件)
var child = getRealChild(rawChild);
/* istanbul ignore if */
if (!child) { // 没有真实子节点时返回原始节点
return rawChild
}
if (this._leaving) { // 处于离开状态时返回占位符
return placeholder(h, rawChild)
}
// 生成唯一 key 用于过渡管理
var id = "__transition-" + (this._uid) + "-";
child.key = child.key == null // 处理不同情况下的 key 生成
? child.isComment
? id + 'comment'
: id + child.tag
: isPrimitive(child.key)
? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
: child.key;
// 注入过渡数据到子节点
var data = (child.data || (child.data = {})).transition = extractTransitionData(this);
var oldRawChild = this._vnode; // 获取旧节点
var oldChild = getRealChild(oldRawChild);
// 处理 v-show 指令
if (child.data.directives && child.data.directives.some(isVShowDirective)) {
child.data.show = true; // 标记需要显示
}
// 新旧子节点不同时的处理逻辑
if (
oldChild &&
oldChild.data &&
!isSameChild(child, oldChild) &&
!isAsyncPlaceholder(oldChild) &&
// 处理组件根节点是注释的情况
!(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)
) {
var oldData = oldChild.data.transition = extend({}, data); // 复制过渡数据
// 处理不同过渡模式
if (mode === 'out-in') { // 先出后进模式
this._leaving = true; // 标记离开状态
mergeVNodeHook(oldData, 'afterLeave', function () { // 注册离开完成回调
this$1._leaving = false;
this$1.$forceUpdate(); // 强制更新
});
return placeholder(h, rawChild) // 返回占位符
} else if (mode === 'in-out') { // 先进后出模式
if (isAsyncPlaceholder(child)) { // 异步组件占位符
return oldRawChild
}
var delayedLeave; // 延迟离开函数
var performLeave = function () { delayedLeave(); }; // 执行离开函数
mergeVNodeHook(data, 'afterEnter', performLeave); // 进入后触发离开
mergeVNodeHook(data, 'enterCancelled', performLeave); // 取消进入时触发离开
mergeVNodeHook(oldData, 'delayLeave', function (leave) { delayedLeave = leave; }); // 保存延迟离开方法
}
}
return rawChild // 返回原始子节点
}
};
/* 过渡组相关代码 */
// 扩展过渡属性(删除 mode 属性)
var props = extend({
tag: String, // 外层包裹标签
moveClass: String // 移动过渡类名
}, transitionProps);
delete props.mode; // 删除 mode 属性
// 过渡组组件定义
var TransitionGroup = {
props: props,
beforeMount: function beforeMount () {
var this$1 = this;
var update = this._update; // 保存原始更新方法
// 重写 _update 方法处理过渡组更新
this._update = function (vnode, hydrating) {
var restoreActiveInstance = setActiveInstance(this$1); // 保存当前激活实例
// 强制移除需要删除的节点
this$1.__patch__(
this$1._vnode,
this$1.kept,
false, // 非服务端渲染
true // 仅移除模式
);
this$1._vnode = this$1.kept; // 更新当前 vnode
restoreActiveInstance(); // 恢复激活实例
update.call(this$1, vnode, hydrating); // 调用原始更新方法
};
},
render: function render (h) {
var tag = this.tag || this.$vnode.data.tag || 'span'; // 获取包裹标签
var map = Object.create(null); // 创建 key 映射表
var prevChildren = this.prevChildren = this.children; // 保存旧子节点
var rawChildren = this.$slots.default || []; // 获取原始子节点
var children = this.children = []; // 初始化当前子节点列表
var transitionData = extractTransitionData(this); // 提取过渡数据
// 处理原始子节点
for (var i = 0; i < rawChildren.length; i++) {
var c = rawChildren[i];
if (c.tag) { // 过滤有效元素
if (c.key != null && String(c.key).indexOf('__vlist') !== 0) { // 验证有效 key
children.push(c);
map[c.key] = c; // 记录 key 映射
;(c.data || (c.data = {})).transition = transitionData; // 注入过渡数据
} else { // 无效 key 警告
var opts = c.componentOptions;
var name = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag;
warn(("<transition-group> 子元素必须带 key: <" + name + ">"));
}
}
}
// 处理旧子节点
if (prevChildren) {
var kept = []; // 保留的节点
var removed = []; // 移除的节点
for (var i$1 = 0; i$1 < prevChildren.length; i$1++) {
var c$1 = prevChildren[i$1];
c$1.data.transition = transitionData; // 注入过渡数据
c$1.data.pos = c$1.elm.getBoundingClientRect(); // 记录位置信息
if (map[c$1.key]) { // 判断是否保留
kept.push(c$1);
} else {
removed.push(c$1);
}
}
this.kept = h(tag, null, kept); // 创建保留节点的 VNode
this.removed = removed; // 记录被移除的节点
}
return h(tag, null, children) // 渲染当前子节点
},
updated: function updated () {
var children = this.prevChildren; // 获取旧子节点
var moveClass = this.moveClass || ((this.name || 'v') + '-move'); // 移动类名
if (!children.length || !this.hasMove(children[0].elm, moveClass)) { // 无需移动时返回
return
}
// 分阶段处理 DOM 操作防止布局抖动
children.forEach(callPendingCbs); // 处理待定回调
children.forEach(recordPosition); // 记录新位置
children.forEach(applyTranslation); // 应用位置变换
// 强制重排以确保布局正确
this._reflow = document.body.offsetHeight; // 触发浏览器重排
// 为移动元素添加过渡效果
children.forEach(function (c) {
if (c.data.moved) { // 需要移动的元素
var el = c.elm;
var s = el.style;
addTransitionClass(el, moveClass); // 添加移动类
s.transform = s.WebkitTransform = s.transitionDuration = ''; // 重置样式
// 添加过渡结束监听器
el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {
if (e && e.target !== el) { // 过滤无关事件
return
}
if (!e || /transform$/.test(e.propertyName)) { // 检查变换属性
el.removeEventListener(transitionEndEvent, cb); // 移除监听
el._moveCb = null;
removeTransitionClass(el, moveClass); // 移除移动类
}
});
}
});
},
methods: {
// 检测元素是否支持移动过渡
hasMove: function hasMove (el, moveClass) {
/* istanbul ignore if */
if (!hasTransition) { // 浏览器不支持过渡
return false
}
/* istanbul ignore if */
if (this._hasMove) { // 已缓存检测结果
return this._hasMove
}
// 克隆元素进行过渡检测
var clone = el.cloneNode();
if (el._transitionClasses) { // 移除其他过渡类
el._transitionClasses.forEach(function (cls) { removeClass(clone, cls); });
}
addClass(clone, moveClass); // 添加移动类
clone.style.display = 'none'; // 隐藏克隆元素
this.$el.appendChild(clone); // 插入 DOM
var info = getTransitionInfo(clone); // 获取过渡信息
this.$el.removeChild(clone); // 移除克隆元素
return (this._hasMove = info.hasTransform) // 返回是否支持变换
}
}
};
// 执行待定的过渡回调
function callPendingCbs (c) {
/* istanbul ignore if */
if (c.elm._moveCb) { // 移动回调
c.elm._moveCb();
}
/* istanbul ignore if */
if (c.elm._enterCb) { // 进入回调
c.elm._enterCb();
}
}
// 记录元素当前位置
function recordPosition (c) {
c.data.newPos = c.elm.getBoundingClientRect(); // 获取新位置
}
// 应用位置变换
function applyTranslation (c) {
var oldPos = c.data.pos; // 旧位置
var newPos = c.data.newPos; // 新位置
var dx = oldPos.left - newPos.left; // X 轴位移
var dy = oldPos.top - newPos.top; // Y 轴位移
if (dx || dy) { // 需要位移时
c.data.moved = true; // 标记需要移动
var s = c.elm.style;
s.transform = s.WebkitTransform = "translate(" + dx + "px," + dy + "px)"; // 应用变换
s.transitionDuration = '0s'; // 立即生效
}
}
// 注册平台组件
var platformComponents = {
Transition: Transition,
TransitionGroup: TransitionGroup
};
/* 平台相关安装代码 */
// 安装平台特定的配置方法
Vue.config.mustUseProp = mustUseProp; // 必须用 prop 绑定的属性
Vue.config.isReservedTag = isReservedTag; // 保留标签检测
Vue.config.isReservedAttr = isReservedAttr; // 保留属性检测
Vue.config.getTagNamespace = getTagNamespace; // 获取标签命名空间
Vue.config.isUnknownElement = isUnknownElement; // 未知元素检测
// 安装平台指令和组件
extend(Vue.options.directives, platformDirectives); // 合并指令
extend(Vue.options.components, platformComponents); // 合并组件
// 安装 DOM patch 方法(浏览器环境)
Vue.prototype.__patch__ = inBrowser ? patch : noop;
// 公共挂载方法
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined; // 标准化元素查询
return mountComponent(this, el, hydrating) // 执行挂载
};
// 开发工具初始化
/* istanbul ignore next */
if (inBrowser) { // 浏览器环境下
setTimeout(function () { // 延迟执行
if (config.devtools) { // 开发工具启用
if (devtools) { // 已安装开发工具
devtools.emit('init', Vue); // 发送初始化事件
} else { // 未安装提示
console[console.info ? 'info' : 'log'](
'下载 Vue Devtools 扩展以获得更好的开发体验:\n' +
'https://github.com/vuejs/vue-devtools'
);
}
}
// 生产环境提示
if (config.productionTip !== false &&
typeof console !== 'undefined'
) {
console[console.info ? 'info' : 'log'](
"您正在运行开发模式的 Vue\n" +
"部署生产环境时请确保开启生产模式\n" +
"更多提示请访问 https://vuejs.org/guide/deployment.html"
);
}
}, 0);
}
// 匹配双花括号插值表达式的正则(如{{content}}
var defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;
// 匹配需要转义的正则特殊字符的正则
var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g;
// 创建带缓存功能的正则生成器(根据自定义分隔符生成对应正则)
var buildRegex = cached(function (delimiters) {
// 转义起始分隔符中的特殊字符
var open = delimiters[0].replace(regexEscapeRE, '\\$&');
// 转义结束分隔符中的特殊字符
var close = delimiters[1].replace(regexEscapeRE, '\\$&');
// 构造新的动态正则表达式
return new RegExp(open + '((?:.|\\n)+?)' + close, 'g')
});
// 解析模板文本生成表达式和原始标记
function parseText(text, delimiters) {
// 选择使用自定义分隔符正则或默认正则
var tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE;
// 如果没有匹配项则直接返回
if (!tagRE.test(text)) {
return
}
// 存储处理后的表达式片段
var tokens = [];
// 存储原始文本片段
var rawTokens = [];
// 初始化正则匹配位置
var lastIndex = tagRE.lastIndex = 0;
var match, index, tokenValue;
// 循环处理所有匹配项
while ((match = tagRE.exec(text))) {
index = match.index;
// 处理匹配前的普通文本
if (index > lastIndex) {
rawTokens.push(tokenValue = text.slice(lastIndex, index));
tokens.push(JSON.stringify(tokenValue));
}
// 处理插值表达式
var exp = parseFilters(match[1].trim());
tokens.push(("_s(" + exp + ")"));
rawTokens.push({ '@binding': exp });
// 更新最后匹配位置
lastIndex = index + match[0].length;
}
// 处理末尾剩余文本
if (lastIndex < text.length) {
rawTokens.push(tokenValue = text.slice(lastIndex));
tokens.push(JSON.stringify(tokenValue));
}
// 返回拼接后的表达式和原始标记
return {
expression: tokens.join('+'),
tokens: rawTokens
}
}
/* 类名处理模块开始 */
// 转换节点中的class属性
function transformNode(el, options) {
// 获取警告方法
var warn = options.warn || baseWarn;
// 提取并移除静态class属性
var staticClass = getAndRemoveAttr(el, 'class');
// 处理静态class属性
if (staticClass) {
// 检查是否包含插值语法
var res = parseText(staticClass, options.delimiters);
if (res) {
// 发出过时语法警告
warn(
"class=\"" + staticClass + "\": " +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div class="{{ val }}">, use <div :class="val">.',
el.rawAttrsMap['class']
);
}
// 序列化静态class
el.staticClass = JSON.stringify(staticClass);
}
// 获取动态class绑定
var classBinding = getBindingAttr(el, 'class', false /* 不获取静态值 */);
if (classBinding) {
el.classBinding = classBinding;
}
}
// 生成class相关数据字符串
function genData(el) {
var data = '';
if (el.staticClass) {
data += "staticClass:" + (el.staticClass) + ",";
}
if (el.classBinding) {
data += "class:" + (el.classBinding) + ",";
}
return data
}
// 类名处理模块配置
var klass$1 = {
staticKeys: ['staticClass'], // 静态属性键名
transformNode: transformNode, // 节点转换方法
genData: genData // 数据生成方法
};
/* 样式处理模块开始 */
// 转换节点中的style属性
function transformNode$1(el, options) {
var warn = options.warn || baseWarn;
// 提取并移除静态style属性
var staticStyle = getAndRemoveAttr(el, 'style');
if (staticStyle) {
// 忽略测试覆盖的代码块
/* istanbul ignore if */
{
// 检查是否包含插值语法
var res = parseText(staticStyle, options.delimiters);
if (res) {
// 发出过时语法警告
warn(
"style=\"" + staticStyle + "\": " +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div style="{{ val }}">, use <div :style="val">.',
el.rawAttrsMap['style']
);
}
}
// 解析并序列化静态样式
el.staticStyle = JSON.stringify(parseStyleText(staticStyle));
}
// 获取动态style绑定
var styleBinding = getBindingAttr(el, 'style', false /* 不获取静态值 */);
if (styleBinding) {
el.styleBinding = styleBinding;
}
}
// 生成style相关数据字符串
function genData$1(el) {
var data = '';
if (el.staticStyle) {
data += "staticStyle:" + (el.staticStyle) + ",";
}
if (el.styleBinding) {
data += "style:(" + (el.styleBinding) + "),";
}
return data
}
// 样式处理模块配置
var style$1 = {
staticKeys: ['staticStyle'], // 静态属性键名
transformNode: transformNode$1, // 节点转换方法
genData: genData$1 // 数据生成方法
};
/* HTML解码模块开始 */
var decoder; // 用于HTML解码的临时元素
// HTML实体解码工具
var he = {
decode: function decode(html) {
// 惰性创建div元素
decoder = decoder || document.createElement('div');
// 通过innerHTML实现解码
decoder.innerHTML = html;
// 返回解码后的文本内容
return decoder.textContent
}
};
/* 标签类型判断模块开始 */
// 自闭合标签列表(如<img/>
var isUnaryTag = makeMap(
'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
'link,meta,param,source,track,wbr'
);
// 可省略闭合标签列表(如<li>
var canBeLeftOpenTag = makeMap(
'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source'
);
// 非短语内容标签列表(不能包含文本内容)
var isNonPhrasingTag = makeMap(
'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' +
'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' +
'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' +
'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' +
'title,tr,track'
);
/* HTML解析正则模块开始 */
// 属性解析正则(捕获属性名和值)
var attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// 动态参数属性正则(如:[key]
var dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// XML名称正则标签名规范
var ncname = "[a-zA-Z_][\\-\\.0-9_a-zA-Z" + (unicodeRegExp.source) + "]*";
// 限定名正则(带命名空间的标签名)
var qnameCapture = "((?:" + ncname + "\\:)?" + ncname + ")";
// 开始标签开头匹配正则(如<div>
var startTagOpen = new RegExp(("^<" + qnameCapture));
// 开始标签结尾匹配正则(捕获闭合符)
var startTagClose = /^\s*(\/?)>/;
// 结束标签匹配正则(如</div>
var endTag = new RegExp(("^<\\/" + qnameCapture + "[^>]*>"));
// 文档类型声明匹配正则
var doctype = /^<!DOCTYPE [^>]+>/i;
// HTML注释匹配正则兼容性处理
var comment = /^<!\--/;
// 条件注释匹配正则IE特性
var conditionalComment = /^<!\[/;
// 纯文本元素列表script/style/textarea
var isPlainTextElement = makeMap('script,style,textarea', true);
// 正则表达式缓存对象
var reCache = {};
// HTML实体解码映射表
var decodingMap = {
'&lt;': '<', // 小于号
'&gt;': '>', // 大于号
'&quot;': '"', // 双引号
'&amp;': '&', // 与符号
'&#10;': '\n', // 换行符
'&#9;': '\t', // 制表符
'&#39;': "'" // 单引号
};
// 匹配常规HTML实体编码的正则表达式不包含换行相关实体
var encodedAttr = /&(?:lt|gt|quot|amp|#39);/g;
// 匹配包含换行符的HTML实体编码的正则表达式
var encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#39|#10|#9);/g;
// 创建需要忽略首行换行的标签映射pre和textarea
var isIgnoreNewlineTag = makeMap('pre,textarea', true);
// 判断是否需要忽略标签首行换行符的函数
var shouldIgnoreFirstNewline = function (tag, html) { return tag && isIgnoreNewlineTag(tag) && html[0] === '\n'; };
// HTML属性值解码函数
function decodeAttr (value, shouldDecodeNewlines) {
// 根据需求选择匹配规则
var re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr;
// 替换实体编码为对应字符
return value.replace(re, function (match) { return decodingMap[match]; })
}
// HTML解析器核心函数
function parseHTML (html, options) {
var stack = []; // 标签堆栈用于追踪嵌套关系
var expectHTML = options.expectHTML; // 是否期望标准HTML结构
var isUnaryTag$$1 = options.isUnaryTag || no; // 自闭合标签检查函数
var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no; // 可省略闭合标签检查函数
var index = 0; // 当前解析位置索引
var last, lastTag; // 上次处理的HTML内容和标签名
// 主解析循环
while (html) {
last = html;
// 检查是否处于纯文本元素中script/style/textarea
if (!lastTag || !isPlainTextElement(lastTag)) {
// 查找最近标签起始位置
var textEnd = html.indexOf('<');
// 处理以'<'开头的内容(标签或注释)
if (textEnd === 0) {
// 处理HTML注释 <!-- -->
if (comment.test(html)) {
var commentEnd = html.indexOf('-->');
if (commentEnd >= 0) {
// 保留注释内容的回调处理
if (options.shouldKeepComment) {
options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3);
}
// 跳过注释部分
advance(commentEnd + 3);
continue
}
}
// 处理IE条件注释 <![if IE]>
if (conditionalComment.test(html)) {
var conditionalEnd = html.indexOf(']>');
if (conditionalEnd >= 0) {
advance(conditionalEnd + 2);
continue
}
}
// 处理DOCTYPE声明
var doctypeMatch = html.match(doctype);
if (doctypeMatch) {
advance(doctypeMatch[0].length);
continue
}
// 处理结束标签 </div>
var endTagMatch = html.match(endTag);
if (endTagMatch) {
var curIndex = index;
advance(endTagMatch[0].length);
parseEndTag(endTagMatch[1], curIndex, index);
continue
}
// 处理开始标签 <div>
var startTagMatch = parseStartTag();
if (startTagMatch) {
handleStartTag(startTagMatch);
// 处理pre/textarea标签的首行换行
if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
advance(1);
}
continue
}
}
// 处理文本内容
var text = (void 0), rest = (void 0), next = (void 0);
if (textEnd >= 0) {
rest = html.slice(textEnd);
// 扫描文本中的特殊字符
while (
!endTag.test(rest) &&
!startTagOpen.test(rest) &&
!comment.test(rest) &&
!conditionalComment.test(rest)
) {
// 处理文本中的'<'字符
next = rest.indexOf('<', 1);
if (next < 0) { break }
textEnd += next;
rest = html.slice(textEnd);
}
text = html.substring(0, textEnd);
}
// 处理剩余全部文本
if (textEnd < 0) {
text = html;
}
// 推进解析位置并处理文本内容
if (text) {
advance(text.length);
if (options.chars && text) {
options.chars(text, index - text.length, index);
}
}
} else {
// 处理纯文本元素内部内容
var endTagLength = 0;
var stackedTag = lastTag.toLowerCase();
// 创建匹配结束标签的正则
var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'));
// 提取纯文本内容
var rest$1 = html.replace(reStackedTag, function (all, text, endTag) {
endTagLength = endTag.length;
// 清理注释和CDATA
if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
text = text
.replace(/<!\--([\s\\S]*?)-->/g, '$1')
.replace(/<!\[CDATA\[([\s\\S]*?)]]>/g, '$1');
}
// 处理首行换行符
if (shouldIgnoreFirstNewline(stackedTag, text)) {
text = text.slice(1);
}
if (options.chars) {
options.chars(text);
}
return ''
});
// 更新解析位置
index += html.length - rest$1.length;
html = rest$1;
// 处理结束标签
parseEndTag(stackedTag, index - endTagLength, index);
}
// 错误处理:检测是否进入死循环
if (html === last) {
options.chars && options.chars(html);
if (!stack.length && options.warn) {
options.warn(("Mal-formatted tag at end of template: \"" + html + "\""), { start: index + html.length });
}
break
}
}
// 清理未闭合标签
parseEndTag();
// 推进解析位置的工具函数
function advance (n) {
index += n;
html = html.substring(n);
}
// 解析开始标签的函数
function parseStartTag () {
var start = html.match(startTagOpen);
if (start) {
var match = {
tagName: start[1], // 标签名
attrs: [], // 属性列表
start: index // 起始位置
};
advance(start[0].length);
var end, attr;
// 循环收集属性
while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute)) {
attr.start = index;
advance(attr[0].length);
attr.end = index;
match.attrs.push(attr); // 添加属性到列表
}
if (end) {
match.unarySlash = end[1]; // 自闭合标记
advance(end[0].length);
match.end = index;
return match
}
}
}
// 处理开始标签结果的函数
function handleStartTag (match) {
var tagName = match.tagName;
var unarySlash = match.unarySlash;
// 处理HTML预期行为
if (expectHTML) {
// 自动闭合p标签
if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
parseEndTag(lastTag);
}
// 处理可省略闭合标签
if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {
parseEndTag(tagName);
}
}
// 判断是否自闭合标签
var unary = isUnaryTag$$1(tagName) || !!unarySlash;
// 处理标签属性
var l = match.attrs.length;
var attrs = new Array(l);
for (var i = 0; i < l; i++) {
var args = match.attrs[i];
// 获取属性值(支持多种引号格式)
var value = args[3] || args[4] || args[5] || '';
// 判断是否解码换行符特别处理a标签的href
var shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'
? options.shouldDecodeNewlinesForHref
: options.shouldDecodeNewlines;
// 构建属性对象
attrs[i] = {
name: args[1],
value: decodeAttr(value, shouldDecodeNewlines)
};
// 记录源码位置信息
if (options.outputSourceRange) {
attrs[i].start = args.start + args[0].match(/^\s*/).length;
attrs[i].end = args.end;
}
}
// 非自闭合标签入栈
if (!unary) {
stack.push({
tag: tagName,
lowerCasedTag: tagName.toLowerCase(),
attrs: attrs,
start: match.start,
end: match.end
});
lastTag = tagName;
}
// 触发start回调
if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end);
}
}
// 处理结束标签的函数
function parseEndTag (tagName, start, end) {
var pos, lowerCasedTagName;
// 处理参数默认值
if (start == null) { start = index; }
if (end == null) { end = index; }
// 查找匹配的起始标签
if (tagName) {
lowerCasedTagName = tagName.toLowerCase();
// 从栈顶向下查找
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos].lowerCasedTag === lowerCasedTagName) {
break
}
}
} else {
// 未指定标签名时清空堆栈
pos = 0;
}
if (pos >= 0) {
// 闭合所有未匹配标签
for (var i = stack.length - 1; i >= pos; i--) {
// 警告未匹配的标签
if (i > pos || !tagName && options.warn) {
options.warn(
("tag <" + (stack[i].tag) + "> has no matching end tag."),
{ start: stack[i].start, end: stack[i].end }
);
}
// 触发end回调
if (options.end) {
options.end(stack[i].tag, start, end);
}
}
// 更新堆栈
stack.length = pos;
lastTag = pos && stack[pos - 1].tag;
} else if (lowerCasedTagName === 'br') {
// 特殊处理<br>标签
if (options.start) {
options.start(tagName, [], true, start, end);
}
} else if (lowerCasedTagName === 'p') {
// 特殊处理<p>标签
if (options.start) {
options.start(tagName, [], false, start, end);
}
if (options.end) {
options.end(tagName, start, end);
}
}
}
}
// 匹配事件绑定语法(@或v-on:开头)
var onRE = /^@|^v-on:/;
// 匹配所有Vue指令语法v-、@、:、#开头)
var dirRE = /^v-|^@|^:|^#/;
// 解析v-for表达式的正则匹配'item in list'结构)
var forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
// 解析v-for迭代器的正则匹配', index'等参数)
var forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/;
// 去除括号的正则(用于处理表达式)
var stripParensRE = /^\(|\)$/g;
// 匹配动态参数(如:[key]
var dynamicArgRE = /^\[.*\]$/;
// 匹配指令参数部分(:后的内容)
var argRE = /:(.*)$/;
// 匹配绑定语法(:、.、v-bind:开头)
var bindRE = /^:|^\.|^v-bind:/;
// 匹配修饰符的正则(.stop等
var modifierRE = /\.[^.\]]+(?=[^\]]*$)/g;
// 匹配插槽语法v-slot或#开头)
var slotRE = /^v-slot(:|$)|^#/;
// 匹配换行符
var lineBreakRE = /[\r\n]/;
// 匹配空白字符
var whitespaceRE$1 = /\s+/g;
// 匹配非法属性字符
var invalidAttributeRE = /[\s"'<>\/=]/;
// 缓存HTML解码函数
var decodeHTMLCached = cached(he.decode);
// 空插槽作用域标识
var emptySlotScopeToken = "_empty_";
// 可配置状态变量
var warn$2; // 警告函数
var delimiters; // 插值分隔符
var transforms; // 转换函数集合
var preTransforms; // 预处理函数集合
var postTransforms; // 后处理函数集合
var platformIsPreTag;// 判断pre标签的方法
var platformMustUseProp; // 判断必须用prop绑定的属性
var platformGetTagNamespace; // 获取标签命名空间
var maybeComponent; // 判断可能是组件的方法
// 创建AST元素节点
function createASTElement(tag, attrs, parent) {
return {
type: 1, // 节点类型为元素
tag: tag, // 标签名
attrsList: attrs, // 属性列表
attrsMap: makeAttrsMap(attrs), // 属性映射表
rawAttrsMap: {}, // 原始属性映射
parent: parent, // 父节点
children: [] // 子节点数组
}
}
// 将HTML字符串转换为AST的主函数
function parse(template, options) {
// 初始化警告函数
warn$2 = options.warn || baseWarn;
// 初始化平台相关方法
platformIsPreTag = options.isPreTag || no;
platformMustUseProp = options.mustUseProp || no;
platformGetTagNamespace = options.getTagNamespace || no;
// 判断保留标签的方法
var isReservedTag = options.isReservedTag || no;
// 判断可能是组件的方法
maybeComponent = function (el) { return !!el.component || !isReservedTag(el.tag); };
// 从模块中提取转换函数
transforms = pluckModuleFunction(options.modules, 'transformNode');
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');
// 设置分隔符
delimiters = options.delimiters;
// 初始化解析状态
var stack = []; // 元素节点栈
var preserveWhitespace = options.preserveWhitespace !== false; // 是否保留空白
var whitespaceOption = options.whitespace; // 空白处理选项
var root; // 根节点
var currentParent; // 当前父节点
var inVPre = false; // 是否在v-pre环境中
var inPre = false; // 是否在pre标签内
var warned = false; // 警告状态标记
// 单次警告函数
function warnOnce(msg, range) {
if (!warned) {
warned = true;
warn$2(msg, range);
}
}
// 闭合元素处理
function closeElement(element) {
// 清理结尾空白
trimEndingWhitespace(element);
// 处理非v-pre环境的元素
if (!inVPre && !element.processed) {
element = processElement(element, options);
}
// 树结构管理
if (!stack.length && element !== root) {
// 处理根元素的条件判断
if (root.if && (element.elseif || element.else)) {
checkRootConstraints(element);
addIfCondition(root, {
exp: element.elseif,
block: element
});
} else {
warnOnce(
"模板应包含单个根元素使用v-else-if连接多个元素",
{ start: element.start }
);
}
}
// 将元素添加到父节点
if (currentParent && !element.forbidden) {
if (element.elseif || element.else) {
processIfConditions(element, currentParent);
} else {
// 处理作用域插槽
if (element.slotScope) {
var name = element.slotTarget || '"default"';
(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element;
}
currentParent.children.push(element);
element.parent = currentParent;
}
}
// 清理子节点
element.children = element.children.filter(function (c) { return !(c).slotScope; });
trimEndingWhitespace(element);
// 重置预处理状态
if (element.pre) inVPre = false;
if (platformIsPreTag(element.tag)) inPre = false;
// 执行后处理
for (var i = 0; i < postTransforms.length; i++) {
postTransforms[i](element, options);
}
}
// 清理结尾空白节点
function trimEndingWhitespace(el) {
if (!inPre) {
var lastNode;
while ((lastNode = el.children[el.children.length - 1]) &&
lastNode.type === 3 &&
lastNode.text === ' ') {
el.children.pop();
}
}
}
// 检查根元素约束
function checkRootConstraints(el) {
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce("禁止使用slot/template作为根元素", { start: el.start });
}
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce("根元素不能使用v-for", el.rawAttrsMap['v-for']);
}
}
// 调用HTML解析器
parseHTML(template, {
warn: warn$2,
expectHTML: options.expectHTML,
isUnaryTag: options.isUnaryTag,
canBeLeftOpenTag: options.canBeLeftOpenTag,
shouldDecodeNewlines: options.shouldDecodeNewlines,
shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
shouldKeepComment: options.comments,
outputSourceRange: options.outputSourceRange,
// 开始标签回调
start: function start(tag, attrs, unary, start$1, end) {
// 处理命名空间
var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);
// 处理IE SVG问题
if (isIE && ns === 'svg') attrs = guardIESVGBug(attrs);
// 创建AST元素
var element = createASTElement(tag, attrs, currentParent);
if (ns) element.ns = ns;
// 处理源码范围
if (options.outputSourceRange) {
element.start = start$1;
element.end = end;
element.rawAttrsMap = element.attrsList.reduce(function (cumulated, attr) {
cumulated[attr.name] = attr;
return cumulated
}, {});
}
// 检查非法属性
attrs.forEach(function (attr) {
if (invalidAttributeRE.test(attr.name)) {
warn$2("动态参数包含非法字符", {
start: attr.start + attr.name.indexOf("["),
end: attr.start + attr.name.length
});
}
});
// 检查禁用标签
if (isForbiddenTag(element) && !isServerRendering()) {
element.forbidden = true;
warn$2("模板包含禁用标签", { start: element.start });
}
// 执行预处理
for (var i = 0; i < preTransforms.length; i++) {
element = preTransforms[i](element, options) || element;
}
// 处理v-pre指令
if (!inVPre) {
processPre(element);
if (element.pre) inVPre = true;
}
// 处理pre标签状态
if (platformIsPreTag(element.tag)) inPre = true;
// 处理原始属性或指令
if (inVPre) {
processRawAttrs(element);
} else if (!element.processed) {
processFor(element); // 处理v-for
processIf(element); // 处理v-if
processOnce(element); // 处理v-once
}
// 设置根元素
if (!root) {
root = element;
checkRootConstraints(root);
}
// 管理节点栈
if (!unary) {
currentParent = element;
stack.push(element);
} else {
closeElement(element);
}
},
// 结束标签回调
end: function end(tag, start, end$1) {
var element = stack[stack.length - 1];
stack.length -= 1;
currentParent = stack[stack.length - 1];
if (options.outputSourceRange) element.end = end$1;
closeElement(element);
},
// 文本内容回调
chars: function chars(text, start, end) {
if (!currentParent) {
// 处理游离文本的警告
if (text.trim()) warnOnce("根元素外的文本将被忽略", { start: start });
return
}
// 处理IE textarea placeholder问题
if (isIE && currentParent.tag === 'textarea' && currentParent.attrsMap.placeholder === text) return
var children = currentParent.children;
// 处理文本内容
text = inPre || text.trim()
? (isTextTag(currentParent) ? text : decodeHTMLCached(text))
: whitespaceOption === 'condense'
? (lineBreakRE.test(text) ? '' : ' ')
: preserveWhitespace ? ' ' : '';
if (text) {
// 压缩连续空白
if (!inPre && whitespaceOption === 'condense') {
text = text.replace(whitespaceRE$1, ' ');
}
// 创建文本节点
var child;
if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
child = { type: 2, expression: res.expression, tokens: res.tokens, text: text };
} else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
child = { type: 3, text: text };
}
if (child) {
if (options.outputSourceRange) {
child.start = start;
child.end = end;
}
children.push(child);
}
}
},
// 注释处理回调
comment: function comment(text, start, end) {
if (currentParent) {
var child = { type: 3, text: text, isComment: true };
if (options.outputSourceRange) {
child.start = start;
child.end = end;
}
currentParent.children.push(child);
}
}
});
return root
}
// 处理v-pre指令
function processPre (el) {
// 检查是否存在v-pre属性
if (getAndRemoveAttr(el, 'v-pre') != null) {
el.pre = true; // 标记元素具有v-pre指令
}
}
// 处理原始属性
function processRawAttrs (el) {
var list = el.attrsList; // 获取属性列表
var len = list.length; // 属性数量
if (len) {
// 创建规范化属性数组
var attrs = el.attrs = new Array(len);
for (var i = 0; i < len; i++) {
// 构建属性对象
attrs[i] = {
name: list[i].name, // 属性名
value: JSON.stringify(list[i].value) // 值序列化
};
// 记录源码位置信息
if (list[i].start != null) {
attrs[i].start = list[i].start;
attrs[i].end = list[i].end;
}
}
} else if (!el.pre) {
// 没有属性的非根节点标记为plain
el.plain = true;
}
}
// 处理元素节点主函数
function processElement (element, options) {
processKey(element); // 处理key属性
// 判断是否为纯元素(无关键属性)
element.plain = (
!element.key && // 无key
!element.scopedSlots && // 无作用域插槽
!element.attrsList.length // 无属性
);
processRef(element); // 处理ref属性
processSlotContent(element);// 处理插槽内容
processSlotOutlet(element); // 处理插槽出口
processComponent(element); // 处理组件
// 执行转换函数
for (var i = 0; i < transforms.length; i++) {
element = transforms[i](element, options) || element;
}
processAttrs(element); // 处理属性
return element
}
// 处理key属性
function processKey (el) {
var exp = getBindingAttr(el, 'key'); // 获取绑定的key
if (exp) {
// 开发环境警告检查
{
if (el.tag === 'template') {
warn$2( // 禁止template使用key
"<template>不能设置key请设置在真实元素上",
getRawBindingAttr(el, 'key')
);
}
if (el.for) {
var iterator = el.iterator2 || el.iterator1;
var parent = el.parent;
// 过渡组件的key警告
if (iterator && iterator === exp && parent && parent.tag === 'transition-group') {
warn$2(
"不要在<transition-group>子元素使用索引作为key",
getRawBindingAttr(el, 'key'),
true
);
}
}
}
el.key = exp; // 设置key表达式
}
}
// 处理ref属性
function processRef (el) {
var ref = getBindingAttr(el, 'ref'); // 获取ref绑定
if (ref) {
el.ref = ref; // 设置ref
el.refInFor = checkInFor(el); // 检查是否在v-for内
}
}
// 处理v-for指令
function processFor (el) {
var exp;
if ((exp = getAndRemoveAttr(el, 'v-for'))) { // 获取并移除v-for属性
var res = parseFor(exp); // 解析表达式
if (res) {
extend(el, res); // 合并解析结果
} else {
warn$2( // 无效表达式警告
"无效的v-for表达式: " + exp,
el.rawAttrsMap['v-for']
);
}
}
}
// 解析v-for表达式
function parseFor (exp) {
var inMatch = exp.match(forAliasRE); // 匹配in/of语法
if (!inMatch) return // 无效格式直接返回
var res = {};
res.for = inMatch[2].trim(); // 获取列表部分
var alias = inMatch[1].trim().replace(stripParensRE, ''); // 去除括号
// 解析迭代参数
var iteratorMatch = alias.match(forIteratorRE);
if (iteratorMatch) {
res.alias = alias.replace(forIteratorRE, '').trim(); // 主迭代变量
res.iterator1 = iteratorMatch[1].trim(); // 索引参数
if (iteratorMatch[2]) {
res.iterator2 = iteratorMatch[2].trim(); // 第三个参数
}
} else {
res.alias = alias; // 无迭代参数
}
return res
}
// 处理v-if指令
function processIf (el) {
var exp = getAndRemoveAttr(el, 'v-if'); // 获取v-if表达式
if (exp) {
el.if = exp; // 设置条件表达式
addIfCondition(el, { // 添加条件块
exp: exp,
block: el
});
} else {
// 处理v-else
if (getAndRemoveAttr(el, 'v-else') != null) {
el.else = true;
}
// 处理v-else-if
var elseif = getAndRemoveAttr(el, 'v-else-if');
if (elseif) {
el.elseif = elseif;
}
}
}
// 处理条件链关系
function processIfConditions (el, parent) {
var prev = findPrevElement(parent.children); // 查找前一个元素节点
if (prev && prev.if) {
addIfCondition(prev, { // 添加条件关系
exp: el.elseif,
block: el
});
} else {
warn$2( // 条件链错误警告
"v-" + (el.elseif ? 'else-if="' + el.elseif + '"' : 'else') +
" 缺少对应的v-if",
el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else']
);
}
}
// 查找前一个元素节点
function findPrevElement (children) {
var i = children.length;
while (i--) {
if (children[i].type === 1) { // 元素节点
return children[i]
} else {
// 处理文本节点警告
if (children[i].text !== ' ') {
warn$2(
"v-if和v-else之间的文本内容将被忽略: " + children[i].text.trim(),
children[i]
);
}
children.pop(); // 移除干扰文本节点
}
}
}
// 添加条件关系
function addIfCondition (el, condition) {
if (!el.ifConditions) {
el.ifConditions = []; // 初始化条件数组
}
el.ifConditions.push(condition); // 添加条件项
}
// 处理v-once指令
function processOnce (el) {
var once$$1 = getAndRemoveAttr(el, 'v-once'); // 获取v-once属性
if (once$$1 != null) {
el.once = true; // 标记只渲染一次
}
}
// 处理插槽内容
function processSlotContent (el) {
var slotScope;
// 处理template插槽
if (el.tag === 'template') {
slotScope = getAndRemoveAttr(el, 'scope'); // 旧语法scope
/* istanbul ignore if */
if (slotScope) {
warn$2( // 废弃语法警告
"scope属性已废弃请使用slot-scope",
el.rawAttrsMap['scope'],
true
);
}
el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope'); // 新语法
} else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
// 普通元素使用slot-scope警告
/* istanbul ignore if */
if (el.attrsMap['v-for']) {
warn$2(
"同时使用slot-scope和v-for可能导致歧义",
el.rawAttrsMap['slot-scope'],
true
);
}
el.slotScope = slotScope;
}
// 处理slot属性
var slotTarget = getBindingAttr(el, 'slot');
if (slotTarget) {
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget; // 默认插槽处理
el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot']); // 动态插槽名
// 保留原生slot属性
if (el.tag !== 'template' && !el.slotScope) {
addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'));
}
}
// 处理v-slot语法2.6+
{
if (el.tag === 'template') {
// template上的v-slot
var slotBinding = getAndRemoveAttrByRegex(el, slotRE);
if (slotBinding) {
{
// 语法混合警告
if (el.slotTarget || el.slotScope) {
warn$2("混用不同插槽语法", el);
}
// 作用域限制检查
if (el.parent && !maybeComponent(el.parent)) {
warn$2("v-slot只能在组件根模板使用", el);
}
}
var ref = getSlotName(slotBinding);
el.slotTarget = ref.name; // 插槽名称
el.slotTargetDynamic = ref.dynamic; // 是否动态
el.slotScope = slotBinding.value || emptySlotScopeToken; // 作用域
}
} else {
// 组件上的v-slot
var slotBinding$1 = getAndRemoveAttrByRegex(el, slotRE);
if (slotBinding$1) {
{
// 使用限制检查
if (!maybeComponent(el)) {
warn$2("v-slot只能用于组件或template", slotBinding$1);
}
// 语法冲突检查
if (el.slotScope || el.slotTarget) {
warn$2("混用不同插槽语法", el);
}
// 默认插槽处理提示
if (el.scopedSlots) {
warn$2("存在具名插槽时默认插槽需用template", slotBinding$1);
}
}
// 创建插槽容器
var slots = el.scopedSlots || (el.scopedSlots = {});
var ref$1 = getSlotName(slotBinding$1);
var slotContainer = slots[ref$1.name] = createASTElement('template', [], el);
slotContainer.slotTarget = ref$1.name; // 插槽名
slotContainer.slotTargetDynamic = ref$1.dynamic; // 动态标识
slotContainer.children = el.children.filter(function (c) { // 转移子节点
if (!c.slotScope) {
c.parent = slotContainer;
return true
}
});
slotContainer.slotScope = slotBinding$1.value || emptySlotScopeToken; // 作用域
el.children = []; // 清空原子节点
el.plain = false; // 标记非普通元素
}
}
}
}
// 解析插槽名称
function getSlotName (binding) {
var name = binding.name.replace(slotRE, ''); // 去除指令前缀
if (!name) { // 处理简写语法
if (binding.name[0] !== '#') {
name = 'default'; // 默认插槽
} else {
warn$2("v-slot简写语法需要指定名称", binding);
}
}
// 返回名称和动态标识
return dynamicArgRE.test(name)
? { name: name.slice(1, -1), dynamic: true } // 动态名称
: { name: ("\"" + name + "\""), dynamic: false } // 静态名称
}
// 处理slot出口<slot>标签)
function processSlotOutlet (el) {
if (el.tag === 'slot') {
el.slotName = getBindingAttr(el, 'name'); // 获取slot名称
if (el.key) { // slot使用key警告
warn$2(
"slot不能使用key请在外层元素设置",
getRawBindingAttr(el, 'key')
);
}
}
}
// 处理动态组件
function processComponent (el) {
var binding;
if ((binding = getBindingAttr(el, 'is'))) { // 获取is绑定
el.component = binding; // 设置组件名
}
if (getAndRemoveAttr(el, 'inline-template') != null) { // 内联模板
el.inlineTemplate = true;
}
}
// 处理元素属性
function processAttrs (el) {
var list = el.attrsList; // 属性列表
for (var i = 0, l = list.length; i < l; i++) {
var name = list[i].name; // 原始属性名
var value = list[i].value; // 属性值
if (dirRE.test(name)) { // 检查是否指令
el.hasBindings = true; // 标记有绑定
var modifiers = parseModifiers(name.replace(dirRE, '')); // 解析修饰符
if (modifiers) name = name.replace(modifierRE, ''); // 清理修饰符
if (bindRE.test(name)) { // v-bind处理
name = name.replace(bindRE, ''); // 获取属性名
value = parseFilters(value); // 解析过滤器
var isDynamic = dynamicArgRE.test(name); // 是否动态参数
if (isDynamic) name = name.slice(1, -1); // 提取动态参数名
// 空值检查
if (value.trim() === "") {
warn$2("v-bind值不能为空");
}
// 处理修饰符
if (modifiers) {
if (modifiers.prop && !isDynamic) { // prop绑定
name = camelize(name);
if (name === 'innerHtml') name = 'innerHTML'; // 特殊处理
}
if (modifiers.camel && !isDynamic) name = camelize(name); // 驼峰化
if (modifiers.sync) { // sync语法糖
var syncGen = genAssignmentCode(value, "$event");
// 添加更新处理器
if (!isDynamic) {
addHandler(el, "update:" + camelize(name), syncGen);
if (hyphenate(name) !== camelize(name)) {
addHandler(el, "update:" + hyphenate(name), syncGen);
}
} else {
addHandler(el, `"update:"+(${name})`, syncGen, null, false, warn$2, list[i], true);
}
}
}
// 判断绑定方式
if ((modifiers && modifiers.prop) || platformMustUseProp(el.tag, el.attrsMap.type, name)) {
addProp(el, name, value, list[i], isDynamic); // 属性绑定
} else {
addAttr(el, name, value, list[i], isDynamic); // 特性绑定
}
} else if (onRE.test(name)) { // v-on处理
name = name.replace(onRE, ''); // 获取事件名
var isDynamic = dynamicArgRE.test(name);
if (isDynamic) name = name.slice(1, -1); // 动态事件名
addHandler(el, name, value, modifiers, false, warn$2, list[i], isDynamic); // 添加事件处理器
} else { // 普通指令处理
name = name.replace(dirRE, ''); // 获取指令名
var argMatch = name.match(argRE); // 解析参数
var arg = argMatch && argMatch[1];
var isDynamic = false;
if (arg) {
name = name.slice(0, -(arg.length + 1)); // 获取指令主名称
if (dynamicArgRE.test(arg)) { // 动态参数处理
arg = arg.slice(1, -1);
isDynamic = true;
}
}
addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]); // 添加指令
if (name === 'model') checkForAliasModel(el, value); // 特殊处理v-model
}
} else { // 普通属性处理
// 插值语法警告
{
if (parseText(value, delimiters)) {
warn$2("属性中的插值已废弃请使用v-bind");
}
}
addAttr(el, name, JSON.stringify(value), list[i]); // 添加静态属性
// Firefox muted属性特殊处理
if (name === 'muted' && !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)) {
addProp(el, name, 'true', list[i]);
}
}
}
}
// 检查元素是否在v-for循环中
function checkInFor (el) {
var parent = el;
while (parent) {
if (parent.for !== undefined) { // 发现父级存在v-for
return true // 返回存在标记
}
parent = parent.parent; // 向上遍历父级元素
}
return false // 未找到返回false
}
// 解析修饰符(如.stop/.prevent
function parseModifiers (name) {
var match = name.match(modifierRE); // 使用正则匹配修饰符
if (match) {
var ret = {}; // 创建修饰符对象
match.forEach(function (m) {
ret[m.slice(1)] = true; // 去除点号后存入对象
});
return ret // 返回修饰符集合
}
}
// 将属性数组转换为映射表
function makeAttrsMap (attrs) {
var map = {};
for (var i = 0, l = attrs.length; i < l; i++) {
// 非IE环境检查重复属性
if (map[attrs[i].name] && !isIE && !isEdge) {
warn$2('重复属性: ' + attrs[i].name, attrs[i]);
}
map[attrs[i].name] = attrs[i].value; // 存储属性值
}
return map // 返回属性映射表
}
// 判断是否为文本标签script/style
function isTextTag (el) {
return el.tag === 'script' || el.tag === 'style'
}
// 判断是否为禁止使用的标签
function isForbiddenTag (el) {
return (
el.tag === 'style' || // 禁止style标签
(el.tag === 'script' && ( // 无类型或JS类型的script标签
!el.attrsMap.type ||
el.attrsMap.type === 'text/javascript'
))
)
}
// IE SVG命名空间bug正则
var ieNSBug = /^xmlns:NS\d+/; // 匹配错误命名空间属性
var ieNSPrefix = /^NS\d+:/; // 匹配错误命名空间前缀
// 修复IE SVG属性bug
function guardIESVGBug (attrs) {
var res = [];
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
if (!ieNSBug.test(attr.name)) { // 过滤非法属性
attr.name = attr.name.replace(ieNSPrefix, ''); // 修复属性名
res.push(attr); // 加入结果集
}
}
return res // 返回修复后的属性
}
// 检查v-model绑定到v-for迭代别名的情况
function checkForAliasModel (el, value) {
var _el = el;
while (_el) { // 遍历父级元素
if (_el.for && _el.alias === value) { // 发现v-for别名绑定
warn$2( // 发出警告
"v-model不能直接绑定到v-for的迭代别名",
el.rawAttrsMap['v-model']
);
}
_el = _el.parent; // 向上遍历
}
}
// input元素的预处理转换
function preTransformNode (el, options) {
if (el.tag === 'input') { // 仅处理input标签
var map = el.attrsMap;
if (!map['v-model']) return // 无v-model直接返回
// 获取类型绑定表达式
var typeBinding;
if (map[':type'] || map['v-bind:type']) {
typeBinding = getBindingAttr(el, 'type');
} else if (!map.type && map['v-bind']) {
typeBinding = `(${map['v-bind']}).type`; // 从v-bind提取类型
}
if (typeBinding) { // 存在动态类型时创建分支逻辑
// 收集条件判断属性
var ifCondition = getAndRemoveAttr(el, 'v-if', true);
var ifConditionExtra = ifCondition ? `&&(${ifCondition})` : "";
var hasElse = getAndRemoveAttr(el, 'v-else', true) != null;
var elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true);
// 创建checkbox分支
var branch0 = cloneASTElement(el);
processFor(branch0); // 处理v-for
addRawAttr(branch0, 'type', 'checkbox'); // 强制类型
processElement(branch0, options); // 处理元素
branch0.processed = true; // 标记已处理
branch0.if = `(${typeBinding})==='checkbox'${ifConditionExtra}`; // 条件表达式
addIfCondition(branch0, { exp: branch0.if, block: branch0 }); // 添加条件
// 创建radio分支
var branch1 = cloneASTElement(el);
getAndRemoveAttr(branch1, 'v-for', true); // 移除v-for
addRawAttr(branch1, 'type', 'radio'); // 设置类型
processElement(branch1, options);
addIfCondition(branch0, { // 添加条件分支
exp: `(${typeBinding})==='radio'${ifConditionExtra}`,
block: branch1
});
// 创建其他类型分支
var branch2 = cloneASTElement(el);
getAndRemoveAttr(branch2, 'v-for', true);
addRawAttr(branch2, ':type', typeBinding); // 动态绑定类型
processElement(branch2, options);
addIfCondition(branch0, { exp: ifCondition, block: branch2 });
// 处理else条件
if (hasElse) {
branch0.else = true;
} else if (elseIfCondition) {
branch0.elseif = elseIfCondition;
}
return branch0 // 返回转换后的分支结构
}
}
}
// 克隆AST元素节点
function cloneASTElement (el) {
return createASTElement(el.tag, el.attrsList.slice(), el.parent)
}
// 模型处理模块配置
var model$1 = {
preTransformNode: preTransformNode // 注册预处理方法
};
// 所有转换模块集合
var modules$1 = [
klass$1, // 类处理模块
style$1, // 样式处理模块
model$1 // 模型处理模块
];
// 文本指令处理
function text (el, dir) {
if (dir.value) {
// 将文本绑定到textContent属性
addProp(el, 'textContent', `_s(${dir.value})`, dir);
}
}
// HTML指令处理
function html (el, dir) {
if (dir.value) {
// 将HTML绑定到innerHTML属性
addProp(el, 'innerHTML', `_s(${dir.value})`, dir);
}
}
// 指令集合
var directives$1 = {
model: model, // 双向绑定指令
text: text, // 文本指令
html: html // HTML指令
};
// 基础编译选项配置
var baseOptions = {
expectHTML: true, // 期望标准HTML结构
modules: modules$1, // 使用的转换模块
directives: directives$1, // 指令处理集合
isPreTag: isPreTag, // 判断pre标签方法
isUnaryTag: isUnaryTag, // 判断自闭合标签方法
mustUseProp: mustUseProp, // 必须用prop绑定的属性判断
canBeLeftOpenTag: canBeLeftOpenTag, // 可省略闭合标签
isReservedTag: isReservedTag,// 保留标签判断
getTagNamespace: getTagNamespace, // 获取标签命名空间
staticKeys: genStaticKeys(modules$1) // 静态属性键名集合
};
// 静态分析相关变量
var isStaticKey; // 静态属性判断函数
var isPlatformReservedTag; // 平台保留标签判断
// 生成静态键名的缓存函数
var genStaticKeysCached = cached(genStaticKeys$1);
// AST优化主函数标记静态节点
function optimize (root, options) {
if (!root) return
// 初始化静态分析工具
isStaticKey = genStaticKeysCached(options.staticKeys || '');
isPlatformReservedTag = options.isReservedTag || no;
// 第一遍:标记静态节点
markStatic$1(root);
// 第二遍:标记静态根节点
markStaticRoots(root, false);
}
// 生成静态属性键名集合
function genStaticKeys$1 (keys) {
return makeMap( // 创建映射表
'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap' +
(keys ? ',' + keys : '')
)
}
// 递归标记静态节点
function markStatic$1 (node) {
node.static = isStatic(node); // 判断当前节点是否静态
if (node.type === 1) { // 元素节点处理
// 跳过组件插槽内容
if (
!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) return
// 递归处理子节点
for (var i = 0, l = node.children.length; i < l; i++) {
var child = node.children[i];
markStatic$1(child);
if (!child.static) node.static = false; // 子节点非静态则当前节点非静态
}
// 处理条件分支中的节点
if (node.ifConditions) {
for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
var block = node.ifConditions[i$1].block;
markStatic$1(block);
if (!block.static) node.static = false;
}
}
}
}
// 标记静态根节点
function markStaticRoots (node, isInFor) {
if (node.type === 1) { // 元素节点处理
// 记录静态节点是否在循环中
if (node.static || node.once) {
node.staticInFor = isInFor;
}
// 标记静态根节点条件:包含有效子节点
if (node.static && node.children.length &&
!(node.children.length === 1 && node.children[0].type === 3)) {
node.staticRoot = true; // 标记为静态根
return
} else {
node.staticRoot = false; // 不满足条件取消标记
}
// 递归处理子节点
if (node.children) {
for (var i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for);
}
}
// 处理条件分支
if (node.ifConditions) {
for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
markStaticRoots(node.ifConditions[i$1].block, isInFor);
}
}
}
}
// 判断节点是否静态
function isStatic (node) {
if (node.type === 2) return false // 表达式节点非静态
if (node.type === 3) return true // 文本节点静态
return !!(node.pre || ( // 包含v-pre或满足以下条件
!node.hasBindings && // 无动态绑定
!node.if && !node.for && // 无流程控制
!isBuiltInTag(node.tag) && // 非内置标签
isPlatformReservedTag(node.tag) && // 平台原生标签
!isDirectChildOfTemplateFor(node) && // 非template的直接v-for子元素
Object.keys(node).every(isStaticKey) // 所有属性都是静态的
))
}
// 判断是否是template标签的直接v-for子元素
function isDirectChildOfTemplateFor (node) {
while (node.parent) { // 向上遍历父节点
node = node.parent;
if (node.tag !== 'template') return false
if (node.for) return true // 发现template有v-for
}
return false
}
// 匹配函数表达式(箭头函数和传统函数)
var fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/;
// 匹配函数调用结尾
var fnInvokeRE = /\([^)]*?\);*$/;
// 匹配简单属性路径obj.prop或obj['prop']
var simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/;
// 键盘事件键码映射表
var keyCodes = {
esc: 27, // ESC键
tab: 9, // Tab键
enter: 13, // Enter键
space: 32, // 空格键
up: 38, // 上箭头
left: 37, // 左箭头
right: 39, // 右箭头
down: 40, // 下箭头
'delete': [8, 46] // 删除/退格键
};
// 键盘事件键名映射表(兼容不同浏览器)
var keyNames = {
esc: ['Esc', 'Escape'], // IE兼容
tab: 'Tab', // Tab键
enter: 'Enter', // 回车键
space: [' ', 'Spacebar'], // IE兼容
up: ['Up', 'ArrowUp'], // 上箭头兼容
left: ['Left', 'ArrowLeft'], // 左箭头兼容
right: ['Right', 'ArrowRight'], // 右箭头兼容
down: ['Down', 'ArrowDown'], // 下箭头兼容
'delete': ['Backspace', 'Delete', 'Del'] // 删除键兼容
};
// 生成条件判断代码的工厂函数
var genGuard = function (condition) { return ("if(" + condition + ")return null;"); };
// 事件修饰符对应的代码片段
var modifierCode = {
stop: '$event.stopPropagation();', // 阻止冒泡
prevent: '$event.preventDefault();', // 阻止默认
self: genGuard("$event.target !== $event.currentTarget"), // 仅当前元素触发
ctrl: genGuard("!$event.ctrlKey"), // 需要ctrl键
shift: genGuard("!$event.shiftKey"), // 需要shift键
alt: genGuard("!$event.altKey"), // 需要alt键
meta: genGuard("!$event.metaKey"), // 需要meta键
left: genGuard("'button' in $event && $event.button !== 0"), // 鼠标左键
middle: genGuard("'button' in $event && $event.button !== 1"), // 鼠标中键
right: genGuard("'button' in $event && $event.button !== 2") // 鼠标右键
};
// 生成事件处理器代码
function genHandlers (events, isNative) {
var prefix = isNative ? 'nativeOn:' : 'on:'; // 判断原生事件
var staticHandlers = ""; // 静态事件集合
var dynamicHandlers = ""; // 动态事件集合
// 遍历所有事件
for (var name in events) {
var handlerCode = genHandler(events[name]);
// 分离动态/静态事件
if (events[name] && events[name].dynamic) {
dynamicHandlers += name + "," + handlerCode + ",";
} else {
staticHandlers += "\"" + name + "\":" + handlerCode + ",";
}
}
staticHandlers = "{" + (staticHandlers.slice(0, -1)) + "}"; // 格式化静态事件
// 组合最终代码
return dynamicHandlers
? prefix + "_d(" + staticHandlers + ",[" + dynamicHandlers.slice(0, -1) + "])"
: prefix + staticHandlers;
}
// 生成单个事件处理器
function genHandler (handler) {
if (!handler) return 'function(){}' // 空处理器
// 处理数组格式的处理器(多个处理函数)
if (Array.isArray(handler)) {
return "[" + handler.map(genHandler).join(',') + "]";
}
// 判断处理器类型
var isMethodPath = simplePathRE.test(handler.value); // 方法路径格式
var isFunctionExpression = fnExpRE.test(handler.value); // 函数表达式
var isFunctionInvocation = simplePathRE.test( // 函数调用格式
handler.value.replace(fnInvokeRE, '')
);
// 无修饰符时的处理
if (!handler.modifiers) {
if (isMethodPath || isFunctionExpression) {
return handler.value; // 直接返回方法名
}
return `function($event){${isFunctionInvocation ? 'return ' + handler.value : handler.value}}`;
}
// 有修饰符时的处理
else {
var code = '';
var genModifierCode = '';
var keys = [];
// 处理每个修饰符
for (var key in handler.modifiers) {
if (modifierCode[key]) { // 预定义修饰符
genModifierCode += modifierCode[key];
if (keyCodes[key]) keys.push(key); // 收集需要键码检查的修饰符
} else if (key === 'exact') { // 精确修饰符
genModifierCode += genGuard(
['ctrl', 'shift', 'alt', 'meta']
.filter(k => !handler.modifiers[k])
.map(k => `$event.${k}Key`)
.join('||')
);
} else {
keys.push(key); // 自定义键名修饰符
}
}
// 生成按键过滤代码
if (keys.length) code += genKeyFilter(keys);
// 添加修饰符代码
if (genModifierCode) code += genModifierCode;
// 生成处理器主体代码
var handlerCode = isMethodPath
? `return ${handler.value}($event)`
: isFunctionExpression
? `return (${handler.value})($event)`
: isFunctionInvocation
? `return ${handler.value}`
: handler.value;
return `function($event){${code}${handlerCode}}`;
}
}
// 生成按键过滤代码
function genKeyFilter (keys) {
return `if(!$event.type.indexOf('key')&&${keys.map(genFilterCode).join('&&')})return null;`;
}
// 生成单个按键过滤条件
function genFilterCode (key) {
var keyVal = parseInt(key, 10);
if (keyVal) return `$event.keyCode!==${keyVal}`; // 数字键码直接比较
// 处理键名和键码别名
var keyCode = keyCodes[key];
var keyName = keyNames[key];
return `_k($event.keyCode,${JSON.stringify(key)},${JSON.stringify(keyCode)},$event.key,${JSON.stringify(keyName)})`;
}
/* 事件处理指令 */
function on (el, dir) {
if (dir.modifiers) {
warn("无参数的v-on不支持修饰符"); // 参数校验
}
el.wrapListeners = code => `_g(${code},${dir.value})`; // 包装监听器
}
/* 属性绑定指令 */
function bind$1 (el, dir) {
el.wrapData = function (code) {
return `_b(${code},'${el.tag}',${dir.value},${dir.modifiers?.prop ? 'true' : 'false'}${dir.modifiers?.sync ? ',true' : ''})`;
};
}
// 基础指令集合
var baseDirectives = {
on: on, // 事件绑定
bind: bind$1,// 属性绑定
cloak: noop // 空指令SSR占位
};
/* 代码生成状态管理 */
var CodegenState = function (options) {
this.options = options; // 编译选项
this.warn = options.warn || baseWarn; // 警告方法
this.transforms = pluckModuleFunction(options.modules, 'transformCode'); // 代码转换函数
this.dataGenFns = pluckModuleFunction(options.modules, 'genData'); // 数据生成函数
this.directives = extend({}, baseDirectives, options.directives); // 合并指令
var isReservedTag = options.isReservedTag || no;
this.maybeComponent = el => !!el.component || !isReservedTag(el.tag); // 组件判断
this.onceId = 0; // v-once计数器
this.staticRenderFns = []; // 静态渲染函数缓存
this.pre = false; // v-pre状态标记
};
// 生成渲染函数入口
function generate (ast, options) {
var state = new CodegenState(options); // 初始化状态
var code = ast ? genElement(ast, state) : '_c("div")'; // 生成组件或默认div
return {
render: `with(this){return ${code}}`, // 渲染函数
staticRenderFns: state.staticRenderFns // 静态渲染函数数组
};
}
// 生成元素代码
function genElement (el, state) {
if (el.parent) {
el.pre = el.pre || el.parent.pre; // 继承父级v-pre状态
}
// 静态根节点处理
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state);
}
// v-once处理
else if (el.once && !el.onceProcessed) {
return genOnce(el, state);
}
// v-for处理
else if (el.for && !el.forProcessed) {
return genFor(el, state);
}
// v-if处理
else if (el.if && !el.ifProcessed) {
return genIf(el, state);
}
// template插槽处理
else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0';
}
// slot处理
else if (el.tag === 'slot') {
return genSlot(el, state);
}
// 普通元素/组件
else {
var code;
if (el.component) { // 组件处理
code = genComponent(el.component, el, state);
} else {
var data;
// 生成属性数据
if (!el.plain || (el.pre && state.maybeComponent(el))) {
data = genData$2(el, state);
}
// 生成子节点
var children = el.inlineTemplate ? null : genChildren(el, state, true);
code = `_c('${el.tag}'${data ? `,${data}` : ''}${children ? `,${children}` : ''})`;
}
// 应用代码转换
for (var i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code);
}
return code;
}
}
// 生成静态节点代码
function genStatic (el, state) {
el.staticProcessed = true; // 标记已处理
var originalPreState = state.pre;
if (el.pre) state.pre = el.pre; // 保存当前v-pre状态
// 缓存静态渲染函数
state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`);
state.pre = originalPreState; // 恢复v-pre状态
return `_m(${state.staticRenderFns.length - 1}${el.staticInFor ? ',true' : ''})`; // 返回缓存索引
}
// 生成v-once节点代码
function genOnce (el, state) {
el.onceProcessed = true;
// 优先处理v-if条件
if (el.if && !el.ifProcessed) return genIf(el, state);
// 处理v-for中的v-once
if (el.staticInFor) {
let parent = el.parent;
let key;
// 查找最近的v-for的key
while (parent) {
if (parent.for) {
key = parent.key;
break;
}
parent = parent.parent;
}
if (!key) { // 无key警告
state.warn("v-once需要用在有key的v-for内", el.rawAttrsMap['v-once']);
return genElement(el, state);
}
return `_o(${genElement(el, state)},${state.onceId++},${key})`; // 生成唯一标识
}
// 普通静态处理
else {
return genStatic(el, state);
}
}
// 生成条件表达式代码
function genIf (el, state, altGen, altEmpty) {
el.ifProcessed = true; // 避免递归
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty);
}
// 递归生成条件分支代码
function genIfConditions (conditions, state, altGen, altEmpty) {
if (!conditions.length) return altEmpty || '_e()'; // 空节点
const condition = conditions.shift();
// 生成三元表达式
return condition.exp
? `(${condition.exp})?${genTernaryExp(condition.block)}:${genIfConditions(conditions, state)}`
: genTernaryExp(condition.block);
// 生成分支代码
function genTernaryExp(el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state);
}
}
// 生成 v-for 指令的渲染代码
function genFor (
el,
state,
altGen,
altHelper
) {
// 获取循环表达式
var exp = el.for;
// 获取循环别名
var alias = el.alias;
// 生成迭代器1参数
var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
// 生成迭代器2参数
var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : '';
// 检查组件是否需要显式key的警告
if (state.maybeComponent(el) &&
el.tag !== 'slot' &&
el.tag !== 'template' &&
!el.key
) {
// 发出缺少key的警告提示
state.warn(
"<" + (el.tag) + " v-for=\"" + alias + " in " + exp + "\">: component lists rendered with " +
"v-for should have explicit keys. " +
"See https://vuejs.org/guide/list.html#key for more info.",
el.rawAttrsMap['v-for'],
true /* tip */
);
}
// 标记已处理避免递归
el.forProcessed = true;
// 返回生成的循环函数代码
return (altHelper || '_l') + "((" + exp + ")," +
"function(" + alias + iterator1 + iterator2 + "){" +
"return " + ((altGen || genElement)(el, state)) +
'})'
}
// 生成元素的数据对象
function genData$2 (el, state) {
// 初始化数据对象字符串
var data = '{';
// 首先生成指令相关代码
var dirs = genDirectives(el, state);
// 如果有指令则添加到数据对象
if (dirs) { data += dirs + ','; }
// 处理key属性
if (el.key) {
data += "key:" + (el.key) + ",";
}
// 处理ref属性
if (el.ref) {
data += "ref:" + (el.ref) + ",";
}
// 处理refInFor标记
if (el.refInFor) {
data += "refInFor:true,";
}
// 处理pre标记
if (el.pre) {
data += "pre:true,";
}
// 记录组件原始标签名
if (el.component) {
data += "tag:\"" + (el.tag) + "\",";
}
// 执行模块的数据生成函数
for (var i = 0; i < state.dataGenFns.length; i++) {
data += state.dataGenFns[i](el);
}
// 处理普通属性
if (el.attrs) {
data += "attrs:" + (genProps(el.attrs)) + ",";
}
// 处理DOM属性
if (el.props) {
data += "domProps:" + (genProps(el.props)) + ",";
}
// 处理事件处理器
if (el.events) {
data += (genHandlers(el.events, false)) + ",";
}
// 处理原生事件
if (el.nativeEvents) {
data += (genHandlers(el.nativeEvents, true)) + ",";
}
// 处理非作用域的slot
if (el.slotTarget && !el.slotScope) {
data += "slot:" + (el.slotTarget) + ",";
}
// 处理作用域插槽
if (el.scopedSlots) {
data += (genScopedSlots(el, el.scopedSlots, state)) + ",";
}
// 处理组件v-model
if (el.model) {
data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
}
// 处理内联模板
if (el.inlineTemplate) {
var inlineTemplate = genInlineTemplate(el, state);
if (inlineTemplate) {
data += inlineTemplate + ",";
}
}
// 清理末尾逗号并闭合对象
data = data.replace(/,$/, '') + '}';
// 处理动态绑定属性
if (el.dynamicAttrs) {
data = "_b(" + data + ",\"" + (el.tag) + "\"," + (genProps(el.dynamicAttrs)) + ")";
}
// 执行数据包装函数
if (el.wrapData) {
data = el.wrapData(data);
}
// 执行监听器包装函数
if (el.wrapListeners) {
data = el.wrapListeners(data);
}
return data
}
// 生成指令数组代码
function genDirectives (el, state) {
var dirs = el.directives;
if (!dirs) { return }
// 初始化指令数组
var res = 'directives:[';
var hasRuntime = false;
var i, l, dir, needRuntime;
// 遍历所有指令
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i];
needRuntime = true;
// 获取指令生成函数
var gen = state.directives[dir.name];
if (gen) {
// 执行指令编译时处理
needRuntime = !!gen(el, dir, state.warn);
}
if (needRuntime) {
hasRuntime = true;
// 生成指令对象代码
res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:" + (dir.isDynamicArg ? dir.arg : ("\"" + (dir.arg) + "\""))) : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
}
}
if (hasRuntime) {
// 返回有效的指令数组
return res.slice(0, -1) + ']'
}
}
// 生成内联模板代码
function genInlineTemplate (el, state) {
// 获取第一个子元素作为模板
var ast = el.children[0];
// 验证子元素数量
if (el.children.length !== 1 || ast.type !== 1) {
state.warn(
'Inline-template components must have exactly one child element.',
{ start: el.start }
);
}
if (ast && ast.type === 1) {
// 生成内联模板的渲染函数
var inlineRenderFns = generate(ast, state.options);
return ("inlineTemplate:{render:function(){" + (inlineRenderFns.render) + "},staticRenderFns:[" + (inlineRenderFns.staticRenderFns.map(function (code) { return ("function(){" + code + "}"); }).join(',') + "]}")
}
}
// 生成作用域插槽代码
function genScopedSlots (
el,
slots,
state
) {
// 判断是否需要强制更新
var needsForceUpdate = el.for || Object.keys(slots).some(function (key) {
var slot = slots[key];
return (
slot.slotTargetDynamic ||
slot.if ||
slot.for ||
containsSlotChild(slot)
});
// 判断是否需要唯一key
var needsKey = !!el.if;
// 检查父级作用域是否需要强制更新
if (!needsForceUpdate) {
var parent = el.parent;
while (parent) {
if (
(parent.slotScope && parent.slotScope !== emptySlotScopeToken) ||
parent.for
) {
needsForceUpdate = true;
break
}
if (parent.if) {
needsKey = true;
}
parent = parent.parent;
}
}
// 生成所有插槽代码
var generatedSlots = Object.keys(slots)
.map(function (key) { return genScopedSlot(slots[key], state); })
.join(',');
// 返回统一处理函数调用
return ("scopedSlots:_u([" + generatedSlots + "]" + (needsForceUpdate ? ",null,true" : "") + (!needsForceUpdate && needsKey ? (",null,false," + (hash(generatedSlots))) : "") + ")")
}
// 生成字符串的哈希值
function hash(str) {
var hash = 5381;
var i = str.length;
while(i) {
hash = (hash * 33) ^ str.charCodeAt(--i);
}
return hash >>> 0
}
// 检查元素是否包含slot子元素
function containsSlotChild (el) {
if (el.type === 1) {
if (el.tag === 'slot') {
return true
}
return el.children.some(containsSlotChild)
}
return false
}
// 生成单个作用域插槽代码
function genScopedSlot (
el,
state
) {
// 检查旧语法格式
var isLegacySyntax = el.attrsMap['slot-scope'];
// 处理带if条件的插槽
if (el.if && !el.ifProcessed && !isLegacySyntax) {
return genIf(el, state, genScopedSlot, "null")
}
// 处理带for循环的插槽
if (el.for && !el.forProcessed) {
return genFor(el, state, genScopedSlot)
}
// 生成插槽作用域参数
var slotScope = el.slotScope === emptySlotScopeToken
? ""
: String(el.slotScope);
// 生成插槽渲染函数
var fn = "function(" + slotScope + "){" +
"return " + (el.tag === 'template'
? el.if && isLegacySyntax
? ("(" + (el.if) + ")?" + (genChildren(el, state) || 'undefined') + ":undefined")
: genChildren(el, state) || 'undefined'
: genElement(el, state)) + "}";
// 处理反向代理配置
var reverseProxy = slotScope ? "" : ",proxy:true";
return ("{key:" + (el.slotTarget || "\"default\"") + ",fn:" + fn + reverseProxy + "}")
}
// 生成子元素代码
function genChildren (
el,
state,
checkSkip,
altGenElement,
altGenNode
) {
var children = el.children;
if (children.length) {
var el$1 = children[0];
// 优化单个v-for子元素的情况
if (children.length === 1 &&
el$1.for &&
el$1.tag !== 'template' &&
el$1.tag !== 'slot'
) {
var normalizationType = checkSkip
? state.maybeComponent(el$1) ? ",1" : ",0"
: "";
return ("" + ((altGenElement || genElement)(el$1, state)) + normalizationType)
}
// 获取子元素规范化类型
var normalizationType$1 = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0;
var gen = altGenNode || genNode;
// 生成子元素数组代码
return ("[" + (children.map(function (c) { return gen(c, state); }).join(',') + "]" +
// 确定子节点数组需要的规范化类型
// 返回值:
// 0: 不需要规范化
// 1: 需要简单规范化(可能存在一级嵌套数组)
// 2: 需要完全规范化
function getNormalizationType (
children, // 子节点数组
maybeComponent // 判断是否为组件的函数
) {
var res = 0; // 初始化规范化类型
for (var i = 0; i < children.length; i++) { // 遍历所有子节点
var el = children[i];
if (el.type !== 1) { // 只处理元素节点
continue
}
// 检查需要完全规范化的条件
if (needsNormalization(el) || // 元素本身需要规范化
(el.ifConditions && el.ifConditions.some(function (c) { return needsNormalization(c.block); }))) { // 条件分支中的元素需要规范化
res = 2; // 标记为完全规范化
break // 提前终止循环
}
// 检查需要简单规范化的条件
if (maybeComponent(el) || // 元素可能是组件
(el.ifConditions && el.ifConditions.some(function (c) { return maybeComponent(c.block); }))) { // 条件分支中的元素可能是组件
res = 1; // 标记为简单规范化
}
}
return res // 返回规范化类型
}
// 判断元素是否需要规范化处理
function needsNormalization (el) {
return el.for !== undefined || // 包含 v-for 指令
el.tag === 'template' || // template 标签
el.tag === 'slot' // slot 标签
}
// 生成节点代码的入口函数
function genNode (node, state) {
if (node.type === 1) { // 元素节点
return genElement(node, state)
} else if (node.type === 3 && node.isComment) { // 注释节点
return genComment(node)
} else { // 文本节点
return genText(node)
}
}
// 生成文本节点代码
function genText (text) {
return ("_v(" + (text.type === 2 // 判断是否动态文本
? text.expression // 直接使用表达式(已用 _s() 包装)
: transformSpecialNewlines(JSON.stringify(text.text))) + ")") // 处理特殊换行符
}
// 生成注释节点代码
function genComment (comment) {
return ("_e(" + (JSON.stringify(comment.text)) + ")") // 使用 _e 创建空注释
}
// 生成插槽代码
function genSlot (el, state) {
var slotName = el.slotName || '"default"'; // 获取插槽名称
var children = genChildren(el, state); // 生成子节点代码
var res = "_t(" + slotName + (children ? ("," + children) : ''); // 基础插槽代码
// 处理插槽属性和动态属性
var attrs = el.attrs || el.dynamicAttrs
? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(function (attr) {
return {
name: camelize(attr.name), // 属性名驼峰化
value: attr.value, // 属性值
dynamic: attr.dynamic // 是否动态属性
};
}))
: null;
var bind$$1 = el.attrsMap['v-bind']; // 获取 v-bind 绑定
// 处理属性和绑定的参数占位
if ((attrs || bind$$1) && !children) {
res += ",null"; // 子节点参数占位
}
if (attrs) {
res += "," + attrs; // 添加属性参数
}
if (bind$$1) {
res += (attrs ? '' : ',null') + "," + bind$$1; // 添加动态绑定参数
}
return res + ')' // 闭合函数调用
}
// 生成组件代码componentName 参数用于规避类型检查)
function genComponent (
componentName, // 组件名
el, // 元素节点
state // 编译状态
) {
// 生成子节点代码(内联模板时跳过)
var children = el.inlineTemplate ? null : genChildren(el, state, true);
return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")") // 生成组件创建函数
}
// 生成属性对象代码
function genProps (props) {
var staticProps = ""; // 静态属性字符串
var dynamicProps = ""; // 动态属性字符串
// 遍历所有属性
for (var i = 0; i < props.length; i++) {
var prop = props[i];
var value = transformSpecialNewlines(prop.value); // 处理特殊换行符
if (prop.dynamic) { // 动态属性
dynamicProps += (prop.name) + "," + value + ","; // 收集动态属性
} else { // 静态属性
staticProps += "\"" + (prop.name) + "\":" + value + ","; // 收集静态属性
}
}
staticProps = "{" + (staticProps.slice(0, -1)) + "}"; // 包裹静态属性为对象
if (dynamicProps) { // 存在动态属性时
return ("_d(" + staticProps + ",[" + (dynamicProps.slice(0, -1)) + "])") // 合并静态和动态属性
} else {
return staticProps // 直接返回静态属性
}
}
// 转换特殊换行符(解决 #3895, #4268 问题)
function transformSpecialNewlines (text) {
return text
.replace(/\u2028/g, '\\u2028') // 替换行分隔符
.replace(/\u2029/g, '\\u2029') // 替换段落分隔符
}
// 匹配不允许在表达式中使用的关键字
var prohibitedKeywordRE = new RegExp('\\b' + (
'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
'super,throw,while,yield,delete,export,import,return,switch,default,' +
'extends,finally,continue,debugger,function,arguments'
).split(',').join('\\b|\\b') + '\\b');
// 匹配不能作为属性名的一元操作符
var unaryOperatorsRE = new RegExp('\\b' + (
'delete,typeof,void'
).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)');
// 用于移除表达式中的字符串内容
var stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g;
// 检测模板中的错误表达式
function detectErrors (ast, warn) {
if (ast) { // 当AST存在时进行检查
checkNode(ast, warn); // 递归检查所有节点
}
}
// 递归检查AST节点
function checkNode (node, warn) {
if (node.type === 1) { // 元素节点类型
for (var name in node.attrsMap) { // 遍历所有属性
if (dirRE.test(name)) { // 检查是否指令属性
var value = node.attrsMap[name];
if (value) {
var range = node.rawAttrsMap[name];
if (name === 'v-for') { // 处理v-for指令
checkFor(node, ("v-for=\"" + value + "\""), warn, range);
} else if (name === 'v-slot' || name[0] === '#') { // 处理插槽指令
checkFunctionParameterExpression(value, (name + "=\"" + value + "\""), warn, range);
} else if (onRE.test(name)) { // 处理事件绑定
checkEvent(value, (name + "=\"" + value + "\""), warn, range);
} else { // 其他指令表达式检查
checkExpression(value, (name + "=\"" + value + "\""), warn, range);
}
}
}
}
if (node.children) { // 递归检查子节点
for (var i = 0; i < node.children.length; i++) {
checkNode(node.children[i], warn);
}
}
} else if (node.type === 2) { // 文本插值类型
checkExpression(node.expression, node.text, warn, node);
}
}
// 检查事件表达式中的非法操作符
function checkEvent (exp, text, warn, range) {
var stripped = exp.replace(stripStringRE, ''); // 移除字符串内容
var keywordMatch = stripped.match(unaryOperatorsRE); // 匹配一元操作符
if (keywordMatch && stripped.charAt(keywordMatch.index - 1) !== '$') { // 检查$前缀
warn( // 发出警告
"avoid using JavaScript unary operator as property name: " +
"\"" + (keywordMatch[0]) + "\" in expression " + (text.trim()),
range
);
}
checkExpression(exp, text, warn, range); // 常规表达式检查
}
// 检查v-for指令相关标识符
function checkFor (node, text, warn, range) {
checkExpression(node.for || '', text, warn, range); // 检查for表达式
checkIdentifier(node.alias, 'v-for alias', text, warn, range); // 检查别名
checkIdentifier(node.iterator1, 'v-for iterator', text, warn, range); // 检查迭代器1
checkIdentifier(node.iterator2, 'v-for iterator', text, warn, range); // 检查迭代器2
}
// 验证标识符合法性
function checkIdentifier (
ident, // 标识符名称
type, // 标识符类型描述
text, // 原始文本
warn, // 警告函数
range // 源码位置范围
) {
if (typeof ident === 'string') { // 仅处理字符串类型
try {
new Function(("var " + ident + "=_")); // 通过函数构造验证合法性
} catch (e) {
warn(("invalid " + type + " \"" + ident + "\" in expression: " + (text.trim())), range);
}
}
}
// 验证表达式合法性
function checkExpression (exp, text, warn, range) {
try {
new Function(("return " + exp)); // 尝试解析表达式
} catch (e) {
var keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE);
if (keywordMatch) { // 匹配到禁止关键字
warn(
"avoid using JavaScript keyword as property name: " +
"\"" + (keywordMatch[0]) + "\"\n Raw expression: " + (text.trim()),
range
);
} else { // 其他解析错误
warn(
"invalid expression: " + (e.message) + " in\n\n" +
" " + exp + "\n\n" +
" Raw expression: " + (text.trim()) + "\n",
range
);
}
}
}
// 检查函数参数表达式
function checkFunctionParameterExpression (exp, text, warn, range) {
try {
new Function(exp, ''); // 验证参数表达式
} catch (e) {
warn( // 参数表达式错误警告
"invalid function parameter expression: " + (e.message) + " in\n\n" +
" " + exp + "\n\n" +
" Raw expression: " + (text.trim()) + "\n",
range
);
}
}
// 生成代码错误位置标记框架
var range = 2; // 显示错误上下文的行数范围
function generateCodeFrame (
source, // 原始代码
start, // 错误起始位置
end // 错误结束位置
) {
if ( start === void 0 ) start = 0; // 默认起始位置
if ( end === void 0 ) end = source.length; // 默认结束位置
var lines = source.split(/\r?\n/); // 按行分割代码
var count = 0; // 字符累计计数器
var res = []; // 结果数组
// 遍历行号定位错误位置
for (var i = 0; i < lines.length; i++) {
count += lines[i].length + 1; // 累加行长度(包含换行符)
if (count >= start) { // 定位到错误起始行
// 生成错误上下文范围行
for (var j = i - range; j <= i + range || end > count; j++) {
if (j < 0 || j >= lines.length) { continue } // 跳过无效行号
// 添加行号和代码内容
res.push(("" + (j + 1) + (repeat$1(" ", 3 - String(j + 1).length)) + "| " + (lines[j]));
var lineLength = lines[j].length;
if (j === i) { // 错误所在行
// 生成错误位置下划线
var pad = start - (count - lineLength) + 1;
var length = end > count ? lineLength - pad : end - start;
res.push(" | " + repeat$1(" ", pad) + repeat$1("^", length));
} else if (j > i) { // 后续行处理
if (end > count) { // 跨行错误处理
var length$1 = Math.min(end - count, lineLength);
res.push(" | " + repeat$1("^", length$1));
}
count += lineLength + 1; // 更新字符计数器
}
}
break // 结束循环
}
}
return res.join('\n') // 拼接成字符串
}
// 生成重复字符串的工具函数
function repeat$1 (str, n) {
var result = '';
if (n > 0) { // 使用位运算优化循环
while (true) { // 通过右移操作实现快速重复
if (n & 1) { result += str; } // 奇数追加字符串
n >>>= 1; // 无符号右移
if (n <= 0) { break }
str += str; // 字符串翻倍
}
}
return result
}
// 创建函数并处理错误的工具函数
function createFunction (code, errors) {
try {
return new Function(code) // 尝试创建函数
} catch (err) {
errors.push({ err: err, code: code }); // 收集错误信息
return noop // 返回空函数
}
}
// 创建编译到函数的工厂函数
function createCompileToFunctionFn (compile) {
var cache = Object.create(null); // 创建编译缓存对象
return function compileToFunctions (
template, // 模板字符串
options, // 编译选项
vm // Vue实例
) {
options = extend({}, options); // 扩展选项对象
var warn$$1 = options.warn || warn; // 获取警告函数
delete options.warn; // 移除选项中的warn
/* istanbul ignore if */ // 忽略代码覆盖率
{
// 检测内容安全策略(CSP)限制
try {
new Function('return 1'); // 尝试创建简单函数
} catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) { // 匹配CSP错误
warn$$1( // 发出CSP警告
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
'templates into render functions.'
);
}
}
}
// 检查缓存是否存在
var key = options.delimiters
? String(options.delimiters) + template // 带分隔符的缓存键
: template; // 普通模板缓存键
if (cache[key]) {
return cache[key] // 返回缓存结果
}
// 执行编译过程
var compiled = compile(template, options);
// 处理编译错误和提示
{
if (compiled.errors && compiled.errors.length) { // 存在错误
if (options.outputSourceRange) { // 支持输出源码位置
compiled.errors.forEach(function (e) {
warn$$1( // 带代码框架的错误提示
"Error compiling template:\n\n" + (e.msg) + "\n\n" +
generateCodeFrame(template, e.start, e.end),
vm
);
});
} else { // 简单错误列表提示
warn$$1(
"Error compiling template:\n\n" + template + "\n\n" +
compiled.errors.map(function (e) { return ("- " + e); }).join('\n') + '\n',
vm
);
}
}
if (compiled.tips && compiled.tips.length) { // 处理提示信息
if (options.outputSourceRange) { // 带源码位置的提示
compiled.tips.forEach(function (e) { return tip(e.msg, vm); });
} else { // 普通提示
compiled.tips.forEach(function (msg) { return tip(msg, vm); });
}
}
}
// 将编译结果转换为函数
var res = {}; // 结果对象
var fnGenErrors = []; // 函数生成错误收集
res.render = createFunction(compiled.render, fnGenErrors); // 生成渲染函数
res.staticRenderFns = compiled.staticRenderFns.map(function (code) { // 生成静态渲染函数数组
return createFunction(code, fnGenErrors)
});
// 处理函数生成阶段的错误
/* istanbul ignore if */ // 忽略代码覆盖率
{
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
warn$$1( // 编译器内部错误警告
"Failed to generate render function:\n\n" +
fnGenErrors.map(function (ref) {
var err = ref.err;
var code = ref.code;
return ((err.toString()) + " in\n\n" + code + "\n");
}).join('\n'),
vm
);
}
}
return (cache[key] = res) // 缓存并返回结果
}
}
// 创建编译器生成器函数
function createCompilerCreator (baseCompile) {
// 返回创建编译器的函数
return function createCompiler (baseOptions) {
// 核心编译函数
function compile (
template,
options
) {
// 继承基础配置创建最终配置
var finalOptions = Object.create(baseOptions);
// 存储错误和提示的数组
var errors = [];
var tips = [];
// 基础警告函数
var warn = function (msg, range, tip) {
(tip ? tips : errors).push(msg);
};
// 合并用户自定义选项
if (options) {
// 处理需要源码位置信息的情况
if (options.outputSourceRange) {
// 计算模板前导空格长度
var leadingSpaceLength = template.match(/^\s*/)[0].length;
// 增强版警告函数(带源码位置)
warn = function (msg, range, tip) {
var data = { msg: msg };
if (range) { // 转换源码位置偏移量
if (range.start != null) {
data.start = range.start + leadingSpaceLength;
}
if (range.end != null) {
data.end = range.end + leadingSpaceLength;
}
}
(tip ? tips : errors).push(data);
};
}
// 合并自定义模块
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules);
}
// 合并自定义指令
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives || null),
options.directives
);
}
// 复制其他选项
for (var key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key];
}
}
}
// 注入警告函数到配置
finalOptions.warn = warn;
// 执行基础编译流程
var compiled = baseCompile(template.trim(), finalOptions);
// 开发环境下检测AST错误
{
detectErrors(compiled.ast, warn);
}
// 附加错误和提示到编译结果
compiled.errors = errors;
compiled.tips = tips;
return compiled
}
// 返回编译器对象
return {
compile: compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
// 创建默认编译器(使用标准解析/优化/生成器)
var createCompiler = createCompilerCreator(function baseCompile (
template,
options
) {
// 解析模板生成AST
var ast = parse(template.trim(), options);
// 执行优化(默认开启)
if (options.optimize !== false) {
optimize(ast, options);
}
// 生成渲染代码
var code = generate(ast, options);
return {
ast: ast,
render: code.render, // 渲染函数代码
staticRenderFns: code.staticRenderFns // 静态渲染函数数组
}
});
// 创建基础编译器实例
var ref$1 = createCompiler(baseOptions);
// 解构编译方法
var compile = ref$1.compile;
var compileToFunctions = ref$1.compileToFunctions;
// 检测浏览器属性值编码行为
var div; // 用于DOM测试的临时元素
function getShouldDecode (href) {
div = div || document.createElement('div');
// 创建包含换行符的属性值测试节点
div.innerHTML = href ? "<a href=\"\n\"/>" : "<div a=\"\n\"/>";
// 检查是否编码为&#10;
return div.innerHTML.indexOf('&#10;') > 0
}
// 记录不同场景下的编码需求
var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false; // 常规属性编码检测
var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false; // href属性编码检测
// 带缓存的模板获取函数
var idToTemplate = cached(function (id) {
var el = query(id); // 查询DOM元素
return el && el.innerHTML // 返回元素内容
});
// 重写Vue的$mount方法
var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (
el,
hydrating
) {
// 标准化元素查询
el = el && query(el);
// 禁止挂载到body/html元素
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
warn(
"Do not mount Vue to <html> or <body> - mount to normal elements instead."
);
return this
}
// 处理渲染函数/模板/元素关系
var options = this.$options;
if (!options.render) { // 无render函数时处理模板
var template = options.template;
if (template) { // 处理template选项
if (typeof template === 'string') { // 字符串模板
if (template.charAt(0) === '#') { // ID选择器
template = idToTemplate(template);
/* istanbul ignore if */
if (!template) { // 模板元素不存在警告
warn(
("Template element not found or is empty: " + (options.template)),
this
);
}
}
} else if (template.nodeType) { // DOM元素节点
template = template.innerHTML;
} else { // 无效模板类型
{
warn('invalid template option:' + template, this);
}
return this
}
} else if (el) { // 使用挂载元素的outerHTML
template = getOuterHTML(el);
}
if (template) { // 编译模板为渲染函数
/* istanbul ignore if */
if (config.performance && mark) { // 性能标记
mark('compile');
}
// 执行模板编译
var ref = compileToFunctions(template, {
outputSourceRange: "development" !== 'production', // 开发环境输出源码位置
shouldDecodeNewlines: shouldDecodeNewlines, // 换行编码处理
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters, // 自定义分隔符
comments: options.comments // 保留注释
}, this);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
// 注入渲染函数到选项
options.render = render;
options.staticRenderFns = staticRenderFns;
/* istanbul ignore if */
if (config.performance && mark) { // 记录编译耗时
mark('compile end');
measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
}
}
}
// 调用原始挂载方法
return mount.call(this, el, hydrating)
};
// 获取元素的outerHTML兼容IE SVG
function getOuterHTML (el) {
if (el.outerHTML) { // 标准浏览器支持
return el.outerHTML
} else { // 兼容处理
var container = document.createElement('div');
container.appendChild(el.cloneNode(true));
return container.innerHTML
}
}
// 暴露编译方法到Vue
Vue.compile = compileToFunctions;
return Vue;
}));