As mentioned in the earlier section, we provided two versions for us to use when creating projects using VUE-CLI, the Runtime Only version and the Runtime + Compiler version. The Runtime Only version does not include a compiler, which compiles the template into the render function, also known as precompilation, when the project is packaged. The Runtime + Compiler version includes a Compiler that allows the compilation process to be done at Runtime.
The entrance
This piece of code is more, mainly for various cases to do some boundary processing. Focus only on the main flow here. Those who are interested in details can do their own research. Generally, the Runtime + Compiler version may be more common.
// src/platforms/web/entry-runtime-with-compiler.js
Vue.prototype.$mount = function (el? :string| Element, hydrating? :boolean
) :Component {
// ...
if(! options.render) {// The template is ready to compile
if (template) {
// Compile the template to get the dynamic rendering function and static rendering function
const { render, staticRenderFns } = compileToFunctions(template, {
// In non-production environments, compile-time records the index of the starting and ending positions of the tag attributes in the template string
outputSourceRange: process.env.NODE_ENV ! = ='production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
// Delimiter, default {{}}
delimiters: options.delimiters,
// Whether to keep comments
comments: options.comments
}, this)}}}Copy the code
The methods to compileToFunctions compile template to render and staticRenderFns.
compileToFunctions
// src/platforms/web/compiler/index.js
import { baseOptions } from './options'
import { createCompiler } from 'compiler/index'
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }
Copy the code
createCompiler
// src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
) :CompiledResult {
// Parse the template into an AST
const ast = parse(template.trim(), options)
// Optimize AST, static markup
if(options.optimize ! = =false) {
optimize(ast, options)
}
// Generate a render function, which converts the AST into a string of executable render functions
// code = {
// render: `with(this){return ${_c(tag, data, children, normalizationType)}}`,
// staticRenderFns: [_c(tag, data, children, normalizationType), ...]
// }
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
Copy the code
CreateCompiler is returned by calling createCompilerCreator, where a baseCompile function is passed in as an argument. This function is the focus, and it is in this function that the core compilation process is performed.
createCompilerCreator
// src/compiler/create-compiler.js
export function createCompilerCreator (baseCompile: Function) :Function {
return function createCompiler (baseOptions: CompilerOptions) {
function compile (
template: string, options? : CompilerOptions) :CompiledResult {
// Create compilation option objects modeled after platform-specific compilation configurations
const finalOptions = Object.create(baseOptions)
const errors = []
const tips = []
// Log, which records error and tip
let warn = (msg, range, tip) = > {
(tip ? tips : errors).push(msg)
}
if (options) {
if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
// $flow-disable-line
const leadingSpaceLength = template.match(/^\s*/) [0].length
warn = (msg, range, tip) = > {
const data: WarningMessage = { msg }
if (range) {
if(range.start ! =null) {
data.start = range.start + leadingSpaceLength
}
if(range.end ! =null) {
data.end = range.end + leadingSpaceLength
}
}
(tip ? tips : errors).push(data)
}
}
// Merge configuration options to finalOptions
// merge custom modules
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules)
}
// merge custom directives
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives || null),
options.directives
)
}
// copy other options
for (const key in options) {
if(key ! = ='modules'&& key ! = ='directives') {
finalOptions[key] = options[key]
}
}
}
finalOptions.warn = warn
// The core compiler function passes the template string and the final configuration item to get the compilation result
const compiled = baseCompile(template.trim(), finalOptions)
if(process.env.NODE_ENV ! = ='production') {
detectErrors(compiled.ast, warn)
}
// Mount errors and prompts generated during compilation to the compilation result
compiled.errors = errors
compiled.tips = tips
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
Copy the code
CreateCompilerCreator returns a createCompiler function. CreateCompiler returns an object containing compile and compileToFunctions, This corresponds to the compileToFunctions method called in $mount. Within the createCompiler function defines the compile method, and pass it to createCompileToFunctionFn, compile the main purpose is the unique platform configuration items do some consolidation, Such as web platforms and handling errors that occur during compilation.
createCompileToFunctionFn
// src/compiler/to-function.js
export function createCompileToFunctionFn (compile: Function) :Function {
const cache = Object.create(null)
return function compileToFunctions (
template: string, options? : CompilerOptions, vm? : Component) :CompiledFunctionResult {
// The compiler option passed in
options = extend({}, options)
const warn = options.warn || baseWarn
delete options.warn
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production') {
// detect possible CSP restriction
// Detect possible CSP limits
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.')}}}// check cache
// If there is a cache, the result of the last compilation will be skipped and fetched directly from the cache
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
// compile
// Execute the compile function to get the compile result
const compiled = compile(template, options)
// check compilation errors/tips
// Check errors/tips generated during compilation and print them to the console, respectively
if(process.env.NODE_ENV ! = ='production') {
if (compiled.errors && compiled.errors.length) {
if (options.outputSourceRange) {
compiled.errors.forEach(e= > {
warn(
`Error compiling template:\n\n${e.msg}\n\n` +
generateCodeFrame(template, e.start, e.end),
vm
)
})
} else {
warn(
`Error compiling template:\n\n${template}\n\n` +
compiled.errors.map(e= > ` -${e}`).join('\n') + '\n',
vm
)
}
}
if (compiled.tips && compiled.tips.length) {
if (options.outputSourceRange) {
compiled.tips.forEach(e= > tip(e.msg, vm))
} else {
compiled.tips.forEach(msg= > tip(msg, vm))
}
}
}
// turn code into functions
// Convert the compiled string code to a Function, implemented by new Function(code)
const res = {}
const fnGenErrors = []
res.render = createFunction(compiled.render, fnGenErrors)
res.staticRenderFns = compiled.staticRenderFns.map(code= > {
return createFunction(code, fnGenErrors)
})
// check function generation errors.
// this should only happen if there is a bug in the compiler itself.
// mostly for codegen development use
/* istanbul ignore if */
// Handle the error that occurred during the code conversion above
if(process.env.NODE_ENV ! = ='production') {
if((! compiled.errors || ! compiled.errors.length) && fnGenErrors.length) { warn(`Failed to generate render function:\n\n` +
fnGenErrors.map(({ err, code }) = > `${err.toString()} in\n\n${code}\n`).join('\n'),
vm
)
}
}
// Cache the compiled results
return (cache[key] = res)
}
}
Copy the code
CreateCompileToFunctionFn returned compileToFunctions this is the final definition we are looking for, it is mainly to do so a few things:
- Execute the compilation function to get the compilation result.
- Converts the compiled string code into an executable function.
- Handle exceptions
- The cache
summary
From the above code, we can see that the actual compilation process is in the baseCompile passed by createCompilerCreator function, which is mainly divided into the following parts:
- Parse the template into an AST
const ast = parse(template.trim(), options)
Copy the code
- Optimize AST (static markup)
optimize(ast, options)
Copy the code
- Generate code string
const code = generate(ast, options)
Copy the code
The reason for doing so much foreplay before actually compiling is to do some processing for different platforms. Let’s look at what we did for these three parts.
parse
Parse is used to parse templates into an AST, an abstract syntax tree. This is a complex process that sets up all of the element’s information on the AST object for each node, such as: parent, child, label information, attribute information, slot information, and so on. A number of regular expressions are used to match the template.
parse
// src/compiler/parser/index.js
export function parse (
template: string,
options: CompilerOptions
) :ASTElement | void {
warn = options.warn || baseWarn
// Whether the label is pre
platformIsPreTag = options.isPreTag || no
// A property that must be bound using props
platformMustUseProp = options.mustUseProp || no
// Get the label's namespace
platformGetTagNamespace = options.getTagNamespace || no
// Whether to keep the tag (HTML + SVG)
const isReservedTag = options.isReservedTag || no
// Whether it is a component
maybeComponent = (el: ASTElement) = >!!!!! ( el.component || el.attrsMap[':is'] ||
el.attrsMap['v-bind:is') | |! (el.attrsMap.is ? isReservedTag(el.attrsMap.is) : isReservedTag(el.tag)) )// Get the transformNode, preTransformNode, and postTransformNode methods in the class, model, and style modules of options.modules
// Handle class, style, v-model on element nodes
transforms = pluckModuleFunction(options.modules, 'transformNode')
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
// Delimiters such as {{}}
delimiters = options.delimiters
const stack = []
// Space options
constpreserveWhitespace = options.preserveWhitespace ! = =false
const whitespaceOption = options.whitespace
// The root node, which takes root as the root, will be mounted to the root node according to the hierarchy, and the last return is root, an AST syntax tree
let root
// The parent of the current element
let currentParent
let inVPre = false
let inPre = false
let warned = false
function warnOnce (msg, range) {
if(! warned) { warned =true
warn(msg, range)
}
}
// Because the code is quite long, the following methods are separate.
function closeElement (element) { / *... * / }
function trimEndingWhitespace (el) { / *... * / }
function checkRootConstraints (el) { / *... * / }
parseHTML(template, {
/ *... * /
})
return root
}
Copy the code
Parse receives two parameters template and options, and is the template string and configuration options, the options defined in/SRC/platforms/web/compiler options, Different platforms (Web and WEEX) have different configuration options.
closeElement
// src/compiler/parser/index.js
function closeElement (element) {
// Remove the space at the end of the node
trimEndingWhitespace(element)
// The current element is not in the pre node and has not been processed
if(! inVPre && ! element.processed) {// Handle element nodes' keys, refs, slots, self-closing slot tags, dynamic components, class, style, V-bind, V-ON, other directives, and some native attributes separately
element = processElement(element, options)
}
// Handle the v-if, V-else, v-else instructions on the root node
// If the root node has a V-if instruction, then a node of the same level with v-else or V-else must be provided in case the root element does not exist
// tree management
if(! stack.length && element ! == root) {// allow root elements with v-if, v-else-if and v-else
if (root.if && (element.elseif || element.else)) {
if(process.env.NODE_ENV ! = ='production') {
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.`,
{ start: element.start }
)
}
}
// Create a parent-child relationship, place yourself in the children array of the parent element, and set your parent property to currentParent
if(currentParent && ! element.forbidden) {if (element.elseif || element.else) {
processIfConditions(element, currentParent)
} else {
if (element.slotScope) {
// scoped slot
// keep it in the children list so that v-else(-if) conditions can
// find it as the prev node.
const name = element.slotTarget || '"default"'
; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
}
currentParent.children.push(element)
element.parent = currentParent
}
}
// Set all of your non-slot children into the Element. children array
// final children cleanup
// filter out scoped slots
element.children = element.children.filter(c= >! (c:any).slotScope)
// remove trailing whitespace node again
trimEndingWhitespace(element)
// check pre state
if (element.pre) {
inVPre = false
}
if (platformIsPreTag(element.tag)) {
inPre = false
}
// Execute the postTransform methods of model, class, and style modules for element
// But the Web platform does not provide this method
// apply post-transforms
for (let i = 0; i < postTransforms.length; i++) {
postTransforms[i](element, options)
}
}
Copy the code
The closeElement method does three things:
- Call if the element has not been processed
processElement
Handle some attributes on the element. - Sets the parent element of the current element.
- Sets the children of the current element.
trimEndingWhitespace
// src/compiler/parser/index.js
function trimEndingWhitespace (el) {
// remove trailing whitespace node
if(! inPre) {let lastNode
while (
(lastNode = el.children[el.children.length - 1]) &&
lastNode.type === 3 &&
lastNode.text === ' '
) {
el.children.pop()
}
}
}
Copy the code
TrimEndingWhitespace removes empty text nodes from elements.
checkRootConstraints
// src/compiler/parser/index.js
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.',
{ start: el.start }
)
}
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.',
el.rawAttrsMap['v-for'])}}Copy the code
CheckRootConstraints is a check on the root element. Slot and template cannot be used as root elements. You cannot use V-for on the root element of a stateful component because it renders multiple elements.
parseHTML
// src/compiler/parser/index.js
parseHTML(template, {
warn,
expectHTML: options.expectHTML,
isUnaryTag: options.isUnaryTag,
canBeLeftOpenTag: options.canBeLeftOpenTag,
shouldDecodeNewlines: options.shouldDecodeNewlines,
shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
shouldKeepComment: options.comments,
outputSourceRange: options.outputSourceRange,
/** * start processing tag *@param {*} Tag Indicates the tag name *@param {*} attrs [{ name: attrName, value: attrVal, start, end }, ...] An array of properties of the form *@param {*} Unary Self-closing label *@param {*} The index * of the start tag in the HTML string@param {*} The end index of the end tag in the HTML string */
start (tag, attrs, unary, start, end) {
// If there is a namespace,, inherits the parent namespace
// check namespace.
// inherit parent ns if there is one
const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
// handle IE svg bug
/* istanbul ignore if */
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs)
}
// Create an AST object
let element: ASTElement = createASTElement(tag, attrs, currentParent)
// Set the namespace
if (ns) {
element.ns = ns
}
if(process.env.NODE_ENV ! = ='production') {
if (options.outputSourceRange) {
element.start = start
element.end = end
element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) = > {
cumulated[attr.name] = attr
return cumulated
}, {})
}
attrs.forEach(attr= > {
if (invalidAttributeRE.test(attr.name)) {
warn(
`Invalid dynamic argument expression: attribute names cannot contain ` +
`spaces, quotes, <, >, / or =.`,
{
start: attr.start + attr.name.indexOf(` [`),
end: attr.start + attr.name.length
}
)
}
})
}
, tags should not appear in the template for non-server rendering
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.',
{ start: element.start }
)
}
// Execute the class, style, and Model module preTransforms methods for the Element object, respectively
// Handle input tags with v-model directives. Handle checkbox, radio, etc
// apply pre-transforms
for (let i = 0; i < preTransforms.length; i++) {
element = preTransforms[i](element, options) || element
}
if(! inVPre) {// If there is a v-pre directive, set element.pre = true
processPre(element)
if (element.pre) {
// If v-pre is present, set inVPre to true
inVPre = true}}// If pre tags, set inPre to true
if (platformIsPreTag(element.tag)) {
inPre = true
}
if (inVPre) {
// There is a v-pre directive, such a node will only render once, set the attributes on the node to the el.attrs array object, as static attributes, data update will not render this part of the content
processRawAttrs(element)
} else if(! element.processed) {// structural directives
// Handle the V-for attribute
processFor(element)
// Handle v-if, v-else, v-else
processIf(element)
// Process the V-once instruction
processOnce(element)
}
// Root does not exist. The element being processed is the first element, the root element of the component
if(! root) { root = elementif(process.env.NODE_ENV ! = ='production') {
// Check the root element, do not use slot, template, v-for
checkRootConstraints(root)
}
}
if(! unary) {// Non-self-closing tags. CurrentParent is used to record the current element, and the next element knows its parent when it is processed
currentParent = element
// Then push the element into the stack array for future processing of the current element's closing tag
stack.push(element)
} else {
// Indicates that the current element is a self-closing label
closeElement(element)
}
},
/** * Process the end tag *@param {*} Tag End tag name *@param {*} Start Indicates the start index of the end tag@param {*} End End index of the end tag */
end (tag, start, end) {
// AST object corresponding to the end tag
const element = stack[stack.length - 1]
// pop stack
stack.length -= 1
currentParent = stack[stack.length - 1]
if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
element.end = end
}
// Set the parent-child relationship
closeElement(element)
},
// Process the text, generate an AST object based on the text, and place the AST in the children of its parent element.
chars (text: string.start: number.end: number) {
// Exception handling
if(! currentParent) {if(process.env.NODE_ENV ! = ='production') {
if (text === template) {
warnOnce(
'Component template requires a root element, rather than just text.',
{ start }
)
} else if ((text = text.trim())) {
warnOnce(
`text "${text}" outside root element will be ignored.`,
{ start }
)
}
}
return
}
// IE textarea placeholder bug
/* istanbul ignore if */
if (isIE &&
currentParent.tag === 'textarea' &&
currentParent.attrsMap.placeholder === text
) {
return
}
const children = currentParent.children
if (inPre || text.trim()) {
// The text is inside the pre tag or text.trim() is not empty
text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
} else if(! children.length) {If text is not in the pre tag and text.trim() is null, and the parent element has no child nodes, set text to null
// remove the whitespace-only node right after an opening tag
text = ' '
} else if (whitespaceOption) {
// compress
if (whitespaceOption === 'condense') {
// in condense mode, remove the whitespace node if it contains
// line break, otherwise condense to a single space
text = lineBreakRE.test(text) ? ' ' : ' '
} else {
text = ' '}}else {
text = preserveWhitespace ? ' ' : ' '
}
// If the text still exists after processing
if (text) {
if(! inPre && whitespaceOption ==='condense') {
// Not in the Pre node, and the compression option exists in the configuration options, compress multiple consecutive whitespace into a single
// condense consecutive whitespaces into single space
text = text.replace(whitespaceRE, ' ')}let res
// Generate AST objects based on text
letchild: ? ASTNodeif(! inVPre && text ! = =' ' && (res = parseText(text, delimiters))) {
// Expressions exist in text (i.e. delimiters (placeholders))
child = {
type: 2.expression: res.expression,
tokens: res.tokens,
text
}
} else if(text ! = =' '| |! children.length || children[children.length -1].text ! = =' ') {
// Plain text node
child = {
type: 3,
text
}
}
// push into the children of the parent element
if (child) {
if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
child.start = start
child.end = end
}
children.push(child)
}
}
},
// Process the comment node
comment (text: string, start, end) {
// adding anything as a sibling to the root node is forbidden
// comments should still be allowed, but ignored
// Do not add anything as a sibling of root's node. Comments should be allowed, but ignored
// If currentParent does not exist, comment is at the same level as root
if (currentParent) {
// Comment the AST of the node
const child: ASTText = {
type: 3,
text,
isComment: true
}
if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
// Record the start index and end index of the node
child.start = start
child.end = end
}
// push into the children of the parent element
currentParent.children.push(child)
}
}
})
return root
}
Copy the code
Templates are parsed using the parseHTML function, which is defined in SRC/Compiler /parser/html-parser:
// src/compiler/parser/html-parser
const attribute = /^\s*([^\s"'<>\/=]+)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /
const dynamicArgAttribute = /^\s*((? :v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp.source}] * `
const qnameCapture = ` ((? :${ncname}\ \ :)?${ncname}) `
const startTagOpen = new RegExp(` ^ <${qnameCapture}`)
const startTagClose = /^\s*(\/?) >/
const endTag = new RegExp(` ^ < \ \ /${qnameCapture}[^ >] * > `)
const doctype = / ^
]+>/i
// #7298: escape - to avoid being passed as HTML comment when inlined in page
const comment = / ^
const conditionalComment = / ^
// Special Elements (can contain anything)
export const isPlainTextElement = makeMap('script,style,textarea'.true)
const reCache = {}
const decodingMap = {
'< ': '<'.'> ': '>'.'" ': '"'.'& ': '&'.'& # 10; ': '\n'.'& # 9. ': '\t'.'& # 39; ': "'"
}
const encodedAttr = / & (? :lt|gt|quot|amp|#39); /g
const encodedAttrWithNewLines = / & (? :lt|gt|quot|amp|#39|#10|#9); /g
/ / # 5992
const isIgnoreNewlineTag = makeMap('pre,textarea'.true)
const shouldIgnoreFirstNewline = (tag, html) = > tag && isIgnoreNewlineTag(tag) && html[0= = ='\n'
function decodeAttr (value, shouldDecodeNewlines) {
const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr
return value.replace(re, match= > decodingMap[match])
}
/** * Loop through the HTML template string, processing each tag in turn, as well as the attribute * on the tag@param {*} HTML HTML template *@param {*} Options Configuration items */
export function parseHTML (html, options) {
const stack = []
const expectHTML = options.expectHTML
// Whether the label is self-closing
const isUnaryTag = options.isUnaryTag || no
// Can only have the start tag
const canBeLeftOpenTag = options.canBeLeftOpenTag || no
// Records the current start position in the raw HTML string
let index = 0
let last, lastTag
while (html) {
last = html
// Make sure we're not in a plaintext content element like script/style
// Make sure it's not in plain text elements like script, style, textarea, etc
if(! lastTag || ! isPlainTextElement(lastTag)) {// find the first < character
let textEnd = html.indexOf('<')
// textEnd === 0 indicates that it is found at the beginning
// Process possible comment tags, conditional comment tags, Doctype, start tag, end tag respectively
// After each case is processed, the loop is truncated (continue) and the HTML string is reset, truncating the processed tags, and the next loop processes the remaining HTML string template
if (textEnd === 0) {
// Comment:
// Handle the comment tag <! -- xx -->
if (comment.test(html)) {
// The end index of the comment tag
const commentEnd = html.indexOf('-->')
if (commentEnd >= 0) {
// Whether comments should be kept
if (options.shouldKeepComment) {
// Get: comment content, comment start index, comment end index
options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3)}// Adjust the HTML and index variables
advance(commentEnd + 3)
continue}}// Handle condition comment tag: <! --[if IE]>
// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
if (conditionalComment.test(html)) {
// End position
const conditionalEnd = html.indexOf('] > ')
if (conditionalEnd >= 0) {
// Adjust the HTML and index variables
advance(conditionalEnd + 2)
continue}}// handle Doctype,
const doctypeMatch = html.match(doctype)
if (doctypeMatch) {
advance(doctypeMatch[0].length)
continue
}
/** * Processing the start tag and end tag is the karyotype part of the whole function * these two parts are constructing element ast */
// Handle closing tags, such as
const endTagMatch = html.match(endTag)
if (endTagMatch) {
const curIndex = index
advance(endTagMatch[0].length)
// Process the closing tag
parseEndTag(endTagMatch[1], curIndex, index)
continue
}
// Process the start tag
const startTagMatch = parseStartTag()
if (startTagMatch) {
// Process the results of the previous step further and finally call the options.start method
// The real parsing is done in the start method
handleStartTag(startTagMatch)
if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
advance(1)}continue}}let text, rest, next
if (textEnd >= 0) {
// It is just a plain piece of text: < I am text
// Find the next < in the HTML until
// The entire process is adjusting the textEnd value as the starting point for the next valid tag in the HTML
// Capture the content after textEnd in the HTML template string, rest =
rest = html.slice(textEnd)
// This while loop deals with plain text after
// Intercepts the text and finds the starting position of the valid tag (textEnd)
while(! endTag.test(rest) && ! startTagOpen.test(rest) && ! comment.test(rest) && ! conditionalComment.test(rest) ) {< < < < < < < < < < < < < < < < < < < < < < < <
next = rest.indexOf('<'.1)
// If < is not found, the loop ends
if (next < 0) break
// The < is found in the following string, indexed at textEnd
textEnd += next
// Intercepts the content after the HTML string template textEnd and assigns it to REST
rest = html.slice(textEnd)
}
< < < is a piece of plain text, or < < is a valid tag found after the text, truncated text
text = html.substring(0, textEnd)
}
// If textEnd < 0, the < is not found in the HTML, and the HTML is just text
if (textEnd < 0) {
text = html
}
// Intercepts the text content from the HTML template string
if (text) {
advance(text.length)
}
// Process text
// Generate an AST object based on text and place the AST in its parent element
// in currentParent.children array
if (options.chars && text) {
options.chars(text, index - text.length, index)
}
} else {
// Handle closed tags for script, style, textarea tags
let endTagLength = 0
// Start the tag in lower case
const stackedTag = lastTag.toLowerCase()
const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?) (< / ' + stackedTag + '[^ >] * >)'.'i'))
// Match and process all text between the start tag and end tag, such as
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 there is any content in the stack array, there is a label that has not been closed, and a message is displayed
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}"`, { start: index + html.length })
}
break}}// Clean up any remaining tags
parseEndTag()
/** * resets HTML, HTML = all characters back from index position n * index is the starting index of HTML in the original template string, and the starting position of the character for the next processing *@param {*} N index * /
function advance (n) {
index += n
html = html.substring(n)
}
/** * parse the start tag, such as <div id="app"> *@returns { tagName: 'div', attrs: [[xx], ...] , start: index }* /
function parseStartTag () {
const start = html.match(startTagOpen)
if (start) {
// Process the result
const match = {
/ / tag name
tagName: start[1].// Attributes, placeholders
attrs: [].// Start position of the label
start: index
}
/** * adjust HTML and index, for example: * HTML = 'id="app">' * index = current index * start[0] = '
advance(start[0].length)
let end, attr
// Process the attributes in the start tag and place them in the match-.attrs array
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)
}
// End = '>' or end = '/>'
if (end) {
match.unarySlash = end[1]
advance(end[0].length)
match.end = index
return match
}
}
}
Attrs, if the tag is not self-closing, the tag information is placed in the stack array, and the tag information is popped out of the stack when its closing tag is being processed. Now all the information about the tag is in the Element AST object. We call the options.start method to process the tag and generate the Element AST from the tag information, along with the attributes and directives on the start tag. Finally, place element AST in the stack array * *@param {*} match { tagName: 'div', attrs: [[xx], ...] , start: index } */
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)
}
}
// Unary tags, such as
constunary = isUnaryTag(tagName) || !! unarySlashAttrs = [{name: attrName, value: attrVal, start: xx, end: xx},...]
/ / such as attrs = [{name: "id", value: 'app', start: xx, end: xx},...
const l = match.attrs.length
const attrs = new Array(l)
for (let i = 0; i < l; i++) {
const args = match.attrs[i]
/ / such as args [3] = > 'id', args [4] = > '=', args [5] = > 'app'
const value = args[3] || args[4] || args[5] | |' '
const shouldDecodeNewlines = tagName === 'a' && args[1= = ='href'
? options.shouldDecodeNewlinesForHref
: options.shouldDecodeNewlines
// attrs[i] = { id: 'app' }
attrs[i] = {
name: args[1].value: decodeAttr(value, shouldDecodeNewlines)
}
// In non-production environments, record the start and end indexes of attributes
if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
attrs[i].start = args.start + args[0].match(/^\s*/).length
attrs[i].end = args.end
}
}
// If the tag is not self-closing, the tag information is placed in the stack array, and it will be popped out of the stack when its closing tag is processed in the future
// If it is a self-closing tag, the tag information does not need to go to the stack. If it is a self-closing tag, the tag information does not need to go to the stack. If it is a self-closing tag, the tag information does not need to go to the stack
if(! unary) {{tag, lowerCasedTag, attrs, start, end}
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end })
// Identifies the end tag of the current tag as tagName
lastTag = tagName
}
/** * Call the start method, which does the following six things: * 1, create AST object * 2, handle input tag with v-model instruction, checkbox, radio, etc. * 3, handle tag with v-model instruction. For example, v-pre, V-for, V-if, v-once * 4, if root does not exist, set the current element to root * 5, if the current element is not self-closing tag, push itself into the stack array, and record currentParent. 6. If the current element is a self-closing tag, it indicates that the tag is finished processing, and it relates itself to the parent element, and sets its child element */
if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end)
}
}
/** * Parse the closing tag, such as </div> * * 2. After processing the end tag, adjust the stack array. Make sure that the last element in the stack array is the start tag corresponding to the next end tag *. 3. Handle exceptions such as the last element in the stack array is not the start tag corresponding to the current end tag, and the * BR and P tags are handled separately *@param {*} TagName specifies the tagName, such as div *@param {*} Start Indicates the start index of the end tag@param {*} End End index of the end tag */
function parseEndTag (tagName, start, end) {
let pos, lowerCasedTagName
if (start == null) start = index
if (end == null) end = index
// Traverse the stack array in reverse order to find the first label that is the same as the current end label. This label is the description object of the start label corresponding to the end label
// Theoretically, the last element in the stack array is the description object of the start tag of the current end tag
// Find the closest opened tag of the same type
if (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 shop
pos = 0
}
// If the same tag name is never found in the stack, pos < 0 and else branch
if (pos >= 0) {
// This for loop is responsible for closing all tags in the stack array whose index is >= pos
The last element of the stack array is the start tag.
// There are exceptions where elements do not provide closing tags, such as:
// stack = ['span', 'div', 'span', 'h1'], the current processing end tag tagName = div
// match to div, pos = 1, then the two tags with index 2 and 3 (span, h1) do not provide the closing tag
// The for loop closes the div, span, and h1 tags,
// And the development environment for the span and h1 tags "did not match the end tag"
// 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.`,
{ start: stack[i].start, end: stack[i].end }
)
}
if (options.end) {
// Call options.end to handle the normal closing tag
options.end(stack[i].tag, start, end)
}
}
// Remove the tags you just processed from the array, ensuring that the last element of the array is the start tag corresponding to the next end tag
// Remove the open elements from the stack
stack.length = pos
// lastTag records the last unprocessed start tag in the stack array
lastTag = pos && stack[pos - 1].tag
} else if (lowerCasedTagName === 'br') {
// The current label being processed is the
tag
if (options.start) {
options.start(tagName, [], true, start, end)
}
} else if (lowerCasedTagName === 'p') {
// Process the tag
if (options.start) {
options.start(tagName, [], false, start, end)
}
// Process the tag
if (options.end) {
options.end(tagName, start, end)
}
}
}
}
Copy the code
The main logic of parseHTML is loop parsing, using the re to match, and then doing different things, using the advance function to change the index during the match until the parsing is done.
Regex matches comment tags, document type tags, start tags, and end tags. Use the handleStartTag method to parse the start tag, push the AST object constructed from the non-unary tag into the stack, parse the close tag using the parseEndTag method, that is, the stack in reverse order, and find the first tag that is identical to the current end tag. The label is the description object of the start label corresponding to the end label.
There are three types of AST element nodes: type 1 indicates a normal element, type 2 indicates an expression, and type 3 indicates plain text. Parse uses regex to parse template characters into an AST syntax tree.
optimize
After the template is parsed, the AST tree is generated, followed by some optimizations to the AST.
optimize
// src/compiler/optimizer.js
/** * Goal of the optimizer: walk the generated template AST tree * and detect sub-trees that are purely static, i.e. parts of * the DOM that never needs to change. * * Once we detect these sub-trees, we can: * * 1. Hoist them into constants, so that we no longer need to * create fresh nodes for them on each re-render; * 2. Completely skip them in the patching process. */
export function optimize (root: ? ASTElement, options: CompilerOptions) {
if(! root)return
/** * options.staticKeys = 'staticClass,staticStyle' * isStaticKey = function(val) { return map[val] } */
isStaticKey = genStaticKeysCached(options.staticKeys || ' ')
// The platform retains the label
isPlatformReservedTag = options.isReservedTag || no
// first pass: mark all non-static nodes.
// Mark the static node
markStatic(root)
// second pass: mark static roots.
// Mark the static root
markStaticRoots(root, false)}Copy the code
markStatic
// src/compiler/optimizer.js
function markStatic (node: ASTNode) {
// Use node.static to indicate whether a node is static
node.static = isStatic(node)
if (node.type === 1) {
// do not make component slot content static. this avoids
// 1. components not able to mutate slot nodes
// 2. static slot content fails for hot-reloading
/** * Do not set the component's slot content to a static node to avoid: * 1, the component cannot change the slot node * 2, static slot content fails when hot reloading */
if(! isPlatformReservedTag(node.tag) && node.tag ! = ='slot' &&
node.attrsMap['inline-template'] = =null
) {
// Recursive terminating condition. If the node is not a platform reserved tag && nor a slot tag && nor an inline template, it ends
return
}
// Iterate over the children, recursively calling markStatic to mark the static properties of those children
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
// If the child node is non-static, update the parent node to non-static
if(! child.static) { node.static =false}}// If the node has v-if, V-else, v-else instructions, then mark the nodes in the block as static
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block
markStatic(block)
if(! block.static) { node.static =false
}
}
}
}
}
/** * Check whether the node is static: * Check whether the node is static: * Check whether the node is static: * Check whether the node is static: Dynamic node */ if the parent is a template tag containing the V-for instruction
function isStatic (node: ASTNode) :boolean {
if (node.type === 2) { // expression
{{MSG}}
return false
}
if (node.type === 3) { // text
return true
}
return!!!!! (node.pre || ( ! node.hasBindings &&// no dynamic bindings! node.if && ! node.for &&// not v-if or v-for or v-else! isBuiltInTag(node.tag) &&// not a built-in
isPlatformReservedTag(node.tag) && // not a component! isDirectChildOfTemplateFor(node) &&Object.keys(node).every(isStaticKey)
))
}
Copy the code
markStaticRoots
// src/compiler/optimizer.js
function markStaticRoots (node: ASTNode, isInFor: boolean) {
if (node.type === 1) {
if (node.static || node.once) {
// A node that is already static or a node for the V-once directive
node.staticInFor = isInFor
}
// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
if(node.static && node.children.length && ! ( node.children.length ===1 &&
node.children[0].type === 3
)) {
In addition to being a static node itself, children must be satisfied, and children cannot be just a text node
node.staticRoot = true
return
} else {
node.staticRoot = false
}
// The current node is not a static root, recursively traverses its children, marking the static root
if (node.children) {
for (let i = 0, l = node.children.length; i < l; i++) { markStaticRoots(node.children[i], isInFor || !! node.for) } }// If the node has v-if, V-else, v-else instructions, then mark the static root of the block node
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}
Copy the code
Not all data in the template are responsive, and some data will not change after the first rendering. By traversing the generated template AST tree and marking these nodes, they can be skipped in the patch process to improve the performance of comparison.
In Optimize, it’s really markStatic(root) for static nodes and markStaticRoots(root, False) for static roots.
generate
After the AST syntax tree is generated, the AST is optimized to mark static nodes and static roots. Finally, generate code strings through generate.
generate
// src/compiler/codegen/index.js
export function generate (
ast: ASTElement | void,
options: CompilerOptions
) :CodegenResult {
const state = new CodegenState(options)
// fix #11483, Root level <script> tags should not be rendered.
const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")'
return {
render: `with(this){return ${code}} `.staticRenderFns: state.staticRenderFns
}
}
Copy the code
State is an instance of CodegenState, and some of its properties and methods are used when generating code. The generate function mainly generates code from genElement and then wraps it around with(this){return ${code}}.
genElement
// src/compiler/codegen/index.js
export function genElement (el: ASTElement, state: CodegenState) :string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if(el.staticRoot && ! el.staticProcessed) {// Process the static root node
return genStatic(el, state)
} else if(el.once && ! el.onceProcessed) {// Process nodes with v-once instructions
return genOnce(el, state)
} else if(el.for && ! el.forProcessed) {/ / v - for processing
return genFor(el, state)
} else if(el.if && ! el.ifProcessed) {/ / v - the if processing
return genIf(el, state)
} else if (el.tag === 'template'&&! el.slotTarget && ! state.pre) {// Process the child nodes
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
// Process the slot
return genSlot(el, state)
} else {
// component or element
let code
if (el.component) {
// Handle dynamic components
code = genComponent(el.component, el, state)
} else {
// Custom components and native tags
let data
if(! el.plain || (el.pre && state.maybeComponent(el))) {// Non-ordinary elements or components with v-pre instructions go here to handle all attributes of the node
data = genData(el, state)
}
// Process the child nodes to get an array of string code for all the child nodes
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${data ? `,${data}` : ' ' // data
}${children ? `,${children}` : ' ' // children
}) `
}
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
Copy the code
The main purpose of genElement is to generate code functions for attributes on AST nodes using different methods.
genStatic
// src/compiler/codegen/index.js
// hoist static sub-trees out
function genStatic (el: ASTElement, state: CodegenState) :string {
// Indicates that the current static node has been processed
el.staticProcessed = true
// Some elements (templates) need to behave differently inside of a v-pre
// node. All pre nodes are static roots, so we can use this as a location to
// wrap a state change and reset it upon exiting the pre node.
const originalPreState = state.pre
if (el.pre) {
state.pre = el.pre
}
// Add the generated code to staticRenderFns
state.staticRenderFns.push(`with(this){return ${genElement(el, state)}} `)
state.pre = originalPreState
/ / return _m function, state. StaticRenderFns. Said in the array subscript length - 1
return `_m(${state.staticRenderFns.length - 1
}${el.staticInFor ? ',true' : ' '
}) `
}
Copy the code
genOnce
// src/compiler/codegen/index.js
function genOnce (el: ASTElement, state: CodegenState) :string {
// Indicates that the v-once instruction for the current node has been processed
el.onceProcessed = true
if(el.if && ! el.ifProcessed) {// If contains v-if and v-if is not processed, processing v-if eventually generates a ternary operator code
return genIf(el, state)
} else if (el.staticInFor) {
// Indicates that the current node is a static node wrapped inside the v-for instruction node
let key = ' '
let parent = el.parent
while (parent) {
if (parent.for) {
key = parent.key
break
}
parent = parent.parent
}
if(! key) { process.env.NODE_ENV ! = ='production' && state.warn(
`v-once can only be used inside v-for that is keyed. `,
el.rawAttrsMap['v-once'])return genElement(el, state)
}
// Generate the _o function
return `_o(${genElement(el, state)}.${state.onceId++}.${key}) `
} else {
// This is a simple static node, generating _m function
return genStatic(el, state)
}
}
Copy the code
genFor
// src/compiler/codegen/index.js
export function genFor (
el: any, state: CodegenState, altGen? :Function, altHelper? :string
) :string {
const exp = el.for
const alias = el.alias
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ' '
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ' '
// Indicate that v-for must use key when on a component
if(process.env.NODE_ENV ! = ='production'&& state.maybeComponent(el) && el.tag ! = ='slot'&& el.tag ! = ='template' &&
!el.key
) {
state.warn(
` <${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
`v-for should have explicit keys. ` +
`See https://vuejs.org/guide/list.html#key for more info.`,
el.rawAttrsMap['v-for'].true /* tip */)}// indicates that the v-for instruction on the current node has been processed
el.forProcessed = true // avoid recursion
/** * generates _l functions, such as: * v-for="(item,index) in data" * * _l((data), function (item, index) { * return genElememt(el, state) * }) */
return `${altHelper || '_l'}((${exp}), ` +
`function(${alias}${iterator1}${iterator2}) {` +
`return ${(altGen || genElement)(el, state)}` +
'}) '
}
Copy the code
genIf
// src/compiler/codegen/index.js
export function genIf (
el: any, state: CodegenState, altGen? :Function, altEmpty? :string
) :string {
// The v-if directive marking the current node has been processed
el.ifProcessed = true // avoid recursion
// Get the ternary expression, condition? render1 : render2
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions (conditions: ASTIfConditions, state: CodegenState, altGen? :Function, altEmpty? :string
) :string {
// The length is empty and returns an empty node rendering function directly
if(! conditions.length) {return altEmpty || '_e()'
}
// Get the first condition from conditions
const condition = conditions.shift()
if (condition.exp) {
// generate a ternary operator by condition. Exp,
// if there are multiple conditions, the multilevel ternary operation is generated
return ` (${condition.exp})?${genTernaryExp(condition.block) }:${genIfConditions(conditions, state, altGen, altEmpty) }`
} else {
return `${genTernaryExp(condition.block)}`
}
// v-if with v-once should generate code like (a)? _m(0):_m(1)
function genTernaryExp (el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
Copy the code
genChildren
// src/compiler/codegen/index.js
export function genChildren (el: ASTElement, state: CodegenState, checkSkip? :boolean, altGenElement? :Function, altGenNode? :Function
) :string | void {
const children = el.children
if (children.length) {
const el: any = children[0]
// optimize single v-for
if (children.length === 1&& el.for && el.tag ! = ='template'&& el.tag ! = ='slot'
) {
// a child && node has v-for && not template tag && not slot
// Call genElement directly to enter genFor
const normalizationType = checkSkip
? state.maybeComponent(el) ? ` `, 1 : ` `, 0
: ` `
return `${(altGenElement || genElement)(el, state)}${normalizationType}`
}
const normalizationType = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0
// a function that generates code
const gen = altGenNode || genNode
// Return an array, each element of which is a render function of a child node,
// Format: ['_c(tag, data, children, normalizationType)',...
return ` [${children.map(c => gen(c, state)).join(', ')}]${normalizationType ? `,${normalizationType}` : ' '
}`}}Copy the code
genSlot
// src/compiler/codegen/index.js
function genSlot (el: ASTElement, state: CodegenState) :string {
// Slot name
const slotName = el.slotName || '"default"'
// Process the child nodes
const children = genChildren(el, state)
// Finally return _t function
let res = `_t(${slotName}${children ? `,function(){return ${children}} ` : ' '}`
const attrs = el.attrs || el.dynamicAttrs
? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(attr= > ({
// slot props are camelized
name: camelize(attr.name),
value: attr.value,
dynamic: attr.dynamic
})))
: null
const bind = el.attrsMap['v-bind']
if((attrs || bind) && ! children) { res +=`,null`
}
if (attrs) {
res += `,${attrs}`
}
if (bind) {
res += `${attrs ? ' ' : ',null'}.${bind}`
}
return res + ') '
}
Copy the code
genProps
// src/compiler/codegen/index.js
function genProps (props: Array<ASTAttr>) :string {
// Static attributes
let staticProps = ` `
// Dynamic attributes
let dynamicProps = ` `
// Iterate over the property array
for (let i = 0; i < props.length; i++) {
/ / property
const prop = props[i]
/ / property values
const value = __WEEX__
? generateValue(prop.value)
: transformSpecialNewlines(prop.value)
if (prop.dynamic) {
// Dynamic properties, 'dAttrName,dAttrVal... `
dynamicProps += `${prop.name}.${value}, `
} else {
// Static attribute, 'attrName,attrVal... '
staticProps += `"${prop.name}":${value}, `}}// Remove the last comma of the static attribute
staticProps = ` {${staticProps.slice(0, -1)}} `
if (dynamicProps) {
// If there is a dynamic attribute, return:
// _d(static attribute string, dynamic attribute string)
return `_d(${staticProps}[${dynamicProps.slice(0, -1)}]) `
} else {
// Indicates that there is no dynamic attribute in the property array
return staticProps
}
}
Copy the code
genData
// src/compiler/codegen/index.js
export function genData (el: ASTElement, state: CodegenState) :string {
let data = '{'
// directives first.
// directives may mutate the el's other properties before they are generated.
const dirs = genDirectives(el, state)
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,`
}
// pre
if (el.pre) {
data += `pre:true,`
}
// record original tag name for components using "is" attribute
if (el.component) {
data += `tag:"${el.tag}", `
}
// Execute the genData method for the node module (class, style),
// module data generation functions
for (let i = 0; i < state.dataGenFns.length; i++) {
data += state.dataGenFns[i](el)
}
// attributes
if (el.attrs) {
data += `attrs:${genProps(el.attrs)}, `
}
// DOM props
if (el.props) {
data += `domProps:${genProps(el.props)}, `
}
// event handlers
{' on${eventName}:handleCode '} or {' on_d(${eventName}:handleCode ', '${eventName},handleCode')}
if (el.events) {
data += `${genHandlers(el.events, false)}, `
}
// Events with.native modifier,
if (el.nativeEvents) {
data += `${genHandlers(el.nativeEvents, true)}, `
}
// slot target
// only for non-scoped slots
// Non-scoped slot
if(el.slotTarget && ! el.slotScope) { data +=`slot:${el.slotTarget}, `
}
// scoped slots
// Scope slot
if (el.scopedSlots) {
data += `${genScopedSlots(el, el.scopedSlots, state)}, `
}
// component v-model
if (el.model) {
data += `model:{value:${el.model.value },callback:${el.model.callback },expression:${el.model.expression }}, `
}
// inline-template
if (el.inlineTemplate) {
const inlineTemplate = genInlineTemplate(el, state)
if (inlineTemplate) {
data += `${inlineTemplate}, `
}
}
data = data.replace($/ /,.' ') + '} '
// v-bind dynamic argument wrap
// v-bind with dynamic arguments must be applied using the same v-bind object
// merge helper so that class/style/mustUseProp attrs are handled correctly.
if (el.dynamicAttrs) {
data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)}) `
}
// v-bind data wrap
if (el.wrapData) {
data = el.wrapData(data)
}
// v-on data wrap
if (el.wrapListeners) {
data = el.wrapListeners(data)
}
return data
}
Copy the code
The genData function constructs a data object string based on the attributes of the AST element node, which will be passed as a parameter later when creating a VNode.
genComponent
// src/compiler/codegen/index.js
function genComponent (
componentName: string,
el: ASTElement,
state: CodegenState
) :string {
// All child nodes
const children = el.inlineTemplate ? null : genChildren(el, state, true)
// Return '_c(compName, data, children)'
CompName is the value of the IS attribute
return `_c(${componentName}.${genData(el, state)}${children ? `,${children}` : ' '
}) `
}
Copy the code
Take a chestnut
The template
<ul :class="bindCls" class="list" v-if="isShow">
<li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li>
</ul>
Copy the code
The parse generated AST
ast = {
'type': 1.'tag': 'ul'.'attrsList': [].'attrsMap': {
':class': 'bindCls'.'class': 'list'.'v-if': 'isShow'
},
'if': 'isShow'.'ifConditions': [{
'exp': 'isShow'.'block': // ul ast element}].'parent': undefined.'plain': false.'staticClass': 'list'.'classBinding': 'bindCls'.'children': [{
'type': 1.'tag': 'li'.'attrsList': [{
'name': '@click'.'value': 'clickItem(index)'}].'attrsMap': {
'@click': 'clickItem(index)'.'v-for': '(item,index) in data'
},
'parent': // ul ast element
'plain': false.'events': {
'click': {
'value': 'clickItem(index)'}},'hasBindings': true.'for': 'data'.'alias': 'item'.'iterator1': 'index'.'children': [
'type': 2.'expression': '_s(item)+":"+_s(index)'
'text': '{{item}}:{{index}}'.'tokens': [{'@binding': 'item' },
':',
{ '@binding': 'index'}]]}Copy the code
Optimize optimization AST
ast = {
'type': 1.'tag': 'ul'.'attrsList': [].'attrsMap': {
':class': 'bindCls'.'class': 'list'.'v-if': 'isShow'
},
'if': 'isShow'.'ifConditions': [{
'exp': 'isShow'.'block': // ul ast element}].'parent': undefined.'plain': false.'staticClass': 'list'.'classBinding': 'bindCls'.'static': false.'staticRoot': false.'children': [{
'type': 1.'tag': 'li'.'attrsList': [{
'name': '@click'.'value': 'clickItem(index)'}].'attrsMap': {
'@click': 'clickItem(index)'.'v-for': '(item,index) in data'
},
'parent': // ul ast element
'plain': false.'events': {
'click': {
'value': 'clickItem(index)'}},'hasBindings': true.'for': 'data'.'alias': 'item'.'iterator1': 'index'.'static': false.'staticRoot': false.'children': [
'type': 2.'expression': '_s(item)+":"+_s(index)'
'text': '{{item}}:{{index}}'.'tokens': [{'@binding': 'item' },
':',
{ '@binding': 'index'}].'static': false]]}}Copy the code
Generate generate code
with (this) {
return (isShow) ?
_c('ul', {
staticClass: "list".class: bindCls
},
_l((data), function (item, index) {
return _c('li', {
on: {
"click": function ($event) {
clickItem(index)
}
}
},
[_v(_s(item) + ":" + _s(index))])
})
) : _e()
}
Copy the code
The resulting shorthand functions, such as _c, _t, _l, and _m, are defined in:
// src/core/instance/render-helpers/index.js
export function installRenderHelpers (target: any) { target._o = markOnce target._n = toNumber target._s = toString target._l = renderList target._t = renderSlot target._q = looseEqual target._i = looseIndexOf target._m = renderStatic target._f = resolveFilter target._k = checkKeyCodes target._b = bindObjectProps target._v = createTextVNode target._e = createEmptyVNode target._u = resolveScopedSlots target._g = bindObjectListeners target._d = bindDynamicKeys target._p = prependModifier }Copy the code
To this, Vue source code general comb finished, during this period to view a lot of big guy’s article, thank you big guys selfless sharing, thank you for the friendly front end circle.
A link to the
Vue (V2.6.14) source code detoxification (pre) : handwritten a simple version of Vue
Vue (V2.6.14) source code detoxification (a) : preparation
Vue (V2.6.14) source code detoxification (two) : initialization and mount
Vue (V2.6.14) source code detoxification (three) : response type principle
Vue (V2.6.14) source code detoxification (four) : update strategy
Vue (v2.6.14) source code detoxification (five) : render and VNode
Vue (V2.6.14) source code detoxification (six) : Update and patch
Vue (V2.6.14) source code detoxification (seven) : template compilation
If you think it’s ok, give it a thumbs up!! You can also visit my personal blog at www.mingme.net/