将模板字符串转成AST思路

抽象语法树(AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

像 vue、react、babel、esbuild 等各类库和工具,在底层的实现上都使用了 AST,在 template 转成 AST 的过程中可以加上不同的配置去实现不同的功能,对于 vue 的 template 转成 AST 按照理解的思路进行简单的实现。

Vue 3 Template Explorer 这里左侧输入 html,右侧显示的是 render 函数,打开 F12 控制台可以看到 vueJs 的 parse 函数将 template 转成的 AST。

image.png

vueJs 将模板字符串转成的 AST 附带的属性比较多,这里只是简单的实现标签,属性和文本。

let template = `
  <div id="app">
    <h1 @click="add" class="item" :id="count">{{count}}<span>111</span></h1>
    <p>静态标签</p>
  </div>
`
function parse(template) {
    let ast = null
    
    return ast
} 
复制代码

首先需要将模板进行词法分析,将标签与属性等全部分离出来。

function tokenizer(template) {
    let tokens = []
    let type = ''
    let val = ''
    
    for (let i = 0; i < template.length; i++) {
        let ch = template[i]
        
        if (ch === '<') {
            push()
            if (template[i + 1] === '/') { // 标签结束
                type = 'tagend'
            } else {                       // 标签开始
                type = 'tagstart'
            }
        } else if (ch === '>') {
            push()
            type = 'text'
            continue
        } else if (/[\s]/.test(ch)) {
            push()
          type = 'props'
          continue
        }
        val += ch
    }
    return tokens
    
    function push() {
        if (val) {
            if (type === 'tagstart') val = val.slice(1);
            if (type === 'tagend') val = val.slice(2);
            tokens.push({type, val})
            val = ''
        }
    }
}
复制代码

执行之后 tokens 的结果:

image.png

将 tokens 转成 AST:

function parse(template) {
    let tokens = tokenizer(template)
    
    let cur = 0
    let ast = {
        type: 'root', // 根节点
        props: [],    // 节点属性
        flag: 0,      // 可以记录哪些属性是动态的
        children: []  // 子节点
    }
    
    while (cur < tokens.length) {
        ast.children.push(walk())
    }
    return ast
    
    function walk() {
        let token = tokens[cur]
        if (!token) return
        
        if (token.type === 'tagstart') {
            let node = {
                type: 'element',
                tag: token.val,
                props: [],
                children: []
            }
            token = tokens[++cur]
            while(token.type !== 'tagend') {  // 在遇到tagend之前,所有元素属性都属于当前节点
                if (token.type === 'props') { // 属性
                    node.props.push(walk())
                } else {                      // 子元素
                    node.children.push(walk())
                }
                token = tokens[cur]
            }
            cur++
            return node
        } else if (token.type === 'tagend') {
            cur++
        } else if (token.type === 'text') {
            cur++
            return token
        } else if (token.type === 'props') {
            cur++
            const [key, val] = token.val.split('=')
            
            return { key, val, type: 0 }
        }
    }
}
复制代码

最后的 AST 的结果:

GIF.gif

对于 render 函数的实现,根据 compiler-core/src/compile.ts,还需要进行 transform 包装,transform 函数会对 AST 进行进一步的属性扩展,然后使用 generate 生成 render。

image.png

image.png

image.png

image.png

code 即为 render 函数的字符串形式。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享