Vue2-模板解析

138 阅读1分钟
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;

const startTagOpen = new RegExp(`^<${qnameCapture}`); // <xxx
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // </xxx>

const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;

const startTagClose = /^\s*(\/?)>/;
const defaultTagRE = /\{\{((?:.\r?\n)+?)\}\}/g



/** @param {string} html */
function parseHTML(html) {
    html = html.trimEnd()
    html = html.trimStart()
    const ELEMENT_TYPE = 1;
    const TEXT_TYPE = 3;
    const stack = [];
    let currentParent; // 当前节点的父亲
    let root; // 根节点
    // ast结构
    function createAstElement(tag,attrs){
        return {
            tag:tag,
            attrs,
            type:ELEMENT_TYPE,
            children:[],
            parent:null,
        }
    }

    // 开始标签处理添加到stack
    function start(tag,attrs){
        let node = createAstElement(tag,attrs)
        if (!root) {
            root = node
        }
        if (currentParent) {
            node.parent = currentParent
            currentParent.children.push(node)
        }
        stack.push(node)
        currentParent = node
    }

    // 文本内容添加到stack
    function chars(text){
        text = text.replace(/\s/g,'')
        text && currentParent.children.push({
            type:TEXT_TYPE,
            text,
            parent:currentParent
        });
    }

    // 闭合标签处理
    function end(){
        let node = stack.pop();
        currentParent = stack[stack.length - 1]
    }

    // 将已经处理过的html部分删除
    function advance(n) {
        html = html.substring(n)
    }

    /**
     * @returns {{tagName:string,attrs:string[]}}
     */
    function parseStartTag() {
        const start = html.match(startTagOpen)
        if (start) {
            const match = {
                tagName: start[1],
                attrs: []
            }
            advance(start[0].length);
            // 如果标签没结束一直匹配里面的内容
            let attr, end;
            while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
                advance(attr[0].length)
                match.attrs.push({ name: attr[1], value: attr[3] || attr[4] || attr[5] || true })
            }
            if (end) {
                advance(end[0].length)
            }
            return match
        }
        return false
    }
    while (html) {
        // html如果第一项是 < 以标签开始处理, 否则不处理 vue2 默认需要一个根标签
        let textEnd = html.indexOf('<');

        // 处理标签自身
        if (textEnd == 0) {
            // 解析开始标签 <div class="name">
            let startTagMatch = parseStartTag()
            if (startTagMatch) {
                start(startTagMatch.tagName,startTagMatch.attrs)
                continue
            }
            // 解析结尾标签 </div>
            let endTagMatch = html.match(endTag)
            if (endTagMatch) {
                end(startTagMatch.tagName)
                advance(endTagMatch[0].length)
                continue;
            }
        }

        // 标签内容 {{xxx}}xxx 
        if (textEnd > 0) {
            // 解析标签内容
            let text = html.substring(0,textEnd); //文本内容
            if (text) {
                chars(text)
                advance(text.length)
            }
        }

    }

    return root
}


function compilerToFunction(elementTemp) {
    let ast = parseHTML(elementTemp)
    return ast
};

const temp = `<div class="name"> {{xxx}} <li style="color:red">{{red}}</li> <input type="text" /> <span style="color:red">{{red}}</span> </div> `
compilerToFunction(temp)


// export {
//     compilerToFunction
// }