It has been a while since Vue3 was released, and recently I had the opportunity to use Vue3 + TypeScript + Vite stack in my company’s projects, so I took some time to read the source code of Vue3 in my spare time. In line with the idea that a good memory is better than a bad pen, while reading the source code incidentally recorded some notes, also hope to strive to write some source code reading notes, to help each want to see the source code but there may be difficulties in reducing the cost of understanding the students.

Since Vue3 was rebuilt, the directory structure of the Vue project has also changed a lot. Each functional module has been put into the Packages directory, the responsibility is clearer, through the directory name can be seen clearly. Today we’ll start with the Vue entry file and see how a single file that declares a Vue is compiled by the compile-core core module into a rendering function.

For your ease of reading, and to control the length of the article, I will fold the logic that you don’t need to care about when reading the source code, or ignore it with comments /* ignore logic */.

Personally, I don’t like to read a source code analysis article and then write a long block of code. It’s easy for people who haven’t read it to get confused. So in this series OF articles I will try to draw a flow chart of the key code. The purpose is to help you reduce the cost of comprehension, and also to give you a flow chart for your next reading.

We’ll start our source reading with a Vue object entry, Packages/Vue /index.ts. The code for this entry file is simple, just one compileToFunction, but the contents of the function body are critical, so let’s look at a diagram to understand what the function body does.

After looking at the flow chart, let’s look at the code together. I believe most of the students may be clear about the code in the picture handed out at this time.

Skip all the code and look at line 35 at the end of the file. The registerRuntiomCompiler function is called and passed to compileToFunction as an argument. This line of code is the start of the corresponding flow chart. Compile is a neat way of decoupling the runtime runtime from compileToFunction by injecting it into the compileToFunction.

At line 17, we call the compile function provided by the compile-DOM library to deconstruct the code variable from the return value. This is the compilation result generated by the compiler after execution. Code is one of the parameters of the compilation result, which is a code string. Such as

<template>
  <div>
    Hello World
  </div>
</template>
Copy the code

This simple template, when compiled, returns code as a string

const _Vue = Vue return function render(_ctx, _cache) {  with (_ctx) {    const { openBlock: _openBlock, createBlock: _createBlock } = _Vue     return (_openBlock(), _createBlock("div".null."Hello World"))}}Copy the code

The secret behind this amazing compile function will be explained in more detail later.

After we get the result of this code string, we move further down the line, declaring a render variable at line 25 and passing the generated code string code as an argument to the new Function constructor. This is the penultimate step in the flowchart, generating the render function. If you can format the code string I put above, you can see that the render function is a Currified function that returns a function that extends the scope chain with with.

The final entry file returns the Render variable and optionally caches the Render function.

In line 1 of the source above, we see that the import file creates a compileCache object to cache the render function generated by compileToFunction, using template as the cache key. There is an if branch at line 11 to check the cache. If the template has been cached before, it will not compile and directly return the render function in the cache to improve performance.

At this point, the import file of package/vue/index.ts is finished. I believe you can see that the most interesting part is to call the compile function to compile the code string, so I will continue to talk about the compile function. The compile function involves two modules, compile-dom and compile-core. In this article, I will only interpret the key flow. Detailed analysis will be provided in a follow-up article. Let’s see how compile runs:

Internally, the compile function directly returns the results of baseCompile, which generates the AST abstract syntax tree and calls transform to process each AST node. For example, the vOn, V-if, V-for and other instructions are converted. Finally, the AST abstract syntax tree after processing is generated by generate function to generate the code string mentioned above, and the compilation result is returned. So far, the compile function is completed. After understanding the general process, then look at the source code.

The source path of compile function is packages/compiler-dom/ SRC /index.ts. We see that in compile function body, return baseCompile processing result directly. The baseCompile source path is packages/compiler-core/ SRC /compile.ts. Why the name baseCompile? Because compile-core is the core module of compilation, which accepts external parameters to complete the compilation according to the rules, while compile-dom is specialized to handle the compilation in the browser scenario. The compile function exported under this module is the compilation function that the entry file actually receives. The compile function in compile-DOM is also a compiler of higher order than baseCompile. For example, when Vue works in weex Native apps such as iOS or Android, the compile-dom may be replaced by the relevant mobile compilation library.

Let’s look at the baseCompile function below:

From the function declaration, baseCompile receives the template template and the options compiler, and returns a CodegenResult result.

export interface CodegenResult {
  code: string
  preamble: string
  ast: RootNode map? : RawSourceMap }Copy the code

The CodegenResult interface declaration clearly shows the code string, the AST abstract syntax tree after processing, and the sourceMap in the returned result.

Look at line 12 above to see if the template template is a string. If so, the string is parsed; otherwise, template is directly used as the AST. In fact, we usually write single-file VUE code, is passed in as a string.

The next line of the source code calls the transform function and passes in utility functions such as instruction transformation and node transformation to transform the AST generated by the template.

Finally, at line 32, we pass in a good AST to generate a return of type CodegenResult.

In the compile-core module, the AST parsing, Transform, CodeGen, compile, and parse functions are all a separate small module, and their internal implementation is very subtle, which will be described in a subsequent article on the compiler.

This article starts from the entry file, explains the general process of compilation, hoping to help you to have a clear process concept when reading the code of the compiler module, with the flow chart more delicious yo.