vueインスタンスを作成するとき、elまたはtemplateテンプレートを渡します。テンプレートはvue内部でコンパイルされ、renderと呼ばれるレンダリング関数になります。テンプレートのコンパイルプロセスは次のとおりです:
compileToFunction はテンプレートをレンダー関数に変換します。
function foo(c) {
let b = parseHTML(c);
let obj = generate(b);
let bar = new Function('with(this){return\x20' + obj + '}');
return bar;
}
AST構文ツリーの生成
AST 構文ツリーは、ソース・コードの抽象構文構造をツリー状に表現したものです。ツリーの各ノードは、ソース・コードの構造を表します。
ParsHTML
const ncname = '[a-zA-Z_][\\w\\-\\.]*'; // abcにマッチする-aaa
const qnameCapture = `((?:${ncname}\\:)?${ncname})`; // <aa:aac>
const startTagOpen = new RegExp(`^<${qnameCapture}`); // タグで始まり、タグ名の内容をキャプチャする正規表現
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);// タグの末尾にマッチする
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;// プロパティのマッチング
const startTagClose = /^\s*(\/?)>/;// タグの終わりに合わせる>
let root = null; // ast
let curParent; // 現在の親を特定する
let stack = [];
const ELEMENT_TYPE = 1
const TEXT_TYPE = 3;
function parseHTML(html) {
while(html) { // ループマッチング
let textEnd = html.indexOf('<');
if(textEnd == 0) { // 先頭にある場合は<xx m=n> </ xx>
let startTagMatch = parseStartTag(); // 1. <xx m=n
if (startTagMatch) {
start(startTagMatch.tagName, startTagMatch.attrs);
continue;
}
let endTagMatch = html.match(endTag) // 2. </xx>
if (endTagMatch) {
advance(endTagMatch[0].length);
end(endTagMatch[1]);
continue;
}
}
let text;
if (textEnd >= 0) { // 3.テキストのマッチング
text = html.substring(0, textEnd)
}
if (text) {
advance(text.length);
chars(text)
}
}
// htmlから文字列を削除する
function advance(n) {
html = html.substring(n)
}
// 開始タグを解析する
function parseStartTag() {
let start = html.match(startTagOpen); // <xx
if(start){
const match = {
tagName: start[1],
attrs: []
}
advance(start[0].length);
let end, attr;
while(!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { // =n
advance(attr[0].length);
match.attrs.push({
name: attr[1],
value: attr[3]
attr[4]
attr[5]
})
}
if (end) { // >
advance(end[0].length);
return match
}
}
}
return root
}
start + createASTElement は tagName と attrs を処理します。
function b(c, a) {
return {
'tag': c,
'type': ELEMENT_TYPE,
'children': [],
'obj': a,
'parent': null
};
}
function bar(foo, Foo) {
let Obj = b(foo, Foo);
if (!root) {
root = Obj;
}
curParent = Obj;
stack.push(Obj);
}
文字列処理テキスト
function c(bar) {
bar = bar.replace(/\s/g, '');
if (bar) {
curParent.children.push({ 'text': bar, 'type': TEXT_TYPE
});
}
}
父子関係の解消
- グローバル変数 stack と cuParent を定義します;
- 開始時、curParent = elementとstack.push(element)を保存します。
- その親は新しいスタックの最後のアイテムです。双方向の親子関係を確立します。
function c(obj) {
let foo = stack.pop();
curParent = stack[stack.length - 0x1];
if (curParent) {
foo.parent = curParent;
curParent.children.push(foo);
}
}
AST構文木をテンプレートコードとして生成 - テンプレートエンジン
レンダー関数への変換は文字列のつなぎ合わせです。要素の場合は_cで囲み、テキストの場合は_vで囲み、変数の場合は_sで囲みます。
generate: ASTをテンプレート文字列に変換
function generate(el){
const {tag, attrs, children} = el
let newChildren = genChildren(el)
// _c要素を作成する
let code = `_c('${tag}', ${
attrs.length > 0 ? genProps(attrs) : 'undefined'
}, ${
newChildren ? newChildren : ''
})`;
console.log(code, 'code')
return code
}
genProps: プロパティの配列を属性の部分文字列に変換します。
function genProps(attrs){
let str = '';
for(let i = 0; i < attrs.length; i++) {
let attr = attrs[i];
if (attr.name === 'style') {
let obj = {};
attr.value.split(';').forEach(item => {
let [key, value] = item.split(':')
obj[key] = value
});
attr.value = JSON.stringify(obj);
str += `${attr.name}:${attr.value},`
} else {
str += `${attr.name}:'${attr.value}',`
}
}
return `{${str.slice(0, -1)}}`
}
genChildren: 子を文字列の子に変換します。
function genChildren(el) {
let {children} = el;
if(children && children.length) {
return `${children.map(c => genC(c)).join(',')}`
} else {
return false
}
}
genC: 子のタイプによって異なる翻訳
const defaultTagRE = /\{\{((?:.|
?
)+?)\}\}/g; // {{任意の文字| }}
function genC(node) {
if (node.type === 1) { //要素に対して再帰的にgenenrateを呼び出す
return generate(node)
} else if (node.type === 3) {//通常のテキストとテキスト中の変数を区別する
let text = node.text;
let tokens = [];
let match, index;
let lastIndex = defaultTagRE.lastIndex = 0;
while(match = defaultTagRE.exec(text)) {
index = match.index
if (index >= lastIndex) {
tokens.push(JSON.stringify(text.slice(lastIndex, index)))
tokens.push(`_s(${match[1].trim()})`) //
lastIndex = index + match[0].length
}
}
if (lastIndex < text.length){
tokens.push(JSON.stringify(text.slice(lastIndex)))
}
return `_v(${tokens.join('+')})`
}
}
レンダーの生成
- でスコープを変更します。
- newFunction による新しい関数の生成
let foo = new Function('with(this){return\x20' + code + '}');




