I have always had a sense of fear of the compilation principle. It feels too difficult to read. Open itvue3
When the source code sees the compilation-related code, it is frightened away. Until I learned from Brother Da Cuimini-vue
,so ga ~
Main process
Now let's analyze a simplevue3
compilation principle. To summarize the function we want to implement in one sentence, that is,template
Template Generate What We Wantrender
Just function. A simple sentence contains a lot of knowledge.
<div>hi, {{message}}</div>
Last generated
import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", null, "hi, " + _toDisplayString(_ctx.message), 1 /* TEXT */)) }
firsttemplate
Will be parsed through lexical analysis and grammatical analysisAST
(Abstract Syntax Tree), then usetransform
rightAST
Perform optimization, and finally passgenerate
Module generation what we wantrender
function.
existvue3
The source code is mainly divided into 3 parts (the following is the simplified source code)
export function baseCompile(template){ const ast = baseParse(template) transform(ast) return generate(ast) }
- pass
parse
Willtemplate
generateast
- pass
transform
optimizationast
- pass
generate
generaterender
function
Since these three parts involve a lot of things, our article mainly explains itparse
Implementation (Friendly reminder: In order to make everyone understand it, all the codes in this article are streamlined)
Implementation of parse
Let's take a simple example
<div><p>hi</p>{{message}}</div>
It seems like a simple example, but there are actually 3 types:element
、text
, interpolation. We define these three types with enumerations.
const enum NodeTypes { ROOT, INTERPOLATION, SIMPLE_EXPRESSION, ELEMENT, TEXT }
ROOT
Type represents the root node,SIMPLE_EXPRESSION
Type represents the content of the interpolation. Finally we want to passparse
Generate aast
。
{ type: children: [ { type: , tag: "div", children: [ { type: , tag: "p", children: [ { type: , content: "hi" } ] }, { type: , content: { type: NodeTypes.SIMPLE_EXPRESSION, content: "message" } } ] } ] }
Based on the source code, we can knowast
is made of functionsbaseParse
generate. Then let's start with this function.
baseParse
export function baseParse(content: string) { const context = createParseContext(content) return createRoot(parserChildren(context, [])) } function createParseContext(content: string) { return { source: content } } function createRoot(children) { return { children, type: } }
First create a global context objectcontext
, and storedsource
。source
It is the template content we passed in. Then create the root node, includingtype
andchildren
. andchildren
Is it fromparseChildren
create.
parseChildren
function parseChildren(context, ancestors) { const nodes: any = [] while (!isEnd(context, ancestors)) { const s = let node if (("{{")) { node = parseInterpolation(context) } else if (s[0] === "<") { if (/[a-z]/(s[1])) { node = parseElement(context, ancestors) } } else { node = parseText(context) } (node) } return nodes }
parseChildren
It is responsible for parsing child nodes and creatingast
Node array.parseChildren
It analyzes each child node from top to bottom, and the template content must be parsed from left to right. Whenever you encounter oneelement
All nodes need to be called recursivelyparseChildren
To parse its child nodes. When I encounter{{
It is believed that the interpolation node needs to be processed, when it encounters<
I think what needs to be dealt with iselement
The nodes, the rest are considered to be handledtext
node. Each node will be generated after processingnode
andpush
arrivenodes
In, finally returnnodes
As a fatherast
Node'schildren
property.
Of course, there must be a condition to exit the loop when looping from left to right.isEnd
function isEnd(context, ancestors) { const s = if (("</")) { for (let i = 0; i < ; i++) { const tag = ancestors[i] if (startsWithEndTagOpen(s, tag)) { return true } } } return !s } function startsWithEndTagOpen(source, tag) { return ( ("</") && (2, 2 + ).toLowerCase() === () ) }
ancestors
expresselement
The set of tags roughly means when you encounter the end identifier</
, and end the tag ((2, 2 + )
)andelement
The tag matching in the tag collection indicates the current oneelement
After the node is processed, the loop is exited.
Let's take a look at the interpolation nodeparseInterpolation
、element
nodeparseElement
and text nodesparseText
How to deal with it separately
parseInterpolation
function parseInterpolation(context) { const openDelimiter = "{{" const closeDelimiter = "}}" const closeIndex = ( closeDelimiter, ) advanceBy(context, ) const rawContentLength = closeIndex - const rawContent = parseTextData(context, rawContentLength) const content = () advanceBy(context, ) return { type: , content: { type: NodeTypes.SIMPLE_EXPRESSION, content } } } function advanceBy(context: any, length: number) { = (length) } function parseTextData(context: any, length) { const content = (0, length) advanceBy(context, ) return content }
We mainly want to get the interpolated content and return an interpolated object.closeIndex
Indicates the location where "}}" is located.advanceBy
The function of the function is to advance. For example, "{{" does not need to be processed, so just intercept it.rawContentLength
Represents the length of the content between "{{" and "}}", byparseTextData
Gets the content between "{{" and "}}" and returns. Then promote the middle content. Since our habit of writing code may leave blank space for the content before and after, we need to usetrim
Do the processing. Then push the last "}}" forward and return an interpolated object.
parseElement
function parseElement(context, ancestors) { const element: any = parseTag(context, ) (element) = parseChildren(context, ancestors) () if (startsWithEndTagOpen(, )) { parseTag(context, ) } else { throw new Error(`Missing end tag: ${}`) } return element } function parseTag(context: any, type: TagType) { const match: any = /^<\/?([a-z]*)/() const tag = match[1] advanceBy(context, match[0].length) advanceBy(context, 1) if (type === ) return return { type: , tag } } function startsWithEndTagOpen(source, tag) { return ( ("</") && (2, 2 + ).toLowerCase() === () ) }
parseElement
The second parameterancestors
is an array to collect labels (used on the aboveisEnd
already mentioned). passparseTag
Get the tag name,parseTag
Get the tag name through regularity and return a tag object, and continue to advance the processed content. If it is an end tag, do nothing. Then passparseChildren
Recursive processingelement
child nodes. Then the end tag is processed.startsWithEndTagOpen
The judgment is that the end tag exists enough, and if it does not exist, an error will be reported.
parseText
function parseText(context: any): any { let endIndex = let endToken = ["<", "{{"] for (let i = 0; i < ; i++) { const index = (endToken[i]) if (index !== -1 && endIndex > index) { endIndex = index } } const content = parseTextData(context, endIndex) return { type: , content } }
endIndex
Indicates the content length (the length of the content is the length of the promoted character to the last character). for example
<div>hi,{{message}}</div>
Able to enterparseText
The function indicates that the start tag has been processed, soIt should be
hi,{{message}}</div>
soendIndex
The length should be the length of the above code. When we encounter "<" or "{{", we need to changeendIndex
The value of the above code should behi,
, so when encountering "{{", changeendIndex
Then passparseTextData
Get the text content and return a text object.
Summarize
parse
The function is totemplate
generateast
Object. You need to be righttemplate
From left to right, process it in turn, and after processing, it will be advanced.element
Tags also need to be processed recursively and added toOn, finally return one
ast
Abstract syntax tree.
The above is a detailed content of the Vue3 compilation principle in one article. For more information about the Vue3 compilation principle, please pay attention to my other related articles!