解析标签和内容

优先级(render函数 > template > el)

TS
JavaScript
const mount = Vue.prototype.$mount

// 函数劫持 aop 切片,增加将模板进行编译的逻辑
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
    const vm = this;
    const options = vm.$options;
    el = document.querySelector(el);
    // 如果没有 render 方法
    if (!options.render) {
        let template = options.template;
        // 如果没有模板但是有 el
        if (!template && el) {
            template = el.outerHTML;
        }

        // 将模板转化成了 render 函数
        const render = compileToFunctions(template);
        options.render = render;
    }

  return mount.call(this, el, hydrating)
}
export function compileToFunctions(template) {
    parseHTML(template);
    return function () { }
}

const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾的 </div>
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性的
const startTagClose = /^\s*(\/?)>/; // 匹配标签结束的 >
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g

function parseHTML(html) {
    while (html) {
        let textEnd = html.indexOf('<');
        if (textEnd == 0) {
            const startTagMatch = parseStartTag();
            if (startTagMatch) {
                start(startTagMatch.tagName, startTagMatch.attrs);
                continue;
            }
            const endTagMatch = html.match(endTag);
            if (endTagMatch) {
                advance(endTagMatch[0].length);
                end(endTagMatch[1]);
                continue;
            }
        }
        let text;
        if (textEnd >= 0) {
            text = html.substring(0, textEnd);
        }
        if (text) {
            advance(text.length);
            chars(text);
        }
    }
    function advance(n) {
        html = html.substring(n);
    }
    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] });
            }
            if (end) {
                advance(end[0].length);
                return match
            }
        }
    }
}

生成 ast 语法树

JavaScript
JavaScript
// 语法树就是用对象描述 js 语法
{
  tag:'div',
  type:1,
  children:[{tag:'span',type:1,attrs:[],parent:'div对象'}],
  attrs:[{name:'zf',age:10}],
  parent:null
}
let root;
let currentParent;
let stack = [];
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;

// 生成 ast 语法树
function createASTElement(tagName, attrs) {
    return {
        tag: tagName,
        type: ELEMENT_TYPE,
        children: [],
        attrs,
        parent: null
    }
}
function start(tagName, attrs) {
    let element = createASTElement(tagName, attrs);
    if (!root) {
        root = element;
    }
    currentParent = element;
    stack.push(element);
}
function end(tagName) {
    let element = stack.pop();
    currentParent = stack[stack.length - 1];
    if (currentParent) {
        element.parent = currentParent;
        currentParent.children.push(element);
    }
}
function chars(text) {
    text = text.replace(/\s/g, '');
    if (text) {
        currentParent.children.push({
            type: TEXT_TYPE,
            text
        })
    }
}

生成代码

HTML
JavaScript
JavaScript
<div style="color:red">hello {{ name }} <span></span></div>
// template 转化成 render 函数的结果
render(){
  return _c('div', { style: { color: 'red' } }, _v('hello' + _s(name)), _c('span', undefined, ''))
}
// 实现代码生成
function gen(node) {
    if (node.type == 1) {
        return generate(node);
    } else {
        let text = node.text
        if (!defaultTagRE.test(text)) {
            return `_v(${JSON.stringify(text)})`
        }
        let lastIndex = defaultTagRE.lastIndex = 0
        let tokens = [];
        let match, index;
        while (match = defaultTagRE.exec(text)) {
            index = match.index;
            if (index > lastIndex) {
                tokens.push(JSON.stringify(text.slice(lastIndex, index)));
            }
            tokens.push(`_s(${match[1].trim()})`)
            lastIndex = index + match[0].length;
        }
        if (lastIndex < text.length) {
            tokens.push(JSON.stringify(text.slice(lastIndex)))
        }
        return `_v(${tokens.join('+')})`;
    }
}
function getChildren(el) { // 生成儿子节点
    const children = el.children;
    if (children) {
        return `${children.map(c => gen(c)).join(',')}`
    } else {
        return false;
    }
}
function genProps(attrs) { // 生成属性
    let str = '';
    for (let i = 0; i < attrs.length; i++) {
        let attr = attrs[i];
        if (attr.name === 'style') {
            let obj = {}
            attr.value.split(';').forEach(item => {
                let [key, value] = item.split(':');
                obj[key] = value;
            })
            attr.value = obj;
        }
        str += `${attr.name}:${JSON.stringify(attr.value)},`;
    }
    return `{${str.slice(0, -1)}}`;
}
function generate(el) {
    let children = getChildren(el);
    let code = `_c('${el.tag}',${el.attrs.length ? `${genProps(el.attrs)}` : 'undefined'
        }${children ? `,${children}` : ''
        })`;
    return code;
}
let code = generate(root);

生成 render 函数

export function compileToFunctions(template) {
    parseHTML(template);
    let code = generate(root);
    let render = `with(this){return ${code}}`;
    let renderFn = new Function(render);
    return renderFn
}
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 解析标签和内容
  2. 2. 生成 ast 语法树
  3. 3. 生成代码
  4. 4. 生成 render 函数