Vue3 source code internal reference <3>Compiler-core core
Previous articles (all have corresponding source code)
- Vue3 source code internal reference <2> Reactivity core
- Vue3 source code internal reference <1> Handwritten mini-vue3 pre-preparation
The address of the code demonstration in this section is more appetizing to eat with the source code!
- The code is implemented with reference to mini-vue, if you are interested, you can also see the original author’s implementation
Prerequisite knowledge
Both compiler-core
and compiler-dom
are compiler modules in Vue3, but their functions and application scenarios are slightly different.
-
The
compiler-core
module is the core implementation of the Vue3 compiler, which is responsible for compiling templates into rendering functions. It includes some basic compiler functions, such as AST generation, instruction and expression processing, and optimization and code generation etc. -
The
compiler-core
module can run in various JavaScript environments, not limited to the browser environment, so it can be used to develop cross-platform applications based on Vue3, such as desktop applications, mobile applications wait. -
The
compiler-dom
module is the implementation of the Vue3 compiler in the browser environment. It extends the functions of thecompiler-core
module to meet the special needs of the browser environment. For example, compiling properties and events of DOM elements. -
The code generated by the
compiler-dom
module is executed directly in the browser, so it generates code specific to the browser environment.
In short, the difference between the compiler-core
module and the compiler-dom
module is that the former is the core implementation of the Vue3 compiler, and the latter is the extension implementation in the browser environment. Mainly used to compile templates into code that can be executed directly in the browser.
Specific operation: the compiler-core module generates ast from template through parse, and then processes instruction and expression processing, optimization and code generation in ast through compile. After generating render, it processes vdom through patch and diff to generate Interface UI.
mini-vue minimal implementation of parse module in compiler-core
- Parse
{{}}
interpolation expressions - Parse
text
text - Parsing tags
Reminder Download the source code of this section, and debug it step by step through jest debug, which makes it easier to understand the function points
Encapsulate public functions
// template is a string, after the parser parses and generates ast, it needs to consume the parsed characters function advanceBy(context, length) {<!-- --> context.source = context.source.slice(length); } // Builder export function baseParse(content) {<!-- --> const context = createParserContent(content) return createRoot(parseChildren(context, [])) } function createRoot(children) {<!-- --> return {<!-- --> children, type: NodeTypes.ROOT } } function createParserContent(content) {<!-- --> return {<!-- --> source: content } } // Analyze the judgment conditions of the template loop function isEnd(context, ancestors) {<!-- --> // 1. When the source has a value // 2. When encountering the end tag const s = context.source; if (s. startsWith('</')) {<!-- --> for (let i = ancestors. length - 1; i >= 0; i--) {<!-- --> const tag = ancestors[i].tag; if (startsWithEndTagOpen(s, tag)) {<!-- --> return true; } } } return !s; }
Parse {{}}
function parseChildren(context, ancestors) {<!-- --> const nodes: any = [] while (!isEnd(context, ancestors)) {<!-- --> let node const s = context.source; if (s.startsWith('{<!-- -->{')) {<!-- --> node = parseInterpolation(context) } nodes. push(node) } return nodes } function parseInterpolation(context) {<!-- --> // {<!-- -->{message}} // The advantage of taking out the definition is that if it needs to be changed, the change will be very small const openDelimiter = '{<!-- -->{' const closeDelimiter = '}}' // We need to know where to close // indexOf means search }} starting from 2 const closeIndex = context.source.indexOf( closeDelimiter, openDelimiter. length ) // delete the first two strings // context.source = context.source.slice(openDelimiter.length) advanceBy(context, openDelimiter. length) // The length of the content is equal to the length of closeIndex - openDelimiter const rawContentLength = closeIndex - openDelimiter.length const rawContent = parseTextData(context, rawContentLength) const content = rawContent. trim() // Then you need to delete this string. The template is a string, and you need to traverse the following content // context.source = context.source.slice(rawContentLength + closeDelimiter.length); advanceBy(context, closeDelimiter. length) return {<!-- --> type: NodeTypes. INTERPOLATION, content: {<!-- --> type: NodeTypes. SIMPLE_EXPRESSION, content } } }
Parse text
function parseChildren(context, ancestors) {<!-- --> const nodes: any = [] while (!isEnd(context, ancestors)) {<!-- --> let node const s = context.source; // parse interpolation if (s.startsWith('{<!-- -->{')) {<!-- --> node = parseInterpolation(context) } // parse text if (!node) {<!-- --> node = parseText(context); } nodes. push(node) } return nodes } function parseText(context) {<!-- --> const endToken = ['{<!-- -->{', '</'] // If the stop conditions exist at the same time, then the index should be left as far as possible to the smallest let endIndex = context.source.length // stop index for (let i = 0; i < endToken. length; i ++ ) {<!-- --> const index = context.source.indexOf(endToken[i]) if (index !== -1 & amp; & amp; endIndex > index) {<!-- --> endIndex = index } } // Before parsing the text, it was intercepted from the beginning to the end, but the real environment is that there will be other types of elements after the text, so it is necessary to specify the stop position const content = parseTextData(context, endIndex) return {<!-- --> type: NodeTypes. TEXT, content } }
Parse tags
import {<!-- --> NodeTypes } from "./ast"; const enum TagType {<!-- --> Start, end } function parseChildren(context, ancestors) {<!-- --> const nodes: any = [] while (!isEnd(context, ancestors)) {<!-- --> let node const s = context.source; if (s.startsWith('{<!-- -->{')) {<!-- -->... } else if (s[0] === "<") {<!-- --> // Need to use regular expression to judge // <div></div> // /^<[a-z]/i/ if (/[a-z]/i.test(s[1])) {<!-- --> node = parseElement(context, ancestors); } } nodes. push(node) } return nodes } function parseElement(context, ancestors) {<!-- --> // parse tags const element: any = parseTag(context, TagType. Start) ancestors. push(element) // After getting the tags, you need to save the internal elements, and you need to traverse the internal elements recursively element.children = parseChildren(context, ancestors) ancestors. pop() // Here you need to judge whether the start tag and the end tag are consistent, and return if you can't directly consume if (startsWithEndTagOpen(context.source, element.tag)) {<!-- --> parseTag(context, TagType. End) } else {<!-- --> throw new Error(`Missing end tag: ${<!-- -->element.tag}`) } return element } function parseTag(context, type) {<!-- --> // <div></div> // match parsing // advance const match: any = /^<\/?([a-z]*)/i.exec(context.source) const tag = match[1] // Advance after acquisition advanceBy(context, match[0].length) advanceBy(context, 1); if (type === TagType. End) return return {<!-- --> type: NodeTypes. ELEMENT, tag } } function startsWithEndTagOpen(source, tag) {<!-- --> // It only makes sense to start with a left parenthesis and also needs to be converted to lowercase for comparison return ( source.startsWith("</") & amp; & amp; source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() ); }
A brief description of the implementation steps
- source =
- Strings are processed recursively. The isEnd function returns a boolean to indicate whether an end tag has been encountered. If a stage is encountered, there is no need to recursively process the string.
- Call the parseElement function and parseTag function to take out the tag name
- tag = div, then the string advances, source =
, ancestors = [{tag:'div'}]
END
In this article, the function of parsing interpolation, text and labels in the parse module in the compiler is simply realized. Detailed content Portal, if you find it useful, please give a star. In addition, the author believes that before learning the source code, you can simply implement some basic functions first, and then look at the source code. There are many boundary conditions in the source code, which is not conducive to reading.