[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) {}
~~~
- 概述
- 框架结构
- 编译入口(\entries)
- web-compiler.js(web编译)
- web-runtime.js(web运行时)
- web-runtime-wih-compiler.js(web编译运行)
- web-server-renderer.js(web服务器渲染)
- 核心实现 (\core)
- index.js(核心入口)
- config.js(核心配置)
- core\util(核心工具)
- core\observer(双向绑定)
- core\vdom(虚拟DOM)
- core\global-api(核心api)
- core\instance(核心实例)
- 模板编译(\compiler)
- compiler\parser(模板解析)
- events.js(事件解析)
- helper.js(解析助手)
- directives\ref.js(ref指令)
- optimizer.js(解析优化)
- codegen.js(渲染生成)
- index.js(模板编译入口)
- web渲染(\platforms\web)
- compiler(web编译目录)
- runtime(web运行时目录)
- server(web服务器目录)
- util(web工具目录)
- 服务器渲染(\server)
- render-stream.js(流式渲染)
- render.js(服务器渲染函数)
- create-renderer.js(创建渲染接口)
- 框架流程
- Vue初始化
- Vue视图数据绑定
- Vue数据变化刷新
- Vue视图操作刷新
- 框架工具
- 基础工具(\shared)
- 模板编译助手
- 核心实例工具
- Web渲染工具
- 基础原理
- dom
- string
- array
- function
- object
- es6
- 模块(Module)
- 类(Class)
- 函数(箭头)
- 字符串(扩展)
- 代理接口(Proxy)
- 数据绑定基础
- 数据绑定实现
- mvvm简单实现
- mvvm简单使用
- vdom算法
- vdom实现
- vue源码分析资料