In the previous sections, we started with the new Vue instance creation and introduced the two important steps in the initialization process when creating an instance, the resource merging of configuration options, and the core idea of a responsive system, the data broker. In the merge section, we have a basic understanding of Vue’s rich option merge strategy, and in the data broker section we have an in-depth understanding of the meaning and usage scenarios of proxy interception. According to the design ideas of Vue source code, the initialization process will also carry out many operations, such as creating associations between components, initializing the event center, initializing data and establishing responsive systems, etc., and ultimately rendering templates and data into DOM nodes. Many concepts are difficult to understand if you analyze the implementation details of each step directly in order of the process. Therefore, in this chapter, we will first focus on the concept of an instance mount rendering process.
3.1 Runtime Only VS Runtime + Compiler
Before we get started, let’s take a look at the two versions of Vue that are built from source code. One is runtime only, and the other is Runtime + compiler. The only difference between the two versions is that the latter includes a compiler.
What is a compiler? Baidu Baike explains:
Simply put, a compiler is a program that translates “one language (usually a high-level language)” into “another language (usually a low-level language).” The main workflow of a modern compiler: Source code → preprocessor → Compiler → Object code → Linker → executables.
In layman’s terms, a compiler is a tool that provides a way to convert source code into object code. From a Vue perspective, the built-in compiler provides the ability to convert the Template template into an executable javascript script.
3.1.1 the Runtime + Compiler
A full Vue version includes the compiler, and we can write templates using template. The compiler will automatically compile the template string into the render function code, the source is the render function. If you need to compile the template on the client side (such as passing a string to the template option, or mounting it to an element and using HTML inside its DOM as the template), you’ll need a version that includes the compiler.
// The version of the compiler required
new Vue({
template: '<div>{{ hi }}</div>'
})
Copy the code
3.1.2 the Runtime Only
Run-time only code has the ability to create Vue instances, render and process the Virtual DOM, and is basically complete code without the compiler. Runtime Only applies to two scenarios: 1. We define the render process by hand in the render function option, which does not require the compiler version to be fully executed.
// No compiler is required
new Vue({
render (h) {
return h('div'.this.hi)
}
})
Copy the code
When we use Webpack for vUE engineering development, we often use vue-loader to compile. Vue, although we also use template template tag to write code. But at this point Vue has no need to use the compiler to be responsible for template compilation work, this process is handed over to the plug-in to achieve.
Obviously, compilation takes a toll on performance, and the total volume of Vue code is larger due to the addition of compiled flow code (the runtime version is about 30% smaller than the full version). Therefore, in practical development, we need to use tools such as Vue-loader of WebPack to compile, and integrate the compilation phase of VUE to the template into the webPack construction process, which not only reduces the volume of the production environment code, but also greatly improves the runtime performance, killing two stones with one stone.
3.2 Basic Idea of Instance Mounting
With that in mind, we go back to the code that initializes _init. In the code, we observe a series of function calls after initProxy, including creating component associations, initializing event handling, defining rendering functions, building data-responsive systems, and finally, in the case of EL, The instance calls $mount to mount the instance.
Vue.prototype._init = function (options) {...// Option merge
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
// Data broker
initProxy(vm);
vm._self = vm;
initLifecycle(vm);
// Initializes event handling
initEvents(vm);
// Define the render function
initRender(vm);
// Build responsive systems
initState(vm);
/ / etc....if(vm.$options.el) { vm.$mount(vm.$options.el); }}Copy the code
Take the handwritten Template template as an example to clarify what a mount is. We pass a template string as an attribute in the options, such as
, and eventually the template string is converted into a real DOM node through an intermediate process and mounted to the root node represented by el in the options for view rendering. This intermediate process is the next mount process to be analyzed.
The process of Vue mounting is quite complicated. Next, I will show you the real process of mounting through flowchart and code analysis.
3.2.1 flowchart
Confirm that the node is mounted and compile the template asrender
Function, render function conversionVirtual DOM
To create a real node.
3.2.2 Code analysis
Let’s take a look at the mount process from a code perspective. There is a lot of code to mount, so only the skeleton related part of the code is extracted below.
// How to actually mount it internally
Vue.prototype.$mount = function (el, hydrating) {
el = el && inBrowser ? query(el) : undefined;
// Call the mountComponent method to mount it
return mountComponent(this, el, hydrating)
};
// Caches the $mount method on the prototype
var mount = Vue.prototype.$mount;
// Redefine $mount to provide different packages for versions with and without compilers, and end up calling the $mount method on the cache prototype
Vue.prototype.$mount = function (el, hydrating) {
// Get the mount element
el = el && query(el);
// The mount element cannot be a follower node
if (el === document.body || el === document.documentElement) {
warn(
"Do not mount Vue to <html> or <body> - mount to normal elements instead."
);
return this
}
var options = this.$options;
// Requires compilation or does not need compilation
// the render option does not exist, representing the form of the template template, which needs to be compiled
if(! Options. Render) {...// Use an internal compiler to compile templates
}
// Either the template template or the handwritten render function ends up calling the cached $mount method
return mount.call(this, el, hydrating)
}
// mountComponent
function mountComponent(vm, el, hydrating) {
// Define the updateComponent method to be called on the watch callback.
updateComponent = function () {
// The render function renders the virtual DOM, which renders the real DOM
vm._update(vm._render(), hydrating);
};
// Instantiate render watcher
new Watcher(vm, updateComponent, noop, {})
}
Copy the code
We use language to describe the basic idea of the mount process.
- Confirm mounted
DOM
Element, this oneDOM
You need to make sure that you don’tHTML, body
These are the following nodes. - We know there are two ways to render, one is through
template
Template strings, the other is handwrittenrender
Function, I mentioned earliertemplate
Templates need to be compiled at run time, and the latter one can be used directlyrender
Option as a render function. So the mount phase has two branches,template
Templates are parsed by templates and compiled intorender
The rendering function participates in instance mounting, while handwritingrender
The function can bypass the compile phase and call mounted$mount
Methods. - for
template
In terms of, it will useVue
The internal compiler compiles the template, and the string template is converted into an abstract syntax tree, i.eAST
Tree, and eventually transformed into a similarfunction(){with(){}}
Render function, which is the focus of our discussion later. - Whether it is
template
Templates or handwritingrender
Delta function, which will eventually go inmountComponent
Process, which instantiates a renderwatcher
, and the specificwatcher
The content, put chapter discussion separately. Let’s start with one conclusion, renderingwatcher
The callback function has two execution times, one at initialization and the other whenvm
The instance executes the callback again when it detects that the data has changed. - The callback function executes
updateComponent
This method has two phases, one isvm._render
And the other one isvm._update
.vm._render
The previous generation will be executedrender
Render function and generate oneVirtual Dom tree
And thevm._update
This will beVirtual Dom tree
Make it realDOM
Node.
3.3 Template Compilation
Through the first half of the article, we have a preliminary understanding of the Vue mount process. There are two big processes that need to be understood in detail, one is compiling the Template template and the other is implementing the updateComponent. The updateComponent process will be analyzed in the next section, and the rest of this section will focus on the design idea of template compilation.
Compiler implementation details are so complex that it is impractical to master the entire compilation process in a short space of time, and it is not necessary to fully clarify the compilation process in the general direction. Therefore, for the template, the article analysis is only superficial, more details can be analyzed by the reader)
3.3.1 Three ways to write template
There are three ways to write a template:
- String template
var vm = new Vue({
el: '#app'.template:
Template string
})
Copy the code
- The selector matches the element
innerHTML
The template
<div id="app"> <div>test1</div> <script type="x-template" id="test"> <p>test</p> </script> </div> var vm = new Vue({ el: '#app', template: '#test' })Copy the code
dom
Element matches the elementinnerHTML
The template
<div id="app">
<div>test1</div>
<span id="test"><div class="test2">test2</div></span>
</div>
var vm = new Vue({
el: '#app',
template: document.querySelector('#test')
})
Copy the code
The premise of template compilation needs to check the validity of the template template string. The three writing methods correspond to three different branches of the code.
Vue.prototype.$mount = function () {...if(! options.render) {var template = options.template;
if (template) {
// Matches the template for string templates and selectors
if (typeof template === 'string') {
// The selector matches the template, a selector prefixed with '#'
if (template.charAt(0) = = =The '#') {
// Get the innerHTML of the matched element
template = idToTemplate(template);
/* istanbul ignore if */
if(! template) { warn( ("Template element not found or is empty: " + (options.template)),
this); }}// Match for DOM elements
} else if (template.nodeType) {
// Get the innerHTML of the matched element
template = template.innerHTML;
} else {
// Other types are considered illegal
{
warn('invalid template option:' + template, this);
}
return this}}else if (el) {
// If no template template is passed, the root node of the EL element is used as the base template by defaulttemplate = getOuterHTML(el); }}}// Check whether the el element exists
function query (el) {
if (typeof el === 'string') {
var selected = document.querySelector(el);
if(! selected) { warn('Cannot find element: ' + el
);
return document.createElement('div')}return selected
} else {
return el
}
}
var idToTemplate = cached(function (id) {
var el = query(id);
return el && el.innerHTML
});
Copy the code
Note: The X-template Template approach is generally used for very large demos or very small applications, and is officially not recommended in other cases because it separates the Template from the rest of the component definition.
3.3.2 Compilation Process Diagram
Vue source code compilation design ideas is more around, involving more function processing logic, the implementation of the process clever use of partial function skills to configure the processing and compilation of core logic extracted, in order to understand this design ideas, I drew a logical diagram to help understand.
3.3.3 Logical Analysis
Even with flowcharts, the compilation logic is difficult to understand. Next, analyze the execution process of each link with the code.
Vue.prototype.$mount = function () {...if(! options.render) {var template = options.template;
if (template) {
var ref = compileToFunctions(template, {
outputSourceRange: "development"! = ='production'.shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
varrender = ref.render; }... }}Copy the code
CompileToFunctions takes three parameters, Template template and compileToFunctions. These methods are exposed and you can customize the configuration information to compileToFunctions. The last parameter is the Vue instance.
// Expose the compileToFunction method to Vue as a static method
Vue.compile = compileToFunctions;
Copy the code
In the official documentation of Vue, Vue.com Pile allows only one template template parameter to be passed. Does this mean that the user cannot determine the behavior of certain compilings? Obviously not. Looking back at the code, there are two option configurations that can be provided to the user. The user just needs to pass the option to change the configuration when instantiating Vue.
1. Delimiters: This option changes the plain text insertion delimiters, which Vue defaults to {{}} when no value is passed. If we want to use another template, we can modify it through delimiters.
2.comments: When set to true, HTML comments in the template will be retained and rendered. The default behavior is to discard them.
Note that since these two options are configurations read in the full build process, they are not valid to be configured in the run time version
Then we step by step to find the source of compileToFunctions.
First of all, we need to keep in mind that the compilation process of Vue is different for different platforms, that is, the basic compilation method will be different for different platforms, and the configuration options of the compilation phase will also be different for different platforms. But designers don’t want to pass in the same configuration options every time they compile different templates on the same platform. This leads to a more complex compilation implementation of the source code.
var createCompiler = createCompilerCreator(function baseCompile (template,options) {
// Parse templates into abstract syntax trees
var ast = parse(template.trim(), options);
// The Ast syntax tree is optimized if there are code tuning options in the configuration
if(options.optimize ! = =false) {
optimize(ast, options);
}
var code = generate(ast, options);
return {
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
});
var ref$1 = createCompiler(baseOptions);
var compile = ref$1.compile;
var compileToFunctions = ref$1.compileToFunctions;
Copy the code
This part of the code is defined during Vue introduction. CreateCompilerCreator returns a compiler generator, createCompiler, after passing a baseCompile function as an argument. When passed the compile-configuration option baseOptions, the compiler generator generates a compileToFunctions that return the object’s compileToFunctions under the specified environment-specific configuration.
Here baseCompile is where the compilation function is actually implemented, which is the platform-specific compilation method mentioned earlier. It is stored as a parameter in a memory variable when the source code is initialized. Let’s take a look at the general process of baseCompile.
The baseCompile function takes two parameters, the template template passed in later, and the configuration parameters required for compilation. The function implements the following functions:
- 1. Parse templates into abstract syntax trees, abbreviated
AST
, corresponding to the codeparse
Part. - 2. Optional: Optimize
AST
Syntax tree, executeoptimize
Methods. - 3. Depending on the platform
AST
Syntax tree to render function, corresponding togenerate
function
Let’s look at the implementation of createCompilerCreator in detail:
function createCompilerCreator (baseCompile) {
return function createCompiler (baseOptions) {
Internally define the compile method
function compile (template, options) {...}return {
compile: compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
Copy the code
The createCompilerCreator function has only one purpose. It uses partial functions to cache the basic compiler method baseCompile and returns a programmer generator when var ref$1 = createCompiler(baseOptions); CreateCompiler returns internally defined compile and compileToFunctions.
We continue to focus on the origin of the compileToFunctions, it is createCompileToFunctionFn function returns the parameters for the compile, then see createCompileToFunctionFn implementation logic.
function createCompileToFunctionFn (compile) {
var cache = Object.create(null);
return function compileToFunctions (template,options,vm) { options = extend({}, options); ...// Caching is useful to avoid wasting performance by compiling the same template repeatedly
if (cache[key]) {
return cache[key]
}
// Execute the compile method
varcompiled = compile(template, options); ...// turn code into functions
var res = {};
var fnGenErrors = [];
// The compiled function body string is passed as an argument to createFunction, which returns the final render function
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
returncreateFunction(code, fnGenErrors) }); ...return (cache[key] = res)
}
}
Copy the code
CreateCompileToFunctionFn using the concept of closure, the compiled template cache, the cache before compiled the results will be preserved, the cache can be used to avoid repeated compile waste caused by the performance. CreateCompileToFunctionFn will eventually compileToFunctions method returns.
Next, let’s examine the implementation logic for compileToFunctions. After determining the result of compiling without caching, compileToFunctions will execute the compile method, which is the internal compile method returned by createCompiler earlier, so we need to look at the compile implementation first.
function createCompiler (baseOptions) {
function compile (template, options) {
var finalOptions = Object.create(baseOptions);
var errors = [];
var tips = [];
var warn = function (msg, range, tip) {
(tip ? tips : errors).push(msg);
};
// Option merge
if(options) {...// The user-passed configuration is merged with the system's own compiled configuration
}
finalOptions.warn = warn;
// Pass the whitespace stripped template and the configuration of the merged options as parameters to the baseCompile method
var compiled = baseCompile(template.trim(), finalOptions);
{
detectErrors(compiled.ast, warn);
}
compiled.errors = errors;
compiled.tips = tips;
return compiled
}
return {
compile: compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
Copy the code
We see that the method that Compile really executes is the basic compilation method baseCompile passed in at the beginning of creating the compiler generator. When baseCompile is really executed, it will combine the compilation configuration passed by the user with the compilation configuration options provided by the system. This is also the essence of the compiler design thought mentioned at the beginning.
After compile is executed, an object will be returned. Ast,as the name implies, is the abstract syntax tree parsed into the template, Render is the final generated with statement,staticRenderFns is the static render existing in the form of an array.
{
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
Copy the code
And will eventually return to the other two packaged createCompileToFunctionFn properties render, staticRenderFns, at their core is with statement encapsulated into executive function.
// The compiled function body string is passed as an argument to createFunction, which returns the final render function
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
return createFunction(code, fnGenErrors)
});
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err: err, code: code });
return noop
}
}
Copy the code
At this point, the design ideas of the compiler in Vue are also basically sorted out. At the beginning of the code, I always feel that the design of the compilation logic is special. After analyzing the code, I find that this is the clever place of the author’s ideas. Vue has different compilation process on different platforms, and the baseOptions of each compilation process will be different, but also provides some options for users to configure, the whole design idea deeply applies the design idea of partial function, and partial function is the application of closure. The author uses partial functions to cache the compilation methods of different platforms and at the same time strip out compile-related options and merge them. These methods are worth our daily learning.
The core of compilation is the process parse and generate. The author did not analyze these two processes, because there are many parse branches in the abstract syntax tree, which can be better understood by combining with the actual code scenarios. The code for these two parts will be covered later in the section on specific logical functions.
3.4 summary
There are two main sections in this section. First, we walk through the complete process of the instance during the mount phase in detail. When we pass in the options for instantiation, the final goal is to render the options as the actual visual nodes of the page. This option comes in two forms, one as a template template string and the other as a handwritten render function. Either way, it ends up being mounted as the render function, which is a function-wrapped with statement. The render function needs to be parsed into the virtual DOM before rendering the real node. The virtual DOM is the bridge between JS and the real DOM. The final _update process renders the virtual DOM as a real node. The second block mainly introduces the author in the compiler design clever implementation ideas. The procedure makes heavy use of the concept of partial functions, caches compilation and removes option merges from compilation. These design concepts and ideas are worthy of our developers to learn and use for reference.
- An in-depth analysis of Vue source code – option merge (1)
- An in-depth analysis of Vue source code – option merge (2)
- In-depth analysis of Vue source code – data agents, associated child and parent components
- In-depth analysis of Vue source code – instance mount, compile process
- In-depth analysis of Vue source code – complete rendering process
- In-depth analysis of Vue source code – component foundation
- In-depth analysis of Vue source code – components advanced
- An in-depth analysis of Vue source code – Responsive System Building (PART 1)
- In – Depth Analysis of Vue source code – Responsive System Building (Middle)
- An in-depth analysis of Vue source code – Responsive System Building (Part 2)
- In-depth analysis of Vue source code – to implement diff algorithm with me!
- In-depth analysis of Vue source code – reveal Vue event mechanism
- In-depth analysis of Vue source code – Vue slot, you want to know all here!
- In-depth analysis of Vue source code – Do you understand the SYNTAX of V-Model sugar?
- In-depth analysis of Vue source – Vue dynamic component concept, you will be confused?
- Thoroughly understand the keep-alive magic in Vue (part 1)
- Thoroughly understand the keep-alive magic in Vue (2)