When learning becomes a habit, knowledge becomes common sense. Thank you for your attention, likes, collections and comments.
The new video and articles will be sent to WeChat official account for the first time. Li Yongning
The article has been included in github warehouse liyongning/blog , welcome to Watch and Star.
Special instructions
Due to the limited length of the article, Vue source code interpretation (8) - compiler parsing is divided into two parts, so please open it at the same time when reading this article Interpretation of Vue source code (8) -- Analysis of compiler (Part 2) Read together.
preface
Interpretation of Vue source code (4) -- asynchronous update Finally, when it comes to refreshing the watcher queue, execute each watcher Run method, by watcher Run calls watcher Get to execute the watcher Getter method to enter the actual update phase. If you are not familiar with this process, I suggest you read this article again.
When updating a render watcher, the updateComponent method is executed:
// /src/core/instance/lifecycle.js const updateComponent = () => { // Execute VM_ Render() function, get the virtual DOM and pass vnode to_ update method, and then it's time to go to the patch stage vm._update(vm._render(), hydrating) }
You can see that you need to execute VM before each update_ Render() method, VM_ Render is the render function we often hear, which is obtained in two ways:
- Provided by users themselves. When writing components, use the render option instead of the template
- The compiler compiles the component template to generate the render option
Today, let's go deep into the compiler to see how it compiles the class html template we usually write into the render function.
The core of the compiler consists of three parts:
- Parse and convert the class html template into AST object
- Optimization, also known as static marking, traverses AST objects, marks whether each node is a static node, and marks the static root node
- Generate rendering function, and generate AST object into rendering function
Due to the large amount of code in the compiler, this part of knowledge is divided into three parts. The first part is parsing.
target
Deeply understand the parsing process of Vue compiler and how to convert class html template string into AST object.
Source code interpretation
Next, we go to the source code to find the answer.
Reading suggestions
Due to the huge amount of code in the parsing process, it is recommended that you grasp the main line: "parse HTML string template and generate AST object", and this AST object is the final result we want to get. Therefore, in the process of reading, you should record this AST object, which is helpful for understanding and makes you less likely to get lost.
You can also read the help section of the next article first to have an advance preparation and psychological expectation.
Entry - $mount
The entry location of the compiler is / SRC / platforms / Web / entry runtime with compiler JS, there are two ways to find this entry
- Breakpoint debugging, Interpretation of Vue source code (2) -- Vue initialization process As mentioned in, the last step of initialization is to execute $mount for mounting. In the full volume of Vue packages, this step will enter the compilation stage.
- Find it step by step through the configuration file of rollup
/src/platforms/web/entry-runtime-with-compiler.js
/** * Compiler entry * Vue at runtime JS package does not have this part of the code. It is precompiled through the packer combined with Vue loader + Vue compiler utils to compile the template into render function * * Just do one thing, get the rendering function of the component and set it to this$ Options */ const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { // Mount point el = el && query(el) // The mount point cannot be body or html /* istanbul ignore if */ 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 } // Configuration item const options = this.$options // resolve template/el and convert to render function /** * If the user provides the render configuration item, the compilation phase will be skipped directly. Otherwise, the compilation phase will be entered * Parse template and el and convert them to render function * Priority: render > template > el */ if (!options.render) { let template = options.template if (template) { // Processing template options if (typeof template === 'string') { if (template.charAt(0) === '#') { // {template: '#app'}. If template is an id selector, get the innerHtml of the element as the template 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) { // Template is a normal element. Get its innerHtml as a template template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { // Set the el option to obtain the outerHtml of the el selector as the template template = getOuterHTML(el) } if (template) { // The template is ready and enters the compilation stage /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } // Static rendering function and dynamic rendering function const { render, staticRenderFns } = compileToFunctions(template, { // In a non production environment, compile time records the position index of the label attribute at the beginning and end of the template string outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, // Delimiter, default {} delimiters: options.delimiters, // Keep comments comments: options.comments }, this) // Put two rendering functions into this$ Options options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } // Perform mount return mount.call(this, el, hydrating) }
compileToFunctions
/src/compiler/to-function.js
/** * 1,Execute the compilation function to get the compilation result - > compiled * 2,Process the error and tip generated during compilation and output them to the console respectively * 3,Convert the compiled string code into an executable function through new Function(codeStr) * 4,Cache compilation results * @param { string } template String template * @param { CompilerOptions } options Compile options * @param { Component } vm Component instance * @return { render, staticRenderFns } */ return function compileToFunctions( template: string, options?: CompilerOptions, vm?: Component ): CompiledFunctionResult { // Compilation options passed in options = extend({}, options) // journal const warn = options.warn || baseWarn delete options.warn /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production') { // Detect possible CSP limitations try { new Function('return 1') } catch (e) { if (e.toString().match(/unsafe-eval|CSP/)) { // It seems that you are using the full version of Vue in a CSP insecure environment JS, the template compiler cannot work in such an environment. // Consider relaxing policy restrictions or precompiling your template as the render function 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.' ) } } } // If there is a cache, skip the compilation and get the results of the last compilation directly from the cache const key = options.delimiters ? String(options.delimiters) + template : template if (cache[key]) { return cache[key] } // Execute the compilation function to get the compilation result const compiled = compile(template, options) // Check the error and tip generated during compilation and output 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)) } } } // Convert the compiled string code into a function, which is realized through new Function(code) // turn code into functions const res = {} const fnGenErrors = [] res.render = createFunction(compiled.render, fnGenErrors) res.staticRenderFns = compiled.staticRenderFns.map(code => { return createFunction(code, fnGenErrors) }) // When dealing with the errors in the above code conversion process, this step generally will not report an error unless the compiler itself makes an error /* istanbul ignore if */ 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 compilation results return (cache[key] = res) }
compile
/src/compiler/create-compiler.js
/** * To compile functions, you do two things: * 1,Option merging: merge the options configuration item into final options (base options) to get the final compiled configuration object * 2,Call the core compiler baseCompile to get the compilation result * 3,Mount the error and tip generated during compilation to the compilation result and return the compilation result * @param {*} template Template * @param {*} options Configuration item * @returns */ function compile( template: string, options?: CompilerOptions ): CompiledResult { // Create a compilation option object based on the platform specific compilation configuration const finalOptions = Object.create(baseOptions) const errors = [] const tips = [] // Log, responsible for recording error and tip let warn = (msg, range, tip) => { (tip ? tips : errors).push(msg) } // If compilation options exist, merge options and baseOptions if (options) { // Development environment if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) { // $flow-disable-line const leadingSpaceLength = template.match(/^\s*/)[0].length // Enhanced logging method 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 the configuration items in options into finalOptions */ // Merge custom module s 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 configuration items for (const key in options) { if (key !== 'modules' && key !== 'directives') { finalOptions[key] = options[key] } } } // journal finalOptions.warn = warn // So far, the key point is finally reached. Call the core compilation function, pass the template string and the final compilation options, and get the compilation result // All of the above is to build the final compilation options of the platform const compiled = baseCompile(template.trim(), finalOptions) if (process.env.NODE_ENV !== 'production') { detectErrors(compiled.ast, warn) } // Mount the errors and prompts generated during compilation to the compilation results compiled.errors = errors compiled.tips = tips return compiled }
baseOptions
/src/platforms/web/compiler/options.js
export const baseOptions: CompilerOptions = { expectHTML: true, // Handle class, style, v-model modules, // Processing instruction // Is it a pre tag isPreTag, // Is it a self closing label isUnaryTag, // Specifies some properties that should be bound using props mustUseProp, // You can only write the label of the start label, and the end label browser will complete it automatically canBeLeftOpenTag, // Is it a reserved tag (html + svg) isReservedTag, // Gets the namespace of the tag getTagNamespace, staticKeys: genStaticKeys(modules) }
baseCompile
/src/compiler/index.js
/** * All the things we have done before have only one purpose, which is to build platform specific compilation options, such as web platform * * 1,Parse html template into ast * 2,Static marking of ast tree * 3,Generate ast render function * Put the static rendering function into code Staticrenderfns array * code.render Is a dynamic rendering function * Execute the rendering function to get vnode when rendering in the future */ function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { // The template is parsed into ast, and all the information of the element is set on the ast object of each node, such as label information, attribute information, slot information, parent node, child node, etc. // For the specific properties, check the start and end methods to process the start and end tags const ast = parse(template.trim(), options) // Optimize, traverse AST and make static marks for each node // Mark whether each node is a static node, and then further mark the static root node // In this way, these static nodes can be skipped in subsequent updates // Mark the static root, which is used to generate the render function stage and generate the render function of the static root node if (options.optimize !== false) { optimize(ast, options) } // Generate rendering function from AST and generate code like this, such as code render = "_c('div',{attrs:{"id":"app"}},_l((arr),function(item){return _c('div',{key:item},[_v(_s(item))])}),0)" const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } }
parse
Note: due to the large amount of code in this part, some adjustments have been made to the structure of the code to facilitate reading and understanding.
/src/compiler/parser/index.js
/** * * Convert HTML string to AST * @param {*} template HTML Template * @param {*} options Platform specific compilation options * @returns root */ export function parse( template: s tring, options: CompilerOptions ): ASTElement | void { // journal warn = options.warn || baseWarn // Is it a pre tag platformIsPreTag = options.isPreTag || no // Properties that must be bound using props platformMustUseProp = options.mustUseProp || no // Gets the namespace of the tag platformGetTagNamespace = options.getTagNamespace || no // Is it a reserved tag (html + svg) const isReservedTag = options.isReservedTag || no // Judge whether an element is a component maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag) // Get options Methods of transformNode, preTransformNode and postTransformNode in the three modules of class, model and style under modules // Responsible for handling class, style and v-model on the element node transforms = pluckModuleFunction(options.modules, 'transformNode') preTransforms = pluckModuleFunction(options.modules, 'preTransformNode') postTransforms = pluckModuleFunction(options.modules, 'postTransformNode') // Delimiter, such as: {}} delimiters = options.delimiters const stack = [] // Space options const preserveWhitespace = options.preserveWhitespace !== false const whitespaceOption = options.whitespace // The root node takes root as the root, and the processed nodes will be attached to root according to the hierarchy. The last return is root, an ast syntax tree let root // Parent element of the current element let currentParent let inVPre = false let inPre = false let warned = false // Parse html template string and handle all tags and attributes on tags // parseHTMLOptions here will be used in later processing and will be further analyzed // If you analyze it in advance, it's easy for you to diverge parseHTML(template, parseHtmlOptions) // Returns the generated ast object return root
parseHTML
/src/compiler/parser/html-parser.js
/** * Loop through the html template string to process each tag and the attributes on the tag in turn * @param {*} html html Template * @param {*} options Configuration item */ export function parseHTML(html, options) { const stack = [] const expectHTML = options.expectHTML // Is it a self closing label const isUnaryTag = options.isUnaryTag || no // Can I have only the start tag const canBeLeftOpenTag = options.canBeLeftOpenTag || no // Record the current starting position in the original html string let index = 0 let last, lastTag while (html) { last = html // Make sure it's not in plain text elements like script, style, textarea if (!lastTag || !isPlainTextElement(lastTag)) { // Find the first < character let textEnd = html.indexOf('<') // textEnd === 0 means found at the beginning // Handle possible annotation labels, conditional annotation labels, Doctype, start labels and end labels respectively // After each case is processed, the loop will be truncated, the html string will be reset, the processed tags will be cut off, and the remaining html string templates will be processed in the next loop if (textEnd === 0) { // Handling annotation labels <-- xx --> if (comment.test(html)) { // End index of annotation label const commentEnd = html.indexOf('-->') if (commentEnd >= 0) { // Should comments be retained if (options.shouldKeepComment) { // Get: annotation content, start index and end index of annotation options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3) } // Adjust html and index variables advance(commentEnd + 3) continue } } // Processing condition comment label: <-- [if IE]> // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment if (conditionalComment.test(html)) { // Find end position const conditionalEnd = html.indexOf(']>') if (conditionalEnd >= 0) { // Adjust html and index variables advance(conditionalEnd + 2) continue } } // Processing Doctype, <! Doctype html> const doctypeMatch = html.match(doctype) if (doctypeMatch) { advance(doctypeMatch[0].length) continue } /** * Dealing with the start tag and end tag is the karyotype part of the whole function. Don't worry about others * These two parts are constructing element ast */ // Process end tag, such as < / div > const endTagMatch = html.match(endTag) if (endTagMatch) { const curIndex = index advance(endTagMatch[0].length) // Processing end tag parseEndTag(endTagMatch[1], curIndex, index) continue } // Process the start tag, such as < div id = "app" >, starttagmatch = {tagName: 'div', attrs: [[XX],...], start: index} const startTagMatch = parseStartTag() if (startTagMatch) { // Further processing the previous step, and finally calling options.start method // The real parsing work is done in this start method handleStartTag(startTagMatch) if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) { advance(1) } continue } } let text, rest, next if (textEnd >= 0) { // If you can come here, it means that although < XX is matched in html, it does not belong to the above situations, // It's just an ordinary paragraph of text: < I'm the text // So find the next < < from the html until < XX is the label of the above situations, and then it ends, // Throughout this process, we have been adjusting the value of textEnd as the starting position of the next valid tag in html // Intercept the content after textEnd in the html template string, rest = < XX rest = html.slice(textEnd) // This while loop is used to process plain text after < XX // Intercept the text content and find the start position (textEnd) of a valid label while ( !endTag.test(rest) && !startTagOpen.test(rest) && !comment.test(rest) && !conditionalComment.test(rest) ) { // It is considered that the content after < is plain text, and then find it again in these plain text< next = rest.indexOf('<', 1) // If < < is not found, the loop ends directly if (next < 0) break // Coming here means that < < is found in the subsequent string, and the index position is textEnd textEnd += next // Intercept the content of the html string template textEnd and assign it to rest, and continue to judge whether there is a label in the subsequent string rest = html.slice(textEnd) } // Go here to explain the end of the traversal. There are two cases: one is < followed by a piece of plain text, or find a valid label and intercept the text text = html.substring(0, textEnd) } // If textend < 0, it means that < < is not found in html, which means that html is a piece of text if (textEnd < 0) { text = html } // Intercept the text content from the html template string if (text) { advance(text.length) } // Processing text // Generate an ast object based on text, and then put the ast into its parent element, // I.e. currentparent In the children array if (options.chars && text) { options.chars(text, index - text.length, index) } } else { // Handle closed tags of script, style, textarea Tags let endTagLength = 0 // Lowercase form of start label 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 the end tag, such as < script > XX < / script > const rest = html.replace(reStackedTag, function (all, text, endTag) { endTagLength = endTag.length if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') { text = text .replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298 .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1') } if (shouldIgnoreFirstNewline(stackedTag, text)) { text = text.slice(1) } if (options.chars) { options.chars(text) } return '' }) index += html.length - rest.length html = rest parseEndTag(stackedTag, index - endTagLength, index) } // The processing ends here. If there are still contents in the stack array, it indicates that there are labels that have not been closed, and a prompt message is given 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() }
advance
/src/compiler/parser/html-parser.js
/** * Reset html, html = all characters backward from the index n position * index It is the starting index of html in the original template string, and it is also the starting position of the character to be processed next time * @param {*} n Indexes */ function advance(n) { index += n html = html.substring(n) }
parseStartTag
/src/compiler/parser/html-parser.js
/** * Parse the start tag, for example: < div id = "app" > * @returns { tagName: 'div', attrs: [[xx], ...], start: index } */ function parseStartTag() { const start = html.match(startTagOpen) if (start) { // Processing results const match = { // Tag name tagName: start[1], // Properties, placeholders attrs: [], // Start position of label start: index } /** * Adjust html and index, such as: * html = ' id="app">' * index = Index at this time * start[0] = '<div' */ advance(start[0].length) let end, attr // Process the attributes in the start tag and put them into match In 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 of start tag, end = > 'or end =' / > ' if (end) { match.unarySlash = end[1] advance(end[0].length) match.end = index return match } } }
handleStartTag
/src/compiler/parser/html-parser.js
/** * Further process the parsing result of the start tag -- the match object * Process attribute match Attrs, if it is not a self closing label, put the label information into the stack array, and pop it up when it is processed to its closed label in the future, indicating that the label has been processed, and all the information of the label is on the element ast object * Next, call options The start method processes the tag and generates an element ast according to the tag information, * And process the attributes and instructions on the start tag, and finally put the element ast into 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 < HR / > const unary = isUnaryTag(tagName) || !!unarySlash // Process match Attrs, get attrs = [{Name: attrname, value: attrval, start: XX, end: XX},...] // For example, 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] // For example: 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) } // Non production environment, start and end indexes of record 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 it is not a self closing label, put the label information into the stack array and pop it up when its closed label is processed in the future // If it is a self closing label, the label information does not need to enter the stack. Directly process many attributes and set them to the element ast object, so there is no step to process the end label. This step is carried out in the process of processing the start label if (!unary) { // Put the tag information into the stack array, {tag, lowerCasedTag, attrs, start, end} stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end }) // The end tag that identifies the current tag is tagName lastTag = tagName } /** * Calling the start method mainly does the following six things: * 1,Create AST object * 2,Handle the input tag with v-model instruction, and handle the cases where the input is checkbox, radio and others respectively * 3,Handle many instructions on labels, such as v-pre, v-for, v-if and v-once * 4,If the root node does not exist, set the current element as the root node * 5,If the current element is a non self closing label, it will push itself into the stack array and record the currentParent, which will be used to tell the child element who its parent node is when processing the child element next * 6,If the current element is a self closing label, it means that the label is finished to be processed, so that it can have a relationship with the parent element and set its own child element */ if (options.start) { options.start(tagName, attrs, unary, match.start, match.end) } }
parseEndTag
/src/compiler/parser/html-parser.js
/** * Parse end tag, for example: < / div > * The most important thing is: * 1,The stack array is processed, and the start tag of the current end tag is found from the stack array, and then the options. is called. End method * 2,After processing the tag, adjust the stack array to ensure that the last element in the stack array is the starting tag corresponding to the next tag. * 3,Handle some exceptions, such as the last element of the stack array is not the start tag corresponding to the current end tag, or * br And p labels are handled separately * @param {*} tagName Tag name, such as div * @param {*} start Start index of end tag * @param {*} end End index of 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 and find the first tag that is the same as the current end tag. This tag is the description object of the start tag corresponding to the end tag // Theoretically, there is no exception. 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 not found in the stack, pos will be < 0 and the subsequent else branch will be carried out if (pos >= 0) { // This for loop is responsible for closing all tags with index > = POS in the stack array // Why use a loop? As mentioned above, under normal circumstances, the last element of the stack array is the start tag we are looking for, // However, there are some exceptions, that is, some elements do not provide an end tag, such as: // Stack = ['span ','div','span ','h1'], end tag of current processing tagName = div // If div and pos = 1 are matched, the two tags (span and h1) with indexes 2 and 3 do not provide the end tag // The for loop is responsible for closing the div, span and h1 tags, // And in the development environment, give a "prompt that the end tag does not match" for span and h1 tags // 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) { // Go here to explain that the above exceptions have been handled, and call options End handles the normal end tag options.end(stack[i].tag, start, end) } } // Remove the tags just processed from the array to ensure 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 currently processed tags are < br / > tags if (options.start) { options.start(tagName, [], true, start, end) } } else if (lowerCasedTagName === 'p') { // p tag if (options.start) { // Process < p > tags options.start(tagName, [], false, start, end) } if (options.end) { // Process < / P > tags options.end(tagName, start, end) } } }
parseHtmlOptions
src/compiler/parser/index.js
Defines how to handle start tags, end tags, text nodes, and comment nodes.
start
/** * We have mainly done the following six things: * 1,Create AST object * 2,Handle the input tag with v-model instruction, and handle the cases where the input is checkbox, radio and others respectively * 3,Handle many instructions on labels, such as v-pre, v-for, v-if and v-once * 4,If the root node does not exist, set the current element as the root node * 5,If the current element is a non self closing label, it will push itself into the stack array and record the currentParent, which will be used to tell the child element who its parent node is when processing the child element next * 6,If the current element is a self closing label, it means that the label is finished to be processed, so that it can have a relationship with the parent element and set its own child element * @param {*} tag Tag name * @param {*} attrs [{ name: attrName, value: attrVal, start, end }, ...] Attribute array in form * @param {*} unary Self closing label * @param {*} start Start index of tag in html string * @param {*} end End index of tag in html string */ function start(tag, attrs, unary, start, end) { // Check the namespace, and if it exists, inherit 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) } // Creates an AST object for the current label let element: ASTElement = createASTElement(tag, attrs, currentParent) // Set namespace if (ns) { element.ns = ns } // This paragraph will go in the non production environment. Add some attributes on the ast object, such as start and end if (process.env.NODE_ENV !== 'production') { if (options.outputSourceRange) { element.start = start element.end = end // Parse the attribute array into {attrname: {Name: attrname, value: attrval, start, end},...} Formal object element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => { cumulated[attr.name] = attr return cumulated }, {}) } // Verify whether the attribute is valid. For example, the attribute name cannot contain: spaces, quotes, <, >, / or = 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 } ) } }) } // In the case of non server rendering, style and script labels should not appear in the template if (isForbiddenTag(element) && !isServerRendering()) { element.forbidden = true process.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 preTransforms methods in the class, style and model modules respectively for the element object * However, only the model module of the web platform has the preTransforms method * Used to handle the input tag with v-model, but not the v-model attribute * Handle the cases where the input is checkbox, radio and others respectively * input The specific situation is determined by el Conditions in ifconditions * <input v-mode="test" :type="checkbox or radio or other(Text) "/ > */ // apply pre-transforms for (let i = 0; i < preTransforms.length; i++) { element = preTransforms[i](element, options) || element } if (!inVPre) { // Indicates whether there is a v-pre instruction for element. If yes, set element pre = true processPre(element) if (element.pre) { // If there is a v-pre instruction, set inVPre to true inVPre = true } } // If the pre tag, set inPre to true if (platformIsPreTag(element.tag)) { inPre = true } if (inVPre) { // It indicates that there is a v-pre instruction on the label. Such a node will only be rendered once. Set the attributes on the node to El In attrs array object, as a static attribute, this part will not be rendered during data update // Set el Attrs array object. Each element is an attribute object {name: attrName, value: attrVal, start, end} processRawAttrs(element) } else if (!element.processed) { // structural directives // Process the v-for attribute to get element For = iteratable object element Alias = alias processFor(element) /** * Handle v-if, v-else-if, v-else * Get element if = "exp",element.elseif = exp, element.else = true * v-if Attribute will be added in element Add {exp, block} object to ifconditions array */ processIf(element) // Process the v-once instruction to get element once = true processOnce(element) } // If the root does not exist, it means that the currently processed element is the first element, that is, the root element of the component if (!root) { root = element if (process.env.NODE_ENV !== 'production') { // Check the root element. There are some restrictions on the root element. For example, slot and template cannot be used as the root element, and v-for instructions cannot be used on the root element of stateful components checkRootConstraints(root) } } if (!unary) { // The non self closing tag records the current element through currentParent. When the next element is processed, it will know who its parent element is currentParent = element // Then push the element into the stack array and take it out when processing the closed label of the current element in the future // push the ast object of the current tag into the stack array. Note that when calling options Start method // There was also a push operation before, which brought in a basic configuration information of the current tag stack.push(element) } else { /** * Note that the current element is a self closing label, which mainly does three things: * 1,If the element has not been processed, that is, El If processed is false, the processElement method is called to process many attributes on the node * 2,Let yourself have a relationship with the parent element, put yourself in the children array of the parent element, and set your parent attribute to currentParent * 3,Set your own child elements and put all your non slot child elements into your own children array */ closeElement(element) } }
end
/** * Processing end tag * @param {*} tag The name of the end tag * @param {*} start Start index of end tag * @param {*} end End index of end tag */ function end(tag, start, end) { // The ast object of the start tag corresponding to the end tag const element = stack[stack.length - 1] // pop stack stack.length -= 1 // It's a little difficult to understand here, because the previous element may be the sibling node of the current element currentParent = stack[stack.length - 1] if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) { element.end = end } /** * Three main things have been done: * 1,If the element has not been processed, that is, El If processed is false, the processElement method is called to process many attributes on the node * 2,Let yourself have a relationship with the parent element, put yourself in the children array of the parent element, and set your parent attribute to currentParent * 3,Set your own child elements and put all your non slot child elements into your own children array */ closeElement(element) }
chars
/** * Process the text, generate an ast object based on the text, and then put the ast into its parent element, currentparent In the children array */ function chars(text: string, start: number, end: number) { // Exception handling: the absence of currentParent indicates that this text has no parent element if (!currentParent) { if (process.env.NODE_ENV !== 'production') { if (text === template) { // Text cannot be the root element of a component warnOnce( 'Component template requires a root element, rather than just text.', { start } ) } else if ((text = text.trim())) { // Text placed outside the root element is ignored 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 } // All child nodes of the current parent element const children = currentParent.children // Carry out a series of processing on text, such as deleting white space characters, or if there is whitespace options option, text will be directly set to null or space if (inPre || text.trim()) { // Text in pre tag or text Trim() is not empty text = isTextTag(currentParent) ? text : decodeHTMLCached(text) } else if (!children.length) { // The description text is not in the pre tag and text Trim() is empty, and the current parent element has no child node, // Set text to empty // remove the whitespace-only node right after an opening tag text = '' } else if (whitespaceOption) { // Compression processing 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 text still exists after processing if (text) { if (!inPre && whitespaceOption === 'condense') { // If it is not in the pre node and there is a compression option in the configuration option, multiple consecutive spaces are compressed into a single space // condense consecutive whitespaces into single space text = text.replace(whitespaceRE, ' ') } let res // Generate AST object based on text let child: ?ASTNode if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) { // There is an expression in the text (i.e. there is a delimiter) child = { type: 2, // expression expression: res.expression, tokens: res.tokens, // text text } } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') { // Plain text node child = { type: 3, text } } // If the child exists, put the child into the belly of the parent element, that is, currentparent In the children array if (child) { if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) { child.start = start child.end = end } children.push(child) } } },
comment
/** * Processing annotation nodes */ function comment(text: string, start, end) { // adding anything as a sibling to the root node is forbidden // comments should still be allowed, but ignored // It is forbidden to add any content as the peer of the root node. Comments should be allowed, but will be ignored // If the currentParent does not exist, it means that the comment and root are at the same level and are ignored if (currentParent) { // ast of annotation node const child: ASTText = { // Node type type: 3, // Note Content text, // Is it a comment 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 } // Place the current annotation node in the children attribute of the parent element currentParent.children.push(child) } }
createASTElement
/src/compiler/parser/index.js
/** * Creates an AST object for the specified element * @param {*} tag Tag name * @param {*} attrs Attribute array, [{name: attrName, value: attrVal, start, end},...] * @param {*} parent Parent element * @returns { type: 1, tag, attrsList, attrsMap: makeAttrsMap(attrs), rawAttrsMap: {}, parent, children: []} */ export function createASTElement( tag: string, attrs: Array<ASTAttr>, parent: ASTElement | void ): ASTElement { return { // Node type type: 1, // Tag name tag, // Attribute array of tag attrsList: attrs, // Attribute object of tag {attrName: attrVal,...} attrsMap: makeAttrsMap(attrs), // Original attribute object rawAttrsMap: {}, // Parent node parent, // Child node children: [] } }
preTransformNode
/src/platforms/web/compiler/modules/model.js
/** * The input tag with v-model is processed, but the v-model attribute is not processed * Handle the cases where the input is checkbox, radio and others respectively * input The specific situation is determined by el Conditions in ifconditions * <input v-mode="test" :type="checkbox or radio or other(Text) "/ > * @param {*} el * @param {*} options * @returns branch0 */ function preTransformNode (el: ASTElement, options: CompilerOptions) { if (el.tag === 'input') { const map = el.attrsMap // No v-model attribute, end directly if (!map['v-model']) { return } // Get the value of: type let typeBinding if (map[':type'] || map['v-bind:type']) { typeBinding = getBindingAttr(el, 'type') } if (!map.type && !typeBinding && map['v-bind']) { typeBinding = `(${map['v-bind']}).type` } // If the type attribute exists if (typeBinding) { // Get the value of v-if, for example: < input V-model = "test": type = "checkbox" v-if = "test" / > const ifCondition = getAndRemoveAttr(el, 'v-if', true) // &&test const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : `` // Whether there is a v-else attribute, < input v-else / > const hasElse = getAndRemoveAttr(el, 'v-else', true) != null // Get the value of v-else-if attribute < input v-else-if = "test" / > const elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true) // Clone a new el object and handle the input as chekbox, radio or other situations respectively // In which case, through El Ifconditions condition // 1. checkbox const branch0 = cloneASTElement(el) // process for on the main node // <input v-for="item in arr" :key="item" /> // Process the v-for expression to get branch0 for = arr, branch0. alias = item processFor(branch0) // At branch 0 Attrsmap and branch0 Add the type attribute to the attrslist object addRawAttr(branch0, 'type', 'checkbox') // Handle the key, ref, slot, closed slot tag, dynamic component, class, style, v-bind, v-on, other instructions and some native attributes of the element node respectively processElement(branch0, options) // Mark that the current object has been processed branch0.processed = true // prevent it from double-processed // Get true & & test or false & & test, and mark whether the current input is a checkbox branch0.if = `(${typeBinding})==='checkbox'` + ifConditionExtra // At branch 0 Put {exp, block} objects into ifconfigurations array addIfCondition(branch0, { exp: branch0.if, block: branch0 }) // Clone a new ast object // 2. add radio else-if condition const branch1 = cloneASTElement(el) // Get v-for attribute value getAndRemoveAttr(branch1, 'v-for', true) // In branch1 Attrsmap and branch1 Add the type attribute to the attrslist object addRawAttr(branch1, 'type', 'radio') // Handle the key, ref, slot, closed slot tag, dynamic component, class, style, v-bind, v-on, other instructions and some native attributes of the element node respectively processElement(branch1, options) // At branch 0 Put {exp, block} objects into ifconfigurations array addIfCondition(branch0, { // Mark whether the current input is radio exp: `(${typeBinding})==='radio'` + ifConditionExtra, block: branch1 }) // 3. other, input is other const branch2 = cloneASTElement(el) getAndRemoveAttr(branch2, 'v-for', true) addRawAttr(branch2, ':type', typeBinding) processElement(branch2, options) addIfCondition(branch0, { exp: ifCondition, block: branch2 }) // Set else or else if condition for branch0 if (hasElse) { branch0.else = true } else if (elseIfCondition) { branch0.elseif = elseIfCondition } return branch0 } } }
getBindingAttr
/src/compiler/helpers.js
/** * Gets the value of the execution property name on the el object */ export function getBindingAttr ( el: ASTElement, name: string, getStatic?: boolean ): ?string { // Gets the value of the specified property const dynamicValue = getAndRemoveAttr(el, ':' + name) || getAndRemoveAttr(el, 'v-bind:' + name) if (dynamicValue != null) { return parseFilters(dynamicValue) } else if (getStatic !== false) { const staticValue = getAndRemoveAttr(el, name) if (staticValue != null) { return JSON.stringify(staticValue) } } }
getAndRemoveAttr
/src/compiler/helpers.js
/** * From El Delete the specified attribute name from attrslist * If removeFromMap is true, then el. Is also deleted The attribute in the attrsmap object, * For example, v-if, v-else-if, v-else and other attributes will be removed, * However, the properties on this object are not normally deleted because it is also needed during code generation from ast * Returns the value of the specified property */ // note: this only removes the attr from the Array (attrsList) so that it // doesn't get processed by processAttrs. // By default it does NOT remove it from the map (attrsMap) because the map is // needed during codegen. export function getAndRemoveAttr ( el: ASTElement, name: string, removeFromMap?: boolean ): ?string { let val // Remove the execution attribute name from El Remove from attrslist if ((val = el.attrsMap[name]) != null) { const list = el.attrsList for (let i = 0, l = list.length; i < l; i++) { if (list[i].name === name) { list.splice(i, 1) break } } } // If removeFromMap is true, from El Remove the specified attribute name from attrsmap // However, El is generally not removed Data in attsmap, because this object is also needed during code generation from ast if (removeFromMap) { delete el.attrsMap[name] } // Returns the value of the execution property return val }
processFor
/src/compiler/parser/index.js
/** * Process v-for, set the result to el object, and get: * el.for = Iteratable objects, such as arr * el.alias = Alias, such as item * @param {*} el ast object of element */ export function processFor(el: ASTElement) { let exp // Gets the value of the v-for attribute on el if ((exp = getAndRemoveAttr(el, 'v-for'))) { // Parse the expression of v-for to get {for: iteratable object, alias: alias}, such as {for: arr, alias: item} const res = parseFor(exp) if (res) { // Copy the attributes on the res object to the el object extend(el, res) } else if (process.env.NODE_ENV !== 'production') { warn( `Invalid v-for expression: ${exp}`, el.rawAttrsMap['v-for'] ) } } }
addRawAttr
/src/compiler/helpers.js
// In El Attrsmap and el Add the specified attribute name to attrslist // add a raw attr (use this in preTransforms) export function addRawAttr (el: ASTElement, name: string, value: any, range?: Range) { el.attrsMap[name] = value el.attrsList.push(rangeSetItem({ name, value }, range)) }
processElement
/src/compiler/parser/index.js
/** * Handle the key, ref, slot, closed slot tag, dynamic component, class, style, v-bind, v-on, other instructions and some native attributes of the element node respectively * Then add the following attributes on the el object: * el.key,ref,refInFor,scopedSlot,slotName,component,inlineTemplate,staticClass * el.bindingClass,staticStyle,bindingStyle,attrs * @param {*} element ast object of the element being processed * @param {*} options Configuration item * @returns */ export function processElement( element: ASTElement, options: CompilerOptions ) { // el.key = val processKey(element) // Determines whether element is a normal element // determine whether this is a plain element after // removing structural attributes element.plain = ( !element.key && !element.scopedSlots && !element.attrsList.length ) // el.ref = val, el.refInFor = boolean processRef(element) // Process the content passed to the component as a slot, and get the slot name, whether it is a dynamic slot, the value of the scope slot, and all child elements in the slot. The child elements are placed in the children attribute of the slot object processSlotContent(element) // Process the self closing slot tag and get the slot name = > el slotName = xx processSlotOutlet(element) // When processing dynamic components, < component: is = "componame" > < / component > gets el component = compName, // And mark whether there is an inline template, El inlineTemplate = true of false processComponent(element) // Execute the transformNode methods in the class, style and model modules respectively for the element object // However, only the class and style modules in the web platform have the transformNode method, which is used to process the class attribute and style attribute respectively // Get el staticStyle, el.styleBinding,el.staticClass,el.classBinding // Store the value of static style attribute, dynamic style attribute, static class attribute and dynamic class attribute respectively for (let i = 0; i < transforms.length; i++) { element = transforms[i](element, options) || element } /** * Process all attributes on the element: * v-bind The command becomes: El Attrs or EL dynamicAttrs = [{ name, value, start, end, dynamic }, ...], * Or the attribute of props must be used, which becomes el props = [{ name, value, start, end, dynamic }, ...] * v-on The command becomes: El Events or EL nativeEvents = { name: [{ value, start, end, modifiers, dynamic }, ...] } * Other instructions: El directives = [{name, rawName, value, arg, isDynamicArg, modifier, start, end }, ...] * Native attribute: El Attrs = [{name, value, start, end}], or some attributes that must use props, become: * el.props = [{ name, value: true, start, end, dynamic }] */ processAttrs(element) return element }
processKey
/src/compiler/parser/index.js
/** * Process the key attribute on the element and set el key = val * @param {*} el */ function processKey(el) { // Get the attribute value of the key const exp = getBindingAttr(el, 'key') if (exp) { // Exception handling on key usage if (process.env.NODE_ENV !== 'production') { // template tag does not allow setting key if (el.tag === 'template') { warn( `<template> cannot be keyed. Place the key on real elements instead.`, getRawBindingAttr(el, 'key') ) } // Do not use the index of v-for as the key on the sub element of < transition = group >, which is no different from using no key if (el.for) { const iterator = el.iterator2 || el.iterator1 const parent = el.parent if (iterator && iterator === exp && parent && parent.tag === 'transition-group') { warn( `Do not use v-for index as key on <transition-group> children, ` + `this is the same as not using keys.`, getRawBindingAttr(el, 'key'), true /* tip */ ) } } } // Set el key = exp el.key = exp } }
processRef
/src/compiler/parser/index.js
/** * Handle ref attribute on element * el.ref = refVal * el.refInFor = boolean * @param {*} el */ function processRef(el) { const ref = getBindingAttr(el, 'ref') if (ref) { el.ref = ref // Determine whether the element containing the ref attribute is included in the element with the v-for instruction or in the descendant element // If so, ref points to an array containing DOM nodes or component instances el.refInFor = checkInFor(el) } }
processSlotContent
/src/compiler/parser/index.js
/** * Process the content passed to the component as a slot and get: * slotTarget => Slot name * slotTargetDynamic => Is it a dynamic slot * slotScope => Value of the scope slot * When using v-slot syntax directly on the < comp > tag, put the above attributes in el Scopedslots object, and other cases are directly placed on el object * handle content being passed to a component as slot, * e.g. <template slot="xxx">, <div slot-scope="xxx"> */ function processSlotContent(el) { let slotScope if (el.tag === 'template') { // Tips for using the scope attribute on the template tag // Scope has been deprecated and replaced with slot scope after 2.5 // Slot scope can be used on template tags or ordinary tags slotScope = getAndRemoveAttr(el, 'scope') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && slotScope) { warn( `the "scope" attribute for scoped slots have been deprecated and ` + `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` + `can also be used on plain elements in addition to <template> to ` + `denote scoped slots.`, el.rawAttrsMap['scope'], true ) } // el.slotScope = val el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope') } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) { // Element cannot use slot scope and v-for at the same time. v-for has higher priority // You should use the template tag as the container and put the slot scope on the template tag warn( `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` + `(v-for takes higher priority). Use a wrapper <template> for the ` + `scoped slot to make it clearer.`, el.rawAttrsMap['slot-scope'], true ) } el.slotScope = val el.slotScope = slotScope } // Gets the value of the slot property // slot="xxx", the writing method of the old named slot const slotTarget = getBindingAttr(el, 'slot') if (slotTarget) { // el.slotTarget = slot name (named slot) el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget // Dynamic slot name el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot']) // preserve slot as an attribute for native shadow DOM compat // only for non-scoped slots. if (el.tag !== 'template' && !el.slotScope) { addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot')) } } // 2.6 v-slot syntax if (process.env.NEW_SLOT_SYNTAX) { if (el.tag === 'template') { // v-slot on the tempalt tag, get the value of v-slot // v-slot on <template> const slotBinding = getAndRemoveAttrByRegex(el, slotRE) if (slotBinding) { // Exception prompt if (process.env.NODE_ENV !== 'production') { if (el.slotTarget || el.slotScope) { // Mixed use of different slot syntax is prohibited warn( `Unexpected mixed usage of different slot syntaxes.`, el ) } if (el.parent && !maybeComponent(el.parent)) { // < template V-slot > can only appear at the root of the component, for example: // <comp> // <template v-slot>xx</template> // </comp> // It can't be // <comp> // <div> // <template v-slot>xxx</template> // </div> // </comp> warn( `<template v-slot> can only appear at the root level inside ` + `the receiving component`, el ) } } // Get slot name const { name, dynamic } = getSlotName(slotBinding) // Slot name el.slotTarget = name // Is it a dynamic slot el.slotTargetDynamic = dynamic // Value of the scope slot el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf } } else { // Process V-slots on components, < comp v-slot: header / > // slotBinding = { name: "v-slot:header", value: "", start, end} // v-slot on component, denotes default slot const slotBinding = getAndRemoveAttrByRegex(el, slotRE) if (slotBinding) { // Exception prompt if (process.env.NODE_ENV !== 'production') { // If el is not a component, prompt that v-slot can only appear on the component or template label if (!maybeComponent(el)) { warn( `v-slot can only be used on components or <template>.`, slotBinding ) } // Grammatical mixing if (el.slotScope || el.slotTarget) { warn( `Unexpected mixed usage of different slot syntaxes.`, el ) } // To avoid Scope Ambiguity, when there are other named slots, the default slot should also use the < template > syntax if (el.scopedSlots) { warn( `To avoid scope ambiguity, the default slot should also use ` + `<template> syntax when there are other named slots.`, slotBinding ) } } // Add the child of the component to its default slot // add the component's children to its default slot const slots = el.scopedSlots || (el.scopedSlots = {}) // Gets the slot name and whether it is a dynamic slot const { name, dynamic } = getSlotName(slotBinding) // Create an ast object with a template tag to hold the slot contents. The parent is el const slotContainer = slots[name] = createASTElement('template', [], el) // Slot name slotContainer.slotTarget = name // Is it a dynamic slot slotContainer.slotTargetDynamic = dynamic // For all children, set the parent attribute of each child to slotContainer slotContainer.children = el.children.filter((c: any) => { if (!c.slotScope) { // Set the parent attribute of the element in the slot to slotContainer, that is, the template element c.parent = slotContainer return true } }) slotContainer.slotScope = slotBinding.value || emptySlotScopeToken // remove children as they are returned from scopedSlots now el.children = [] // mark el non-plain so data gets generated el.plain = false } } } }
getSlotName
/src/compiler/parser/index.js
/** * Resolve the binding to get the slot name and whether it is a dynamic slot * @returns { name: Slot name, dynamic: whether it is a dynamic slot} */ function getSlotName(binding) { let name = binding.name.replace(slotRE, '') if (!name) { if (binding.name[0] !== '#') { name = 'default' } else if (process.env.NODE_ENV !== 'production') { warn( `v-slot shorthand syntax requires a slot name.`, binding ) } } return dynamicArgRE.test(name) // dynamic [name] ? { name: name.slice(1, -1), dynamic: true } // static name : { name: `"${name}"`, dynamic: false } }
processSlotOutlet
/src/compiler/parser/index.js
// Handle < slot / > outlets to handle closed slot Tags // Get the slot name, El slotName function processSlotOutlet(el) { if (el.tag === 'slot') { // Get slot name el.slotName = getBindingAttr(el, 'name') // Tip: do not use the key attribute on the slot tag if (process.env.NODE_ENV !== 'production' && el.key) { warn( `\`key\` does not work on <slot> because slots are abstract outlets ` + `and can possibly expand into multiple elements. ` + `Use the key on a wrapping element instead.`, getRawBindingAttr(el, 'key') ) } } }
processComponent
/src/compiler/parser/index.js
/** * Processing dynamic components, < component: is = "componame" > < / component > * Get el component = compName */ function processComponent(el) { let binding // Resolve the is attribute to get the attribute value, that is, the component name, El component = compName if ((binding = getBindingAttr(el, 'is'))) { el.component = binding } // <component :is="compName" inline-template>xx</component> // The inline template attribute exists on the component and is marked: El inlineTemplate = true // Indicates that the contents in the start and end labels of components appear as component templates, rather than distributed as slots, so as to facilitate the definition of component templates if (getAndRemoveAttr(el, 'inline-template') != null) { el.inlineTemplate = true } }
transformNode
/src/platforms/web/compiler/modules/class.js
/** * Handling class attributes on elements * The static class attribute value is assigned to El Staticclass attribute * The dynamic class attribute value is assigned to El Classbinding property */ function transformNode (el: ASTElement, options: CompilerOptions) { // journal const warn = options.warn || baseWarn // Get the value xx of the static class attribute on the element, < div class = "xx" > < / div > const staticClass = getAndRemoveAttr(el, 'class') if (process.env.NODE_ENV !== 'production' && staticClass) { const res = parseText(staticClass, options.delimiters) // Tips, like the tips of style, you can't use < div class = "{Val}" > < / div >, please use // < div: class = "Val" > < / div > replace if (res) { warn( `class="${staticClass}": ` + 'Interpolation inside attributes has been removed. ' + 'Use v-bind or the colon shorthand instead. For example, ' + 'instead of <div class="{{ val }}">, use <div :class="val">.', el.rawAttrsMap['class'] ) } } // The static class attribute value is assigned to El staticClass if (staticClass) { el.staticClass = JSON.stringify(staticClass) } // Get the class attribute value of dynamic binding and assign it to El classBinding const classBinding = getBindingAttr(el, 'class', false /* getStatic */) if (classBinding) { el.classBinding = classBinding } }
transformNode
/src/platforms/web/compiler/modules/style.js
/** * Resolve the static style attribute and the dynamically bound style attribute from el and assign them to: * el.staticStyle And el styleBinding * @param {*} el * @param {*} options */ function transformNode(el: ASTElement, options: CompilerOptions) { // journal const warn = options.warn || baseWarn // <div style="xx"></div> // Get style attribute const staticStyle = getAndRemoveAttr(el, 'style') if (staticStyle) { // Prompt: if the delimiter is parsed from xx, it indicates a dynamic style, // For example, < div style = "{Val}}" > < / div > gives a prompt: // For dynamic style, please use < div: style = "Val" > < / div > /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production') { const res = parseText(staticStyle, options.delimiters) if (res) { warn( `style="${staticStyle}": ` + 'Interpolation inside attributes has been removed. ' + 'Use v-bind or the colon shorthand instead. For example, ' + 'instead of <div style="{{ val }}">, use <div :style="val">.', el.rawAttrsMap['style'] ) } } // Assign static style to El staticStyle el.staticStyle = JSON.stringify(parseStyleText(staticStyle)) } // Get the style attribute of dynamic binding, such as < div: style = "{Val}}" > < / div > const styleBinding = getBindingAttr(el, 'style', false /* getStatic */) if (styleBinding) { // Assigned to El styleBinding el.styleBinding = styleBinding } }
link
- Supporting video, WeChat official account reply Get "proficient in Vue technology stack source code principle video version":
- Proficient in Vue technology stack source code principle column
- github warehouse liyongning/Vue Welcome, Star
Thank you for your attention, likes, collections and comments. See you next time.
When learning becomes a habit, knowledge becomes common sense. Thank you for your attention, likes, collections and comments.
The new video and articles will be sent to WeChat official account for the first time. Li Yongning
The article has been included in github warehouse liyongning/blog , welcome to Watch and Star.