web 模式的编译主要做了 3 件事:
/**
* 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
}))
}
问题:AST 是什么?
答:AST 是抽象语法树,用于描述节点在模板中完整的映射信息。
问题:为什么 AST 的根节点是虚拟节点?
答:因为 Vue3 中支持
语法,即:组件可以有多个根节点,但是树状结构只能有一个根节点,所以需要在外层套一个虚拟节点。
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, '