wedge
The author of the company’s front end group set off a Vue source learning group, before and after a few months of common learning, so that group members have to Vue on the general framework of a vague outline. Now it’s on to the second stage: tidying up.
We divided the team into four parts, and the collation of vUE pairs was also divided into three big modules: data binding, from Template to Vnode, and the transformation of Vnode into DOM to patch.
My team was divided into template and VNode pairs. After I got them, I felt there was a lot of content, so I first divided the content into two pieces according to the source pair layout: parse and Render process.
Moreover, the ancients said: the army did not move, the first food. The author intends to introduce ideas and ideas first, and then to some details in detail.
The preparation of this article is part of the idea of Parse.
The body of the
I. Purpose of the whole compilation process
We all know when to use Vue, we use custom styles into several ways roughly:
- 1. The options of the template.
- 2. El. (Mounted node)
- 3. The render method.
We need to realize that either way, our ultimate goal is to generate a VDOM tree on a vNode basis
The generated VDOM tree is ultimately used in the patch process to generate real DOM nodes.
So the ultimate goal of our compilation is to get:
The Virtue of the dom.
The form is roughly as follows:
Regardless of what each attribute does for a while, we already know what our goal is. So what we need to know is the starting point and the process.
Normally, if we initialize it like this:
new Vue({
el: '#el'.template: `
{{item.id}}
{{item.text}}
`.data: {
name:"dinglei".options: [{id: 1.text: 'Hello' },
{ id: 2.text: 'World'}]}})Copy the code
So our initialization state is a String of template.
Of course, it’s full to initialize to String. Such as:
- Instead of passing template, use $el.
- Template, using a template.
So our transformation from theta to theta is theta to thetaA template for a String
到 virtue dom tree
In the process.
It’s worth noting, however, that vUE’s transition from template to VDOM is not straightforwardA one-time
The process in place. This may be due to the large gap between the designed VNode and the native DOM properties, which makes compiling directly to VNode difficult. The second is to consider the performance optimization and other aspects.
So the vUE compilation process is actually divided into three processes:
- parse
- optimize
- codegen for render And render
There are three processes:
- From template to astElement
- Optimized the static tag staticRoot based on the astElement, and of course the static tag for this layer is finally used in codeGen 3.
- Codegen generates render function, render binding instance, then execute to get vNode tree.
The process is roughly as follows:
Let's move on to the topic of this article
Parse, Optimize, CodeGen core ideas interpretation.
1) Parse
First, you need to understand what attributes astElement contains. AstElement’s model can be found in compiler.js in the vue source flow file directory
declare type ASTElement = {
type: 1; tag: string; attrsList: Array<ASTAttr>; attrsMap: { [key: string]: any }; rawAttrsMap: { [key: string]: ASTAttr }; parent: ASTElement | void; children: Array<ASTNode>; start? : number; end? : number; processed? :true; static? : boolean; staticRoot? : boolean; staticInFor? : boolean; staticProcessed? : boolean; hasBindings? : boolean; text? : string; attrs? : Array<ASTAttr>; dynamicAttrs? : Array<ASTAttr>; props? : Array<ASTAttr>; plain? : boolean; pre? :true; ns? : string; component? : string; inlineTemplate? :true; transitionMode? : string | null; slotName? :? string; slotTarget? :? string; slotTargetDynamic? : boolean; slotScope? :? string; scopedSlots? : { [name: string]: ASTElement }; ref? : string; refInFor? : boolean;if? : string;ifProcessed? : boolean; elseif? : string;else? :true;
ifConditions? : ASTIfConditions;for? : string;forProcessed? : boolean; key? : string;alias? : string; iterator1? : string; iterator2? : string; staticClass? : string; classBinding? : string; staticStyle? : string; styleBinding? : string; events? : ASTElementHandlers; nativeEvents? : ASTElementHandlers; transition? : string |true; transitionOnAppear? : boolean; model? : { value: string; callback: string; expression: string; }; directives? : Array<ASTDirective>; forbidden? :true; once? :true; onceProcessed? : boolean; wrapData? : (code: string) => string; wrapListeners? : (code: string) => string; // 2.4 SSR optimization? : number; // weex specific appendAsTree? : boolean; };Copy the code
Before you start talking about the process, list the important roles that will appear throughout the process. The following processes and flowcharts are based on this.
role
- Recognizer (parseHTML) (1)
- Storage Stack (2)
- Create function createASTElement (3)
- Context (currentParent) (4)
- Start handler function start (5)
- End (6)
The overall working process is roughly as follows:
-
1. The recognizer (1) uses re to identify all sensitive fields from front to back. Such as (tags, events, iterations, data binding, slots, and so on)
-
{{test}}
. Once the identifier (1) recognizes such a tag, it uses the start function start (5) to unify the start tag. Start creates an astElement and stores it in the stack. The way it was created was created using the create function (3), which also handled non-component properties (specifically v-model, V-if, V-for). Here are some things to note:
- 1. Unary tags, such as IMG, are processed by 3.
- The P tag will be specially processed for the purpose of matching
Browser recognition
Ways to achieve the same goal. specificClick here to
-
3. When a closed tag is identified, it is transferred to the end function for processing. The end function extracts the native attributes such as ID, class, ref, slot, Component, key, etc., and establishes the parent-child relationship with (4) below. The result is a tree structure.
-
4. Repeat 1 to 3 until no label is displayed on the stack.
The flowchart is roughly as follows:
There are a lot of details, if you are interested, please pay attention to our dynamic, we will explain the detailed process in the next issue. Click here to learn more
2) Optimize ideas.
We have given the astElement properties in detail above, including two properties called staticRoot and staticInFor. The process of Optimize is marking astElement with those two tags. The purpose here is to allow such static nodes to be cached only once in the render process.
The obvious benefit is that static nodes do not need to be repeatedly compared and re-rendered to improve overall performance.
3) Code generate interpretation
This process does not generate a vNode. Instead, it generates an execution function with an execution code for this in the following format:
with(this) { _c('div'...xxx...xxx) }
Copy the code
Again, let’s emphasize that we go from astElement directly to another String. AstElement is a tree structure.
And then in this process, basically an astElement corresponds to a short function. The basic short function is createElement, which is _c. The final tree structure will represent the processing function as a function as follows
_c(
'div',
{
key:'xxx',
ref:'xx',
pre:'xxx',
domPro:xxx,
....
},
[ // chidren
_v(_s('ding')),
_c('p',{model:'isshow',}, [ ...xxx ])
]
)
Copy the code
As you can see, the resulting string is still a tree, represented as a function tree, and all the attributes have been extracted into the second parameter of createElement.
Here’s a quick explanation for short functions:
-
1. The short function finally executes the short this which is actually a Vue instance, or component instance.
-
2. The three important creation functions are _c(createElement), _v(createTextVNode), and _e(createEmptyVNode), which generate three types of VNodes respectively.
In a nutshell, what Code Generate does is:
Generate vNode precursors that extract all of astElement’s attributes to form short function chains.
The corresponding short function is roughly as follows:
export function installRenderHelpers (target: any) {
target._o = markOnce // v-once
target._n = toNumber
target._s = toString
target._l = renderList // v-for
target._t = renderSlot // slot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic // static
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots // scopeSlot
target._g = bindObjectListeners // linsners
target._d = bindDynamicKeys
target._p = prependModifier
}
Copy the code
4) Render function.
This process is performed directly, resulting in a virtual DOM tree with the vNode as the node. The details include component handling, which is covered in a separate section. Finally, these virtual trees will be thrown to the patch function, and the dom tree of the target real pair will be generated in the process of continuous comparison. Here is the vnode format.
declareinterface VNodeData { key? : string | number; slot? : string; ref? : string; is? : string; pre? : boolean; tag? : string; staticClass? : string; class? : any; staticStyle? : { [key: string]: any }; style? : string | Array<Object> | Object; normalizedStyle? : Object; props? : { [key: string]: any }; attrs? : { [key: string]: string }; domProps? : { [key: string]: any }; hook? : { [key: string]: Function }; on? :? { [key: string]: Function | Array<Function> }; nativeOn? : { [key: string]: Function | Array<Function> }; transition? : Object; show? : boolean; // markerforv-show inlineTemplate? : { render: Function; staticRenderFns: Array<Function>; }; directives? : Array<VNodeDirective>; keepAlive? : boolean; scopedSlots? : { [key: string]: Function }; model? : { value: any; callback: Function; }; };Copy the code
conclusion
- 1. Parse, Optimize, CodeGen, render
- 2. User – defined render actually ignores the previous three steps, directly customize the way.
- 3. Vdom is ultimately a tree structure with vnodes as nodes. During the patch process, the old and new VDOM will be compared to determine which DOM needs to be updated, added or deleted.