上一篇简单的介绍了下标签是怎么被解析的,但是没有说属性是怎么处理的,这一部分来看如何解析属性
解析属性
function parseAttribute(
context: ParserContext,
nameSet: Set<string>
): AttributeNode | DirectiveNode {
__TEST__ && assert(/^[^\t\r\n\f />]/.test(context.source))
// Name. 解析属性名
const start = getCursor(context)
const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)! // 匹配属性
const name = match[0]
// 如果已经存在,抛出错误
if (nameSet.has(name)) {
emitError(context, ErrorCodes.DUPLICATE_ATTRIBUTE)
}
// 添加已解析的属性
nameSet.add(name)
// 如果属性为=,抛出错误
if (name[0] === '=') {
emitError(context, ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME)
}
{
const pattern = /["'<]/g
let m: RegExpExecArray | null
// 如果属性出现意外字符
while ((m = pattern.exec(name))) {
emitError(
context,
ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
m.index
)
}
}
advanceBy(context, name.length)
// Value 解析属性值
let value: AttributeValue = undefined
if (/^[\t\r\n\f ]*=/.test(context.source)) {
advanceSpaces(context)
advanceBy(context, 1)
advanceSpaces(context)
value = parseAttributeValue(context)
// 如果属性不存在,抛出错误
if (!value) {
emitError(context, ErrorCodes.MISSING_ATTRIBUTE_VALUE)
}
}
const loc = getSelection(context, start)
// 解析指令
if (!context.inVPre && /^(v-|:|@|#)/.test(name)) {
const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(
name
)!
// 获取指令名称 如果是:开头为bind 是@为on 否则为slot
let dirName =
match[1] ||
(startsWith(name, ':') ? 'bind' : startsWith(name, '@') ? 'on' : 'slot')
let arg: ExpressionNode | undefined
if (match[2]) {
const isSlot = dirName === 'slot'
const startOffset = name.lastIndexOf(match[2]) // 获取开始位置
const loc = getSelection(
context,
getNewPosition(context, start, startOffset),
getNewPosition(
context,
start,
startOffset + match[2].length + ((isSlot && match[3]) || '').length
)
)
let content = match[2]
let isStatic = true // 是否静态
if (content.startsWith('[')) {
isStatic = false
// 如果找不到] 抛出错误:x缺少动态指令结尾
if (!content.endsWith(']')) {
emitError(
context,
ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END
)
}
content = content.substr(1, content.length - 2)
} else if (isSlot) {
// #1241 special case for v-slot: vuetify relies extensively on slot
// names containing dots. v-slot doesn't have any modifiers and Vue 2.x
// supports such usage so we are keeping it consistent with 2.x.
content += match[3] || ''
}
arg = {
type: NodeTypes.SIMPLE_EXPRESSION,
content,
isStatic,
constType: isStatic
? ConstantTypes.CAN_STRINGIFY
: ConstantTypes.NOT_CONSTANT,
loc
}
}
// 是引号
if (value && value.isQuoted) {
const valueLoc = value.loc
valueLoc.start.offset++
valueLoc.start.column++
valueLoc.end = advancePositionWithClone(valueLoc.start, value.content)
valueLoc.source = valueLoc.source.slice(1, -1)
}
const modifiers = match[3] ? match[3].substr(1).split('.') : []
// 2.x compat v-bind:foo.sync -> v-model:foo
if (__COMPAT__ && dirName === 'bind' && arg) {
if (
modifiers.includes('sync') &&
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
context,
loc,
arg.loc.source
)
) {
dirName = 'model'
modifiers.splice(modifiers.indexOf('sync'), 1)
}
if (__DEV__ && modifiers.includes('prop')) {
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_BIND_PROP,
context,
loc
)
}
}
return {
type: NodeTypes.DIRECTIVE, // 7
name: dirName,
exp: value && {
type: NodeTypes.SIMPLE_EXPRESSION,
content: value.content,
isStatic: false,
// Treat as non-constant by default. This can be potentially set to
// other values by `transformExpression` to make it eligible for hoisting.
// 将在transform阶段去做转换
constType: ConstantTypes.NOT_CONSTANT,
loc: value.loc
},
arg,
modifiers,
loc
}
}
return {
type: NodeTypes.ATTRIBUTE, // 6
name,
value: value && {
type: NodeTypes.TEXT,
content: value.content,
loc: value.loc
},
loc
}
}
解析属性值
function parseAttributeValue(context: ParserContext): AttributeValue {
const start = getCursor(context) // 获取上次解析的值
let content: string
const quote = context.source[0]
const isQuoted = quote === `"` || quote === `'`
// 如果是" | '
if (isQuoted) {
// Quoted value. 前进1位
advanceBy(context, 1)
// 获取结束引号的位置
const endIndex = context.source.indexOf(quote)
if (endIndex === -1) {
content = parseTextData(
context,
context.source.length,
TextModes.ATTRIBUTE_VALUE
)
} else {
// 拿到属性值
content = parseTextData(context, endIndex, TextModes.ATTRIBUTE_VALUE)
// 前进1位
advanceBy(context, 1)
}
} else {
// Unquoted
const match = /^[^\t\r\n\f >]+/.exec(context.source)
if (!match) {
return undefined
}
const unexpectedChars = /["'<=`]/g
let m: RegExpExecArray | null
while ((m = unexpectedChars.exec(match[0]))) {
emitError(
context,
ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
m.index
)
}
content = parseTextData(context, match[0].length, TextModes.ATTRIBUTE_VALUE)
}
return { content, isQuoted, loc: getSelection(context, start) }
}
获取解析到的key和属性
function parseAttributes(
context: ParserContext,
type: TagType
): (AttributeNode | DirectiveNode)[] {
// debugger
const props = []
const attributeNames = new Set<string>()
while (
context.source.length > 0 &&
!startsWith(context.source, '>') &&
!startsWith(context.source, '/>')
) {
if (startsWith(context.source, '/')) {
emitError(context, ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG)
advanceBy(context, 1)
advanceSpaces(context)
continue
}
// 是否为结束
if (type === TagType.End) {
emitError(context, ErrorCodes.END_TAG_WITH_ATTRIBUTES)
}
const attr = parseAttribute(context, attributeNames) // 拿到解析的属性
if (type === TagType.Start) {
props.push(attr) // 添加
}
// 如果属性缺少空格
if (/^[^\t\r\n\f />]/.test(context.source)) {
emitError(context, ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES)
}
advanceSpaces(context)
}
return props
}