preface
Students familiar with Vue know that, starting from Vue2, in the actual operation, it is to convert the template written by the user into the render function to obtain vNode data (virtual DOM), and then continue to execute, finally through the patch to the real DOM. When data is updated, it is also used to diff the vNode data and ultimately decide which real DOM to update.
This is also a core advantage of Vue, Utah university more than once said, because the user wrote a static template, so Vue can do a lot of markup according to the template information, and then can do targeted performance optimization, this in Vue 3 to do further optimization processing, block related design.
So, let’s take a look, in Vue, template to render function, exactly experienced what kind of process, here side what is worth our reference and learning.
The text analysis
What
Template to render, Vue is the corresponding part of compile, the term compiler cn.vuejs.org/v2/guide/in… Essentially, this is the same approach that many frameworks use, AOT, which is to do things at runtime that need to be done at compile time to improve runtime performance.
The syntax of the Vue template itself is not explained in detail here. If you are interested, you can go to cn.vuejs.org/v2/guide/sy… , which is roughly the syntax (interpolation and instructions) as follows:
How about the render function, which is also covered in Vue at cn.vuejs.org/v2/guide/re… In a nutshell, it looks something like this:
So here’s our core goal:
If you want to experience this, you can do it here template-explorer.vuejs.org
Of course, Vue 3 is also available https://vue-next-template-explorer, although here we are going to analyze Vue 2 version.
How
To understand how to do this, we need to start with the source code, the compiler is available at github.com/vuejs/vue/t… Here we start with the entry file index.js:
import { parse } from './parser/index'
import { optimize } from './optimizer'
import { generate } from './codegen/index'
import { createCompilerCreator } from './create-compiler'
// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
export const createCompiler = createCompilerCreator(function baseCompile (template: string, options: CompilerOptions) :CompiledResult {
/ / the point!
// Step 1 parse the template to get the AST
const ast = parse(template.trim(), options)
// Optimizations can be ignored for now
if(options.optimize ! = =false) {
optimize(ast, options)
}
// Step 2 generate code based on the AST
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
Copy the code
This, you’ll see, is a classic Parsing, or simplification, implementation of the compiler:
- Parse to get the AST
- Generate, get the object code
Let’s take a look at the respective implementations.
1. parse
The implementation of Parse is available at github.com/vuejs/vue/b… Here, since the code is quite long, let’s take a look at the exposed parse function first:
export function parse (template: string, options: CompilerOptions) :ASTElement | void {
// Options processing has been ignored here
// Important stack
const stack = []
constpreserveWhitespace = options.preserveWhitespace ! = =false
const whitespaceOption = options.whitespace
// There is only one root node, because we know that Vue 2's template can only have one root element
// Ast is a tree structure, and root is the root node of the tree
let root
let currentParent
let inVPre = false
let inPre = false
let warned = false
/ / parseHTML processing
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 end chars comment
// About the same as the hook exposed by parseHTML for external processing
// So purely, parseHTML is just responsible for parse, but does not generate AST logic
// The ast generation depends on the hook function
// It is easy to understand:
// start is called every time a start tag is encountered
// end is called to end the tag
// Focus on the logic in start and end.
// chars comment corresponds to plain text and comments
start (tag, attrs, unary, start, end) {
// check namespace.
// inherit parent ns if there is one
const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
// handle IE svg bug
/* istanbul ignore if */
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs)
}
// Create an ASTElement based on the tag attribute
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 =trueprocess.env.NODE_ENV ! = ='production' && warn(
'Templates should only be responsible for mapping the state to the ' +
'UI. Avoid placing tags with side-effects in your templates, such as ' +
` <${tag}> ` + ', as they will not be parsed.',
{ start: element.start }
)
}
// Some pre-conversions can be ignored
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
}
if (inVPre) {
processRawAttrs(element)
} else if(! element.processed) {// structural directives
// Handle vUE instructions, etc
processFor(element)
processIf(element)
processOnce(element)
}
if(! root) {// If there is no root, the current element is the root element
root = element
if(process.env.NODE_ENV ! = ='production') {
checkRootConstraints(root)
}
}
if(! unary) {// Set the current parent element, needed when dealing with children
currentParent = element
// Then start and then end twice
// is a classic stack handling, first in, last out
// Any compiler can't do without the stack, and the processing is similar
stack.push(element)
} else {
closeElement(element)
}
},
end (tag, start, end) {
// The element being processed
const element = stack[stack.length - 1]
// Pop the last one
// pop stack
stack.length -= 1
// The latest tail is the parent of the element to be processed next
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
}
// IE textarea placeholder bug
/* istanbul ignore if */
if (isIE &&
currentParent.tag === 'textarea' &&
currentParent.attrsMap.placeholder === text
) {
return
}
const children = currentParent.children
if (inPre || text.trim()) {
text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
} else if(! children.length) {// remove the whitespace-only node right after an opening tag
text = ' '
} else if (whitespaceOption) {
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) {
if(! inPre && whitespaceOption ==='condense') {
// condense consecutive whitespaces into single space
text = text.replace(whitespaceRE, ' ')}let res
letchild: ? ASTNodeif(! 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) {
// adding anything as a sibling to the root node is forbidden
// comments should still be allowed, but ignored
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 the root node
return root
}
Copy the code
As you can see, the most important thing to do is call parseHTML, and the most important thing to do in the passed hook is in the start tag. For the Vue scenario, using the hook processing, the final root we return is actually the root node of a tree, which is our AST, like this:
The template as follows:
<div id="app">{{ msg }}</div>
Copy the code
{
"type": 1."tag": "div"."attrsList": [{"name": "id"."value": "app"}]."attrsMap": {
"id": "app"
},
"rawAttrsMap": {},
"children": [{"type": 2."expression": "_s(msg)"."tokens": [{"@binding": "msg"}]."text": "{{ msg }}"}]."plain": false."attrs": [{"name": "id"."value": "app"}}]Copy the code
ParseHTML is the core part of the company, and the core part (not all) is analyzed in parts. The source file github.com/vuejs/vue/b…
// Parse is a process of traversing an HTML string
export function parseHTML (html, options) {
// HTML is an HTML string
// Stack appears again, the best data structure for handling nested parsing problems
// HTML handles tag nesting
const stack = []
const expectHTML = options.expectHTML
const isUnaryTag = options.isUnaryTag || no
const canBeLeftOpenTag = options.canBeLeftOpenTag || no
// Initial index position index
let index = 0
let last, lastTag
// The violence loop is for traversal
while (html) {
last = html
// Make sure we're not in a plaintext content element like script/style
if(! lastTag || ! isPlainTextElement(lastTag)) {// No lastTag is the initial state or lastTag is script style
// This label element needs to be treated as plain text
// This branch should be entered in normal state
// Determine the tag position, which is the same as the non-tag end position
let textEnd = html.indexOf('<')
// In the starting position
if (textEnd === 0) {
// Comments are ignored for now
if (comment.test(html)) {
const commentEnd = html.indexOf('-->')
if (commentEnd >= 0) {
if (options.shouldKeepComment) {
options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3)
}
advance(commentEnd + 3)
continue}}// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
// Conditional comments are ignored for now
if (conditionalComment.test(html)) {
const conditionalEnd = html.indexOf('] > ')
if (conditionalEnd >= 0) {
advance(conditionalEnd + 2)
continue}}// Doctype is ignored
const doctypeMatch = html.match(doctype)
if (doctypeMatch) {
advance(doctypeMatch[0].length)
continue
}
// End tag, the first time to ignore, other cases will enter
const endTagMatch = html.match(endTag)
if (endTagMatch) {
const curIndex = index
advance(endTagMatch[0].length)
// Process the closing tag
parseEndTag(endTagMatch[1], curIndex, index)
continue
}
// Focus, in general, start tag
const startTagMatch = parseStartTag()
// If there is a start tag
if (startTagMatch) {
// Handle the related logic
handleStartTag(startTagMatch)
if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
advance(1)}continue}}let text, rest, next
if (textEnd >= 0) {
// The rest of the HTML after removing the text
rest = html.slice(textEnd)
while(! endTag.test(rest) && ! startTagOpen.test(rest) && ! comment.test(rest) && ! conditionalComment.test(rest) ) {// < in plain text
// < in plain text, be forgiving and treat it as text
next = rest.indexOf('<'.1)
if (next < 0) break
textEnd += next
rest = html.slice(textEnd)
}
// Get the actual text content
text = html.substring(0, textEnd)
}
// There is no < so the content is plain text
if (textEnd < 0) {
text = html
}
if (text) {
// focus forward to specify the length
advance(text.length)
}
if (options.chars && text) {
// Handle the hook function
options.chars(text, index - text.length, index)
}
} else {
// lastTag exists and is a script style such that its contents are treated as plain text
let endTagLength = 0
// The tag name exists in the stack
const stackedTag = lastTag.toLowerCase()
const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?) (< / ' + stackedTag + '[^ >] * >)'.'i'))
/ / do replacement
// Replace
XXXX
const rest = html.replace(reStackedTag, function (all, text, endTag) {
// The end tag itself is the length of
endTagLength = endTag.length
if(! isPlainTextElement(stackedTag) && stackedTag ! = ='noscript') {
text = text
.replace(/
/g.'$1') / / # 7298
.replace(/
/g.'$1')}if (shouldIgnoreFirstNewline(stackedTag, text)) {
text = text.slice(1)}// Handle the hook function
if (options.chars) {
options.chars(text)
}
// Replace with null
return ' '
})
// Index forward note that advance is not used because HTML is actually modified to rest
index += html.length - rest.length
html = rest
// Process the closing tag
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}}}Copy the code
There are several key functions defined in the context of parseHTML, so they can directly access key variables like index, stack, and lastTag:
// It is easy to understand, advance n positions
function advance (n) {
index += n
html = html.substring(n)
}
Copy the code
// Start tag
function parseStartTag () {
// The regular match starts like
const start = html.match(startTagOpen)
if (start) {
// It is matched
const match = {
tagName: start[1].attrs: [].start: index
}
// Move after
advance(start[0].length)
let end, attr
// Before the end is > before
while(! (end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {// Match the attributes
attr.start = index
// Move gradually
advance(attr[0].length)
attr.end = index
// Collect attributes
match.attrs.push(attr)
}
// encounter > end
if (end) {
// Whether the label is self-closing, for example, < XXXX />
match.unarySlash = end[1]
advance(end[0].length)
match.end = index
return match
}
}
}
Copy the code
// Handle start tags when they are encountered
// We use a separate function to handle the start tag
function handleStartTag (match) {
const tagName = match.tagName
const unarySlash = match.unarySlash
if (expectHTML) {
/ / HTML
// the p tag cannot contain the isNonPhrasingTag tag
/ / a detailed look at https://github.com/vuejs/vue/blob/v2.6.14/src/platforms/web/compiler/util.js#L18
if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
// So in the browser environment it is also automatically fault-tolerant to close them directly
parseEndTag(lastTag)
}
if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
parseEndTag(tagName)
}
}
// The case of the closure tag can be omitted
// a scene like < XXX /> or
constunary = isUnaryTag(tagName) || !! unarySlashconst 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) {// If it is not autism and case, it means it can be treated as children
// push a current on the stack
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end })
// Set lastTag to current
// Prepare for the next entry into children
lastTag = tagName
}
// start hook processing
if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end)
}
}
Copy the code
// End the label processing
function parseEndTag (tagName, start, end) {
let pos, lowerCasedTagName
if (start == null) start = index
if (end == null) end = index
// Find the closest opened tag of the same type
if (tagName) {
lowerCasedTagName = tagName.toLowerCase()
// Here we need to find the nearest unclosed tag of the same type
// The corresponding paired element
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 (pos >= 0) {
// Return to the unclosed tag, where all elements need to be closed
// 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 }
)
}
/ / end hook
if (options.end) {
options.end(stack[i].tag, start, end)
}
}
// There is no need to change the stack length
// Remove the open elements from the stack
stack.length = pos
Remember to update lastTag
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)
}
}
}
Copy the code
So with an overview of what these three functions do, and the main logic of parseHTML, we can sort through the whole process.
For convenience, a concrete example is used here, for example
<div id="app">
<p :class="pClass">
<span>
This is dynamic msg:
<span>{{ msg }}</span>
</span>
</p>
</div>
Copy the code
So I’m going to go straight into parseHTML, go into the while loop, and obviously go into the processing of the start tag, parseStartTag
At this point, after the previous round of processing, the HTML already looks like this, because advance advances each time:
The beginning of the original root tag div
It then goes into the logic of handleStartTag
At this point, the stack has pushed an element, our start tag div, and holds the location and attribute information, and the lastTag points to that element.
We continue with the while loop
Because there are Spaces and newlines, the value of textEnd is 3, so we need to go into the text processing logic (Spaces and newlines are text content).
So this loop will process the text and then proceed to the next loop, which is already similar to what we did in the first loop:
Again, lastTag changes to P, and then goes to the logic that handles text (Spaces, newlines), which is omitted here, and the process is the same;
Let’s skip to the first time we deal with span
The loop repeats the same process as the first one, processing the normal element and producing the result:
The top element on the stack is the outer span. Then enter a new round of processing text:
And then it goes to the span element in the inner layer again, same logic, when it’s done
It then processes the innermost layer of text and reaches the innermost layer of the closing tag ,
At this time we focus on the cycle of this round:
You can see that after this loop, the innermost span has been closed, and the stack and lastTag have been updated to the outer span.
The rest of the loop, as you can probably guess, processes the text (newline whitespace) and parseEndTag, and goes off the stack again and again until the HTML string is empty, which stops the loop.
Our parse functions work the same way. They use parseHTML’s hook functions to press, process, and stack them until they’re finished. The core of what these hooks do is build their ast step by step from the parseHTML process. So the final AST result
The phase to Parse is complete here.
2. generate
Let’s see how we can get the desired render function based on the above AST. The code is available at github.com/vuejs/vue/b…
export function generate (
ast: ASTElement | void,
options: CompilerOptions
) :CodegenResult {
const state = new CodegenState(options)
// fix #11483, Root level <script> tags should not be rendered.
const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")'
return {
render: `with(this){return ${code}} `.staticRenderFns: state.staticRenderFns
}
}
Copy the code
As can be seen, generate core. The first step is to create a CodegenState instance with no specific functions, which is equivalent to the processing of configuration items, and then enter the core logic genElement with relevant codes github.com/vuejs/vue/b…
// Generate element code
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.pre) {return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {
// component or element
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
}) `
}
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
Copy the code
Basically, it does this by element type, as in the previous example, it goes into
Next up is an important genChildren github.com/vuejs/vue/b…
export function genChildren (el: ASTElement, state: CodegenState, checkSkip? : boolean, altGenElement? :Function, altGenNode? :Function
) :string | void {
const children = el.children
if (children.length) {
const el: any = children[0]
// optimize single v-for
if (children.length === 1&& el.for && el.tag ! = ='template'&& el.tag ! = ='slot'
) {
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}` : ' '
}`}}Copy the code
You can see that basically loop children and then call genNode to generate children code, genNode github.com/vuejs/vue/b…
function genNode (node: ASTNode, state: CodegenState) :string {
if (node.type === 1) {
return genElement(node, state)
} else if (node.type === 3 && node.isComment) {
return genComment(node)
} else {
return genText(node)
}
}
Copy the code
Here is to determine each node type, and then basically recursively call genElement or genComment or genText to generate the corresponding code.
The final generated code is as follows:
This can be interpreted as traversing the ast above, generating their corresponding code separately, and easily handling each case with the help of recursion. Of course, there are a lot of details here are actually ignored by us, mainly look at the normal core of the general brief process, easy to understand.
At this point, this is how the complete process of compiling templates to render functions is handled in Vue.
Why
To find out why, we can break it down into two points:
- Why introduce the Virtual DOM
- Why is template recommended (convert template to render function to get VNode data)
Why introduce the Virtual DOM
In fact, Judah himself talked about why Virtual DOM was introduced in Vue 2, whether it was necessary and so on.
Answer from Fang Yinghang:
Here are some articles and answers for your reference (including summaries from others) :
- The rendering function section of the official website cn.vuejs.org/v2/guide/re…
- zhuanlan.zhihu.com/p/23752826
- zhuanlan.zhihu.com/p/108899766
- zhuanlan.zhihu.com/p/58335278
- www.zhihu.com/question/28…
- www.zhihu.com/question/31…
Why do YOU recommend templates?
This is mentioned in the comparison of the official website framework, the original cn.vuejs.org/v2/guide/co…
Of course, in addition to the above reasons, as we mentioned in the introduction, templates are static, and Vue can be optimized to further improve runtime performance using AOT technology.
This is why there are different versions built in Vue, see cn.vuejs.org/v2/guide/in…
conclusion
Through the above analysis, we know that in Vue, the approximate process from template to render function, the most core is:
- Parse the HTML string to get your own AST
- According to the AST, generate the final render function code
This is the core of what compilers do.
So what can we learn from this?
The compiler
Compiler, that sounds pretty cool. Through our above analysis, we also know how it is handled in Vue.
The core principles of the compiler and the relative standardization process are basically mature, whether it is the parsing of HTML, which is analyzed and studied here, and then generating the final render function code, or any other language, or your own “language”.
If you want to learn more, the best way is to look at the compilation principle. In the community, there is also a well-known project github.com/jamiebuilds… There is a “five organs” compiler, the core of only 200 lines of code, in addition to the code, annotations are the essence, even more useful than the code, it is worth our in-depth study and research, and easy to understand.
The tree
The ast we obtained from Parse is actually a tree structure. The application of trees can be found everywhere, as long as you are good at finding them. Use him, can very good help us to carry out logical abstraction, unified processing.
The stack
In the above analysis, we have seen the use of the stack several times. It has been mentioned in the reactive Principle before, but here it is a very typical scenario and one of the best practices of the stack data structure.
Basically, you can see the application of stack in many frameworks or excellent libraries in the community, which can be said to be a very useful data structure.
hook
We saw hooks in action in parseHTML options, but it’s not just there. ParseHTML exposes hook functions such as start, end, chars, and comment to allow users to hook into the execution logic of parseHTML. This is a simple but useful idea. Of course, this idea itself is often associated with plug-in design schemes or microkernels; For different scenarios, there can be more complex implementations that provide more powerful functionality, such as the underlying Tapable library in WebPack, which is essentially an application of this idea.
Regular expression
Throughout the parser process, we encountered a variety of regex scenarios, especially github.com/vuejs/vue/b… Here:
const attribute = /^\s*([^\s"'<>\/=]+)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /
const dynamicArgAttribute = /^\s*((? :v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp.source}] * `
const qnameCapture = ` ((? :${ncname}\ \ :)?${ncname}) `
const startTagOpen = new RegExp(` ^ <${qnameCapture}`)
const startTagClose = /^\s*(\/?) >/
const endTag = new RegExp(` ^ < \ \ /${qnameCapture}[^ >] * > `)
const doctype = / ^
]+>/i
// #7298: escape - to avoid being passed as HTML comment when inlined in page
const comment = / ^
const conditionalComment = / ^
const encodedAttr = / & (? :lt|gt|quot|amp|#39); /g
const encodedAttrWithNewLines = / & (? :lt|gt|quot|amp|#39|#10|#9); /g
Copy the code
There are many kinds of re usage and re generation. Regular itself has simple, there are complex, if you can not understand the regular expression here, recommend you to read the book proficient in regular expressions, I believe that after reading, you will harvest a lot.
Other small Tips
- Directory module split, still worth our good study
- Ast optimization operations, although there is no detailed analysis above, but in the source code or specifically to do the AST optimization related things
- Simple factory mode using Creator
- StaticRenderFns, what does it do, why does it exist
- Reuse of caching technology to improve performance
- Avoid repeated processing, the use of various marks
- Because it involves HTML parsing, so it is necessary to understand the HTML specification, as well as the normal browser parsing HTML fault tolerant processing, some tools in the source code also reflect github.com/vuejs/vue/b…
- The role of makeMap is heavily used in Vue
The team number of Didi front-end technology team has been online, and we have synchronized certain recruitment information. We will continue to add more positions, and interested students can chat with us.