Template Compilation for Vue Source Code Learning

Compile entry

// src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
    
    
    Vue.prototype._init = function (options?: Object) {
    
    
        const vm: Component = this
        // ... 其他代码
        if (vm.$options.el) {
    
    
            vm.$mount(vm.$options.el)
        }
    }
}

$mountThe definition of : define the variable mountas $mountthe method on the prototype, and then $mountredefine the ;

// src/platforms/web/entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount
// 重新定义 $mount
Vue.prototype.$mount = function (
    el?: string | Element,
    hydrating?: boolean
): Component {
    
    
    el = el && query(el)
    if (el === document.body || el === document.documentElement) {
    
    
        process.env.NODE_ENV !== 'production' && warn(
            `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
        )
        return this
    } 
    const options = this.$options
    // 解析 template 或 el 并转换为 render
    if (!options.render) {
    
    
        let template = options.template
        // 对模板进行处理
        if (template) {
    
    
            // 如果是字符串,且首字符是 #,表示根据 id 来获取 template 模板
            if (typeof template === 'string') {
    
    
                if (template.charAt(0) === '#') {
    
    
                    template = idToTemplate(template)
                    if (process.env.NODE_ENV !== 'production' && !template) {
    
    
                        warn(
                            `Template element not found or is empty: ${
      
      options.template}`,
                            this
                        )
                    }
                }
            } else if (template.nodeType) {
    
    
                // 如果是节点,获取它的内容
                template = template.innerHTML
            } else {
    
    
                if (process.env.NODE_ENV !== 'production') {
    
    
                    warn('invalid template option:' + template, this)
                }
                return this
            }
        } else if (el) {
    
    
            // 如果不存在 render 和 template,但存在 el,则直接将 el 对应的外层 html 结构赋值给 template
            template = getOuterHTML(el)
        }
        // 处理完 template 之后,将其转换为 render
        if (template) {
    
    
            if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    
    
                mark('compile')
            }
            // 根据 template 获取 render 和静态节点
            const {
    
     render, staticRenderFns } = compileToFunctions(template, {
    
    
                outputSourceRange: process.env.NODE_ENV !== 'production',
                shouldDecodeNewlines,
                shouldDecodeNewlinesForHref,
                delimiters: options.delimiters,
                comments: options.comments
            }, this)
            options.render = render
            options.staticRenderFns = staticRenderFns
            if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    
    
                mark('compile end')
                measure(`vue ${
      
      this._name} compile`, 'compile', 'compile end')
            }
        }
    }
    // 调用原先 Vue 原型上 $mount 方法
    return mount.call(this, el, hydrating)
}

As can be seen from the above code, $mountthe following things are mainly done:

  • Determine elwhether is mounted to a normal node, not bodyor html;
  • If templatethe attribute , it is processed:
    • If the incoming string #starts with , it is considered to idget templatethe template ;
    • If a node is passed in, get the node content directly;
    • If none of the above two conditions exist, a warning is thrown;
  • If the renderand template, directly assign elthe corresponding outer html structure to template;
  • templateAfter processing , call compileToFunctionsto convert it into rendera function and assign it to options;
  • Return the result mount.call(this, el, hydrating)of , and then make supplementary explanations later;

compileToFunctionsis the core method of template compilation, we can see how it is implemented:

// src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile (
	template: string,
    options: CompilerOptions
): CompiledResult {
    
    
    // 将传进来的 template 转化为 ast 树,描述自身形成的结构
   	const ast = parse(template.trim(), options)
	// 静态节点的优化
    if (options.optimize !== false) {
    
    
        optimize(ast, options)
    }
	// 根据生成的 ast 树和配置 options 生成代码,也就是 render 函数
    const code = generate(ast, options)
    return {
    
    
        ast,
        render: code.render,
        staticRenderFns: code.staticRenderFns
    }
})

Analyzing the above code, we can divide it into several steps:

  • Convert the incoming templatetemplate into an ast abstract syntax tree;
  • Optimize static nodes;
  • Generated from the transformed asttree code, that is, renderthe function ;

Let's first look at the specific implementation of parsethe method :

// src/compiler/parser/index.js
// 创建 ast 
export function createASTElement (
    tag: string,
    attrs: Array<ASTAttr>,
    parent: ASTElement | void
): ASTElement {
    
    
    return {
    
    
        type: 1,
        tag,
        attrsList: attrs,
        attrsMap: makeAttrsMap(attrs),
        rawAttrsMap: {
    
    },
        parent,
        children: []
    }
}

export function parse (
  template: string,
  options: CompilerOptions
): ASTElement | void {
    
    
    // ...
    // 只关注重点代码,其他代码省略
    function closeElement (element) {
    
    
        trimEndingWhitespace(element)
        if (!inVPre && !element.processed) {
    
    
            element = processElement(element, options)
        }
        if (!stack.length && element !== root) {
    
    
            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 }
                )
            }
        }
        if (currentParent && !element.forbidden) {
    
    
            if (element.elseif || element.else) {
    
    
                processIfConditions(element, currentParent)
            } else {
    
    
                if (element.slotScope) {
    
    
                    const name = element.slotTarget || '"default"'
                    ;(currentParent.scopedSlots || (currentParent.scopedSlots = {
    
    }))[name] = element
                }
                // 建立父子关系
                currentParent.children.push(element)
                element.parent = currentParent
            }
        }
    }
    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 (tag, attrs, unary, start, end) {
    
    
            const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)

            if (isIE && ns === 'svg') {
    
    
                attrs = guardIESVGBug(attrs)
            }
        	// 创建节点
            let element: ASTElement = createASTElement(tag, attrs, currentParent)
            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
                            }
                        )
                    }
                })
            }

            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 }
                )
            }

            for (let i = 0; i < preTransforms.length; i++) {
    
    
                element = preTransforms[i](element, options) || element
            }

            if (!inVPre) {
    
    
                processPre(element)
                if (element.pre) {
    
    
                    inVPre = true
                }
            }
            if (platformIsPreTag(element.tag)) {
    
    
                inPre = true
            }
        	// for、if、once 等指令的处理
            if (inVPre) {
    
    
                processRawAttrs(element)
            } else if (!element.processed) {
    
    
                processFor(element)
                processIf(element)
                processOnce(element)
            }

            if (!root) {
    
    
                root = element
                if (process.env.NODE_ENV !== 'production') {
    
    
                    checkRootConstraints(root)
                }
            }

            if (!unary) {
    
    
                currentParent = element
                stack.push(element)
            } else {
    
    
                closeElement(element)
            }
        },
        // 匹配到结束标签的回调
        end (tag, start, end) {
    
    
            // 取栈中最后一个开始标签,即与结束标签匹配的开始标签
            const element = stack[stack.length - 1]
            stack.length -= 1
            // 将 currentParent 置为当前匹配到的完整标签内容的父节点
            currentParent = stack[stack.length - 1]
            if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
    
    
                element.end = end
            }
            closeElement(element)
        },
        // 匹配到文本时的回调
        chars (text: string, start: number, end: number) {
    
    
            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
            }
            if (isIE &&
                currentParent.tag === 'textarea' &&
                currentParent.attrsMap.placeholder === text
               ) {
    
    
                return
            }
            const children = currentParent.children
            // 对文本进行处理
            if (inPre || text.trim()) {
    
    
                text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
            } else if (!children.length) {
    
    
                text = ''
            } else if (whitespaceOption) {
    
    
                if (whitespaceOption === 'condense') {
    
    
                    text = lineBreakRE.test(text) ? '' : ' '
                } else {
    
    
                    text = ' '
                }
            } else {
    
    
                text = preserveWhitespace ? ' ' : ''
            }
            // 创建 ast 文本节点
            if (text) {
    
    
                if (!inPre && whitespaceOption === 'condense') {
    
    
                    text = text.replace(whitespaceRE, ' ')
                }
                let res
                let child: ?ASTNode
                if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
    
    
                    child = {
    
    
                        type: 2,
                        expression: res.expression,
                        tokens: res.tokens,
                        text
                    }
                } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
    
    
                    child = {
    
    
                        type: 3,
                        text
                    }
                }
                if (child) {
    
    
                    if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
    
    
                        child.start = start
                        child.end = end
                    }
                    children.push(child)
                }
            }
        },
        // 匹配到注释时的回调
        comment (text: string, start, end) {
    
    
            if (currentParent) {
    
    
                const child: ASTText = {
    
    
                    type: 3,
                    text,
                    isComment: true
                }
                if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
    
    
                    child.start = start
                    child.end = end
                }
                currentParent.children.push(child)
            }
        }
    })
    return root
}

It can be seen from parsethe method that it mainly executes parseHTMLto parse the template. The parsing process generates the corresponding astand . The timing of executing the callback includes matching the start tag, end tag, text, and comment . parseHTMLHow to do it specifically We can continue to look at:

// src/compiler/parser/html-parser.js
// 解析 HTML 相关的正则表达式
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 = /^<!DOCTYPE [^>]+>/i
const comment = /^<!\--/
const conditionalComment = /^<!\[/

export function parseHTML (html, options) {
    
    
    const stack = []
    const expectHTML = options.expectHTML
    const isUnaryTag = options.isUnaryTag || no
    const canBeLeftOpenTag = options.canBeLeftOpenTag || no
    let index = 0 // 当前字符串的索引
    let last, lastTag
    while (html) {
    
    
        last = html
        if (!lastTag || !isPlainTextElement(lastTag)) {
    
    
            // 查找第一个 "<" 的位置
            let textEnd = html.indexOf('<')
            if (textEnd === 0) {
    
    
                // 匹配到注释
                if (comment.test(html)) {
    
    
                    // 找到第一个 "-->" 的位置
                    const commentEnd = html.indexOf('-->')
                    if (commentEnd >= 0) {
    
    
                        if (options.shouldKeepComment) {
    
    
                            // 截取注释内容,执行回调
                            options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3)
                        }
                        // index 往后走,并截取字符串
                        advance(commentEnd + 3)
                        continue
                    }
                }
                // 匹配条件注释
                if (conditionalComment.test(html)) {
    
    
                    const conditionalEnd = html.indexOf(']>')

                    if (conditionalEnd >= 0) {
    
    
                        advance(conditionalEnd + 2)
                        continue
                    }
                }
                // doctype 匹配
                const doctypeMatch = html.match(doctype)
                if (doctypeMatch) {
    
    
                    advance(doctypeMatch[0].length)
                    continue
                }
                // 匹配结束标签
                const endTagMatch = html.match(endTag)
                if (endTagMatch) {
    
    
                    const curIndex = index
                    advance(endTagMatch[0].length)
                    parseEndTag(endTagMatch[1], curIndex, index)
                    continue
                }
                // 匹配开始标签
                const startTagMatch = parseStartTag()
                if (startTagMatch) {
    
    
                    handleStartTag(startTagMatch)
                    if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
    
    
                        advance(1)
                    }
                    continue
                }
            }

            let text, rest, next
            if (textEnd >= 0) {
    
    
                rest = html.slice(textEnd)
                // 循环往后走,直到找到开始标签 | 结束标签 | 注释 | 条件注释
                while (
                    !endTag.test(rest) &&
                    !startTagOpen.test(rest) &&
                    !comment.test(rest) &&
                    !conditionalComment.test(rest)
                ) {
    
    
                    next = rest.indexOf('<', 1)
                    if (next < 0) break
                    textEnd += next
                    rest = html.slice(textEnd)
                }
                text = html.substring(0, textEnd)
            }

            // 纯文本
            if (textEnd < 0) {
    
    
                text = html
            }

            if (text) {
    
    
                advance(text.length)
            }

            if (options.chars && text) {
    
    
                options.chars(text, index - text.length, index)
            }
        } else {
    
    
            let endTagLength = 0
            const stackedTag = lastTag.toLowerCase()
            const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))
            const rest = html.replace(reStackedTag, function (all, text, endTag) {
    
    
                endTagLength = endTag.length
                if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
    
    
                    text = text
                        .replace(/<!\--([\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)
        }

        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
        }
    }

    parseEndTag()
    // 截取 html, 记录当前字符串的位置
    function advance (n) {
    
    
        index += n
        html = html.substring(n)
    }
    // 解析开始标签,返回一个对象,包括该标签的属性和位置
    function parseStartTag () {
    
    
        // 匹配开始标签
        const start = html.match(startTagOpen)
        if (start) {
    
    
            const match = {
    
    
                tagName: start[1],
                attrs: [],
                start: index
            }
            // 匹配到,字符串向前截取匹配的长度
            advance(start[0].length)
            // 匹配结束标签和属性
            let end, attr
            // 处理开始标签到闭合符号 ">" 之间的属性
            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)
            }
            // 匹配到结束标签,开始标签解析结束
            if (end) {
    
    
                match.unarySlash = end[1]
                advance(end[0].length)
                match.end = index
                return match
            }
        }
    }

    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)
            }
        }

        const unary = isUnaryTag(tagName) || !!unarySlash

        const l = match.attrs.length
        const attrs = new Array(l)
        // 属性处理
        for (let i = 0; i < l; i++) {
    
    
            const args = match.attrs[i]
            const value = args[3] || args[4] || args[5] || ''
            const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'
            ? options.shouldDecodeNewlinesForHref
            : options.shouldDecodeNewlines
            attrs[i] = {
    
    
                name: args[1],
                value: decodeAttr(value, shouldDecodeNewlines)
            }
            if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
    
    
                attrs[i].start = args.start + args[0].match(/^\s*/).length
                attrs[i].end = args.end
            }
        }

        if (!unary) {
    
    
            // 入栈,后续可用来匹配结束标签
            stack.push({
    
     tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end })
            lastTag = tagName
        }

        if (options.start) {
    
    
            options.start(tagName, attrs, unary, match.start, match.end)
        }
    }

    function parseEndTag (tagName, start, end) {
    
    
        let pos, lowerCasedTagName
        if (start == null) start = index
        if (end == null) end = index

        if (tagName) {
    
    
            lowerCasedTagName = tagName.toLowerCase()
            // 找到匹配标签在栈中的位置
            for (pos = stack.length - 1; pos >= 0; pos--) {
    
    
                if (stack[pos].lowerCasedTag === lowerCasedTagName) {
    
    
                    break
                }
            }
        } else {
    
    
            pos = 0
        }

        if (pos >= 0) {
    
    
            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) {
    
    
                    options.end(stack[i].tag, start, end)
                }
            }
            // 从栈中移除开始标签
            stack.length = pos
            lastTag = pos && stack[pos - 1].tag
        } else if (lowerCasedTagName === 'br') {
    
    
            if (options.start) {
    
    
                options.start(tagName, [], true, start, end)
            }
        } else if (lowerCasedTagName === 'p') {
    
    
            if (options.start) {
    
    
                options.start(tagName, [], false, start, end)
            }
            if (options.end) {
    
    
                options.end(tagName, start, end)
            }
        }
    }
}

Analyzing the key code above, we can know parseHTMLthat it uses regular expressions to match template strings, and parses when encountering start tags, end tags, text, and comments. After parsing, the corresponding asttree and the corresponding parent and child can be established. relationship, and then continue to intercept the string until the string is parsed;

After parsing the template string to generate asta tree , the next step is to astgenerate the corresponding one according to the tree code:

// src/compiler/codegen/index.js
export function generate (
    ast: ASTElement | void,
    options: CompilerOptions
): CodegenResult {
    
    
    const state = new CodegenState(options)
    const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")'
    return {
    
    
        render: `with(this){return ${
      
      code}}`,
        staticRenderFns: state.staticRenderFns
    }
}

export function genElement (el: ASTElement, state: CodegenState): string {
    
    
    if (el.parent) {
    
    
        el.pre = el.pre || el.parent.pre
    }
    // 静态节点、指令节点、插槽和 template 标签的处理
    if (el.staticRoot && !el.staticProcessed) {
    
    
        return genStatic(el, state)
    } else if (el.once && !el.onceProcessed) {
    
    
        return genOnce(el, state)
    } else if (el.for && !el.forProcessed) {
    
    
        return genFor(el, state)
    } else if (el.if && !el.ifProcessed) {
    
    
        return genIf(el, state)
    } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
    
    
        return genChildren(el, state) || 'void 0'
    } else if (el.tag === 'slot') {
    
    
        return genSlot(el, state)
    } else {
    
    
        let code
        // 组件
        if (el.component) {
    
    
            code = genComponent(el.component, el, state)
        } else {
    
    
            let data
            if (!el.plain || (el.pre && state.maybeComponent(el))) {
    
    
                data = genData(el, state)
            }

            const children = el.inlineTemplate ? null : genChildren(el, state, true)
            code = `_c('${
      
      el.tag}'${
      
      
            data ? `,${ 
        data}` : '' // data
            }${
      
      
                children ? `,${ 
        children}` : '' // children
            })`
        }
        for (let i = 0; i < state.transforms.length; i++) {
    
    
            code = state.transforms[i](el, code)
        }
        return code
    }
}

// 获取子节点的 code
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]
        if (children.length === 1 &&
            el.for &&
            el.tag !== 'template' &&
            el.tag !== 'slot'
           ) {
    
    
            const normalizationType = checkSkip
            ? state.maybeComponent(el) ? `,1` : `,0`
            : ``
            return `${
      
      (altGenElement || genElement)(el, state)}${
      
      normalizationType}`
        }
        const normalizationType = checkSkip
        ? getNormalizationType(children, state.maybeComponent)
        : 0
        const gen = altGenNode || genNode
        return `[${
      
      children.map(c => gen(c, state)).join(',')}]${
      
      
        	normalizationType ? `,${ 
        normalizationType}` : ''
    	}`
    }
}

function genNode (node: ASTNode, state: CodegenState): string {
    
    
    // 判断节点类型,根据节点类型返回相应的 code
    if (node.type === 1) {
    
    
        return genElement(node, state)
    } else if (node.type === 3 && node.isComment) {
    
    
        return genComment(node)
    } else {
    
    
        return genText(node)
    }
}

The above code is part of the code in the file corresponding to generatethe method . If you are interested in methods such as genComment, genText, and so on, you can look at the source code by yourself. Here we mainly talk about the main idea of ​​generating ;genPropscode

generateThe method is mainly obtained by getting astthe tree for parsing, executing getElementto obtain code, getElementand elwill judge

  • If it is a static node, a node containing instructions v-once, v-for, v-if(here it can also be seen v-forthat the instruction v-ifhas a higher priority than the instruction), template, slot, etc., then perform corresponding processing;
  • If it is a component, it is genComponentgenerated code;
  • Otherwise execute genChildrenrecursive creation;

After the final execution, a similar structure will be generated:

// _c 代表创建节点,_v 代表创建文本,_s 代表变量,下面只是个例子,还有其他的函数
_c('div',{
    
    id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world"))))

Summarize:

The entry point of template compilation is vm.$mount(vm.$options.el)to $mountparse templateor eland convert it to render, and then execute the original $mountfor rendering. renderThe core method of conversion is that compileToFunctionsthe core steps of this method are mainly divided into three steps:

  • templateConvert the incoming into asta tree;
  • Optimize static nodes;
  • generated from the generated asttree code;

Guess you like

Origin blog.csdn.net/Ljwen_/article/details/124787912