blog

vueテンプレートのコンパイル

vueインスタンスを作成するときに、elまたはtemplateテンプレートを渡します。テンプレートは、内部でrenderというrender関数にコンパイルされます。テンプレートのコンパイルプロセスは次...

Nov 15, 2020 · 5 min. read
シェア

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 + '}');
Read next

Vue Xiaokai bookkeeping フレックスレイアウトとフロートレイアウトのおさらい、センタリングテクニック

学んだcssレイアウトの内容はすべて忘れて、プロジェクトを書くことで、フレックスレイアウトとフロートレイアウトの使い方を再確認し、フレックスレイアウトとフロートレイアウトの使い方を理解できるようになります。 まず、フレックスレイアウトはIE9と互換性がありません。 フレックスレイアウトはコンテナとアイテムを考慮する必要があります。 まず、display:... を調整する必要があります。

Nov 15, 2020 · 2 min read