This article we together by learning Vue template compilation principle (two) -AST generate Render string to analyze Vue source code. It is expected that some articles will be organized around the Vue source code, as follows.
- Learn about Vue bidirectional binding principles – data hijacking and publish/subscribe
- Template generates an AST
- -AST generates the Render string
- Let’s learn Vue Virtual DOM parsing -Virtual DOM implementation and DOM -diff algorithm
These articles are in my git repository: github.com/yzsunlei/ja… . Remember star collection if useful.
The build process
Template compilation is a core part of Vue. The overall logic of Vue compilation principle is divided into three parts, or three steps, as follows:
Step 1: Convert template strings to Element ASTs (parsers)
Step 2: Mark the AST with static nodes, mainly for rendering optimization of the virtual DOM (optimizer)
Step 3: Use element ASTs to generate render function code strings (code generators)
The corresponding Vue source code is as follows, in SRC/Compiler /index.js
export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ): Parse (template.trim(), options) const AST = parse(template.trim(), options) // 2.optimize, Static node markup for AST if (options.optimize! == false) {optimize(ast, options)} // generate, Const code = generate(AST, options) return {AST, render: code.render, staticRenderFns: code.staticRenderFns } })Copy the code
This document focuses on the third step: using the Element ASTs to generate the render function code string, the corresponding source code implementation is commonly referred to as the code generator.
The code generator runs the procedure
Before looking at how code generators work, let’s take a look at some examples of how code generators work.
<div>
<p>{{name}}</p>
</div>
Copy the code
In the previous section “Template generates an AST”, we explained that the parser parses the above Template into an abstract syntax tree (AST). The result is as follows:
{
tag: "div"
type: 1.staticRoot: false.static: false.plain: true.parent: undefined.attrsList: [].attrsMap: {},
children: [{tag: "p"
type: 1.staticRoot: false.static: false.plain: true.parent: {tag: "div". },attrsList: [].attrsMap: {},
children: [{
type: 2.text: "{{name}}".static: false.expression: "_s(name)"}}}]]Copy the code
- In this section we will convert the AST into a JavaScript string that can be executed directly, resulting in the following:
with(this) {
return _c('div', [_c('p', [_v(_s(name))]), _v(""), _m(0)])}Copy the code
It doesn’t matter if you don’t understand it now. The process of generating a generator is to generate a function that takes a parsed AST object, recurses the AST tree, creates different internal call methods for different AST nodes, and then assembles them into executable JavaScript strings to wait for later calls.
Internally called method
If we look at the JavaScript string generated by the example above, we’ll see that it contains things like _v, _c, and _S, which are actually the call methods defined internally by Vue.
Including _c functions defined in SRC/core/instance/render. Js.
vm.$slots = resolveSlots(options._renderChildren, $scopedSlots = emptyObject._c = (a, b, c, d) => createElement(vm, a, b, c, d); false) vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)Copy the code
SRC /core/instance/render-helpers/index.js
export function installRenderHelpers (target: Any) {target._o = markOnce target._n = toNumber target._s = toString target._l = renderList // Generate list VNode target._t = RenderSlot // Generates the resolution slot node target._q = looseEqual target._i = looseIndexOf target._m = renderStatic // generates static elements target._f = ResolveFilter target._k = checkKeyCodes target._b = bindObjectProps // bind object properties target._v = createTextVNode // createTextVNode VNode target._u = resolveScopedSlots Target. _g = bindObjectListeners Target._d = bindDynamicKeys target._p = prependModifier }Copy the code
All of these are used in generated JavaScript strings, such as _c createElement to create a VNode, _l to render the list; _v creates a text VNode for createTextVNode. _e Creates an empty VNode for createEmptyVNode.
The overall logical
Entrance to the function of the code generator is the generate, concrete are defined as follows, source location in SRC/compiler/codegen/index. Js
export function generate ( ast: ASTElement | void, options: CompilerOptions ): CodegenResult {// Initialize some options const state = new CodegenState(options) // Pass ast and options to generate const code = ast? GenElement (ast, state) : '_c("div")' `with(this){return ${code}}`, // VNodes marked as staticRoot will generate staticRenderFns separately: state.staticRenderFns } }Copy the code
The generator entry function is relatively simple. It initializes some configuration options, passes it to the AST for generation, generates an empty div without the AST, and returns the generated JavaScript string and the render function for the static node. The static nodes will be separated out so that the Vnode diff (Vue comparison algorithm) can update the DOM, which will be skipped for now.
Now we look at genElement function, code location in SRC/compiler/codegen/index. Js
export function genElement (el: ASTElement, state: CodegenState): string { if (el.parent) { el.pre = el.pre || el.parent.pre } if (el.staticRoot && ! El.staticprocessed) {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. The pre) {return genChildren (el, state) | | 'void 0 / processing/child node generating function} else if (el) tag = = =' slot ') {return genSlot (el, Else {// Component or element let code if (el.component.html) {code = genComponent(el.component.html, el.component.html, el.component.html, el.component.html, el.component.html, el.component.html, el.component.html) } else {let data if (! el.plain || (el.pre && state.maybeComponent(el))) { data = genData(el, } const children = el.inlineTemplate? null : genChildren(el, state, true) code = `_c('${el.tag}'${ data ? `,${data}` : '' // data }${ children ? `,${children}` : '' // children })` } // module transforms for (let i = 0; i < state.transforms.length; i++) { code = state.transforms[i](el, code) } return code } }Copy the code
You’ll notice that there are a lot of conditional judgments in the genElement function. This is because there are so many instructions written in Vue, such as V-if, V-for, v-slot, and so on. Each instruction is written in a separate function, so that the code is clear to read. Next, let’s focus on the specific processing logic of genFor and genData.
genFor
GenFor function is used to deal with v – for instruction of writing, source location in SRC/compiler/codegen/index. Js
export function genFor ( el: any, state: CodegenState, altGen? : Function, altHelper? : string ): string { const exp = el.for const alias = el.alias const iterator1 = el.iterator1 ? `,${el.iterator1}` : '' const iterator2 = el.iterator2 ? `,${el.iterator2}` : '' if (process.env.NODE_ENV ! == 'production' && state.maybeComponent(el) && el.tag ! == 'slot' && el.tag ! == 'template' && ! el.key ) { state.warn( `<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` + `v-for should have explicit keys. ` + `See https://vuejs.org/guide/list.html#key for more info.`, el.rawAttrsMap['v-for'], true /* tip */ ) } el.forProcessed = true // avoid recursion return `${altHelper || '_l'}((${exp}),` + `function(${alias}${iterator1}${iterator2}){` + `return ${(altGen || genElement)(el, state)}` + '})' }Copy the code
The logic of genFor is that the for attributes are retrieved from the AST element node and a code string is returned.
genData
GenData function is used for processing nodes attributes, source location in SRC/compiler/codegen/index. Js
export function genData (el: ASTElement, state: CodegenState): string { let data = '{' // directives first. // directives may mutate the el's other properties before they are generated. const dirs = genDirectives(el, state) if (dirs) data += dirs + ',' // key if (el.key) { data += `key:${el.key},` } // ref if (el.ref) { data += `ref:${el.ref},` } if (el.refInFor) { data += `refInFor:true,` } // pre if (el.pre) { data += `pre:true,` } // record original tag name for components using "is" attribute if (el.component) { data += `tag:"${el.tag}",` } // module data generation functions for (let i = 0; i < state.dataGenFns.length; i++) { data += state.dataGenFns[i](el) } // attributes if (el.attrs) { data += `attrs:${genProps(el.attrs)},` } // DOM props if (el.props) { data += `domProps:${genProps(el.props)},` } // event handlers if (el.events) { data += `${genHandlers(el.events, false)},` } if (el.nativeEvents) { data += `${genHandlers(el.nativeEvents, }, '} // Slot target, only for non-scoped slots if (el.slottarget &&! el.slotScope) { data += `slot:${el.slotTarget},` } // scoped slots if (el.scopedSlots) { data += `${genScopedSlots(el, el.scopedSlots, state)},` } // component v-model if (el.model) { data += `model:{value:${ el.model.value },callback:${ el.model.callback },expression:${ el.model.expression }},` } // inline-template if (el.inlineTemplate) { const inlineTemplate = genInlineTemplate(el, state) if (inlineTemplate) { data += `${inlineTemplate},` } } data = data.replace(/,$/, '') + '}' // v-bind dynamic argument wrap if (el.dynamicAttrs) { data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})` } // v-bind data wrap if (el.wrapData) { data = el.wrapData(data) } // v-on data wrap if (el.wrapListeners) { data = el.wrapListeners(data) } return data }Copy the code
To summarize
Code generation is the third step in template compilation, and it completes the AST to Render transformation, which translates the abstract syntax tree AST into a JavaScript string that can be executed directly.
Among them, genElement has a lot of code, because there are a lot of cases that need to be dealt with separately. Here, the code logic of genFor and genData is only explained.
related
- Ustbhuangyi. Making. IO/vue – analysi…
- Github.com/liutao/vue2…
- Github.com/lihongxun94…
- Segmentfault.com/a/119000001…