preface
This article is mainly written Vue2.0 source code – template compilation principle
Last article we mainly introduced the responsive principle of Vue data for the advanced front-end responsive principle is the basic interview Vue must test the source code base class if not very clear it was basically passed so today we write the template compilation principle is also a point of Vue interview more frequently And it’s a little bit more complicated than the reactive principle and it involves the AST and a lot of regeformat matching and you can look at the mind map and hand write it together to impress you
Applicable to the crowd: no time to look at the official source code or look at the source code to see the more meng and do not want to see the students
Suggestion: students who want to learn regular expressions can take a look at this article
The body of the
// Instantiate Vue
new Vue({
el: "#app".data() {
return {
a: 111}; },// render(h) {
// return h('div',{id:'a'},'hello')
// },
// template:`<div id="a">hello</div>`
});
Copy the code
You can manually configure template or render in the options option
Note 1: In normal development, we use the Vue version with no compiled version (run-time only) and pass the template option directly in options
Note 2: The template option passed in here should not be confused with the <template> template in the. Vue file. The template component of the vue single-file component needs to be processed by the Vue-Loader
The el or template options we pass in will be resolved to render to keep the template parsing consistent
1. Template compilation entry
// src/init.js
import { initState } from "./state";
import { compileToFunctions } from "./compiler/index";
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this;
// This represents the object (instance object) on which the _init method was called.
// this.$options is the property passed in when the user new Vue
vm.$options = options;
// Initialize the state
initState(vm);
// Render the template if there is an EL attribute
if(vm.$options.el) { vm.$mount(vm.$options.el); }};// This code is stored in entry-Runtime-with-compiler. js
// the Vue source code contains the compile function. This is separate from the runtime-only version
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
// If the render attribute does not exist
if(! options.render) {// If there is a template attribute
let template = options.template;
if(! template && el) {// If render and template do not exist but el attribute does exist, assign the template to the outer HTML structure where EL is located.
template = el.outerHTML;
}
// Finally we need to convert the Tempalte template to render function
if (template) {
constrender = compileToFunctions(template); options.render = render; }}}; }Copy the code
The main concern is that the $mount method eventually turns the rendered template into the render function
2. Templates convert core compileToFunctions
// src/compiler/index.js
import { parse } from "./parse";
import { generate } from "./codegen";
export function compileToFunctions(template) {
// We need to turn the HTML string into the render function
// 1. Convert HTML code into an AST syntax tree. The AST is used to describe the code itself, forming a tree structure that describes not only HTML but also CSS and JS syntax
// Many libraries use ast, such as Webpack, Babel, esLint, etc
let ast = parse(template);
// 2. Optimize static nodes
// This interested can see the source code does not affect the core function is not implemented
// if (options.optimize ! == false) {
// optimize(ast, options);
/ /}
// 3. Regenerate the code from the AST
// Our final generated code needs to be the same as the render function
/ / similar _c (' div '{id: "app"}, _c (' div', undefined, _v (" hello "+ _s (name)), _c (' span, undefined, _v (" world"))))
// _c for create element, _v for create text, _s for text json. stringify-- parses the object into text
let code = generate(ast);
// Use the call to render to change the value of the variable in this convenience code
let renderFn = new Function(`with(this){return ${code}} `);
return renderFn;
}
Copy the code
Creating a compiler folder means that the core of compileToFunctions is compiled. There are three main steps to exporting compileToFunctions. Generate the AST 2. Optimize static nodes 3. Generate the render function based on the AST
3. Parse HTML and generate an AST
// src/compiler/parse.js
// The following is the source of the regular expression is not clear students can refer to xiaobian before the article (front advanced salary must see - regular article);
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; // The name of the matching tag is abc-123
const qnameCapture = ` ((? :${ncname}\ \ :)?${ncname}) `; // Match special tags such as ABC :234 before ABC: optional
const startTagOpen = new RegExp(` ^ <${qnameCapture}`); // The matching tag starts with the form
const startTagClose = /^\s*(\/?) >/; // Match tag end >
const endTag = new RegExp(` ^ < \ \ /${qnameCapture}[^ >] * > `); // Match tag endings such as to capture tag names inside
const attribute = /^\s*([^\s"'<>\/=]+)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /; // Match attribute like id="app"
let root, currentParent; // represents the root node and the current parent
// The stack structure represents the start and end tags
let stack = [];
// Identify the element and text type
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;
// Generate the AST method
function createASTElement(tagName, attrs) {
return {
tag: tagName,
type: ELEMENT_TYPE,
children: [],
attrs,
parent: null}; }// Handle the start tag
function handleStartTag({ tagName, attrs }) {
let element = createASTElement(tagName, attrs);
if(! root) { root = element; } currentParent = element; stack.push(element); }// Handle the closing tag
function handleEndTag(tagName) {
// Stack structure []
< /div>
let element = stack.pop();
// The current parent element is the one at the top of the stack
currentParent = stack[stack.length - 1];
// Establish a relationship between parent and children
if(currentParent) { element.parent = currentParent; currentParent.children.push(element); }}// Processing the text
function handleChars(text) {
// Remove Spaces
text = text.replace(/\s/g."");
if (text) {
currentParent.children.push({
type: TEXT_TYPE, text, }); }}// Parse the tag to generate the AST core
export function parse(html) {
while (html) {
/ / find the <
let textEnd = html.indexOf("<");
// If < is first then the proof is followed by a tag whether it is the beginning or the end tag
if (textEnd === 0) {
// If the start tag parsing results
const startTagMatch = parseStartTag();
if (startTagMatch) {
// Parse the tag names and attributes to generate an AST
handleStartTag(startTagMatch);
continue;
}
// Match the end tag </
const endTagMatch = html.match(endTag);
if (endTagMatch) {
advance(endTagMatch[0].length);
handleEndTag(endTagMatch[1]);
continue; }}let text;
/ / like hello < div > < / div >
if (textEnd >= 0) {
// Get the text
text = html.substring(0, textEnd);
}
if(text) { advance(text.length); handleChars(text); }}// Match the start tag
function parseStartTag() {
const start = html.match(startTagOpen);
if (start) {
const match = {
tagName: start[1].attrs: [],};// Cut off the start tag when it matches
advance(start[0].length);
// Start matching attributes
// End indicates the end symbol > if the end tag is not matched
// attr indicates the matching attribute
let end, attr;
while (
!(end = html.match(startTagClose)) &&
(attr = html.match(attribute))
) {
advance(attr[0].length);
attr = {
name: attr[1].value: attr[3] || attr[4] || attr[5].// This is because the re captures attribute values that support double quotes, single quotes, and no quotes
};
match.attrs.push(attr);
}
if (end) {
// If a tag matches the end >, the start tag is resolved
advance(1);
returnmatch; }}}// Cut the HTML string and continue matching each time it matches
function advance(n) {
html = html.substring(n);
}
// Return the generated AST
return root;
}
Copy the code
After the start tag, the end tag, and the text are parsed, the corresponding AST is generated and the corresponding parent-child association is established. Advance constantly intercepts the rest of the string until all the HTML is parsed The main thing we’ve written here is the parseStartTag attribute
4. Regenerate the code based on the AST
// src/compiler/codegen.js
const defaultTagRE = / \ {\ {((? :.|\r? \n)+?) \}\}/g; // Match the curly braces {{}} to capture what's inside the curly braces
function gen(node) {
// Check the node type
// Mainly contains processing text core
// The source code contains complex processing such as v-once v-for V-if custom instruction slot, etc. We only consider ordinary text and variable expression {{}} processing here
// If it is an element type
if (node.type == 1) {
// Create recursively
return generate(node);
} else {
// If it is a text node
let text = node.text;
// There is no braced variable expression
if(! defaultTagRE.test(text)) {return `_v(The ${JSON.stringify(text)}) `;
}
// Regex is a global pattern. You need to reset the lastIndex property of the regex every time otherwise a matching bug will be raised
let lastIndex = (defaultTagRE.lastIndex = 0);
let tokens = [];
let match, index;
while ((match = defaultTagRE.exec(text))) {
// Index indicates the matched position
index = match.index;
if (index > lastIndex) {
// Insert normal text into tokens at the matched {{position
tokens.push(JSON.stringify(text.slice(lastIndex, index)));
}
// Put the contents of the captured variable
tokens.push(`_s(${match[1].trim()}) `);
// The match pointer moves backward
lastIndex = index + match[0].length;
}
// Continue push if there is still plain text left inside the curly braces
if (lastIndex < text.length) {
tokens.push(JSON.stringify(text.slice(lastIndex)));
}
// _v creates text
return `_v(${tokens.join("+")}) `; }}// Process the ATTRs attribute
function genProps(attrs) {
let str = "";
for (let i = 0; i < attrs.length; i++) {
let attr = attrs[i];
// Make the style in the ATTRs attribute special
if (attr.name === "style") {
let obj = {};
attr.value.split(";").forEach((item) = > {
let [key, value] = item.split(":");
obj[key] = value;
});
attr.value = obj;
}
str += `${attr.name}:The ${JSON.stringify(attr.value)}, `;
}
return ` {${str.slice(0, -1)}} `;
}
// Generate a child node and call the gen function to recursively create it
function getChildren(el) {
const children = el.children;
if (children) {
return `${children.map((c) => gen(c)).join(",")}`; }}// Create code recursively
export function generate(el) {
let children = getChildren(el);
let code = `_c('${el.tag}',${
el.attrs.length ? `${genProps(el.attrs)}` : "undefined"
}${children ? `,${children}` : ""}) `;
return code;
}
Copy the code
So once you’ve got the ast that’s made, you need to take the AST Into a similar _c (‘ div ‘{id: “app”}, _c (‘ div’, undefined, _v (” hello “+ _s (name)), _c (‘ span, undefined, _v (” world”)))) this string
5. Code string generates render function
export function compileToFunctions(template) {
let code = generate(ast);
// Use the with syntax to change the scope to this and then call the render function to use the call to change the value of the variable in this code
let renderFn = new Function(`with(this){return ${code}} `);
return renderFn;
}
Copy the code
6. Mind maps compiled from templates
summary
So far, Vue template compiling principle has been completed, you can look at the mind map to write their own core code ha, it is important to note that this is a large number of use of string concatenation and regular knowledge related to the place you do not understand can consult more information is also welcome to comment comments
Finally, if you find this article helpful, remember to like it three times. Thank you very much!
Series of links (will be updated later)
- Handwriting Vue2.0 source code (a) – response data principle
- Handwriting Vue2.0 source code (2) – template compilation principle
- Handwriting Vue2.0 source code (three) – initial rendering principle
- Handwriting Vue2.0 source code (four) – rendering update principle
- Handwriting Vue2.0 source code (five) – asynchronous update principle
- Handwriting Vue2.0 source code (six) -diff algorithm principle
- Handwriting Vue2.0 source code (seven) -Mixin Mixin principle
- Handwriting Vue2.0 source code (eight) – component principle
- Handwriting Vue2.0 source code (nine) – listening attribute principle
- Handwriting Vue2.0 source code (ten) – the principle of computing attributes
- Handwriting Vue2.0 source code (eleven) – global API principle
- The most complete Vue interview questions + detailed answers
- Handwritten vue-Router source code
- Write vuex source code
- Handwriting vue3.0 source code
Shark brother front touch fish technology group
Welcome technical exchanges within the fish can be pushed for help – link