$mount
Mount is a manual loading process, so let’s see how it works:
src/platforms/web/entry-runtime-with-compiler.js
Copy the code
/* Uncompiled$mountThe method is saved and called at the end. */ const mount = Vue.prototype.$mount/* Mount component, build with template */ vue.prototype.$mount = function( el? : string | Element, hydrating? : boolean ): Component { el = el && query(el) /* istanbul ignoreif* /if(el === document.body || el === document.documentElement) { process.env.NODE_ENV ! = ='production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function/* The template will only be compiled if render is not present, otherwise use render*/if(! options.render) {letTemplate = options.template /* If template exists, outerHTML*/ of el does notif(template) {/* When template is a string */if (typeof template === 'string') {/* The first letter of template is retrieved# indicates id*/
if (template.charAt(0) === The '#') {
template = idToTemplate(template)
/* istanbul ignore if* /if(process.env.NODE_ENV ! = ='production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if(template.nodeType) {/* When template is a DOM node */ template = template.innerhtml}else{/* Error */if(process.env.NODE_ENV ! = ='production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if(el) {/* Get element's outerHTML*/ template = getOuterHTML(el)}if (template) {
/* istanbul ignore if* /if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
mark('compile'} /* Compile template into render function, which returns render and staticRenderFns. This is a compile-time optimization for vue. Const {render, staticRenderFns} = compileToFunctions(template, {shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignoreif* /if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile'.'compile end'}} /* Call const mount = vue.prototype.$mountMount */ saved without compilationreturn mount.call(this, el, hydrating)
}
Copy the code
It is clear from the mount compilation code that during the mount process, Template compileToFunctions to render and staticRenderFns if the Render function does not exist (which would give precedence to render if it exists). For example, if a template is added to a handwritten component, it will be compiled at run time. The Render function returns a VNode for rendering and patch during update. Let’s take a look at how template is compiled.
The compile function (SRC /compiler/index.js) compiles the template as a string of render functions. Let’s take a closer look at this function:
import { parse } from './parser/index'
import { optimize } from './optimizer'
import { generate } from './codegen/index'
import { createCompilerCreator } from './create-compiler'
// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)
if(options.optimize ! = =false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
Copy the code
The createCompiler function generates an object containing ast, Render, and staticRenderFns through three steps: Parse, optimize, and Generate.
parse
Before we talk about the parse function, let’s look at the concept of the AST(Abstract Syntax Tree) : In computer science, an abstract syntax tree (AST for short), or syntax tree, is a tree-like representation of the abstract syntactic structure of source code, specifically the source code of a programming language. See the abstract syntax tree for details.
The AST will generate the render function through generate, and the return value of render is VNode, which is the virtual DOM node of Vue, as defined below:
exportdefault class VNode { tag: string | void; data: VNodeData | void; children: ? Array<VNode>; text: string | void; elm: Node | void; ns: string | void; context: Component | void; // renderedin this component's scope key: string | number | void; componentOptions: VNodeComponentOptions | void; componentInstance: Component | void; // component instance parent: VNode | void; // component placeholder node // strictly internal raw: boolean; // contains raw HTML? (server only) isStatic: boolean; // hoisted static node isRootInsert: boolean; // necessary for enter transition check isComment: boolean; // empty comment placeholder? isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? asyncFactory: Function | void; // async component factory function asyncMeta: Object | void; isAsyncPlaceholder: boolean; ssrContext: Object | void; fnContext: Component | void; // real context vm for functional nodes fnOptions: ? ComponentOptions; // for SSR caching fnScopeId: ? string; // functional scope id support constructor ( tag? : string, data? : VNodeData, children? :? Array
, text? : string, elm? : Node, context? : Component, componentOptions? : VNodeComponentOptions, asyncFactory? : Function) {/* This. Tag = tag /* This. */ this.data = data /* Child nodes of the current node, Is an array */ this.children = children /* the text of the current node */ this.text = text /* the real DOM node corresponding to the current virtual node */ this.elm = elm /* the namespace of the current node */ this.ns This. FunctionalContext = undefined /* compiler scope */ this.context = context /* functional component scope */ this.functionalContext = undefined /* key property of the node, used as a node flag, To optimize */ this.key = data && data.key /* Component's option option */ this.componentOptions = componentOptions /* Instance of the component corresponding to the current node */ This.parent = undefined /* The parent node of the current node */ this.parent = undefined /* TextContent is false*/ this.raw = false /* Static node flag */ this.isStatic = false /* Whether to insert with node */ this.isRootInsert = true /* Whether there is a annotation node */ this.iscomment = false /* Whether there is a clone node */ this.isonce = false /* Whether there is a V-once instruction */ this.isonce = false /* Factory method of asynchronous components */ This. asyncFactory = asyncFactory /* Async source */ this.asyncMeta = undefined /* Async preassignment */ this.isAsyncPlaceholder = false} // DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ get child (): Component | void { return this.componentInstance } }
Copy the code
Let’s take a look at parse’s source code:
src/compiler/parser/index.js
Copy the code
function parse(template) {
...
const stack = [];
letcurrentParent; // Current parent nodeletroot; // Finally returns the AST root node... parseHTML(template, { start:function start(tag, attrs, unary) {
......
},
end: function end() {... }, chars:functionchars(text) { ...... }})return root
}
Copy the code
This method is too long, so it leaves out the relevant content of parse and just looks at the general function. The main function should be the parseHTML method. We take two arguments, one to make our template, and the other to contain the start, end, and chars methods. Before we look at parseHTML, we need to look at these re’s:
// This regex matches to <div id="index"Id = >"index"Attribute part const attribute = /^\s*([^\s"' < > \ / =] +)? :\s*(=)\s*(? :"([^"] *)"+|'([^'] *)'+|([^\s"'= < > `] +)))? / const ncname ='[a-zA-Z_][\\w\\-\\.]*'const qnameCapture = `((? :${ncname}\ \ :)?${ncname}// Match start tag const startTagOpen = new RegExp(' ^<${qnameCapture}`) const startTagClose = /^\s*(\/?) >/ // match endTag const endTag = new RegExp(' ^<\\/${qnameCapture}[^ >] * > `) / / match DOCTYPE, comments, and other special label const DOCTYPE = / ^ <! DOCTYPE [^>]+>/i const comment = /^<! \--/ const conditionalComment = /^<! \ [/Copy the code
Vue uses these regular expressions to match start and end tags, tag names, attributes, and so on. With that in mind, let’s look at the internal implementation of parseHtml:
export function parseHTML (html, options) {
const stack = []
const expectHTML = options.expectHTML
const isUnaryTag = options.isUnaryTag || no
const canBeLeftOpenTag = options.canBeLeftOpenTag || no
let index = 0
let last, lastTag
while(HTML) {// Keep the HTML copy last = HTML // if there is no lastTag, and make sure we are not in a plain text content element: script, style, textareaif(! lastTag || ! isPlainTextElement(lastTag)) {let textEnd = html.indexOf('<')
if (textEnd === 0) {
// Comment:
if (comment.test(html)) {
...
}
if (conditionalComment.test(html)) {
...
}
// Doctype:
const doctypeMatch = html.match(doctype)
if (doctypeMatch) {
...
}
// End tag:
const endTagMatch = html.match(endTag)
if (endTagMatch) {
...
}
// Start tag:
const startTagMatch = parseStartTag()
if(startTagMatch) { ... }}let text, rest, next
if (textEnd >= 0) {
...
}
if (textEnd < 0) {
text = html
html = ' '} // Draw the text content, using the options.char method.if (options.chars && text) {
options.chars(text)
}
} else{... }... }Copy the code
Just look at the general meaning of the code:
1. First loop through while (HTML) to determine if the HTML content exists.
2. Check whether the text content is in the script/style tag.
3. If all of the above conditions are met, start parsing the HTML string.
The parseStartTag and handleStartTag methods are worth paying attention to:
function parseStartTagConst start = html.match(startTagOpen) const start = html.match(startTagOpen)ifConst match = {tagName: start[1], // tag attrs: [], // attribute start: /** * advance(start[0].length); advance(start[0].length);letEnd, attr // If you haven't reached the end tag // store attributeswhile(! (end = html.match(startTagClose)) && (attr = html.match(attribute))) { advance(attr[0].length) match.attrs.push(attr) } // Returns the processed tag match structureif (end) {
match.unarySlash = end[1]
advance(end[0].length)
match.end = index
return match
}
}
}
Copy the code
Suppose we set an HTML string
functionHandleStartTag (match) {// Match is the data structure passed by the method above . const unary = isUnaryTag(tagName) || !! // Create an empty Array of length 1 const attrs = new Array(l)for (leti = 0; i < l; i++) { const args = match.attrs[i] ... / / define the value of the attribute const value = the args [3] | | args [4] | | args [5] | |' '// Change the format of attr to [{name:'id', value: 'demo'}] attrs[i] = { name: args[1], value: DecodeAttr (the value of the options. ShouldDecodeNewlines)}} / / stack recorded in the current analytic label / / if not autistic and label / / stack the variables defined in parseHTML here, The function is to store the label name in order to match the end tag.if(! unary) { stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: Options. start(tagName, attrs, unary, mate.start, mate.end)}Copy the code
ParseHTML is mainly used to parseHTML string elements, such as tagName, attrs, match, etc., and pass in the start method:
start (tag, attrs, unary) { ... // Create the base ASTElementlet element: ASTElement = createASTElement(tag, attrs, currentParent)
if (ns) {
element.ns = ns
}
...
if (!inVPre) {// Check whether there are elements of the V-pre directive. Element. pre = if availabletrue<span v-pre>{{this will not be compiled}}</span> You can use it to display the original Mustache tag. Skipping a large number of nodes without instructions speeds up compilation. processPre(element)if (element.pre) {
inVPre = true}}if (platformIsPreTag(element.tag)) {
inPre = true
}
if (inVPre) {// Process the original attribute processRawAttrs(Element)}else if(! element.processed) { // structural directives // v-for v-if v-once processFor(element) processIf(element) ProcessOnce (element) // Element-scope stuff processElement(Element, options)} // Check the root constraintfunction checkRootConstraints (el) {
if(process.env.NODE_ENV ! = ='production') {
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) {// If no root exists root = element checkRootConstraints(root)}else if(! Stack.length) {// Allow root elements with V-if, V-else, and V-else...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{/ / insert element children array currentParent. Children. Push element (element). The parent = currentParent}}if(! unary) { currentParent = element stack.push(element) }else {
endPre(element)
}
// apply post-transforms
for (let i = 0; i < postTransforms.length; i++) {
postTransforms[i](element, options)
}
}
Copy the code
In fact, the start method is the process of processing element. Determine the namespace; Create the AST element Element Element; Perform preprocessing; Define the root; Logic for handling various V-tags; The root, currentParent, and stack results are finally updated. Finally, a new AST object is defined through the createASTElement method.
conclusion
Let’s take a look at the whole process:
1. ParseHtml to parse the tags, elements, text, comments passed into the HTML string step by step. .
2. During parseHtml parsing, call the start, end, and chars methods passed in to generate the AST syntax tree
Let’s look at the resulting AST syntax tree object:
Give me a star, Github, if you like
Thanks to Muwoo for the analytical thought.