您的位置:首页 > 产品设计 > UI/UE

vue源码分析day1-ast实现.md

2018-03-09 16:56 696 查看

源码分析

parseHTML主要对html进行截取,匹配,分析,然后调用传入的options中的处理函数,在父级函数中形成ast结构。

/*
* 主要的html处理
*/
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;
// Make sure we're not in a plaintext content element like script/style
// 检测不是script,textarea,style
if (!lastTag || !isPlainTextElement(lastTag)) {
var textEnd = html.indexOf('<');
// 检测<开始的标间
if (textEnd === 0) {
// Comment:
// 处理html 中的注释
if (comment.test(html)) {
var commentEnd = html.indexOf('-->');

if (commentEnd >= 0) {
if (options.shouldKeepComment) {
options.comment(html.substring(4, commentEnd));
}
advance(commentEnd + 3);
continue
}
}

// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment if (conditionalComment.test(html)) {
var conditionalEnd = html.indexOf(']>');

if (conditionalEnd >= 0) {
advance(conditionalEnd + 2);
continue
}
}

// Doctype:
var doctypeMatch = html.match(doctype);
if (doctypeMatch) {
advance(doctypeMatch[0].length);
continue
}

// End tag:
// ["</div>", "div", index: 0, input: "</div></div>"]
var endTagMatch = html.match(endTag);
if (endTagMatch) {
var curIndex = index;
// 需改index并截取html
advance(endTagMatch[0].length);
parseEndTag(endTagMatch[1], curIndex, index);
continue
}

// Start tag:
//{tagName: "div", attrs: Array(1), start: 23, unarySlash: "", end: 41}
/*
* 匹配开始到结束,获取属性
*/
var startTagMatch = parseStartTag();
if (startTagMatch) {
handleStartTag(startTagMatch);
if (shouldIgnoreFirstNewline(lastTag, html)) {
advance(1);
}
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) &&
!comment.test(rest) &&
!conditionalComment.test(rest)
) {
// < in plain text, be forgiving and treat it as text
next = rest.indexOf('<', 1);
if (next < 0) { break }
textEnd += next;
rest = html.slice(textEnd);
}
text = html.substring(0, textEnd);
advance(textEnd);
}

if (textEnd < 0) {
text = html;
html = '';
}

if (options.chars && text) {
options.chars(text);
}
} 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;
if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
text = text
.replace(/<!--([\s\S]*?)-->/g, '$1')
.replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
}
if (shouldIgnoreFirstNewline(stackedTag, text)) {
text = text.slice(1);
}
if (options.chars) {
options.chars(text);
}
return ''
});
index += html.length - rest$1.length;
html = rest$1;
parseEndTag(stackedTag, index - endTagLength, index);
}

if (html === last) {
console.log(html);
options.chars && options.chars(html);
if ("development" !== 'production' && !stack.length && options.warn) {
options.warn(("Mal-formatted tag at end of template: \"" + html + "\""));
}
break
}
}

// Clean up any remaining tags
parseEndTag();

function advance (n) {
index += n;
html = html.substring(n);
console.log(html);
}

function parseStartTag () {
// ["<div", "div", index: 0, input: "<div class="demo">{{name}}</div></div>"]
var start = html.match(startTagOpen);
if (start) {
var match = {
tagName: start[1],
attrs: [],// 二维数组
start: index
};
advance(start[0].length);
var end, attr;
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
/*
* [" class="demo"", "class",
*  "=", "demo", undefined, undefined,
* index: 0, input: " class="demo">{{name}}</div></div>"]

dbe0
*/
advance(attr[0].length);
match.attrs.push(attr);
}
if (end) {
// [">", "", index: 0, input: ">{{name}}</div></div>"]
match.unarySlash = end[1];
advance(end[0].length);
match.end = index;
return match
}
}
}

function handleStartTag (match) {
//{tagName: "div", attrs: Array(1), start: 23, unarySlash: "", end: 41}
var tagName = match.tagName;
var unarySlash = match.unarySlash;

if (expectHTML) {
if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
parseEndTag(lastTag);
}
if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {
parseEndTag(tagName);
}
}
// 判断是否为闭合
var unary = isUnaryTag$$1(tagName) || !!unarySlash;
var l = match.attrs.length;
var attrs = new Array(l);
for (var i = 0; i < l; i++) {
var args = match.attrs[i];
// hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778 if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
if (args[3] === '') { delete args[3]; }
if (args[4] === '') { delete args[4]; }
if (args[5] === '') { delete args[5]; }
}
var value = args[3] || args[4] || args[5] || '';
var shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'
? options.shouldDecodeNewlinesForHref
: options.shouldDecodeNewlines;
attrs[i] = {
name: args[1],
value: decodeAttr(value, shouldDecodeNewlines)
};
}

if (!unary) {
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs });
lastTag = tagName;
}

if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end);
}
}

function parseEndTag (tagName, start, end) {
var pos, lowerCasedTagName;

if (tagName) {
lowerCasedTagName = tagName.toLowerCase();
}

// Find the closest opened tag of the same type
if (tagName) {
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos].lowerCasedTag === lowerCasedTagName) {
// 找到最近一个stack中匹配的索引
break
}
}
} else {
// If no tag name is provided, clean shop
pos = 0;
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (var i = stack.length - 1; i >= pos; i--) {
if ("development" !== 'production' &&
(i > pos || !tagName) &&
options.warn
) {
options.warn(
("tag <" + (stack[i].tag) + "> has no matching end tag.")
);
}
if (options.end) {
options.end(stack[i].tag, start, end);
}
}

// Remove the open elements from the stack
stack.length = pos;
lastTag = pos && stack[pos - 1].tag;
} else if (lowerCasedTagName === 'br') {
if (options.start) {
options.start(tagName, [], true, start, end);
}
} else if (lowerCasedTagName === 'p') {
if (options.start) {
options.start(tagName, [], false, start, end);
}
if (options.end) {
options.end(tagName, start, end);
}
}
}
}


parse函数主要进行parseHTML结果的处理,形成ast的结构

/**
* Convert HTML string to AST.
*/
function parse (
template,
options
) {
warn$2 = options.warn || baseWarn;

platformIsPreTag = options.isPreTag || no;
platformMustUseProp = options.mustUseProp || no;
platformGetTagNamespace = options.getTagNamespace || no;

transforms = pluckModuleFunction(options.modules, 'transformNode');
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');

delimiters = options.delimiters;

var stack = [];
var preserveWhitespace = options.preserveWhitespace !== false;
var root;
var currentParent;
var inVPre = false;
var inPre = false;
var warned = false;

function warnOnce (msg) {
if (!warned) {
warned = true;
warn$2(msg);
}
}

function closeElement (element) {
// check pre state
if (element.pre) {
inVPre = false;
}
if (platformIsPreTag(element.tag)) {
inPre = false;
}
// apply post-transforms
for (var i = 0; i < postTransforms.length; i++) {
postTransforms[i](element, options);
}
}
parseHTML(template, {
warn: warn$2,
expectHTML: options.expectHTML,
isUnaryTag: options.isUnaryTag,
canBeLeftOpenTag: options.canBeLeftOpenTag,
shouldDecodeNewlines: options.shouldDecodeNewlines,
shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
shouldKeepComment: options.comments,
start: function start (tag, attrs, unary) {
// check namespace.
// inherit parent ns if there is one
var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);
console.log(ns);
// handle IE svg bug
/* istanbul ignore if */
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs);
}

var element = createASTElement(tag, attrs, currentParent);
if (ns) {
element.ns = ns;
}

if (isForbiddenTag(element) && !isServerRendering()) {
element.forbidden = true;
"development" !== 'production' && warn$2(
'Templates should only be responsible for mapping the state to the ' +
'UI. Avoid placing tags with side-effects in your templates, such as ' +
"<" + tag + ">" + ', as they will not be parsed.'
);
}

// apply pre-transforms
for (var i = 0; i < preTransforms.length; i++) {
element = preTransforms[i](element, options) || element;
}
if (!inVPre) {
processPre(element);
if (element.pre) {
inVPre = true;
}
}
if (platformIsPreTag(element.tag)) {
inPre = true;
}
if (inVPre) {
processRawAttrs(element);
} else if (!element.processed) {
// structural directives
// 处理v-for指令
processFor(element);
// 处理 v-if
processIf(element);
// 处理v-once
processOnce(element);
// element-scope stuff
processElement(element, options);
}

function checkRootConstraints (el) {
{
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(
"Cannot use <" + (el.tag) + "> as component root element because it may " +
'contain multiple nodes.'
);
}
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.'
);
}
}
}

// tree management
if (!root) {
// root的确定
root = element;
checkRootConstraints(root);
} else if (!stack.length) {
// allow root elements with v-if, v-else-if and v-else
if (root.if && (element.elseif || element.else)) {
checkRootConstraints(element);
addIfCondition(root, {
exp: element.elseif,
block: element
});
} else {
warnOnce(
"Component template should contain exactly one root element. " +
"If you are using v-if on multiple elements, " +
"use v-else-if to chain them instead."
);
}
}
if (currentParent && !element.forbidden) {
if (element.elseif || element.else) {
processIfConditions(element, currentParent);
} else if (element.slotScope) { // scoped slot
currentParent.plain = false;
var name = element.slotTarget || '"default"';(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element;
} else {
currentParent.children.push(element);
element.parent = currentParent;
}
}
if (!unary) {
// 非自闭合标签才需要处理children,进行入栈
currentParent = element;
stack.push(element);
} else {
closeElement(element);
}
},

end: function end () {
// remove trailing whitespace
var element = stack[stack.length - 1];
var lastNode = element.children[element.children.length - 1];
if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
element.children.pop();
}
// 主要用来处理父子关系,进行出栈操作
stack.length -= 1;
currentParent = stack[stack.length - 1];
closeElement(element);
},

chars: function chars (text) {
if (!currentParent) {
// 第一个节点是根节点,所以肯定有currentParent
{
if (text === template) {
warnOnce(
'Component template requires a root element, rather than just text.'
);
} else if ((text = text.trim())) {
warnOnce(
("text \"" + text + "\" outside root element will be ignored.")
);
}
}
return
}
// IE textarea placeholder bug
/* istanbul ignore if */
if (isIE &&
currentParent.tag === 'textarea' &&
currentParent.attrsMap.placeholder === text
) {
return
}
var children = currentParent.children;
text = inPre || text.trim()
? isTextTag(currentParent) ? text : decodeHTMLCached(text)
// only preserve whitespace if its not right after a starting tag
: preserveWhitespace && children.length ? ' ' : '';
if (text) {
var res;
// 检测{{}}
if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
children.push({
type: 2,
expression: res.expression,
tokens: res.tokens,
text: text
});
} else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
children.push({
type: 3,
text: text
});
}
}
},
comment: function comment (text) {
// comment的处理
currentParent.children.push({
type: 3,
text: text,
isComment: true
});
}
});
return root
}


简单的html转换ast的实现

github地址:html转换ast的简单实现,如果喜欢的话,请给个start。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  vue ast vue源码学习