When I was sorting out this article, I felt confused. What I was confused was that I didn’t know how to use a better way to make this part of the tedious content easy for you to understand. I also hope that this article can be used as a guide to your reading, so that you can look at the guide and the source code together.
How do we find the final approach we need to focus on
Remember the previous “hand in hand to take you through the vUE part of the source”? From there, we already know that SRC /platform/ Web/entry-runtime-with-Compiler. js converted the template to render function when it overrode the prototype’s $mount method, so we’ll use that as an entry point.
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
Copy the code
Where do compileToFunctions come from? Seriously the problem to see the source code is quite round, when first quoted on platforms/web/compiler/index, js, and found that is invoked the SRC/compiler/index js createCompiler method, CreateCompiler calls SRC /compiler/ create-Compiler. js createCompilerCreator, and compileToFunctions. Is by calling the SRC/compiler/to – function. The js createCompileToFunctionFn to create, so here, in order to easy to remember, we temporarily ignore all of the steps in the middle. Let’s start with the last step.
createCompileToFunctionFn(src/compiler/to-function.js)
Code a little long not here not all posted, I said, you watch.
try {
new Function('return 1')}catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) {
warn(
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
'templates into render functions.')}}Copy the code
What does this code do? Eval (‘function fn() {return 1} ‘) {return 1} Why are we doing tests here? Good question, remember, keep reading and you’ll get the answer for yourself.
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
Copy the code
Another piece of code for efficiency checks directly from the cache to see if there are already compiled results, and returns them directly.
const compiled = compile(template, options)
Copy the code
CreateCompilerCreator (SRC /compiler/create-compiler.js); createCompilerCreator (SRC /compiler/create-compiler.js);
compile(src/compiler/create-compiler.js)
In the compile method, there are three main things that are done
-
Pass the passed CompilerOptions and mount it to finalOptions
Where finalOptions will eventually become:
- will
template
,finalOptions
The incomingsrc/compiler/index.js
Of the filebaseCompile
In the. - collect
ast
Error message during conversion.
baseCompile(src/compiler/index.js)
Here we go into baseCompile and see what baseCompile does.
parse
// Convert the incoming HTML to an AST syntax tree
const ast = parse(template.trim(), options)
Copy the code
Here we go. Yesparse
Method converts the contents of the template we pass into the AST syntax tree
Together under the SRC/compiler/parser/index. The parse method in js file.
function parse (template, options){ warn = options.warn || baseWarn platformIsPreTag = options.isPreTag || no platformMustUseProp = options.mustUseProp || no platformGetTagNamespace = options.getTagNamespace || no// pluckModuleFunction: Find out options.mudules where each property contains the key method
transforms = pluckModuleFunction(options.modules, 'transformNode')
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
delimiters = options.delimiters
/ / store astNode
const stack = []
constpreserveWhitespace = options.preserveWhitespace ! = =false
// Define the root node
let root
// The parent of the current processing node
let currentParent
// Indicates whether there is a v-pre in the attribute
/ / what is v - pre, see https://cn.vuejs.org/v2/api/#v-pre
let inVPre = false
// Indicates whether the label is pre
let inPre = false
// Indicates whether WARN has been triggered
let warned = false
function warnOnce (msg) {}
function closeElement (element) {}
// Parsing incoming HTML in a loop
parseHTML(params)
/** * handle v-pre * @param {*} el */
function processPre() {}
/** * Handles HTML native attributes * @param {*} el */
function processRawAttrs (el) {}
return root
}
Copy the code
As you can see from the above section, it is the parseHTML method that does the actual conversion. We omit the parseHTML parameter above, because the parseHTML method uses the start, end, chars, and comment methods. Here I’ll break it down at the end of the article and provide a special comment for each method so you can read it.
SRC/Compiler /parser/html-parser.js to look at the parseHTML method.
function parseHTML (html, options) {
const stack = []
const expectHTML = options.expectHTML
const isUnaryTag = options.isUnaryTag || no
const canBeLeftOpenTag = options.canBeLeftOpenTag || no
// Declare index, identifying the index currently processing the incoming HTML
let index = 0
let last, lastTag
// Loop through the HTML
while(html) {... }// Clean up any remaining tags
parseEndTag()
/** * modifies the current processing tag index and truncates the HTML processed portion * @param {*} n digits */
function advance (n) {}
/** * process the start tag and place the attribute in attrs */
function parseStartTag () {}
/** * Process the result of the parseStartTag process and generate the ast node * @param {*} match result of the parseStartTag process */
function handleStartTag (match) {}
* @param {*} tagName tagName * @param {*} start start position in HTML * @param {*} end position in HTML */
function parseEndTag (tagName, start, end) {}}Copy the code
Here we’ve kept some of the snippets, the full comments, which I’ll put at the end of the article.
With the parse method, we get the entire abstract syntax tree.
optimize
Optimizing the current abstract syntax tree to identify static nodes will be covered in our next vNode article.
generate(scr/compiler/codegen/index.js)
This will convert our abstract syntax tree into a string for the corresponding Render method. If you are interested, read it for yourself and you will be a little clearer about the purpose of the method of mounting various _ letters for the prototype in Vue Instance
With (this){with(this){… }, so why does it check to see if eval is allowed when compiling template? .
summary
Convert template to render with parse, optimize, and generate in Compile.
The last
If you like, I will continue to bring you render time, virtual DOM related articles.
Appendix – Source code + notes
options.start
start (tag, attrs, unary) {
// check namespace.
// inherit parent ns if there is one
const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
// handle IE svg bug
/* istanbul ignore if */
// Handle IE SVG BUG
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs)
}
// Create an AST NODE
let element: ASTElement = createASTElement(tag, attrs, currentParent)
if (ns) {
element.ns = ns
}
// Warning if the current node is
if(isForbiddenTag(element) && ! isServerRendering()) { element.forbidden =trueprocess.env.NODE_ENV ! = ='production' && warn(
'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 (let 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
}
// Skip compilation if v-pre is included
if (inVPre) {
processRawAttrs(element)
} else if(! element.processed) {// structural directives
/ / v - for processing
processFor(element)
// handle v-if v-else-if v-else
processIf(element)
/ / processing v - once
processOnce(element)
// element-scope stuff
// Handle ast node nodes, key, ref, slot, Component, attrs
processElement(element, options)
}
// root Node constraint check
function checkRootConstraints (el) {
if(process.env.NODE_ENV ! = ='production') {
// Slot and template cannot be root
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(
`Cannot use <${el.tag}> as component root element because it may ` +
'contain multiple nodes.')}// The root node cannot contain v-for
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 = 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 if(process.env.NODE_ENV ! = ='production') {
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
const 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 {
closeElement(element)
}
}
Copy the code
options.end
end () {
// remove trailing whitespace
// Fetch the last AST node in the stack
const element = stack[stack.length - 1]
// Find the most recently processed node
const lastNode = element.children[element.children.length - 1]
if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
element.children.pop()
}
// pop stack
// Remove element from the stack
stack.length -= 1
currentParent = stack[stack.length - 1]
closeElement(element)
}
Copy the code
options.chars
chars (text: string) {
// Text has no parent node processing
if(! currentParent) {if(process.env.NODE_ENV ! = ='production') {
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
}
const children = currentParent.children
// Format text
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) {
// Process the {{text}} part and convert {{text}} to
// {expression: '_s(text)', token: [{'@binding': 'text'}]}
let res
if(! inVPre && text ! = =' ' && (res = parseText(text, delimiters))) {
children.push({
type: 2.expression: res.expression,
tokens: res.tokens,
text
})
} else if(text ! = =' '| |! children.length || children[children.length -1].text ! = =' ') {
children.push({
type: 3,
text
})
}
}
}
Copy the code
parseHTML
function parseHTML (html, options) {
const stack = []
const expectHTML = options.expectHTML
const isUnaryTag = options.isUnaryTag || no
const canBeLeftOpenTag = options.canBeLeftOpenTag || no
// Declare index, identifying the index currently processing the incoming HTML
let index = 0
let last, lastTag
while (html) {
last = html
// Make sure we're not in a plaintext content element like script/style
if(! lastTag || ! isPlainTextElement(lastTag)) {let textEnd = html.indexOf('<')
// Whether to start with <
if (textEnd === 0) {
// Comment:
// Check if <! -- opening comment
if (comment.test(html)) {
const commentEnd = html.indexOf('-->')
if (commentEnd >= 0) {
if (options.shouldKeepComment) {
options.comment(html.substring(4, commentEnd))
}
advance(commentEnd + 3)
continue}}// Check for compatibility comments with
/ / <! --[if IE 6]>
// Special instructions for IE 6 here
/ /
// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
if (conditionalComment.test(html)) {
const conditionalEnd = html.indexOf('] > ')
if (conditionalEnd >= 0) {
advance(conditionalEnd + 2)
continue}}// Check if
// Doctype:
const doctypeMatch = html.match(doctype)
if (doctypeMatch) {
advance(doctypeMatch[0].length)
continue
}
// Check whether it is an end label
// End tag:
const endTagMatch = html.match(endTag)
if (endTagMatch) {
const curIndex = index
advance(endTagMatch[0].length)
parseEndTag(endTagMatch[1], curIndex, index)
continue
}
// After passing parseStartTag, HTML = '
...
'
// handle the tag as HTML = '...
'
// Return match = {
// start: 0, // start index
// end: 15, // end index
// tagName: 'div'
// attrs: [] // The array here matches the attributes of the tag for the re
// }
// Start tag:
const startTagMatch = parseStartTag()
if (startTagMatch) {
handleStartTag(startTagMatch)
if (shouldIgnoreFirstNewline(lastTag, html)) {
advance(1)}continue}}let text, rest, next
// Because our HTML code may have TAB position newlines and other operations that do not need parsing
// Here we remove the garbage and continue looping through the HTML
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 = ' '
}
// Process characters
if (options.chars && text) {
options.chars(text)
}
} else {
let endTagLength = 0
const stackedTag = lastTag.toLowerCase()
const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?) (< / ' + stackedTag + '[^ >] * >)'.'i'))
const rest = html.replace(reStackedTag, function (all, text, endTag) {
endTagLength = endTag.length
if(! isPlainTextElement(stackedTag) && stackedTag ! = ='noscript') {
text = text
.replace(/
/g.'$1') / / # 7298
.replace(/
/g.'$1')}if (shouldIgnoreFirstNewline(stackedTag, text)) {
text = text.slice(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(process.env.NODE_ENV ! = ='production' && !stack.length && options.warn) {
options.warn(`Mal-formatted tag at end of template: "${html}"`)}break}}// Clean up any remaining tags
parseEndTag()
/** * modifies the current processing tag index and truncates the HTML processed portion * @param {*} n digits */
function advance (n) {
index += n
html = html.substring(n)
}
/** * process the start tag and place the attribute in attrs */
function parseStartTag () {
const start = html.match(startTagOpen)
if (start) {
const match = {
tagName: start[1].attrs: [].start: index
}
// After processing the header, remove the header
advance(start[0].length)
let end, attr
[>]. If there is no tail, add the attribute of the current re to attrs.
while(! (end = html.match(startTagClose)) && (attr = html.match(attribute))) { advance(attr[0].length)
match.attrs.push(attr)
}
// Remove the tail [>] from the HTML to record the currently processed index
if (end) {
match.unarySlash = end[1]
advance(end[0].length)
match.end = index
return match
}
}
}
/** * Process the result of the parseStartTag process and generate the ast node * @param {*} match result of the parseStartTag process */
function handleStartTag (match) {
const tagName = match.tagName
const unarySlash = match.unarySlash
if (expectHTML) {
if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
parseEndTag(lastTag)
}
if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
parseEndTag(tagName)
}
}
constunary = isUnaryTag(tagName) || !! unarySlashconst l = match.attrs.length
const attrs = new Array(l)
for (let i = 0; i < l; i++) {
const 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]}}const value = args[3] || args[4] || args[5] | |' '
const 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)
}
}
* @param {*} tagName tagName * @param {*} start start position in HTML * @param {*} end position in HTML */
function parseEndTag (tagName, start, end) {
let 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
// Find the node in the stack that matches the current tag, using reverse order to match the nearest one
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 (let i = stack.length - 1; i >= pos; i--) {
if(process.env.NODE_ENV ! = ='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
Br / / processing
} else if (lowerCasedTagName === 'br') {
if (options.start) {
options.start(tagName, [], true, start, end)
}
// Process the p tag
} else if (lowerCasedTagName === 'p') {
if (options.start) {
options.start(tagName, [], false, start, end)
}
if (options.end) {
options.end(tagName, start, end)
}
}
}
}
Copy the code