ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] ***** ## 1 compiler\parser\ 模板解析目录 ### 1-1 目录文件 ~~~ compiler\parser entity-decoder.js ;生成节点文本 html-parser.js ;html解析 text-parser.js ;text解析 filter-parser.js ;filter解析 index.js ;入口解析 ~~~ ### 1-2 entity-decoder.js >[info] module ~~~ ;生成div元素 const decoder = document.createElement('div') ;生成节点并输出 export function decodeHTML (html) { decoder.innerHTML = html return decoder.textContent } ~~~ >[info] export ~~~ ;(导出)生成节点片段 export function decodeHTML (html) {} ~~~ ### 1-3 html-parser.js >[info] import ~~~ ;(导入)生成节点文本,基础工具,web工具 import { decodeHTML } from 'entities' import { makeMap } from 'shared/util' import { isNonPhrasingTag, canBeLeftOpenTag } from 'web/util/index' ~~~ >[info] module ~~~ ;属性正则表达式 const singleAttrIdentifier = /([^\s"'<>\/=]+)/ const singleAttrAssign = /=/ const singleAttrAssigns = [singleAttrAssign] const singleAttrValues = [ /"([^"]*)"+/.source, /'([^']*)'+/.source, /([^\s"'=<>`]+)/.source ] ;文本正则表达式 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 ;正则控制 let IS_REGEX_CAPTURING_BROKEN = false 'x'.replace(/x(.)?/g, function (m, g) { IS_REGEX_CAPTURING_BROKEN = g === '' }) ;特殊元素节点 const special = makeMap('script,style', true) ;缓存 const reCache = {} ;生成 function attrForHandler (handler) { const pattern = singleAttrIdentifier.source + '(?:\\s*(' + joinSingleAttrAssigns(handler) + ')' + '\\s*(?:' + singleAttrValues.join('|') + '))?' return new RegExp('^\\s*' + pattern) } ; function joinSingleAttrAssigns (handler) { return singleAttrAssigns.map(function (assign) { return '(?:' + assign.source + ')' }).join('|') } ;解析html export function parseHTML (html, handler) { const stack = [] const attribute = attrForHandler(handler) const expectHTML = handler.expectHTML const isUnaryTag = handler.isUnaryTag || (() => false) let last, prevTag, nextTag, lastTag while (html) { last = html // Make sure we're not in a script or style element if (!lastTag || !special(lastTag)) { const textEnd = html.indexOf('<') if (textEnd === 0) { // Comment: if (/^<!--/.test(html)) { const commentEnd = html.indexOf('-->') if (commentEnd >= 0) { html = html.substring(commentEnd + 3) prevTag = '' continue } } // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment if (/^<!\[/.test(html)) { const conditionalEnd = html.indexOf(']>') if (conditionalEnd >= 0) { html = html.substring(conditionalEnd + 2) prevTag = '' continue } } // Doctype: const doctypeMatch = html.match(doctype) if (doctypeMatch) { if (handler.doctype) { handler.doctype(doctypeMatch[0]) } html = html.substring(doctypeMatch[0].length) prevTag = '' continue } // End tag: const endTagMatch = html.match(endTag) if (endTagMatch) { html = html.substring(endTagMatch[0].length) endTagMatch[0].replace(endTag, parseEndTag) prevTag = '/' + endTagMatch[1].toLowerCase() continue } // Start tag: const startTagMatch = parseStartTag(html) if (startTagMatch) { html = startTagMatch.rest handleStartTag(startTagMatch) prevTag = startTagMatch.tagName.toLowerCase() continue } } let text if (textEnd >= 0) { text = html.substring(0, textEnd) html = html.substring(textEnd) } else { text = html html = '' } // next tag let nextTagMatch = parseStartTag(html) if (nextTagMatch) { nextTag = nextTagMatch.tagName } else { nextTagMatch = html.match(endTag) if (nextTagMatch) { nextTag = '/' + nextTagMatch[1] } else { nextTag = '' } } if (handler.chars) { handler.chars(text, prevTag, nextTag) } prevTag = '' } else { const stackedTag = lastTag.toLowerCase() const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)</' + stackedTag + '[^>]*>', 'i')) html = html.replace(reStackedTag, function (all, text) { if (stackedTag !== 'script' && stackedTag !== 'style' && stackedTag !== 'noscript') { text = text .replace(/<!--([\s\S]*?)-->/g, '$1') .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1') } if (handler.chars) { handler.chars(text) } return '' }) parseEndTag('</' + stackedTag + '>', stackedTag) } if (html === last) { throw new Error('Error parsing template:\n\n' + html) } } if (!handler.partialMarkup) { // Clean up any remaining tags parseEndTag() } ;解析开始标签 function parseStartTag (input) { const start = input.match(startTagOpen) if (start) { const match = { tagName: start[1], attrs: [] } input = input.slice(start[0].length) let end, attr while (!(end = input.match(startTagClose)) && (attr = input.match(attribute))) { input = input.slice(attr[0].length) match.attrs.push(attr) } if (end) { match.unarySlash = end[1] match.rest = input.slice(end[0].length) return match } } } ;处理开始标签 function handleStartTag (match) { const tagName = match.tagName let unarySlash = match.unarySlash if (expectHTML) { if (lastTag === 'p' && isNonPhrasingTag(tagName)) { parseEndTag('', lastTag) } if (canBeLeftOpenTag(tagName) && lastTag === tagName) { parseEndTag('', tagName) } } const unary = isUnaryTag(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash const attrs = match.attrs.map(function (args) { // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778 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] } } return { name: args[1], value: decodeHTML(args[3] || args[4] || args[5] || '') } }) if (!unary) { stack.push({ tag: tagName, attrs: attrs }) lastTag = tagName unarySlash = '' } if (handler.start) { handler.start(tagName, attrs, unary, unarySlash) } } ;处理结束标签 function parseEndTag (tag, tagName) { let pos // Find the closest opened tag of the same type if (tagName) { const needle = tagName.toLowerCase() for (pos = stack.length - 1; pos >= 0; pos--) { if (stack[pos].tag.toLowerCase() === needle) { break } } } else { // If no tag name is provided, clean shop pos = 0 } if (pos >= 0) { // Close all the open elements, up the stack for (let i = stack.length - 1; i >= pos; i--) { if (handler.end) { handler.end(stack[i].tag, stack[i].attrs, i > pos || !tag) } } // Remove the open elements from the stack stack.length = pos lastTag = pos && stack[pos - 1].tag } else if (tagName.toLowerCase() === 'br') { if (handler.start) { handler.start(tagName, [], true, '') } } else if (tagName.toLowerCase() === 'p') { if (handler.start) { handler.start(tagName, [], false, '', true) } if (handler.end) { handler.end(tagName, []) } } } } ~~~ >[info] export ~~~ ;(导出)解析html export function parseHTML (html, handler) {} ~~~ ### 1-4 text-parser.js >[info] import ~~~ ;(导入)基础工具,属性解析 import { cached } from 'shared/util' import { parseFilters } from './filter-parser' ~~~ >[info] module ~~~ ;正则表达式 const defaultTagRE = /\{\{((?:.|\\n)+?)\}\}/g const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g ;生成正则表达式 const buildRegex = cached(delimiters => { const open = delimiters[0].replace(regexEscapeRE, '\\$&') const close = delimiters[1].replace(regexEscapeRE, '\\$&') return new RegExp(open + '((?:.|\\n)+?)' + close, 'g') }) ;文本解析 export function parseText (text, delimiters) { ;终结符正则 const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE ;终结符匹配检测 if (!tagRE.test(text)) { return null } ;文本解析结果 const tokens = [] ;结果数字索引 let lastIndex = tagRE.lastIndex = 0 ;解析文本当前变量 let match, index ;循环解析 while ((match = tagRE.exec(text))) { ;解析位置 index = match.index if (index > lastIndex) { tokens.push(JSON.stringify(text.slice(lastIndex, index))) } ;过滤器解析 const exp = parseFilters(match[1].trim()) tokens.push(`__toString__(${exp})`) ;解析位置 lastIndex = index + match[0].length } ;检测是否解析完成 if (lastIndex < text.length) { tokens.push(JSON.stringify(text.slice(lastIndex))) } ;返回解析结果 return tokens.join('+') } ~~~ >[info] export ~~~ ;(导出)文本解析 export function parseText (text, delimiters) {} ~~~ ### 1-5 filter-parser.js >[info] module ~~~ ;表达式中过滤器解析 export function parseFilters (exp) { let inSingle = false let inDouble = false let curly = 0 let square = 0 let paren = 0 let lastFilterIndex = 0 let c, prev, i, expression, filters ;遍历表达式 for (i = 0; i < exp.length; i++) { ;指定位置字符编码 prev = c c = exp.charCodeAt(i) ;状态解析 if (inSingle) { if (c === 0x27 && prev !== 0x5C) inSingle = !inSingle } else if (inDouble) { if (c === 0x22 && prev !== 0x5C) inDouble = !inDouble } else if ( c === 0x7C && exp.charCodeAt(i + 1) !== 0x7C && exp.charCodeAt(i - 1) !== 0x7C && !curly && !square && !paren ) { if (expression === undefined) { lastFilterIndex = i + 1 expression = exp.slice(0, i).trim() } else { pushFilter() } } else { switch (c) { case 0x22: inDouble = true; break // " case 0x27: inSingle = true; break // ' case 0x28: paren++; break // ( case 0x29: paren--; break // ) case 0x5B: square++; break // [ case 0x5D: square--; break // ] case 0x7B: curly++; break // { case 0x7D: curly--; break // } } } } if (expression === undefined) { expression = exp.slice(0, i).trim() } else if (lastFilterIndex !== 0) { pushFilter() } ;过滤器解析结果 function pushFilter () { (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim()) lastFilterIndex = i + 1 } ;过滤器解析结果包装 if (filters) { for (i = 0; i < filters.length; i++) { expression = wrapFilter(expression, filters[i]) } } ;返回解析结果 return expression } ;包装过滤器解析结果 function wrapFilter (exp, filter) { const i = filter.indexOf('(') if (i < 0) { return `__resolveFilter__("${filter}")(${exp})` } else { const name = filter.slice(0, i) const args = filter.slice(i + 1) return `__resolveFilter__("${name}")(${exp},${args}` } } ~~~ >[info] export ~~~ ;(导出)过滤器解析 export function parseFilters (exp) {} ~~~ ### 1-6 index.js >[info] import ~~~ ;(导入)元素节点生成,html解析,text解析,基础工具,解析助手 import { decodeHTML } from 'entities' import { parseHTML } from './html-parser' import { parseText } from './text-parser' import { hyphenate, cached } from 'shared/util' import { getAndRemoveAttr, addProp, addAttr, addStaticAttr, addHandler, addDirective, getBindingAttr, baseWarn } from '../helpers' ~~~ >[info] module ~~~ ;正则表达式 const dirRE = /^v-|^@|^:/ const bindRE = /^:|^v-bind:/ const onRE = /^@|^v-on:/ const argRE = /:(.*)$/ const modifierRE = /\.[^\.]+/g const forAliasRE = /(.*)\s+(?:in|of)\s+(.*)/ const forIteratorRE = /\((.*),(.*)\)/ const camelRE = /[a-z\d][A-Z]/ ;解析缓存 const decodeHTMLCached = cached(decodeHTML) ;解析配置 let warn let platformGetTagNamespace let platformMustUseProp let delimiters ;解析入口 export function parse (template, options) { ;解析器状态 warn = options.warn || baseWarn platformGetTagNamespace = options.getTagNamespace || (() => null) platformMustUseProp = options.mustUseProp || (() => false) delimiters = options.delimiters ;解析结果 const stack = [] let root let currentParent let inPre = false let warned = false ;html解析 parseHTML(template, { ;解析控制选项 expectHTML: options.expectHTML, isUnaryTag: options.isUnaryTag, ;开始标签解析处理 start (tag, attrs, unary) { ;标签大小写检测 if (camelRE.test(tag)) { process.env.NODE_ENV !== 'production' && warn( `Found camelCase tag in template: <${tag}>. ` + `I have converted it to <${hyphenate(tag)}> for you.` ) tag = hyphenate(tag) } ;标签转换为小写 tag = tag.toLowerCase() ;解析元素结果 const element = { tag, attrsList: attrs, attrsMap: makeAttrsMap(attrs), parent: currentParent, children: [] } ;检测标签属性 if (isForbiddenTag(element)) { element.forbidden = true process.env.NODE_ENV !== 'production' && warn( 'Templates should only be responsbile for mapping the state to the ' + 'UI. Avoid placing tags with side-effects in your templates, such as ' + `<${tag}>.` ) } ;获取标签命名空间 let ns if ((ns = currentParent && currentParent.ns) || (ns = platformGetTagNamespace(tag))) { element.ns = ns } ;v-pre解析 if (!inPre) { processPre(element) if (element.pre) { inPre = true } } if (inPre) { processRawAttrs(element) } else { ;非v-pre解析其他属性 processFor(element) processIf(element) processOnce(element) element.plain = !element.key && !attrs.length processRender(element) processSlot(element) processComponent(element) processClassBinding(element) processStyleBinding(element) processTransition(element) processAttrs(element) } ;节点树管理 if (!root) { root = element } else if (process.env.NODE_ENV !== 'production' && !stack.length && !warned) { warned = true warn( `Component template should contain exactly one root element:\n\n${template}` ) } ; if (currentParent && !element.forbidden) { if (element.else) { processElse(element, currentParent) } else { currentParent.children.push(element) element.parent = currentParent } } if (!unary) { currentParent = element stack.push(element) } }, ;结束标签处理 end (tag) { const element = stack[stack.length - 1] const lastNode = element.children[element.children.length - 1] if (lastNode && lastNode.text === ' ') element.children.pop() stack.length -= 1 currentParent = stack[stack.length - 1] if (element.pre) { inPre = false } }, ;标签文本解析 chars (text) { ;文本环境获取 if (!currentParent) { if (process.env.NODE_ENV !== 'production' && !warned) { warned = true warn( 'Component template should contain exactly one root element:\n\n' + template ) } return } ;文本内容获取 text = currentParent.tag === 'pre' || text.trim() ? decodeHTMLCached(text) : options.preserveWhitespace && currentParent.children.length ? ' ' : null ;文本内容解析 if (text) { let expression if (!inPre && text !== ' ' && (expression = parseText(text, delimiters))) { currentParent.children.push({ expression }) } else { currentParent.children.push({ text }) } } } }) ;返回解析结果 return root } ;v-pre解析 function processPre (el) { if (getAndRemoveAttr(el, 'v-pre') != null) { el.pre = true } } ;v-pre下原生属性解析 function processRawAttrs (el) { const l = el.attrsList.length if (l) { el.attrs = new Array(l) for (let i = 0; i < l; i++) { el.attrs[i] = { name: el.attrsList[i].name, value: JSON.stringify(el.attrsList[i].value) } } } } ;v-for属性解析 function processFor (el) { let exp if ((exp = getAndRemoveAttr(el, 'v-for'))) { const inMatch = exp.match(forAliasRE) if (!inMatch) { process.env.NODE_ENV !== 'production' && warn( `Invalid v-for expression: ${exp}` ) return } el.for = inMatch[2].trim() const alias = inMatch[1].trim() const iteratorMatch = alias.match(forIteratorRE) if (iteratorMatch) { el.iterator = iteratorMatch[1].trim() el.alias = iteratorMatch[2].trim() } else { el.alias = alias } if ((exp = getAndRemoveAttr(el, 'track-by'))) { el.key = exp } } } ;v-if属性解析 function processIf (el) { const exp = getAndRemoveAttr(el, 'v-if') if (exp) { el.if = exp } if (getAndRemoveAttr(el, 'v-else') != null) { el.else = true } } ;v-else解析 function processElse (el, parent) { const prev = findPrevElement(parent.children) if (prev && (prev.if || prev.attrsMap['v-show'])) { if (prev.if) { prev.elseBlock = el } else { addDirective(el, 'show', `!(${prev.attrsMap['v-show']})`) el.transition = prev.transition el.show = true parent.children.push(el) } } else if (process.env.NODE_ENV !== 'production') { warn( `v-else used on element <${el.tag}> without corresponding v-if/v-show.` ) } } ;v-once解析 function processOnce (el) { const once = getAndRemoveAttr(el, 'v-once') if (once != null) { el.once = true } } ;<render>标签解析 function processRender (el) { if (el.tag === 'render') { el.render = true el.renderMethod = el.attrsMap.method el.renderArgs = el.attrsMap[':args'] || el.attrsMap['v-bind:args'] if (process.env.NODE_ENV !== 'production') { if (!el.renderMethod) { warn('method attribute is required on <render>.') } if (el.attrsMap.args) { warn('<render> args should use a dynamic binding, e.g. `:args="..."`.') } } } } ;<slot>标签解析 function processSlot (el) { if (el.tag === 'slot') { el.slotName = getBindingAttr(el, 'name') } else { const slotTarget = getBindingAttr(el, 'slot') if (slotTarget) { el.slotTarget = slotTarget } } } ;<component>标签解析 function processComponent (el) { if (el.tag === 'component') { el.component = getBindingAttr(el, 'is') } if (getAndRemoveAttr(el, 'inline-template') != null) { el.inlineTemplate = true } } ;class属性解析 function processClassBinding (el) { const staticClass = getAndRemoveAttr(el, 'class') if (process.env.NODE_ENV !== 'production') { const expression = parseText(staticClass, delimiters) if (expression) { warn( `class="${staticClass}": ` + 'Interpolation inside attributes has been deprecated. ' + 'Use v-bind or the colon shorthand instead.' ) } } el.staticClass = JSON.stringify(staticClass) const classBinding = getBindingAttr(el, 'class', false /* getStatic */) if (classBinding) { el.classBinding = classBinding } } ;style属性解析 function processStyleBinding (el) { const styleBinding = getBindingAttr(el, 'style', false /* getStatic */) if (styleBinding) { el.styleBinding = styleBinding } } ;v-*指令属性解析 function processAttrs (el) { const list = el.attrsList let i, l, name, value, arg, modifiers for (i = 0, l = list.length; i < l; i++) { name = list[i].name value = list[i].value if (dirRE.test(name)) { modifiers = parseModifiers(name) if (modifiers) { name = name.replace(modifierRE, '') } if (bindRE.test(name)) { // v-bind name = name.replace(bindRE, '') if (platformMustUseProp(name)) { addProp(el, name, value) } else { addAttr(el, name, value) } } else if (onRE.test(name)) { // v-on name = name.replace(onRE, '') addHandler(el, name, value, modifiers) } else { // normal directives name = name.replace(dirRE, '') if ((arg = name.match(argRE)) && (arg = arg[1])) { name = name.slice(0, -(arg.length + 1)) } addDirective(el, name, value, arg, modifiers) } } else { if (process.env.NODE_ENV !== 'production') { const expression = parseText(value, delimiters) if (expression) { warn( `${name}="${value}": ` + 'Interpolation inside attributes has been deprecated. ' + 'Use v-bind or the colon shorthand instead.' ) } } addStaticAttr(el, name, JSON.stringify(value)) } } } ; function parseModifiers (name) { const match = name.match(modifierRE) if (match) { const ret = {} match.forEach(m => { ret[m.slice(1)] = true }) return ret } } ;生成属性Map function makeAttrsMap (attrs) { const map = {} for (let i = 0, l = attrs.length; i < l; i++) { if (process.env.NODE_ENV !== 'production' && map[attrs[i].name]) { warn('duplicate attribute: ' + attrs[i].name) } map[attrs[i].name] = attrs[i].value } return map } ;查找上个元素节点 function findPrevElement (children) { let i = children.length while (i--) { if (children[i].tag) return children[i] } } ;检查是否是style,script,text/javascript等非解析标签 function isForbiddenTag (el) { return ( el.tag === 'style' || (el.tag === 'script' && ( !el.attrsMap.type || el.attrsMap.type === 'text/javascript' )) ) } ~~~ >[info] export ~~~ ;(导出)模板解析入口 export function parse (template, options) {} ~~~