Vue 源码
你想要的 vue 源码分析 · 24
解析模板,生成 AST 的过程 parse 的目的 把 template 解析成 AST,AST 是一个对象,parse 过程包括,从 option 中取参数,parseHTML,retrun rootElement parseHTML 对 template 进行正则匹配,每匹配到一个标签或属性都会 advance,并剔除已经匹配的字符串,在匹配的过程中,解析出开始标签,闭合标签,属性(attrs…
解析模板,生成 AST 的过程 parse 的目的 把 template 解析成 AST,AST 是一个对象,parse 过程包括,从 option 中取参数,parseHTML,retrun rootElement parseHTML 对 template 进行正则匹配,每匹配到一个标签或属性都会 advance,并剔除已经匹配的字符串,在匹配的过程中,解析出开始标签,闭合标签,属性(attrs…
解析模板,生成 AST 的过程
// src/compiler/parser/index.js
// 接收两个参数,template是模板字符串,options是平台相关的参数
export function parse(template: string, options: CompilerOptions): ASTElement | void {
// 从 options 中获取方法和配置
getFnsAndConfigFromOptions(options)
/*
// 从 options 中获取方法和配置
warn = options.warn || baseWarn
platformIsPreTag = options.isPreTag || no
platformMustUseProp = options.mustUseProp || no
platformGetTagNamespace = options.getTagNamespace || no
transforms = pluckModuleFunction(options.modules, 'transformNode')
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
*/
delimiters = options.delimiters
parseHTML(template, {
start(tag, attrs, unary) {
// 创建ast
// 每一个ast都是一个js对象,有type 表示 AST 元素类型,tag 表示标签名,attrsList 表示属性列表,attrsMap 表示属性映射表,parent 表示父的 AST 元素,children 表示子 AST 元素集合
let element = createASTElement(tag, attrs)
// 处理ast:扩展ast属性
// 拿到v-for指定,解析出for,alias等属性到ast对象上
// 拿到v-if v-else v-else-if指令等
processElement(element)
// ast管理
// 如果有currentParent,则把ast push到currentParent.children中
// 如果当前不是一个一元标签,需要把ast push到stack中,并把ast元素赋给currentParent
treeManagement()
},
end() {
// 处理了尾部空格问题,把stack弹出一个出栈,把stack最后一个元素赋给currentParent,保证了遇到闭合标签,正确更新stack
treeManagement()
// 更新状态
closeElement()
},
// 处理文本内容,多个表达式中间的字符串
// 根据分隔符{{}},匹配到表达式
chars(text: string) {
handleText()
createChildrenASTOfText()
},
comment(text: string) {
createChildrenASTOfComment()
}
})
// parseHTML伪代码
/*
// 循环解析template,用正则匹配,直到整个template被解析完毕,匹配过程中利用advance不断前进整个模板字符串,直到字符串末尾
export function parseHTML (html, options) {
let lastTag
while (html) {
if (!lastTag || !isPlainTextElement(lastTag)){
let textEnd = html.indexOf('<')
if (textEnd === 0) {
if(matchComment) {
advance(commentLength)
continue
}
if(matchDoctype) {
advance(doctypeLength)
continue
}
if(matchEndTag) {
advance(endTagLength)
parseEndTag()
continue
}
if(matchStartTag) {
parseStartTag()
handleStartTag()
continue
}
}
handleText()
advance(textLength)
} else {
handlePlainTextElement()
parseEndTag()
}
}
}
*/
return astRootElement
}
// src/platforms/web/compiler/options
import { isPreTag, mustUseProp, isReservedTag, getTagNamespace } from '../util/index'
import modules from './modules/index'
import directives from './directives/index'
import { genStaticKeys } from 'shared/util'
import { isUnaryTag, canBeLeftOpenTag } from './util'
export const baseOptions: CompilerOptions = {
expectHTML: true,
modules,
directives,
isPreTag,
isUnaryTag,
mustUseProp,
canBeLeftOpenTag,
isReservedTag,
getTagNamespace,
staticKeys: genStaticKeys(modules)
}
// 匹配到注释节点,文档类型节点,开闭合标签等
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
const startTagClose = /^\s*(\/?)>/
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
const doctype = /^<!DOCTYPE [^>]+>/i
const comment = /^<!\--/
const conditionalComment = /^<!\[/
function advance(n) {
index += n
html = html.substring(n)
}
if (comment.test(html)) {
const commentEnd = html.indexOf('-->')
if (commentEnd >= 0) {
if (options.shouldKeepComment) {
options.comment(html.substring(4, commentEnd))
}
advance(commentEnd + 3)
continue
}
}
if (conditionalComment.test(html)) {
const conditionalEnd = html.indexOf(']>')
if (conditionalEnd >= 0) {
advance(conditionalEnd + 2)
continue
}
}
const doctypeMatch = html.match(doctype)
if (doctypeMatch) {
advance(doctypeMatch[0].length)
continue
}
const startTagMatch = parseStartTag()
if (startTagMatch) {
handleStartTag(startTagMatch)
if (shouldIgnoreFirstNewline(lastTag, html)) {
advance(1)
}
continue
}
首先通过 parseStartTag 解析开始标签
function parseStartTag() {
// 先通过正则startTagOpen,匹配到开始标签,
const start = html.match(startTagOpen)
if (start) {
// 定义match对象
const match = {
tagName: start[1],
attrs: [],
start: index
}
advance(start[0].length)
let end, attr
// 循环匹配开始标签中的属性,并添加到match.attrs中,直到匹配的开始标签闭合符结束
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
advance(attr[0].length)
match.attrs.push(attr)
}
// 如果匹配到闭合符,获取一元斜线符,前进到闭合符尾,并把当前索引赋给match.end
if (end) {
match.unarySlash = end[1]
advance(end[0].length)
match.end = index
return match
}
}
}
再通过 handleStartTag 对 match 做处理
function handleStartTag(match) {
const tagName = match.tagName
const unarySlash = match.unarySlash
if (expectHTML) {
if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
parseEndTag(lastTag)
}
if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
parseEndTag(tagName)
}
}
// 先判断开始标签是否一元标签,类似img,br
const unary = isUnaryTag(tagName) || !!unarySlash
// 对match.attrs进行处理
const l = match.attrs.length
const attrs = new Array(l)
for (let i = 0; i < l; i++) {
const args = match.attrs[i]
if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
if (args[3] === '') {
delete args[3]
}
if (args[4] === '') {
delete args[4]
}
if (args[5] === '') {
delete args[5]
}
}
const value = args[3] || args[4] || args[5] || ''
const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' ? options.shouldDecodeNewlinesForHref : options.shouldDecodeNewlines
attrs[i] = {
name: args[1],
value: decodeAttr(value, shouldDecodeNewlines)
}
}
// 如果不是一元标签,往stack push一个对象,并把tagName赋值给lastTag
if (!unary) {
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs })
lastTag = tagName
}
if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end)
}
}
// 先通过endTag匹配到闭合标签
const endTagMatch = html.match(endTag)
// 前进到标签末尾,执行parseEndTag
if (endTagMatch) {
const curIndex = index
advance(endTagMatch[0].length)
parseEndTag(endTagMatch[1], curIndex, index)
continue
}
parseEndTag
// handleStartTag压栈,parseEndTag出栈,且需要保证第一个和前端ending是正常的匹配,如<div><span></span></div>
// 如果是<div><span></div>,把<span>弹出,
function parseEndTag(tagName, start, end) {
let pos, lowerCasedTagName
if (start == null) start = index
if (end == null) end = index
if (tagName) {
lowerCasedTagName = tagName.toLowerCase()
}
if (tagName) {
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos].lowerCasedTag === lowerCasedTagName) {
break
}
}
} else {
pos = 0
}
if (pos >= 0) {
for (let i = stack.length - 1; i >= pos; i--) {
if (process.env.NODE_ENV !== 'production' && (i > pos || !tagName) && options.warn) {
options.warn(`tag <${stack[i].tag}> has no matching end tag.`)
}
if (options.end) {
options.end(stack[i].tag, start, end)
}
}
stack.length = pos
lastTag = pos && stack[pos - 1].tag
} else if (lowerCasedTagName === 'br') {
if (options.start) {
options.start(tagName, [], true, start, end)
}
} else if (lowerCasedTagName === 'p') {
if (options.start) {
options.start(tagName, [], false, start, end)
}
if (options.end) {
options.end(tagName, start, end)
}
}
}
let text, rest, next
// 如果textEnd大于等于0,则代表当前位置到textEnd都是文本,如果<是文本,就继续找文本结束的位置
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)
advance(textEnd)
}
// 如果textEnd小于0,则说明template解析完毕
if (textEnd < 0) {
text = html
html = ''
}
if (options.chars && text) {
options.chars(text)
}
// 每一个ast都是一个js对象
const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
// handle IE svg bug
/* istanbul ignore if */
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs)
}
let element: ASTElement = createASTElement(tag, attrs, currentParent)
if (ns) {
element.ns = ns
}
export function createASTElement(tag: string, attrs: Array<Attr>, parent: ASTElement | void): ASTElement {
return {
// 元素类型
type: 1,
// 标签名
tag,
// 属性列表
attrsList: attrs,
// 属性映射表
attrsMap: makeAttrsMap(attrs),
// 父ast
parent,
// 子ast
children: []
}
}
// 扩展ast元素的属性
if (isForbiddenTag(element) && !isServerRendering()) {
element.forbidden = true
process.env.NODE_ENV !== 'production' && warn('Templates should only be responsible for mapping the state to the ' + 'UI. Avoid placing tags with side-effects in your templates, such as ' + `<${tag}>` + ', as they will not be parsed.')
}
// apply pre-transforms
for (let i = 0; i < preTransforms.length; i++) {
element = preTransforms[i](element, options) || element
}
if (!inVPre) {
processPre(element)
if (element.pre) {
inVPre = true
}
}
if (platformIsPreTag(element.tag)) {
inPre = true
}
if (inVPre) {
processRawAttrs(element)
} else if (!element.processed) {
// 拿到v-for指定,解析出for,alias等属性到ast对象上
processFor(element)
// 拿到v-if v-else v-else-if指令等
processIf(element)
processOnce(element)
// element-scope stuff
processElement(element, options)
}
// 如果有currentParent,则把ast push到currentParent.children中
// 如果当前不是一个一元标签,需要把ast push到stack中,并把ast元素赋给currentParent
function checkRootConstraints(el) {
if (process.env.NODE_ENV !== 'production') {
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(`Cannot use <${el.tag}> as component root element because it may ` + 'contain multiple nodes.')
}
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce('Cannot use v-for on stateful component root element because ' + 'it renders multiple elements.')
}
}
}
// tree management
if (!root) {
root = element
checkRootConstraints(root)
} else if (!stack.length) {
// allow root elements with v-if, v-else-if and v-else
if (root.if && (element.elseif || element.else)) {
checkRootConstraints(element)
addIfCondition(root, {
exp: element.elseif,
block: element
})
} else if (process.env.NODE_ENV !== 'production') {
warnOnce(`Component template should contain exactly one root element. ` + `If you are using v-if on multiple elements, ` + `use v-else-if to chain them instead.`)
}
}
if (currentParent && !element.forbidden) {
if (element.elseif || element.else) {
processIfConditions(element, currentParent)
} else if (element.slotScope) {
// scoped slot
currentParent.plain = false
const name = element.slotTarget || '"default"'
;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
} else {
currentParent.children.push(element)
element.parent = currentParent
}
}
if (!unary) {
currentParent = element
stack.push(element)
} else {
closeElement(element)
}
// 处理了尾部空格问题,把stack弹出一个出栈,把stack最后一个元素赋给currentParent,保证了遇到闭合标签,正切更新stack
// remove trailing whitespace
const element = stack[stack.length - 1]
const lastNode = element.children[element.children.length - 1]
if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
element.children.pop()
}
// pop stack
stack.length -= 1
currentParent = stack[stack.length - 1]
closeElement(element)
// 更新状态
function closeElement(element) {
// check pre state
if (element.pre) {
inVPre = false
}
if (platformIsPreTag(element.tag)) {
inPre = false
}
// apply post-transforms
for (let i = 0; i < postTransforms.length; i++) {
postTransforms[i](element, options)
}
}
暂无评论
确认操作
请再次确认编辑图片
待上传图片 (剩余 0 张)
项目详情
确认评论邮箱
邀请制评论