前言
本文是vue2.x源码分析的第六篇,主要讲解编译compile过程!
调用方式
var compiled = compile(template, options);
1 分析compile
//tips:请结合断点调试,该函数位于闭包createCompiler中,有的变量是在上层函数中定义的
function compile (template, options) {
var finalOptions = Object.create(baseOptions);
var errors = [];
var tips = [];
finalOptions.warn = function (msg, tip$$1) {
(tip$$1 ? tips : errors).push(msg);
};
if (options) {
//合并自定义modules
if (options.modules) {
finalOptions.modules = (baseOptions.modules || []).concat(options.modules);
}
//合并自定义directives
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives),
options.directives
);
}
// copy other options
for (var key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key];
}
}
}
/*以上都是处理finalOptions,到这里finalOptions如下:
{
delimiters:undefined,
shouldDecodeNewlines:false,
warn:function (msg, tip$$1),
__proto__:Object
}
这个__proto__指向一个预先定义好的baseOptions对象,该对象长这样:
var baseOptions = {
expectHTML: true,
modules: modules$1,//modules$1=[klass$1,style$1]
directives: directives$1, //这里预先定义了html,text,model三个指令
isPreTag: isPreTag,
isUnaryTag: isUnaryTag,
mustUseProp: mustUseProp,
canBeLeftOpenTag: canBeLeftOpenTag,
isReservedTag: isReservedTag,
getTagNamespace: getTagNamespace,
staticKeys: genStaticKeys(modules$1)
};
*/
var compiled = baseCompile(template, finalOptions); //主要函数
{
errors.push.apply(errors, detectErrors(compiled.ast));
}
compiled.errors = errors;
compiled.tips = tips;
return compiled
}
来看看baseCompile(template, finalOptions)
function baseCompile (template,options) {
var ast = parse(template.trim(), options); //主要函数1
optimize(ast, options);
var code = generate(ast, options); //主要函数2
return {
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
}
2 分析 parse(template.trim(), options);
//主要是调用parseHTML(html, options)解析html,返回结果ast是含有如下属性的对象
// attrs:Array
// attrsList:Array
// attrsMap:Object
// children:Array
// parent:undefined
// plain:false
// static:false
// staticRoot:false
// tag:"div"
// type:1
// __proto__:Object
function parse (template,options) {
warn$2 = options.warn || baseWarn;
platformGetTagNamespace = options.getTagNamespace || no;
platformMustUseProp = options.mustUseProp || no;
platformIsPreTag = options.isPreTag || no;
//这个pluckModuleFunction函数作用就是从options.modules中取出key为'preTransformNode'的值
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
transforms = pluckModuleFunction(options.modules, 'transformNode');
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 endPre (element) {
// check pre state
if (element.pre) {
inVPre = false;
}
if (platformIsPreTag(element.tag)) {
inPre = false;
}
}
//parseHTML第二个参数里有很重要的三个函数:start,end,chars
parseHTML(template, {
warn: warn$2,
expectHTML: options.expectHTML,
isUnaryTag: options.isUnaryTag,
canBeLeftOpenTag: options.canBeLeftOpenTag,
shouldDecodeNewlines: options.shouldDecodeNewlines,
//start和end函数负责构建节点树
start: function start (tag, attrs, unary) {
// check namespace.
// inherit parent ns if there is one
var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);
// handle IE svg bug
/* istanbul ignore if */
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs);
}
var element = { //节点
type: 1,
tag: tag,
attrsList: attrs,
attrsMap: makeAttrsMap(attrs),
parent: currentParent,
children: []
};
if (ns) {
element.ns = ns;
}
//不处理style和script标签
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.'
);
}
// 猜测:html如果用了其他的模板,如ejs等需要先转换
// apply pre-transforms
for (var i = 0; i < preTransforms.length; i++) {
preTransforms[i](element, options);
}
//处理v-pre指令
if (!inVPre) {
processPre(element);
if (element.pre) {
inVPre = true;
}
}
if (platformIsPreTag(element.tag)) {
inPre = true;
}
//如果含有v-pre指令,则直接调用processRawAttrs(element);处理原始属性
if (inVPre) {
processRawAttrs(element);
} else {
processFor(element);//处理v-for指令,会将v-for='xx'替换成其他字符串
processIf(element);//处理v-if指令
processOnce(element);//处理v-once指令
processKey(element);//处理key
// determine whether this is a plain element after
// removing structural attributes
// 移除结构性属性后判断该元素是不是plain元素
element.plain = !element.key && !attrs.length;
processRef(element);//处理ref
processSlot(element);//处理slot
processComponent(element);//处理component
for (var i$1 = 0; i$1 < transforms.length; i$1++) {
transforms[i$1](element, options); //对class和style属性进行处理
}
//以上处理了v-for,v-if,v-once,v-pre等指令,但还有其它指令,如v-on,v-bind,
//以及它们的快捷写法'@:',':',该函数就是处理这些指令以及普通元素,处理的结果就是
//在element上加了一个attrs属性,存放原始属性
processAttrs(element);
}
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.'
);
}
}
}
// 经过上述处理后,由于可能有v-if这种会改变树结构的指令,所以需要对结构树
// 进一步处理,至此第一轮while循环解析完成,接下来就是重复这个过程了
if (!root) {
root = element;
checkRootConstraints(root); //根节点不能是slot/template元素,且不能含有v-for指令
} else if (!stack.length) {
// 允许根元素使用 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) {
currentParent = element;
stack.push(element);
} else {
endPre(element);
}
// apply post-transforms
for (var i$2 = 0; i$2 < postTransforms.length; i$2++) {
postTransforms[i$2](element, options);
}
},
end: function end () {
// 删除尾随空格
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();
}
// pop stack
stack.length -= 1;
currentParent = stack[stack.length - 1];
endPre(element);
},
chars: function chars (text) {
if (!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()
? decodeHTMLCached(text)
// only preserve whitespace if its not right after a starting tag
: preserveWhitespace && children.length ? ' ' : '';
if (text) {
var expression;
if (!inVPre && text !== ' ' && (expression = parseText(text, delimiters))) {
children.push({
type: 2,
expression: expression,
text: text
});
} else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
children.push({
type: 3,
text: text
});
}
}
}
});
return root
}
来看看parseHTML(template,options)
/*解析过程中最重要的函数,因此代码量较大*/
function parseHTML (html, options) { //将template传给html
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) { //通过while循环一步步处理html,每处理一步就缩短html,直至html为空
last = html;
// 不处理script/style/textarea元素
if (!lastTag || !isPlainTextElement(lastTag)) {
var textEnd = html.indexOf('<');
if (textEnd === 0) {
// 当匹配到Comment,只对html推进,不做其他处理
if (comment.test(html)) {
var commentEnd = html.indexOf('-->');
if (commentEnd >= 0) {
advance(commentEnd + 3);
continue
}
}
//当匹配到conditionalComment,同Comment一样处理
// 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,同Comment一样处理,/^]+>/i
var doctypeMatch = html.match(doctype);
if (doctypeMatch) {
advance(doctypeMatch[0].length);
continue
}
// 当匹配到end tag,同Comment一样处理,/^<\/((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)[^>]*>/
var endTagMatch = html.match(endTag);
if (endTagMatch) {
var curIndex = index;
advance(endTagMatch[0].length);
parseEndTag(endTagMatch[1], curIndex, index);
continue
}
// 除以上四种,就默认以下处理
var startTagMatch = parseStartTag();
if (startTagMatch) {
handleStartTag(startTagMatch);
continue
}
}
var text = (void 0), rest$1 = (void 0), next = (void 0);
if (textEnd >= 0) {
rest$1 = html.slice(textEnd);
while (
!endTag.test(rest$1) &&
!startTagOpen.test(rest$1) &&
!comment.test(rest$1) &&
!conditionalComment.test(rest$1)
) {
// < in plain text, be forgiving and treat it as text
next = rest$1.indexOf('<', 1);
if (next < 0) { break }
textEnd += next;
rest$1 = 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 stackedTag = lastTag.toLowerCase();
var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(' + stackedTag + '[^>]*>)', 'i'));
var endTagLength = 0;
var rest = html.replace(reStackedTag, function (all, text, endTag) {
endTagLength = endTag.length;
if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
text = text
.replace(//g, '$1')
.replace(//g, '$1');
}
if (options.chars) {
options.chars(text);
}
return ''
});
index += html.length - rest.length;
html = rest;
parseEndTag(stackedTag, index - endTagLength, index);
}
if (html === last) {
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);
}
function parseStartTag () {
//startTagOpen='/^<((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)/'
var start = html.match(startTagOpen);
if (start) {
var match = {
tagName: start[1],
attrs: [],
start: index
};
advance(start[0].length);
var end, attr;
//开始寻找属性
//startTagClose='/^\s*(\/?)>/'
//attribute='/^\s*([^\s"'<>\/=]+)(?:\s*((?:=))\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/'
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
advance(attr[0].length);
match.attrs.push(attr);
}
if (end) {
match.unarySlash = end[1];//若'/'存在,则赋值给unarySlash
advance(end[0].length);
match.end = index;
return match //至此,parseStartTag结束,接下来执行handleStartTag(match);match此时长这样
/*
attrs:Array(1)
end:14
start:0
tagName:"div"
unarySlash:""
__proto__:Object
*/
}
}
}
function handleStartTag (match) {
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) || tagName === 'html' && lastTag === 'head' || !!unarySlash;
var l = match.attrs.length;
//新建一个attrs属性,遍历match.attrs,使得attrs=[{name:'id',value:'app'}]这种map结构
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] || '';
attrs[i] = {
name: args[1],
value: decodeAttr(
value,
options.shouldDecodeNewlines
)
};
}
if (!unary) {
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs });
lastTag = tagName;
}
//这个是最重要的函数,对一些特殊的属性做特殊处理,例如指令属性v-text='message'
if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end);//tips:返回到parse函数中看start执行过程
}
}
function parseEndTag (tagName, start, end) {
var pos, lowerCasedTagName;
if (start == null) { start = index; }
if (end == null) { end = index; }
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) {
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);
}
}
}
}
以上这个过程只是一个解析过程,将相应的属性放到相应的位置,但是还没有产生可执行代码,以下
generate函数的作用就是根据这些属性来产生相应的代码。
3 分析 generate(ast, options)
// 温故下,返回结果ast是含有如下属性的对象
// ```javascript
// attrs:Array //保存原始的html特性
// attrsList:Array
// attrsMap:Object
// children:Array
// parent:undefined
// plain:false
// static:false
// staticRoot:false
// tag:"div"
// type:1
// __proto__:Object
function generate (ast,options) {
// save previous staticRenderFns so generate calls can be nested
var prevStaticRenderFns = staticRenderFns;
var currentStaticRenderFns = staticRenderFns = [];
var prevOnceCount = onceCount;
onceCount = 0;
currentOptions = options;
warn$3 = options.warn || baseWarn;
transforms$1 = pluckModuleFunction(options.modules, 'transformCode');
dataGenFns = pluckModuleFunction(options.modules, 'genData');
platformDirectives$1 = options.directives || {};
isPlatformReservedTag$1 = options.isReservedTag || no;
var code = ast ? genElement(ast) : '_c("div")'; //主要函数,执行genElement(ast)
staticRenderFns = prevStaticRenderFns;
onceCount = prevOnceCount;
return {
render: ("with(this){return " + code + "}"),
staticRenderFns: currentStaticRenderFns
}
}
来看看 genElement(ast)
//根据ast的属性是否有once,for,if,slot,component等属性执行不同函数,否则当普通元素处理并执行genData和genChildren函数,这两个函数都是在做字符串的拼装工作,最后返回拼装完成的code字符串
function genElement (el) {
if (el.staticRoot && !el.staticProcessed) { //静态节点
return genStatic(el)
} else if (el.once && !el.onceProcessed) { //v-once节点
return genOnce(el)
} else if (el.for && !el.forProcessed) { //v-for节点
return genFor(el)
} else if (el.if && !el.ifProcessed) { //v-if节点
return genIf(el)
} else if (el.tag === 'template' && !el.slotTarget) {
return genChildren(el) || 'void 0'
} else if (el.tag === 'slot') { //处理slot
return genSlot(el)
} else {
// component or element
var code;
if (el.component) { //处理组件节点
code = genComponent(el.component, el);
} else {
var data = el.plain ? undefined : genData(el); //处理元素节点的data
var children = el.inlineTemplate ? null : genChildren(el, true);//处理元素节点的子元素
code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
}
// module transforms
for (var i = 0; i < transforms$1.length; i++) {
code = transforms$1[i](el, code);
}
return code
}
}
来看看genData(el)
//整个函数都是在做data字符串的拼装工作,最后返回data
function genData (el) {
var data = '{';
// 首先处理指令,因为在el产生之前,指令可能会改变el的其他属性
var dirs = genDirectives(el);
if (dirs) { data += dirs + ','; }
// 处理key属性
if (el.key) {
data += "key:" + (el.key) + ",";
}
// 处理ref属性
if (el.ref) {
data += "ref:" + (el.ref) + ",";
}
if (el.refInFor) {
data += "refInFor:true,";
}
// 处理v-pre指令
if (el.pre) {
data += "pre:true,";
}
// record original tag name for components using "is" attribute
if (el.component) { //处理组件
data += "tag:\"" + (el.tag) + "\",";
}
// 处理class和style属性
for (var i = 0; i < dataGenFns.length; i++) {
data += dataGenFns[i](el);
}
//处理特性 attributes
if (el.attrs) {
data += "attrs:{" + (genProps(el.attrs)) + "},";
}
//处理属性 DOM property
if (el.props) {
data += "domProps:{" + (genProps(el.props)) + "},";
}
//处理事件
if (el.events) {
data += (genHandlers(el.events)) + ",";
}
//处理本地事件
if (el.nativeEvents) {
data += (genHandlers(el.nativeEvents, true)) + ",";
}
处理slot目标
if (el.slotTarget) {
data += "slot:" + (el.slotTarget) + ",";
}
//处理scoped的slot
if (el.scopedSlots) {
data += (genScopedSlots(el.scopedSlots)) + ",";
}
//处理v-model
if (el.model) {
data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
}
// 处理内联模板
if (el.inlineTemplate) {
var inlineTemplate = genInlineTemplate(el);
if (inlineTemplate) {
data += inlineTemplate + ",";
}
}
data = data.replace(/,$/, '') + '}';
// v-bind data wrap 处理v-bind
if (el.wrapData) {
data = el.wrapData(data);
}
return data
}
来看看genDirectives(el)
function genDirectives (el) {
var dirs = el.directives;
if (!dirs) { return }
var res = 'directives:[';
var hasRuntime = false;
var i, l, dir, needRuntime;
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i];
needRuntime = true;
var gen = platformDirectives$1[dir.name] || baseDirectives[dir.name];
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el, dir, warn$3);
}
if (needRuntime) {
hasRuntime = true;
res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:\"" + (dir.arg) + "\"") : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
}
}
if (hasRuntime) {
return res.slice(0, -1) + ']'
}
}
来看看genChildren
function genChildren (el, checkSkip) {
var children = el.children;
if (children.length) {
var el$1 = children[0];
// optimize single v-for
if (children.length === 1 &&
el$1.for &&
el$1.tag !== 'template' &&
el$1.tag !== 'slot') {
return genElement(el$1) //只有一个子元素并且有v-for属性时,递归调用genElement
}
var normalizationType = checkSkip ? getNormalizationType(children) : 0;
return ("[" + (children.map(genNode).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
}
}
来看看genNode
function genNode (node) {
if (node.type === 1) { //type=1,表示是元素节点,递归调用genElement
return genElement(node)
} else {
return genText(node) //按照vue的用法,不是元素节点就只能是文本节点了
}
}
/*
function genText (text) {
return ("_v(" + (text.type === 2
? text.expression // no need for () because already wrapped in _s()
: transformSpecialNewlines(JSON.stringify(text.text))) + ")")
}
*/
4 小结
- compile过程即baseCompile过程:
- 调用parse函数对原始模板进行解析得到ast;
- 调用generate函数处理ast得到最终render函数
- 本篇偏向对编译的整体过程分析,没有对诸如指令到底是怎么编译的进行分析,后面章节将结合实例具体分析指令等编译过程,让我们先瞅瞅vue一共提供了哪些内置指令:
v-text
v-html
v-show
v-if
v-else
v-else-if
v-for
v-on
v-bind
v-model
v-pre
v-cloak
v-once
除此之外,还有三个特殊属性key,ref,slot以及内置组件component,transition,transition-group,keep-alive,slot,接下来的章节将对这些内容进行分析