vue模板編譯原理

我們都知道 vue 寫代碼是需要按照固定的格式,比如下面這樣:
<template><div>{{msg}}</div></template><script>export default {data () {return {msg: ''}}}</script>
把 html 的內(nèi)容寫在 template 里面,經(jīng)過 vue 的處理,首先會把模板轉(zhuǎn)換為 AST 抽象語法樹,然后把 AST 轉(zhuǎn)換為可執(zhí)行的 render 函數(shù),最后才可以生成真是 DOM,那么今天要分析的內(nèi)容是如何將模板生成 AST 的。
首先要生成的 AST 結(jié)構(gòu)如下:
function createASTElement (tag,attrs,parent{return {type: 1,tag: tag,attrsList: attrs,attrsMap: attrs,rawAttrsMap: {},parent: parent,children: []}}
然后就是把模板的內(nèi)容都遍歷一遍,通過正則去匹配標(biāo)簽的內(nèi)容。
var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;// Regular Expressions for parsing tags and attributesvar attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;var dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;var ncname = "[a-zA-Z_][\\-\\.0-9_a-zA-Z" + (unicodeRegExp.source) + "]*";var qnameCapture = "((?:" + ncname + "\\:)?" + ncname + ")";var startTagOpen = new RegExp(("^<" + qnameCapture));var startTagClose = /^\s*(\/?)>/;var endTag = new RegExp(("^<\\/" + qnameCapture + "[^>]*>"));var doctype = /^<!DOCTYPE [^>]+>/i;// #7298: escape - to avoid being passed as HTML comment when inlined in pagevar reCache = {};var comment = /^<!\--/;var conditionalComment = /^<!\[/;
這些正則都是源碼里面粘貼過來的,包括匹配開始標(biāo)簽,結(jié)束標(biāo)簽,注釋等等。
function parseHTML(html, options) {var stack = [];var expectHTML = options.expectHTML;var isUnaryTag$$1 = options.isUnaryTag || no;var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;var index = 0;var last, lastTag;while (html) {last = html;// 確保不是 script 或者 style 者 textarea 標(biāo)簽if (!lastTag || !isPlainTextElement(lastTag)) {var textEnd = html.indexOf('<');if (textEnd === 0) {// End tag: 匹配結(jié)束標(biāo)簽var endTagMatch = html.match(endTag);if (endTagMatch) {var curIndex = index;advance(endTagMatch[0].length);parseEndTag(endTagMatch[1], curIndex, index);continue}// Start tag: 匹配開始標(biāo)簽var startTagMatch = parseStartTag();if (startTagMatch) {handleStartTag(startTagMatch);continue}}var text = (void 0), rest = (void 0), next = (void 0);if (textEnd >= 0) {rest = html.slice(textEnd);while (!endTag.test(rest) &&!startTagOpen.test(rest)) {// 把標(biāo)簽里面的文字提取出來 hello</div> 提取 hellonext = rest.indexOf('<', 1);if (next < 0) { break }textEnd += next;rest = html.slice(textEnd);}text = html.substring(0, textEnd);}if (textEnd < 0) {text = html;}if (text) {advance(text.length); // 去除文字 hello 剩下 </div>}if (options.chars && text) {options.chars(text, index - text.length, index);}} else {var endTagLength = 0;var stackedTag = lastTag.toLowerCase();var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'));var rest$1 = html.replace(reStackedTag, function (all, text, endTag) {endTagLength = endTag.length;return ''});index += html.length - rest$1.length;html = rest$1;parseEndTag(stackedTag, index - endTagLength, index);}}// Clean up any remaining tagsparseEndTag();function advance(n) {index += n;html = html.substring(n);}function parseStartTag() {var start = html.match(startTagOpen);// [ '<div', 'div', index: 0, input: '<div id="1" bg="2">hello</div>', groups: undefined ]if (start) {var match = {tagName: start[1],attrs: [],start: index};advance(start[0].length); // 去除 <div,剩下 id="1" bg="2">hello</div>var end, attr;// 把屬性都存儲起來 id="1" bg="2" 去除屬性之后剩下 >hello</div>while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {attr.start = index;advance(attr[0].length);attr.end = index;match.attrs.push(attr);}if (end) {match.unarySlash = end[1];advance(end[0].length); // 把>或者/>去除剩下 hello</div>match.end = index;return match}}}function handleStartTag(match) {var tagName = match.tagName;var unarySlash = match.unarySlash; // 自閉合標(biāo)簽為/ 雙標(biāo)簽為‘’空var unary = !!unarySlash;var l = match.attrs.length;var attrs = new Array(l);for (var i = 0; i < l; i++) {var args = match.attrs[i];var value = args[3] || args[4] || args[5] || '';attrs[i] = {name: args[1],value: value};// 按照{(diào) name: value } 的形式保存起來,{ id: 1 }if (options.outputSourceRange) {attrs[i].start = args.start + args[0].match(/^\s*/).length;attrs[i].end = args.end;}}// 雙標(biāo)簽if (!unary) {stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end });lastTag = tagName;}if (options.start) {options.start(tagName, attrs, unary, match.start, match.end);}}// stackedTag, index - endTagLength, indexfunction parseEndTag(tagName, start, end) {var pos, lowerCasedTagName;if (start == null) { start = index; }if (end == null) { end = index; }// Find the closest opened tag of the same typeif (tagName) {lowerCasedTagName = tagName.toLowerCase();for (pos = stack.length - 1; pos >= 0; pos--) {if (stack[pos].lowerCasedTag === lowerCasedTagName) {break}}} else {// If no tag name is provided, clean shoppos = 0;}if (pos >= 0) {// Close all the open elements, up the stackfor (var i = stack.length - 1; i >= pos; i--) {if (options.end) {options.end(stack[i].tag, start, end);}}// Remove the open elements from the stackstack.length = pos;lastTag = pos && stack[pos - 1].tag;}}}
上面是把所有的標(biāo)簽進(jìn)行處理,按照順序遍歷之后就可以生成 AST 了。
var currentParent;var stack = [];var root;function end(tag, start) {var element = stack[stack.length - 1];// pop stack 出棧stack.length -= 1;currentParent = stack[stack.length - 1];closeElement(element);}function start(tag, attrs, unary, start, end) {var element = createASTElement(tag, attrs, currentParent);if (!root) {root = element;}if (!unary) {currentParent = element;// push stack 入棧stack.push(element);}}function chars(text, start, end) {var children = currentParent.children;if (text) {var child;child = {type: 2,expression: '',tokens: '',text: text};if (child) {children.push(child);}}}function closeElement (element) {if (currentParent && !element.forbidden) {currentParent.children.push(element);element.parent = currentParent;}element.children = element.children.filter(function (c) { return !(c).slotScope; });}function createASTElement (tag,attrs,parent) {return {type: 1,tag: tag,attrsList: attrs,attrsMap: attrs,rawAttrsMap: {},parent: parent,children: []}}const content = '<template><div bg="1">hello<span>11</span></div></template>'parseHTML(content, {start: start,end: end,chars: chars});console.log(root) // ast
源碼在 vue-template-compiler
評論
圖片
表情
