jiefancis / blogs

个人博客,学习笔记(issues)
1 stars 0 forks source link

vue手写源码之编译器 #33

Open jiefancis opened 1 year ago

jiefancis commented 1 year ago

看完编译器,能明白以下问题

  1. template解析成ast,如何确定ast的父子关系? stack栈的先入后出的特性,当遇到开始标签时入栈,遇到结束标签时出栈。dom树形结构具备这样的特性

  2. template如何解析标签、属性、节点?

  3. 怎么匹配并解析非插值节点? html.indexOf('{{') === 0.,如果你的正则表达式掌握的还不错,可以尝试引入正则匹配。 if (html.match(/{{(.*)}}/)) { RegExp.$1} 获取插值表达式

    var template = `
    <div class="container" id="container">
        <input type="text" v-model="a.b"></input>
        {{state}}
        <div class="h1">
            <span>标题</span>  
            {{title}}
        </div>
    </div>
    `
    parse(template)
    function parse(template) {
    let root = null, stack = [], html = template, i = 0;
    
    return parseHTML()
    function parseHTML() {
        while(html) {
            // 开始
            // 保证当前是 < 开头
            html = html.trim()
            if(html.indexOf('<') === 0) {
    
                if(html.indexOf('</') === 0) {
                    parseTagEnd()
                } else {
                    parseTagStart()
                }
            } else if(html.indexOf('{{')===0) {
                console.log('匹配插值',html)
                // 文本节点中的插值节点
                parseInterpolate()
            } else {
                parseStaticText()
            }
        }
    
        return root
    }
    // 处理静态文本节点
    function parseStaticText(){
        let end = html.indexOf('<');
        let content = html.slice(0, end);
        html = html.slice(end)
    
        let staticNode = {
            type: 'text',
            text: content,
            isStatic: true,
        }
    
        if(stack.length) {
            stack[stack.length - 1].children.push(staticNode)
        }
    
    }
    // 找到插值,对象类型 a.b 如何处理?
    function parseInterpolate() {
        html = html.trim()
        let end = html.indexOf('}}')
        let content = html.slice(2, end)
        html = html.slice(end+2).trim()
    
        const textNode = {
            type: 'interpolate',
            text: content
        }
        console.log('插值节点', content)
        stack[stack.length - 1].children.push(textNode)
    }
    
    function parseTagStart() {
        html = html.trim()
        let end = html.indexOf('>');
        console.log('起始标签', html)
        let content = html.slice(1,end).trim();
        html = html.slice(end+1).trim()
    
        let space = content.indexOf(' '), tagName = '';
        if(space === -1) {
            tagName = content
            content = ''
        } else {
            tagName = content.slice(0, space).trim()
            content = content.slice(space+1).trim()
        }
    
        console.log(html, space, 123456, tagName, 'starttttttt', content)
    
        let attrsMap = content ? parseAttrs(content) : {}
    
        let node = createVnode(tagName, attrsMap, [])
        if(!root) {
            console.log('start Node', root)
            root = node
        }
        stack.push(node)
    }
    
    function parseTagEnd() {
        // 处理>前面有空格的情况
        html = html.slice(html.indexOf('>') + 1)
        processElement()
    }
    
    function parseAttrs(attrStr) {
        let list = attrStr.split(' ').map(attr => attr.trim())
        let map = {}
        console.log(list,1234567890)
        list.forEach(attr => {
            let kv = attr.split('=')
            map[kv[0]] = kv[1].replace(/["']?/g, '') 
        })
        return map
    }
    
    function createVnode(tag, rawAttrs, children = []) {
        return  { type: 'element', tag, rawAttrs, children }
    }
    
    function processElement() {
        let currentEle = stack.pop(),
            len = stack.length,
            topEle = stack[len-1];
    
        currentEle.attrs = {}
    
        if(len) {
            const { tag, rawAttrs } = currentEle;
            let keys = Object.keys(rawAttrs)
            // 处理属性
            keys.forEach(prop => {
                if(prop === 'v-model') {
                    processVmodel(currentEle)
                } else if(prop.match(/^v-bind:(.*)/)) {
                    processVbind(currentEle, RegExp.$1, rawAttrs[`v-bind:${RegExp.$1}`])
                } else if(prop.match(/^v-on:(.*)/)) {
                    processVon(currentEle,RegExp.$1, rawAttrs[`v-on:${RegExp.$1}`])
                }
            })
    
            topEle.children.push(currentEle)
        }
    
    }
    
    function processVmodel(ele) {
        const { tag, rawAttrs, attrs } = ele;
        const { type, 'v-model': vModelValue} = rawAttrs;
    
        // type = 'text' 'checkbox' 'gradio'
        if(tag === 'input') {
            attrs.vModel = { tag, type, value: vModelValue}
        } else if(type === 'textarea') {
            attrs.vModel = { tag, value: vModelValue}
        } else if(type === 'select') {
            attrs.vModel = { tag, value: vModelValue}
        }
    }
    
    function processVbind(ele, bindKey, bindValue) {
        ele.attrs.vBind = {[bindKey]: bindValue}
    }
    
    function processVon(ele, onKey, onValue) {
        ele.attrs.vOn = {[onKey]: onValue}
    }
    }