编译
web 模式的编译主要做了 3 件事:
- 解析 template 生成 AST
- AST 转换
- 生成代码
/**
* web 编译
* @param {string} template - 待编译的模板字符串
* @param {string} options - 配置对象
*/
function compile(template, options = {}) {
return baseCompile(template, extend({}, parserOptions, options, {
nodeTransforms: [...DOMNodeTransforms, ...(options.nodeTransforms || [])],
directiveTransforms: extend({}, DOMDirectiveTransforms, opotins.directiveTransforms || {}),
transformHoist: null
}))
}
/**
* 基础编译
* baseCompile 主要做了 3 件事:
* 1、解析 template 生成 AST
* 2、AST 转换
* 3、生成代码
*/
function baseCompile(template, options = {}) {
const prefixIdentifiers = false
// 1、解析 template 生成 AST
const ast = isString(template) ? baseParse(template, options) : template
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset()
// 2、AST 转换
transform(ast, extend({}, options, {
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || [])
],
directiveTransforms: extend({}, directiveTransforms, options.directiveTransforms || {})
}))
// 3、生成代码
return generage(ast, extend({}, options, {
prefixIdentifiers
}))
}
解析 template 生成 AST
问题:AST 是什么?
答:AST 是抽象语法树,用于描述节点在模板中完整的映射信息。
问题:为什么 AST 的根节点是虚拟节点?
答:因为 Vue3 中支持
<Fragment>
语法,即:组件可以有多个根节点,但是树状结构只能有一个根节点,所以需要在外层套一个虚拟节点。
HTML 的嵌套结构的解析过程其实就是一个递归解析元素节点的过程。
/**
* 解析 template 生成 AST
* baseParse 主要做了 2 件事:
* 1、创建解析上下文
* 2、解析子节点,并创建 AST 根节点
*/
function baseParse(content, options = {}) {
// 1、创建解析上下文
const context = createParserContext(content, options)
const start = getCursor(context)
// 2、解析子节点,并创建 AST 根节点
return createRoot(parseChildren(context, 0/* DATA */, []), getSelection(context, start))
}
// 默认解析配置
const defaultParserOptions = {
delimiters: [`{{`, `}}`],
getNamespace: () => 0/* HTML */,
getTextMode: () => 0/* DATA */,
isVoidTag: NO,
isPreTag: NO,
isCustomElement: NO,
decodeEntities: (rawText) => rawText.replace(decodeRE, (_, p1) => decodeMap[p1]),
onError: defaultOnError
}
/**
* 创建解析器上下文
*/
function createParserContext(content, options) {
return {
options: extend({}, defaultParserOptions, options), // 解析相关配置
column: 1, // 当前代码的列号
line: 1, // 当前代码的行号
offset: 0, // 当前代码相对于原始代码的偏移量
originalSource: content, // 最初的原始代码
source: content, // 当前代码
inPre: false, // 当前代码是否在 pre 标签内
inVPre: false // 当前代码是否在 VPre 指定的环境下
}
}
/**
* 解析子节点
* 目的:解析并创建 AST 节点数组
* parseChildren 主要做了 2 件事:
* 1、自顶向下分析代码,生成 nodes
* 2、空白字符管理
*/
function parseChildren(context, mode, ancestors) {
const parent = last(ancestors) // 父节点
const ns = parent ? parent.ns : 0/* HTML */
const nodes = []
// 1、自顶向下分析代码,生成 nodes(AST 节点数组)
// 自顶向下遍历代码,然后根据不同情况去解析代码
while (!isEnd(context, mode, ancestors)) {
const s = context.source
let node = undefined
if (mode === 0/* DATA */ || mode === 1/* RCDATA */) {
// 处理 {{ 插值代码
if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
node = parseInterpolation(context, mode)
}
// 处理 < 开头的代码
else if (mode === 0/* DATA */ && s[0] === '<') {
// s 长度为 1,说明代码结尾是 <,报错
if (s.length === 1) {
emitError(context, 5/* EOF_BEFORE_TAG_NAME */, 1)
}
// 处理 <! 开头的代码
else if (s[1] === '!') {
// 处理注释节点
if (startsWith(s, '<!--')) {
node = parseComment(context)
}
// 处理 <!DOCTYPE 节点
else if (startsWith(s, '<!DOCTYPE')) {
node = parseBogusComment(context)
}
// 处理 <![CDATA] 节点
else if (startsWith(s, '<![CDATA]')) {
if (ns !== 0/* HTML */) {
node = parseCDATA(context, ancestors)
} else {
emitError(context, 1/* CDATA_IN_HTML_CONTENT */)
node = parseBogusComment(context)
}
}
// 处理其他情况
else {
emitError(context, 11/* INCORRECTLY_OPENED_COMMENT */)
node = parseBogusComment(context)
}
}
// 处理 </ 结束标签
else if (s[1] === '/') {
// s 长度为2,说明代码结尾是 </,报错
if (s.length === 2) {
emitError(context, 5/* EOF_BEFORE_TAG_NAME */, 2)
}
// </> 缺少结束标签,报错
else if (s[2] === '>') {
emitError(context, 14/* MISSING_END_TAG_NAME */, 2)
advanceBy(context, 3)
continue
}
// 多余的结束标签
else if (/[a-z]/i.test(s[2])) {
emitError(context, 23/* X_INVALID_END_TAG */)
parseTag(context, 1/* END */, parent)
continue
} else {
emitError(context, 12/* INVALID_FIRST_CHARACTER_OF_TAG_NAME */, 2)
node = parseBogusComment(context)
}
}
// 解析标签元素节点
else if (/[a-z]/i.test(s[1])) {
node = parseElement(context, ancestors)
} else if (s[1] === '?') {
emitError(context, 21/* UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME */, 1)
node = parseBogusComment(context)
} else {
emitError(context, 12/* INVALID_FIRST_CHARACTER_OF_TAG_NAME */, 1)
}
}
}
// 解析普通文本节点
if (!node) {
node = parseText(context, mode)
}
// 如果 node 是数组,则遍历添加
if (isArray(node)) {
for (let i = 0; i < node.length; i++) {
pushNode(nodes, node[i])
}
}
// 否则,添加单个 node
else {
pushNode(nodes, node)
}
}
// 2、空白字符管理(用于提高编译效率)
let removedWhitespace = false
if (mode !== 2/* RAWTEXT */) {
if (!context.inPre) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === 2 /* TEXT */) {
if (!/[Atr\n\f]/.test(node.content)) {
// 匹配空白字符
const prev = nodes[i - 1]
const next = nodes[i + 1]
// 如果 空白字符是开头或者结尾节点 或者 空白字符与注释节点相连 或者 空白字符在两个元素之间并包含换行符 那么这些空白字符节点都应该被移除
if (!prev || !next || prev.type === 3/* COMMENT */ || next.type === 3/* COMMENT */ ||
(prev.type === 1/* ELEMENT */ && next.type === 1/* ELEMENT */ && /[\r\n]/.test(node.content))) {
removedWhitespace = true
nodes[i] = null
}
// 否则压缩这些空白字符到一个空格
else {
node.content = ''
}
}
// 替换内容中的空白空间到一个空格
else {
node.content = node.content.replace(/[\t\r\n\f]+/g, '')
}
}
// 生产环境移除注释节点
else if (!(process.env.NODE_ENV !== 'production') && node.type === 3/* COMMENT */) {
removedWhitespace = true
nodes[i] = null
}
}
}
// 根据 HTML 规范删除前导换行符
else if (parent && context.options.isPreTag(parent.tag)) {
const first = nodes[0]
if (first && first.type === 2/* TEXT */) {
first.content = first.content.replace(/^\r?\n/, '')
}
}
}
// 过滤空白字符节点
return removedWhitespace ? nodes.filter(Boolean) : nodes
}
解析子节点
解析注释节点
/**
* 解析注释节点
*/
function parseComment(context) {
const start = getCursor(context)
let content
const match = /--(\!)?>/.exec(context.source) // 注释结束符
// 如果没有匹配的注释结束符,则报错
if (!match) {
content = context.source.slice(4)
advanceBy(context, context.source.length)
emitError(context, 7/* EOF_IN_COMMENT */)
} else {
// 非法的注释符号
if (match.index <= 3) {
emitError(context, 0/* ABRUPT_CLOSING_OF_EMPTY_COMMENT */)
}
// 注释结束符不正确
if (match[1]) {
emitError(context, 10/* INCORRECTLY_CLOSED_COMMENT */)
}
content = context.source.slice(4, match.index) // 获取注释的内容
const s = context.source.slice(0, match.index) // 截取到注释结尾之间的代码,用于后续判断嵌套注释
let prevIndex = 1, nestedIndex = 0
// 判断嵌套注释符的情况,存在即报错
while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {
advanceBy(context, nestedIndex - prevIndex + 1)
if (nestedIndex + 4 < s.length) {
emitError(context, 16/* NESTED_COMMENT */)
}
prevIndex = nestedIndex + 1
}
// 前进代码到注释符结束后,目的是用来前进代码,更新 context 解析上下文
advanceBy(context, match.index + match[0].length - prevIndex + 1)
}
return {
type: 3/* COMMENT */,
content,
loc: getSelection(context, start)
}
}
/**
* 前进代码
*/
function advanceBy(context, numberOfCharacters) {
const { source } = context
// 更新 context 的 offset?line?column?
advancePositionWithMutation(context, source, numberOfCharacters)
// 更新 context 的 source
context.source = source.slice(numberOfCharacters)
}
/**
* 更新 context 的 offset?line?column?
*/
function advancePositionWithMutation(pos, source, numberOfCharacters = source.length) {
let linesCount = 0
let lastNewLinePos = -1
for (let i = 0; i < numberOfCharacters; i++) {
if (source.charCodeAt(i) === 10/* newline char code */) {
linesCount++
lastNewLinePos = i
}
}
pos.offset += numberOfCharacters
pos.line += linesCount
pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos
return pos
}
解析插值
/**
* 解析插值
*/
function parseInterpolation(context, mode) {
// 从配置中获取插值开始和结束分隔符,默认是 {{ 和 }}
const [open, close] = context.options.delimiters
const closeIndex = context.source.indexOf(close, open, length)
// 没有结束符,报错
if (closeIndex === -1) {
emitError(context, 25/* X_MISSING_INTERPOLATION_END */)
return undefined
}
const start = getCursor(context)
// 代码前进到插值开始分隔符后
advanceBy(context, open.length)
const innerStart = getCursor(context) // 内部插值开始位置
const innerEnd = getCursor(context) // 内部插值结束位置
const rawContentLength = closeIndex - open.length // 插值原始内容的长度
const rawContent = context.source.slice(0, rawContentLength) // 插值原始内容
const preTrimContent = parseTextData(context, rawContentLength, mode) // 获取插值的内容,并前进代码到插值的内容后
const content = preTrimContent.trim()
const startOffset = preTrimContent.indexOf(contend) // 内容相对于插值开始分隔符的头偏移
// 更新内部插值开始位置
if (startOffset > 0) {
advancePositionWithMutation(innerStart, rawContent, startOffset)
}
// 内容相对于插值结束分隔符的尾偏移
const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset)
// 更新内部插值结束位置
advancePositionWithMutation(innerEnd, rawContent, endOffset)
// 前进代码到插值结束分隔符后
advanceBy(context, close.length)
// 返回一个描述节点的对象
return {
type: 5/* INTERPOLATION */, // 插值节点
content: { // 表达式节点的对象
type: 4/* SIMPLE_EXPRESSION */, // 表达式节点
isStatic: false,
isConstant: false,
content, // 插值的内容
loc: getSelection(context, innerStart, innerEnd) // 内容代码开头和结束的位置信息
},
loc: getSelection(context, start) // 插值代码开头和结束的位置信息
}
}
解析普通文本
/**
* 解析普通文本
*/
function parseText(context, mode) {
const endTokens = ['<', context.options.delimiters[0]] // 文本结束符
// CDATA 标记 XML 中的纯文本
if (mode === 3/* CDATA */) {
endTokens.push(']]>')
}
let endIndex = context.source.length
// 遍历文本结束符,匹配找到结束的位置
for (let i = 0; i < endTokens.length; i++) {
const index = context.source.indexOf(endTokens[i], 1)
if (index !== -1 && endIndex > index) {
endIndex = index
}
}
// 获取文本的内容,并前进代码到文本的内容后
const start = getCursor(context)
const content = parseTextData(context, endIndex, mode)
return {
type: 2/* TEXT */,
content,
loc: getSelection(context, start)
}
}
解析元素节点
/**
* 解析元素节点
* parseElement 主要做了 3 件事:
* 1、解析开始标签
* 2、解析子节点
* 3、解析结束标签
*/
function parseElement(context, ancestors) {
const wasInPre = context.inPre // 是否在 pre 标签内
const wasInVPre = context.inVPre // 是否在 v-pre 指令内
const isPreBoundary = context.inPre && !wasInPre // 是否在 pre 标签的边界
const isVPreBoundary = context.inVPre && !wasInVPre // 是否在 v-pre 指令的边界
// 获取当前元素的父标签节点
const parent = last(ancestors)
// 1、解析开始标签,生成一个标签节点,并前进代码到开始标签后
const element = parseTag(context, 0/* Start */, parent)
// 如果是自闭和标签,直接返回标签节点
if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
return element
}
// 下面是处理子节点的逻辑
// 先把标签节点添加到 ancestors,入栈
ancestors.push(element)
const mode = context.options.getTextMode(element, parent)
// 2、递归解析子节点,传入 ancestors
const children = parseChildren(context, mode, ancestors)
// ancestors 出栈
ancestors.pop()
// 添加到 children 属性中
element.children = children
// 结束标签
if (startsWithEndTagOpen(context.source, element.tag)) {
// 3、解析结束标签,并前进代码到结束标签后
parseTag(context, 1/* End */, parent)
}
else {
emitError(context, 24/* X_MISSING_END_TAG */, 0, element.loc.start);
if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
const first = children[0];
if (first && startsWith(first.loc.source, '<!--')) {
emitError(context, 8/* EOF_IN SCRIPT_HTML COMMENT_LIKE_TEXT */)
}
}
}
// 更新标签节点的代码位置,结束位置到结束标签后
element.loc = getSelection(context, element.loc.start)
if (isPreBoundary) {
context.inPre = false
}
if (isVPreBoundary) {
context.inVPre = false
}
return element
}
/**
* 解析并创建一个标签节点
*/
function parseTag(context, type, parent) {
const start = getCursor(context) // 开始标签
// 1、匹配标签文本结束的位置
const match = /^<\/?([a-z][^\t\r\n\f/>]*)/i.exec(context.source)
const tag = match[1]
const ns = context.options.getNamesxpace(tag, parent)
// 前进代码到标签文本结束位置
advanceBy(context, match[0].length)
// 2、前进代码到标签文本后面的空白字符后
advanceSpaces(context)
// 保存当前状态以防我们需要用 v-pre 重新解析属性
const cursor = getCursor(context)
const currentSource = context.source
// 3、解析标签中的属性,并前进代码到属性后
let props = parseAttributes(context, type)
// 4、检查是不是一个 pre 标签
if (context.options.isPreTag(tag)) {
context.inPre = true
}
// 5、检查属性中有没有 v-pre 指令,如果有,则设置 context.inVPre 为 true,并重置上下文 context 和 重新解析属性
if (!context.inVPre && props.some(p => p.type === 7/* DIRECTIVE */ && p.name === 'pre')) {
context.inVPre = true;
// 重置 context
extend(context, cursor);
context.source = currentSource;
// 重新解析属性,并把 v-pre 过滤了
props = parseAttributes(context, type).filter(p => p.name !== 'v-pre');
}
let isSelfClosing = false; // 是否是闭合标签
// 6、判断是否自闭合标签
if (context.source.length === 0) {
emitError(context, 9/* EOF IN TAG */);
} else {
isSelfClosing = startsWith(context.source, '/>')
if (type === 1/* End */ && isSelfClosing) {
// 结束标签不应该是自闭和标签
emitError(context, 4/* END_TAG_WITH_TRAILING_SOLIDUS */);
}
// 前进代码到闭合标签后
advanceBy(context, isSelfClosing ? 2 : 1);
}
let tagType = 0/* ELEMENT */
const options = context.options
// 7、判断标签类型是组件、插槽还是模板
if (!context.inVPre && !options.isCustomElement(tag)) {
const hasVIs = props.some(p => p.type === 7/* DIRECTIVE */ && p.name === 'is') // 判断是否有 is 属性
if (options.isNativeTag && !hasVIs) {
if (!options.isNativeTag(tag)) {
tagType = 1/* COMPONENT*/
}
} else if (hasVIs || isCoreComponent(tag) || (options.isBuiltInComponent && options.isBuiltInComponent(tag)) || /^[A-Z]/.test(tag) || tag === 'component') {
tagType = 1/* COMPONENT */
}
if (tag === 'slot') {
tagType = 2/* SLOT */
} else if (tag === 'template' && props.some(p => {
return (p.type === 7/* DIRECTIVE */ && isSpecialTemplateDirective(p.name));
})) {
tagType = 3/* TEMPLATE */
}
}
// 返回描述标签节点的对象
return {
type: 1/* ELEMENT */, // 节点类型
ns,
tag, // 标签名
tagType, // 标签类型
props,
isSelfClosing, // 是否是闭合标签
children: [], // 标签子节点数组(初始化为空)
loc: getSelection(context, start), // 文本的开头和结尾信息
codegenNode: undefined
}
}
问题:在 parseTag 的过程中,如果解析的属性有
v-pre
标签,为什么要回到之前的 context,重新解析一次?答:避免标签中的内容不会被其他属性影响。
创建 AST 根节点
/**
* 创建 AST 根节点
*/
function createRoot(children, loc = locStub) {
return {
type: 0/* ROOT */,
children,
helpers: [],
components: [],
directives: [],
hoists: [],
imports: [],
cached: 0,
temps: 0,
codegenNode: undefined,
loc
}
}
AST 转换
AST 转换流程:
-
创建 transform 上下文
-
遍历 AST 节点
-
静态提升
好处:针对静态节点不用每次在 render 阶段都执行一次 createVNode 创建 vnode 对象。
-
创建根代码生成节点
AST 转换其实就是语法分析阶段,把 AST 节点做一层转换,构造出语义化更强信息更丰富的 codegenCode,它在后续的代码生成阶段起非常重要的作用。
创建 transform 上下文
/**
* 获取节点和指令转换的方法
*/
function getBaseTransformPreset(prefixIdentifiers) {
return [
[
transformOnce,
transformIf,
transformFor,
transformExpression,
transformSlotOutlet,
transformElement,
trackSlotScopes,
transformText,
],
{
on: transformOn,
bind: transformBind,
model: transformModel
}
]
}
/**
* Node 环境的转换
* transform 主要做了 4 件事:
* 1、创建 transform 上下文
* 2、遍历 AST 节点
* 3、静态变量提升
* 4、创建根代码的生成节点
*/
function transform(root, options) {
// 1、创建 transform 上下文
const context = createTransformContext(root, options)
// 2、遍历 AST 节点
traverseNode(root, context)
// 3、静态变量提升
if (options.hoistStatic) {
hoistStatic(root, context)
}
// 4、创建根代码的生成节点
if (!options.ssr) {
createRootCodegen(root, context)
}
root.helpers = [...context.helpers]
root.components = [...context.components]
root.directives = [...context.directives]
root.imports = [...context.imports]
root.hoists = context.hoists
root.temps = context.temps
root.cached = context.cached
}
/**
* 创建转换上下文
*/
function createTransformContext(root, { prefixIdentifiers = false, hoistStatic = false, cacheHandlers = false, nodeTransforms = [], directiveTransforms = {}, transformHoist = null, isBuiltInComponents = NOOP, expressionPlugins = [], scopeId = null, ssr = false, onError = defaultOnError }) {
// transform 过程中的一些配置 和 状态数据等
const context = {
// 配置
prefixIdentifiers,
hoistStatic,
cacheHandlers,
nodeTransforms,
directiveTransforms,
transformHoist,
isBuiltInComponents,
expressionPlugins,
scopeId,
ssr,
onError,
// 状态数据
root,
helpers: new Set(),
components: new Set(),
directives: new Set(),
hoists: [],
imports: new Set(),
temps: 0,
cached: 0,
identifiers: {},
scopes: {
vFor: 0,
vSlot: 0,
vPre: 0,
vOnce: 0
},
parent: null,
currentNode: root,
childIndex: 0,
// methods
helper(name) {
context.helpers.add(name)
return name
},
helperString(name) {
return `_${helperNameMap[context.helper(name)]}`
},
replaceNode(node) {
context.parent.children[context.childIndex] = context.currentNode = node
},
removeNode(node) {
const list = context.parent.children
const removalIndex = node ? list.indexOf(node) : context.currentNode ? context.childIndex : -1
// 移除当前节点
if (!node || node === context.currentNode) {
context.currentNode = null
context.onNodeRemoved()
}
// 移除兄弟节点
else {
if (context.childIndex > removalIndex) {
context.childIndex--
context.onNodeRemoved()
}
}
// 移除节点
context.parent.children.splice(removalIndex, 1)
},
onNodeRemoved: () => { },
addIdentifiers(exp) { },
removeIdentifiers(exp) { },
hoist(exp) {
context.hoists.push(exp)
const identifier = createSimpleExpression(`_hoisted_${context.hoists.length}`, false, exp.loc, true)
identifier.hoisted = exp
return identifier
},
cache(exp, isVNode = false) {
return createCacheExpression(++context.cached, exp, isVNode)
}
}
return context
}
/**
* 遍历 AST 节点
*/
function traverseNode(node, context) {
context.currentNode = node
// 节点转换函数
const { nodeTransforms } = context
const exitFns = []
for (let i = 0; i < nodeTransforms.length; i++) {
// 有些转换函数会涉及一个退出函数,在处理完子节点后执行
const onExit = nodeTransforms[i](node, context)
if (onExit) {
if (isArray(onExit)) {
exitFns.push(...onExit)
} else {
exitFns.push(onExit)
}
}
// 节点被移除
if (!context.currentNode) return
// 因为在转换的过程中节点可能被替代,恢复到之前的节点
else {
node = context.currentNode
}
}
switch (node.type) {
case 3/* COMMENT */:
if (!context.ssr) {
// 需要导入 createComment 符主函数
context.helper(CREATE_COMMENT)
}
break
case 5/* INTERPOLATION */:
// 需要导入 toString 辅助函数
if (!context.ssr) {
context.helper(TO_DISPLAY_STRING)
}
break
case 9/* IF */:
// 递归遍历每个分支节点
for (let i = 0; i < node.branches.length; i++) {
traverseNode(node.branches[i], context)
}
break
case 10/* IF_BRANCH */:
case 11/* FOR */:
case 1/* ELEMENT */:
case 0/* ROOT */:
// 遍历子节点
traverseChildren(node, context)
break
}
// 执行转换函数返回的退出函数
let i = exitFns.length
while (i--) { }
}
遍历 AST 节点
转换元素节点
/**
* 转换元素节点
*/
const transformElement = (node, context) => {
// 如果节点不会 元素 或 组件,则直接返回
if (!(node.type === 1/* ELEMENT */ && (node.tagType === 0/* ELEMENT */ || node.tagType === 1/* COMPONENT */))) return
// 返回退出函数,在所有子表达式处理并合并后执行
return function postTransformElement() {
// 转换的目标是创建一个实现 VNodeCall 接口的代码生成节点
const { tag, props } = node
const isComponent = node.tagType === 1/* COMPONENT */
const vnodeTag = isComponent ? resolveComponentType(node, context) : `"${tag}"`
const isDynamicComponent = isObject(vnodeTag) && vnodeTag.callee === RESOLVE_DYNAMIC_COMPONENT
let vnodeProps // 属性
let vnodeChildren // 子节点
// 标记更新的类型标识,用于运行时优化
let vnodePatchFlag
let patchFlag = 0
// 动态绑定的属性
let vnodeDynamicProps
let dynamicPropNames
let vnodeDirectives
// 动态组件、svg、foreignObejct 标签以及动态绑定 prop 的节点都被视作为一个 Block
let shouldUseBlock = isDynamicComponent || (!isComponent && (tag === 'svg' || tag === 'foreignObject' || findProp(node, 'key', true)))
// 处理 props - 从 AST 节点的 props 对象中进一步解析出指令 directives、动态属性 dynamicPropsNames、更新标识 patchFlag
if (props.length > 0) {
const propsBuildResult = buildProps(node, context)
vnodeProps = propsBuildResult.props
patchFlag = propsBuildResult.patchFlag // 更新标识 - 用于标识节点更新的类型,在组件更新的过程中会用到
dynamicPropNames = propsBuildResult.dynamicPropsNames // 动态属性
const directives = propsBuildResult.directives // 指令
vnodeDirectives = directives && directives.length ? createArrayExpression(directives.map(dir => buildDirectiveArgs(dir, context))) : undefined
}
// 处理 children
if (node.children.length > 0) {
// 把 KeepAlive 看做是一个 Block,这样可以避免它的子节点的动态节点被父 Block 收集
if (vnodeTag === KEEP_ALIVE) {
shouldUseBlock = true
// 确保它始终更新
patchFlag |= 1024/* DYNAMIC_SLOTS */
if ((propcess.env.NODE_ENV !== 'production') && node.children.length > 1) {
context.onError(createCompilertError(42/* X_KEEP_ALIVE_INVALID_CHILDREN */, {
start: node.children[0].loc.start,
end: node.children[node.children.length - 1].loc.end,
source: ''
}))
}
}
// Teleport 不是一个真正的组件,它有专门的运行时处理
const shouldBuildAsSlots = isComponent && vnodeTag !== TELEPORT && vnodeTag !== KEEP_ALIVE
// 如果节点只有一个子节点,并且子节点是一个普通的文本节点插值或表达式,则直接把节点赋值给 vnode.children
if (shouldBuildAsSlots) {
// 组件有 children,则处理插槽
const { slots, hasDynamicSlots } = buildSlots(node, context)
vnodeChildren = slots
if (hasDynamicSlots) {
patchFlag |= 1024/* DYNAMIC_SLOTS */
}
} else if (node.children.length === 1 && vnodeTag !== TELEPORT) {
const child = node.children[0]
const type = child.type
const hasDyanmicTextChild = type === 5/* INTERPOLATION */ || type === 8/* COMPOUND_EXPRESSION */
if (hasDynamicTextChild && !getStaticType(child)) {
patchFlag |= 1/* TEXT */
}
// 如果只是一个普通文本节点、插值或者表达式,直接把节点赋值给 vnodeChildren
if (hasDynamicTextChild || type === 2/* TEXT */) {
vnodeChildren = child
} else {
vnodeChildren = node.children
}
} else {
vnodeChildren = node.children
}
}
// 处理 patchFlag 和 dynamicPropNames
if (patchFlag !== 0) {
// 根据 patchFlag 的值从 patchFlagNames 中获取 flag 对应的名字,从而生成注释
if ((process.env.NODE_ENV !== 'production')) {
if (patchFlag < 0) {
vnodePatchFlag = patchFlag + `/*${PatchFlagNames[patchFlag]}*/`
} else {
const flagNames = object.keys(PatchFlagNames).map(Number).filter(m => n > 0 && patchFlag & n).map(n => PatchFlagNames[n]).join(`,`)
vnodePatchFlag = patchFlag + `/* ${flagNames} */`
}
} else {
vnodePatchFlag = String(patchFlag)
}
// 将 dynamicPropNames 转化为 vnodeDynamicProps 字符串,便于后续对节点生成代码逻辑的处理
if (dynamicPropNames && dynamicPropNames.length) {
vnodeDynamicProps = stringifyFynamicPropNames(dynamicPropNames)
}
}
// 通过 createVNodeCall 创建了实现 VNodeCall 接口的代码生成节点
node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren, vnodeFlag, vnodeDynamicProps, vnodeDirectives, !!shouldUseBlock, false/* disableTrancking */, node.loc)
}
}
/**
* 创建了实现 VNodeCall 接口的代码生成节点
*/
function createVNodeCall(context, tag, props, children, patchFlag, dynamicProps, directives, isBlock = false, disableTranking = false, loc = locStub) {
if (context) {
// context.helper 会将一些 simple 对象添加到 context.helps 数组中,目的是为了后续代码生成阶段生成一些辅助代码
if (isBlock) {
context.helper(OPEN_BLOCK)
context.helper(CREATE_BLOCK)
} else {
context.helper(CREATE_VNODE)
}
if (directives) {
context.helper(WITH_DIRECTIVES)
}
}
// 返回一个对象,包含了传入的参数
return {
type: 13/* VNODE_CALL */,
tag,
props,
children,
patchFlag,
dynamicProps,
directives,
isBlock,
disableTracking,
loc
}
}
转换表达式节点
/**
* 转换表达式节点(只有在 node 环境下的编译或者是 web 端的非生产环境下才会执行)
* transformExpression 主要做了 2 件事:
* 1、转换插值中的动态表达式
* 2、转换元素指令中的动态表达式
*/
const transformExpression = (node, context) => {
// 处理插值中的动态表达式
if (node.type === 5/* INTERPOLATION */) {
node.content = processExpression(node.content, context)
}
// 处理元素指令中的动态表达式
else if (node.type === 1/* ELEMENT */) {
for (let i = 0; i < node.props.length; i++) {
const dir = node.props[i]
// v-on 和 v-for 不处理,因为它们都有各自的处理逻辑
if (dir.type === 7/* DIRECTIVE */ && dir.name !== 'for') {
const exp = dir.exp
const arg = dir.arg
if (exp && exp.type === 4/*SIMPLE EXPRESSION*/ && !(dir.name === 'on' && arg)) {
dir.exp = processExpression(exp, context, dir.name === 'slot')
}
if (arg && argtype === 4 /* SIMPLE_EXPRESSION */ && !arg.isStatic) {
dir.arg = processExpresstion(arg, context)
}
}
}
}
}
/*
processExpression 的实现:
1、内部依赖了 @babel/parser 库去解析表达式生成 AST 节点
2、依赖了 estree-walker 库去遍历这个 AST 节点,然后对节点分析去判断是否需要加前缀
3、接着对 AST 节点修改,最终转换生成新的表达式对象
*/
转换文本节点
/**
* 转换文本节点 - 只处理根节点、元素节点、v-for、v-if分支
* 目的:合并一些相邻的文本节点,然后为内部每一个文本节点创建一个代码生成节点,
*/
const transformText = (node, context) => {
// 在节点退出时执行转换,保证所有表达式都已经被处理
if (node.type === 0/* ROOT */ || node.type === 1/* ELEMENT */ || node.type === 11/* FOR */ || node.type === 10/* IF_BRANCH */) {
// 返回退出函数,因为要保证所有表达式节点都已经被处理才执行转换逻辑
return () => {
const children = node.children
let currentContainer = undefined
let hasText = false
// 遍历节点的子节点数组,把子节点中相邻的文本节点合并成一个
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isText(child)) {
hasText = true
for (let j = i + 1; j < children.length; j++) {
const next = children[j]
if (isText(next)) {
if (!currentContainer) {
// 创建复合表达式节点
currentContainer = children[i] = {
type: 8/* COMPOUND_EXPRESSION */,
loc: child.loc,
children: [child]
}
}
currentContainer.children.push(`+`, next)
children.splice(j, 1)
j--
}
else {
currentContainer = undefined
break
}
}
}
}
// 如果是一个带有单个文本子元素的纯元素节点,则什么都不需要转换,因为这种情况在运行时可以直接设置元素的 textContent 来更新文本
if (!hasText || (children.length === 1 && (node.type === 0/* ROOT */ || (node.type === 1/* ELEMENT */ &&
node.tagType === 0/* ELEMENT*/)))) {
return
}
// 为子文本节点创建一个调用函数表达式的代码生成节点
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isText(child) || child.type === 8/* COMPOUND_EXPRESSION */) {
const callArgs = []
// 为 createTextVNode 添加执行参数
if (child.type !== 2/* TEXT */ || child.content !== '') {
callArgs.push(child)
}
// 标记动态文本
if (!context.ssr && child.type !== 2/* TEXT */) {
callArgs.push(`${1/* TEXT */}/* ${PatchFlagNames[1/* TEXT */]} */`)
}
children[i] = {
type: 12/* TEXT_CALL */,
content: child,
loc: child.loc,
codegenNode: createCallExpression(context.helper(CREATETEXT), callArgs)
}
}
}
}
}
}
/**
* 创建调用函数表达式的代码生成节点
* @description 创建的函数表达式所生成的节点,对应的函数名是 createTextVNode,参数 callArgs 是子节点本身 child,如果是动态插值节点,那么参数还会多一个 TEXT 的 patchFlag
*/
function createCallExpression(callee, args = [], loc = locStub) {
// 返回一个类型为 JS_CALL_EXPRESSION 的对象,并包含了执行的函数名和参数
return {
type: 14/* JS_CALL_EXPRESSION */, // 类型
loc, // 位置
callee, // 函数名
arguments: args // 参数
}
}
转换 v-if 节点
/**
* 转换 v-if 节点
* 通过 createStructuralDirectiveTransform 创建的一个结构化指令的转换函数
*/
const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/, (node, dir, context) => {
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// 返回退出回调函数,当所有子节点转换完成执行
return () => {
if (isRoot) {
// v-if 节点的退出函数
// 创建 IF 节点的 codegenNode
ifNode.codegenNode = createCodegenNodeForBranch(branch, 0, context)
} else {
// v-else-if、v-else节点的退出函数
// 将此分支的 codegenNode 附加到 上一个条件节点的 codegenNode 的 alternate 中
let parentCondition = ifNode.codegenNode
while (parentCondition.alternate.type === 19/* JS_CONDITIONAL_EXPRESSION */) {
parentCondition = parentCondition.alternate
}
// 更新候选节点
parentCondition.alternate = createCodegenNodeForBranch(branch, ifNode.branches.length, 1, context)
}
}
})
})
/**
* 创建结构指令转换
* @param {string} name - 指令名
* @param {Function} fn - 构造转换退出函数的方法
*/
function createStructuralDirectiveTransform(name, fn) {
const matches = isString(name) ? (n) => n === name : (n) => name.test(n)
// 返回 transformIf 转换函数
return (node, context) => {
// 只处理元素节点,因为只有元素节点才会有 v-if 指令
if (node.type === 1/* ELEMENT */) {
const { props } = node
// 结构化指令的转换与插槽无关,插槽相关处理逻辑在 vSlot.ts 中
if (node.tagType === 3/* TEMPLATE */ && props.some(isVSlot)) return
const exitFns = []
for (let i = 0; i < props.length; i++) {
const prop = props[i]
// 删除结构指令以避免无限递归
if (prop.type === 7/* DIRECTIVE */ && matches(prop.name)) {
props.splice(i, 1)
i--
// 执行 fn 获取退出函数
const onExit = fn(node, prop, context)
if (onExit) {
exitFns.push(onExit)
}
}
}
return exitFns
}
}
}
/**
* 处理 if 分支节点
*/
function processIf(node, dir, context, processCodegen) {
// 处理 if 节点
if (dir.name === 'if') {
// 创建分支节点
const branch = createIfBranch(node, dir)
// 创建 IF 节点,替换当前节点
const ifNode = {
type: 9 /* IF*/,
loc: node.loc,
branches: [branch]
}
context.replaceNode(ifNode)
if (processCodegen) {
return processCodegen(ifNode, branch, true)
}
}
// 处理 v-else-if、v-else 节点
else {
const siblings = context.parent.children
let i = siblings.indexof(node)
while (i-- >= -1) {
const sibling = siblings[i]
if (sibling && siblingtype === 9/* IF */) {
// 把节点移动到 IF 节点的 branches 中
context.removeNode()
const branch = createIfBranch(node, dir)
sibling.branches.push(branch)
const onExit = processCodegen && processCodegen(sibling, branch, false)
// 因为分支已被删除,所以它的子节点需要在这里遍历
traverseNode(branch, context)
// 执行退出函数
if (onExit) {
onExit()
}
// 恢复 currentNode 为 null,因为它已经被移除
context.currentNode = null
} else {
context.onError(createCompilerError(28/* X_V_ELSE_NO_ADJACENT_IF */, node.loc))
}
break
}
}
}
/**
* 创建分支节点
*/
function createIfBranch(node, dir) {
return {
type: 10/* IF_BRANCH */,
loc: node.loc,
condition: dir.name === 'else' ? undefined : dir.exp,
// 如果节点 node 不是 template,那么 children 指向的就是这个单个 node 构造的数组
children: node.tagType === 3/* TEMPLATE */ ? node.children : [node]
}
}
/**
* 创建 IF 节点的 codegenNode
*/
function createCodegenNodeForBranch(branch, index, context) {
if (branch.condition) {
// 当分支节点存在 condition 的时候,比如 v-if 和 v-else-if,它通过 createConditionalExpression 返回一个条件表达式节点
return createConditionalExpression(branch.condition, createChildrenCodegenNode(branch, index, context), createCallExpression(context.helper(CREATE_COMMENT), [(process.env.NODE_ENV !== 'production') ? '"v-if"' : '""', 'true']))
} else {
return createChildrenCodegenNode(branch, index, context)
}
}
/**
* 当分支节点存在 condition 时,返回条件表达式节点
*/
function createConditionalExpression(test, consequent, alterater, newline = true) {
return {
type: 19/* JS_CONDIITONAL_EXPRESSION */,
test,
consequent, // if 分支的子节点对应的代码生成节点
alternate,// 后面分支的子节点对应的代码生成节点
newline,
loc: locStub
}
}
/**
* 判断每个分支子节点是否是 vnodeCall
*/
function createChildrenCodegenNode(branch, index, context) {
const { helper } = context
//根据 index 创建 key 属性
const keyProperty = createObjectProperty(`key`, createSimpleExpression(index + '', false))
const { children } = branch
const firstChild = children[0]
const needFragmentWrapper = children.length !== 1 || firstChild.type !== 1/* ELEMENT */
if (needFragmentWrapper) {
if (children.length === 1 && firstChild.type === 11/* FOR */) {
const vnodeCall = firstChild.codegenNode
injectProp(vnodeCall, keyProperty, context)
return vnodeCall
} else {
return createVNodeCall(context, helper(FRAGMENT), createObjectExpression([keyProperty]), children, `${64 /* STABLE_FRAGMENT */} /* ${PatchFlagNames[64/* STABLE_FRAGMENT */]}*/`, undefined, undefined, true, false, branch.loc)
}
} else {
const vnodeCall = firstChild.codegenNode
// 把 createVNode 改变为 createBlock
if (vnodeCall.type === 13/* VNODE_CALL */ && /* 组件节点的 children 会被视为插槽,不需要添加 block */ (firstChildtagType !== 1/* COMPONENT */ || vnodeCall.tag === TELEPORT)) {
vnodeCallisBlock = true
// 创建 block 的辅助代码
helper(OPEN_BLOCK)
helper(CREATE_BLOCK)
}
// 给 branhc 注入 key 属性
injectProp(vnodeCall, keyProperty, context)
return vnodeCall
}
}
静态提升
/**
* 静态提升
*/
function hoistStatic(root, context) {
// 注意:根节点无法实现静态提升
walk(root, context, new Map(), isSingleElementRoot(root, root.children[0]));
}
/**
* 移动
*/
function walk(node, context, resultCache, doNotHoistNode = false) {
let hasHoistedNode = false
let hasRuntimeConstant = false // 是否包含运行时常量
const { children } = node
for (leti = 0; i < children.length; i++) {
const child = children[i]
// 普通元素可以静态提升
if (child.type === 1/* ELEMENT */ && child.tagType === 0/* ELEMENT */) {
let staticType
// 获取静态节点的类型,如果是元素,则递归检查它的子节点
if (!doNotHoistNode && (staticType = getStaticType(child, resultCache)) > 0) {
// 运行时常量,值只有在运行时才能被确定,所以不能静态提升
if (staticType === 2/* HAS_RUNTIME_CONSTANT */) {
hasRuntimeConstant = true
}
// 更新 patchFlag
child.codegenNode.patchFlag = -1/* HOISTED */ + ((process.env.NODE_ENV !== 'production') ? `/* HOISTED */` : ``)
// 更新节点的 codegenNode
child.codegenNode = context.hoist(child.codegenNode)
hasHoistedNode = true
continue
}
// 节点可能会包含一些动态子节点,但它的静态属性还是可以被静态提升
else {
const codegenNode = child.codegenNode
if (codegenNode.type === 13/* VNODE_CALL */) {
const flag = getPatchFlag(codegenNode)
if ((!flag || flag === 512/* NEED_PATCH */ || flag === 1/* TEXT */) && !hasDynamicKeyOrRef(child) && !hasCachedProps()) {
const props = getNodeProps(child)
if (props) {
codegenNode.props = context.hoist(props)
}
}
}
}
}
// 文本节点也可以静态提升
else if (child.type === 12/* TEXT_CALL */) {
const staticType = getStaticType(child.content, resultCache)
if (staticType > 0) {
// 运行时常量,由于它的值只能在运行时才能被确定,所以不能静态提升
if (staticType === 2/* HAS_RUNTIME_CONSTANT */) {
hasRuntimeConstant = true
}
child.codegenNode = context.hoist(child.codegenNode)
hasHoistedNode = true
}
}
// 递归遍历子节点
if (child.type === 1/* ELEMENT */) {
walk(child, context, resultCache)
} else if (child.type === 11/* FOR */) {
walk(child, context, resultCache, child.children.length === 1)
} else if (child.type === 9/* IF */) {
for (leti = 0; i < child.branches.length; i++) {
walk(child.branches[i], context, resultCache, child.branches[i].children.length === 1)
}
}
}
// 如果编译配置了 transformHoist,则执行
if (!hasRuntimeConstant && hasHoistedNode && context.transformHoist) {
context.transformHoist(children, context, node)
}
}
/**
* 提升
*/
function hoist(exp) {
context.hoists.push(exp)
const identifier = createSimpleExpression(`_hoisted_${context.hoists.length}`, false, exp.loc, true)
identifier.hoisted = exp
return identifier
}
child.codegenNode = context.hoist(child.codegenNode)
问题:静态提升的好处是针对静态节点不用每次在 render 阶段都执行一次 createVNode 创建 vnode 对象,但它有没有成本呢?为什么?
答:有成本。
- 在初始化时需要判断区分动态节点和静态节点,这会带来一定的性能损耗
- 需要内存去保存静态节点,因此会占用更多的内存
创建根代码生成节点
/**
* 创建根代码生成节点
*/
function createRootCodegen(root, context) {
const { helper } = context
const { children } = root
const child = children[0]
if (children.length === 1) {
// 如果子节点是单个元素节点,则将其转换成一个 block
if (isSingleElementRoot(root, child) && child.codegenNode) {
const codegenNode = child.codegenNode
if (codegenNode.type === 13/* VNODE_CALL */) {
codegenNode.isBlock = true
helper(OPEN_BLOCK)
helper(CREATE_BLOCK)
}
root.codegenNode = codegenNode
} else {
root.codegenNode = child;
}
} else if (children.length > 1) {
// 如果子节点是多个节点,则返回一个 fragement 的代码生成节点
root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children, `${64 /* STABLE_FRAGMENT */} /* ${PatchFlagNames[64 /* STABLE_FRAGMENT */]} */`, undefined, undefined, true)
}
}
生成代码
生成代码流程:
- 创建代码生产上下文
- 生成预设代码
- 生成渲染函数
- 生成资源声明代码
- 生成创建 VNode 树的表达式
/**
* 生成代码
* @param {Object} ast - 转换后的 AST 树
* generate 主要做 5 件事:
* 1、创建代码生成上下文
* 2、生成预设代码
* 3、生成渲染函数
* 4、生成资源声明代码
* 5、生成创建 VNode 树的表达式
*/
function generate(ast, options = {}) {
// 1、创建代码生成上下文
const context = createCodegenContext(ast, options);
const { mode, push, prefixldentifiers, indent, deindent, newline, scopeld, ssr } = context;
const hasHelpers = ast.helpers.length > 0;
const useWithBlock = !prefixldentifiers && mode !== 'module'
const genScopeld = scopeld != null && mode === 'module';
// 2、生成预设代码
if (mode === 'module') {
genModulePreamble(ast, context, genScopeId)
} else {
genFunctionPreamble(ast, context);
}
// 3、生成渲染函数
if (!ssr) {
push(`function render(_ctx, _cache) {`);
} else {
push('function ssrRender( _ctx, _push,_parent,_attrs) {');
}
indent();
if (useWithBlock) {
// 处理带 with 的情况,Web 端运行时编译
push(`with (_ctx) {`);
indent();
if (hasHelpers) {
push(`const { ${ast.helpers.map(s => `${helperNameMap[s]}:_${helperNameMap[s]}`).join(',')}} = _Vue`);
push(`\n`);
newline();
}
}
// 4、生成资源声明代码
// 生成自定义组件声明代码
if (ast.components.length) {
genAssets(ast.components, 'component', context);
if (ast.directives.length || ast.temps > 0) {
newline();
}
}
// 生成自定义指令声明代码
if (ast.directives.length) {
genAssets(ast.directives, 'directive', context);
if (ast.temps > 0) {
newline();
}
}
// 生成临时变量代码
if (ast.temps > 0) {
push('let');
for (let i = 0; i < ast.temps; i++) {
push('Si>0?,:]_tempsli');
}
}
// 如果生成了资源的声明代码,则在尾部添加一个换行符,然后再生成一个空行
if (ast.components.length || ast.directives.length || ast.temps) {
push(`\n`);
newline();
}
// 如果不是 ssr,则再添加一个 return 字符串
if (!ssr) {
push(`return`);
}
// 5、生成创建 VNode 树的表达式
if (ast.codegenNode) {
genNode(ast.codegenNode, context)
} else {
push(`null`);
}
if (useWithBlock) {
deindent();
push(`}`);
}
deindent();
push(`}`);
return {
ast,
code: context.code,
map: context.map ? context.map.toJSON() : undefined
}
}
创建代码生成上下文
/**
* 创建代码生成上下文
*/
function createCodegenContext(ast, { mode = 'function', prefixldentifiers = mode === 'module', sourceMap = false, filename = `template.vue.html`, scopeld = null, optimizeBindings = false, runtimeGlobalName = `Vue`, runtimeModuleName = `vue`, ssr = false }) {
// 上下文对象 context 维护了 generate 过程的一些配置、状态数据、辅助函数
const context = {
mode,
prefixIdentifiers,
sourceMap,
filename,
scopeId,
optimizeBindings,
runtimeGlobalName,
runtimeModuleName,
ssr,
source: ast.loc.source,
code: ``,
column: 1,
line: 1,
offset: 0,
indentLevel: 0,
pure: false,
map: undefined,
helper(key) {
return `${SfhelperNameMap[key]}`
},
// context.code 后追加 code 来更新它的值
push(code) {
context.code += code
},
// 增加代码的缩进,让上下文维护的代码缩进 context.indentLevel 加 1
indent() {
// 执行 newline 方法,添加一个换行符,以及两倍 indentLevel 对应的空格来表示缩进的长度
newline(++context.indentLevel)
},
// 减少代码的缩进,让上下文维护的代码缩进 context.indentLevel 减 1
deindent(withoutNewLine = false) {
if (withoutNewLine) {
--context.indentLevel
} else {
// 执行 newline 方法去添加一个换行符,并减少两倍 indentLevel 对应的空格的缩进长度
newline(--context.indentLevel)
}
},
// 添加换行符
newline() {
newline(context.indentLevel)
}
}
function newline(n) {
context.push('n' + ``.repeat(n))
}
return context
}
生成预设代码
/**
* 生成 module 模式的预设代码
*/
function genModulePreamble(ast, context, genScopeId) {
const { push, newline, optimizeBindings, runtimeModuleName }
= context
// 处理 scopeId...
// 生成 import 声明代码
// ast.helpers 是在 transform 阶段通过 context.helper 添加的
if (ast.helpers.length) {
if (optimizeBindings) {
push(`import { ${ast.helpers.map(s => helperNameMap[s]).join(',')}}} from ${JSON.stringif(runtimeModule)} \n`)
push(`\n// Binding optimization for webpack code-split \nconst ${ast.helpers.map(s => `${helperNameMap[s]}= ${helperNameMap[s]}`).join(',')}\n`)
} else {
push(`import { ${ast.helpers.map(s => `${helperNameMap[s]}as_${helperNameMap[s]}`).join(',')}} from ${JSON.stringify(runtimeModuleName)} \n`)
}
}
// 处理 ssrHelpers...
// 处理imports...
// 处理scopeId...
genHoists(ast.hoists, context)
newline()
push(`export `)
}
/**
* 生成静态提升的相关代码
*/
function genHoists(hoists, context) {
if (!hoists.length) return
context.pure = true
const [push, newline] = context
// 调用 newline 生成空行
newline()
// 遍历 hoists 数组生成静态变量提升的方法
hoists.forEach((exp, i) => {
if (exp) {
push(`const_hoisted_${i + 1}=`)
genNode(exp, context)
newline()
}
})
context.pure = false
}
生成渲染函数
// 无额外定义的函数
生成资源声明代码
/**
* 生成自定义组件/指令声明代码
*/
function genAssets(assets, type, { helper, push, newline }) {
// 获取自定义组件/指令
const resolver = helper(type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE)
// 遍历 assets 数组,生成自定义组件声明代码
for (let i = 0; i < assets.length; i++) {
const id = assetslil
push(`const ${toValidAssetId(id, type)}=
${resolver}(${JSON.stringify(id)})`)
if (i < assets.length - 1) {
newline()
}
}
}
/**
* 包装变量
*/
function toValidAssetId(name, type) {
return `_${type}_${name.replace(/[^\w]/g, '_')}`;
}
生成创建 VNode 树的表达式
/**
* 生成创建 VNode 树的表达式
*/
function genNode(node, context) {
if (shared.isString(node)) {
context.push(node)
return
}
if (sharedisSymbol(node)) {
context.push(context.helper(node))
return
}
// 根据不同类型生成不同代码
switch (node.type) {
case 1/* ELEMENT*/:
case 9 /*IF*/:
case 11/* FOR */:
genNode(node.codegenNode, context)
break
case 2/* TEXT */:
genText(node, context)
break
case 4/* SIMPLE_EXPRESSION */:
genExpression(node, context)
break
case 5/* INTERPOLATION */:
genInterpolation(node, context)
break
case 12/* TEXT_CALL */:
genNode(node.codegenNode, context)
break
case 8/* COMPOUND EXPRESSION */:
genCompoundExpression(node, context)
break
case 3/* COMMENT */:
break
case 13/* VNODE_CALL */:
genVNodeCall(node, context)
break
case 14/* JS_CALL_EXPRESSION */:
genCallExpression(node, context)
break
case 15/* JS_OBJECT_EXPRESSION */:
genObjectExpression(node, context)
break
case 17/* JS_ARRAY_EXPRESSION */:
genArrayExpression(node, context)
break
case 18/* JS_FUNCTION_EXPRESSION */:
genFunctionExpression(node, context)
break
case 19/* JS CONDITIONAL_EXPRESSION */:
genConditionalExpression(node, context)
break
case 20/* JS_CACHE_EXPRESSION */:
genCacheExpression(node, context)
break
// SSR only types
case 21/* JS_BLOCK_STATEMENT */:
genNodeList(node.body, context, true, false)
break
case 22/* JS_TEMPLATE_LITERAL */:
genTemplateLiteral(node, context)
break
case 23/* JS_IF_STATEMENT */:
genIfStatement(node, context)
break
case 24/* JS_ASSIGNMENT_EXPRESSION */:
genAssignmentExpression(node, context)
break
case 25/* JS_SEQUENCE_EXPRESSION */:
genSequenceExpression(node, context)
break
case 26/* JS_RETURN_STATEMENT */:
genReturnStatement(node, context)
break
}
}
/**
* 生成创建 VNode 节点的表达式代码
*/
function genVNodeCall(node, context) {
const { push, helper, pure } = context
const { tag, props, children, patchFlag, dynamicProps, directives, isBlock, disableTracking } = node
if (directives) {
push(helper(WITH_DIRECTIVES) + `(`)
}
/*
isBlock 为 true:生成创建 Block 相关代码
isBlock 为 false,生成创建 VNode 的相关代码
*/
if (isBlock) {
push(`${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}),`)
}
// 是否生成注释代码
if (pure) {
push(PURE_ANNOTATION)
}
push(helper(isBlock ? CREATE_BLOCK : CREATE_VNODE) + `(`, node)
genNodeList(genNullableArgs([tag, props, children, patchFlag, dynamicProps]), context)
push(`)`)
if (isBlock) {
push(`)`)
}
if (directives) {
push(`,`)
genNode(directives, context)
push(`)`)
}
}
/**
* 从后往前遍历,直到找到第一个不为 null 的索引,并返回前面(包含)的所有数组
*/
function genNullableArgs(args) {
let i = args.length
while (i--) {
if (args[i] != null) break
}
return args.slice(0, i + 1).map(arg => arg || `null`)
}
/**
* 生成参数相关代码
*/
function genNodeList(nodes, context, multilines = false, comma = true) {
const { push, newline } = context
// 遍历 nodes,并判断其类型
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
// 字符转,则直接添加到代码中
if (shared.isString(node)) {
push(node)
}
// 数组,则生成数组形式代码
else if (shared.isArray(node)) {
genNodeListAsArray(node, context)
}
// 对象,则递归执行生成节点代码
else {
genNode(node, context)
}
if (i < nodes.length - 1) {
if (multilines) {
comma && push(',')
newline()
} else {
comma && push(',')
}
}
}
}
/**
* 生成 nodes 第二个元素 - props 参数
*/
function genExpression(node, context) {
const { content, isStatic } = node
// 往代码中添加 context 内容
context.push(isStatic ? JSON.stringify(content) : content, node)
}
/**
* 生成 nodes 第二个元素 - children 子节点数组
*/
function genNodeListAsArray(nodes, context) {
const multilines = nodes.length > 3 || nodes.some(n => isArray(n) || !isText$1(n))
context.push(`[`)
multilines && context.indent()
// 递归调用
genNodeList(nodes, context, multilines)
multilines && context.deindent()
context.push(`]`)
}
/**
* 生成条件表达式代码
*/
function genConditionalExpression(node, context) {
const { test, consequent, alternate, newline: needNewline } = node
const { push, indent, deindent, newline } = context
// 生成条件表达式
if (test.type === 4/* SIMPLE EXPRESSION*/) {
const needsParens = !isSimpleldentifier(test.content)
needsParens && push(`(`)
genExpression(test, context)
needsParens && push(`)`)
} else {
push(`(`)
genNode(test, context)
push(`)`)
}
// 换行加缩进
needNewline && indent()
context.indentLevel++
needNewline || push(` `)
// 生成主逻辑代码
push(`?`)
genNode(consequent, context)
context.indentLevel--
needNewline && newline()
needNewline || push(` `)
}
Vue3 优化
/**
* 运行时优化:Vue3 采用 Block 的概念来优化性能,即只更新渲染动态节点
*/
const blockStack = []
let currentBlock = null
/**
* 收集动态的 vnode 节点,在 patch 阶段只对比这些动态 vnode 节点
*/
function openBlock(disableTracking = false) {
blockStack.push((currentBlock = disableTracking ? null : []));
}
/**
* 创建 vnode 节点
*/
function createVNode(type, props = null, children = null) {
// 处理 props 相关逻辑,标准化 class 和 style
// ...
// 对vnode 类型信息编码
// ...
// 创建 vnode 对象
// ...
// 标准化子节点,把不同数据类型的 children 转成数组或者文本类型
// ...
// 添加动态 vnode 节点到 currentBlock 中
if (shouldTrack > 0 && !isBlockNode && currentBlock && patchFlag !== 32/* HYDRATE EVENTS */ && (patchFlag > 0 || shapeFlag & 128/* SUSPENSE */ || shapeFlag & 64/* TELEPORT */ || shapeFlag & 4/* STATEFUL_COMPONENT */ || shapeFlag & 2/* FUNCTION_COMPONENT */)) {
currentBlock.push(vnode)
}
return vnode
}
/**
* 创建 Block
*/
function createBlock(type, props, children, patchFlag, dynamicProps) {
// 创建 vnode
const vnode = createVNode(type, props, children, patchFlag, dynamicProps, true/* isBlock:阻止这个 block 收集自身 */)
// 在 vnode 上保留当前 Block 收集的动态子节点
vnode.dynamicChildren = currentBlock || EMPTY_ARR
blockStack.pop()
// 当前 Block 恢复到父 Block
currentBlock = blockStack[blockStack.length - 1] || null
// 节点本身作为父 Block 收集的子节点
if (currentBlock) {
currentBlock.push(vnode)
}
return vnode
}
/**
* 重新渲染元素
*/
const patchElement = (nl, n2, parentComponent, parentSuspense, isSVG, optimized) => {
const el = (n2.el = n1.el)
const oldProps = (n1 && n1.props) || EMPTY_OBJ
const newProps = n2.props || EMPTY_OBJ
// 更新 props
patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG)
const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
// 更新子节点
// 如果是 blockVNode,则只需要比较动态子节点就行
if (n2.dynamicChildren) {
patchBlockChildren(n1.dynamicChildren, n2.dynamicChildren, currentContainer, parentComponent, parentSuspense, isSVG);
} else if (!optimized) {
patchChildren(n1, n2, currentContainer, currentAnchor, parentComponent, parentSuspense, isSVG)
}
}
/**
* 比较动态子节点
*/
const patchBlockChildren = (oldChildren, newChildren, fallbackContainer, parentComponent, parentSuspense, isSVG) => {
for (leti = 0; i < newChildren.length; i++) {
const oldVNode = oldChildren[i]
const newVNode = newChildren[i]
// 确定待更新节点的容器
const container =
// 对于 Fragment,我们需要提供正确的父容器
oldVNode.type === Fragment ||
// 在不同节点的情况下,将有一个替换节点,我们也需要正确的父容器
!isSameVNodeType(oldVNode, newVNode) ||
// 组件的情况,我们也需要提供一个父容器
oldVNode.shapeFlag & 6/* COMPONENT */
? hostParentNode(oldVNode.el) :
// 在其他情况下,父容器实际上并没有被使用,所以这里只传递 Block 元素即可
fallbackContainer
patch(oldVNode, newVNode, container, null, parentComponent, parentSuspense, isSVG, true)
}
}