解析 template 生成 AST
packages/compiler-core/src/compile.ts
packages/compiler-core/src/parse.ts
又是又臭又长,自己记录用的
compile (Web编译入口)
文件目录:packages\compiler-core\src\compile.ts
baseCompile (解析 template 生成 AST、AST 转换、生成代码)
function baseCompile(template,options){
// 解析template,生成AST
const ast = isString(template) ? baseParse(template, options) : template
const [nodeTransforms, directiveTransforms] =
getBaseTransformPreset(prefixIdentifiers)
// AST转换
transform(
ast,
extend({}, options, {
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: extend(
{},
directiveTransforms,
options.directiveTransforms || {} // user transforms
)
})
)
// 生成代码
return generate(
ast,
extend({}, options, {
prefixIdentifiers
})
)
}
baseCompile:解析 template 生成 AST,AST 转换和生成代码。
baseParse (解析template,生成AST)
function baseParse(content,options) {
// 创建解析上下文
const context = createParserContext(content, options)
const start = getCursor(context)
// 解析子节点,生产AST
return createRoot(
parseChildren(context, TextModes.DATA, []),
getSelection(context, start)
)
}
createParserContext (初始化默认解析配置)
function createParserContext(content,rawOptions) {
// extend 就是 Object.assign
const options = extend({}, defaultParserOptions)
// 未配置就去默认解析配置
let key
for (key in rawOptions) {
options[key] =
rawOptions[key] === undefined
? defaultParserOptions[key]
: rawOptions[key]
}
return {
options,
column: 1,
line: 1,
offset: 0,
originalSource: content,
source: content,
inPre: false,
inVPre: false,
onWarn: options.onWarn
}
}
getCursor
function getCursor(context) {
const { column, line, offset } = context
return { column, line, offset }
}
创建解析上下文
context = {
options, // 表示解析相关配置
column: 1, // 表示当前代码的列号
line: 1, // 表示当前代码的行号
offset: 0, // 表示当前代码相对于原始代码的偏移量
originalSource: content, // 表示最初的原始代码
source: content, // 表示当前代码
inPre: false, // 表示当前代码是否在 pre 标签内
inVPre: false, // 表示当前代码是否在 v-pre 指令的环境下
onWarn: options.onWarn
}
start = {
column: 1, // 表示当前代码的列号
line: 1, // 表示当前代码的行号
offset: 0 // 表示当前代码相对于原始代码的偏移量
}
pre 标签内 或 v-pre 指令下,跳过这个元素和它的子元素的编译过程。
parseChildren (解析子节点,也是重点)
// 初始化进入的三个参数 context , mode = 1, ancestors = []
function parseChildren(context,mode,ancestors){
// last = xs:[] => xs[xs.length - 1]; 数组最后一个参数
const parent = last(ancestors)
// parent = ''
const ns = parent ? parent.ns : Namespaces.HTML
// ns = Namespaces.HTML
const nodes = []
开始遍历所有文本或节点,编译为nodes
// isEnd : 判断是否有还有 < 结束标签或 /> 自封闭标签
while (!isEnd(context, mode, ancestors)) {
...
}
}
遍历处理所有文本或节点
开始遍历所有文本或节点,AST 节点数组
'{{' 或 '}}'
| '{{' 或 '}}' | |
|---|---|
| if | 不为 Pre 标签或 v-pre,开始为'{{' 或 '}}' |
| Render | 声明式渲染 |
| Notes | pre 不进行渲染作为文本直接输出 |
'<'
| < | |
|---|---|
| if | 只有一个字符串 '<' |
| 处理流程 | 控制台提示,结束标记解析阶段错误 |
| 注释 | 只有一个字符串。控制台提示,以文本输出。 |
| <! | <!-- | <!DOCTYPE | <![CDATA[ | <!** |
|---|---|---|---|---|
| if | 起始字符串为'<!--' | 起始字符串为'<!DOCTYPE' | 起始字符串为'<![CDATA[' | <!开头,后面的没匹配上 |
| 注释 | 处理为注释节点 | 处理为<!DOCTYPE节点 | CDATA 节状态,而且它不是 HTML 命名空间中的元素,那么切换到 CDATA 节状态。否则,创建一个注释节点,其数据为"[CDATA["字符串。 | 这是一个不正确打开注释的解析错误。创建一个注释节点,其数据为空字符串。 |
- <![CDATA[ 展示的数据取决与后面的数据
- <!** 后的数据没有或未匹配上 <!-- 、<!DOCTYPE 、 <![CDATA[,那么创建注释节点并且数据是空字符串
| </ | </ | /[a-z]/i.test(s[2]) | </** | |
|---|---|---|---|---|
| if | 只有两个字符 '</' | 第三个字符是 '>' | 正则匹配,第三个字符a-z之间 | 未匹配上 |
| 处理流程 | 控制台提示,结束标记解析阶段错误 | 控制台提示,结束标记解析阶段错误 | 无效的标记 | |
| 注释 | 数据是'</' | 这是一个缺少结束标记名的解析错误。数据是'</>'。 | 多余的结束标签,不展示 | 无效的标记。创建一个注释节点,其数据为空字符串。 |
无效标记,数据为空字符串
app.component('my-component', {
template:"</**"
})
| < | /[a-z]/i.test(s[1]) | ? |
|---|---|---|
| if | 第二个字符在 a-z 之间 | 第二个字符是 '?' |
| 处理流程 | 创建元素节点。vue2.x兼容处理template元素节点 node = node.children | |
| 注释 | vue2.x兼容处理template元素节点,使用子节点替换当前节点 | 无效的标记。创建一个注释节点,其数据为空字符串。 |
文本节点
开头未匹配上 '<'、'{{'、'}}' ,创建文本节点
后续还有 AST 转换和生成代码