Vue編譯器實現代碼生成方法介紹
這裡將討論如何根據JavaScript AST生成渲染函數的代碼,即代碼生成。代碼生成本質上是字符串拼接的藝術。需要訪問JavaScript AST中的節點,為每一種類型的節點生成相符的 JavaScript代碼。
本節,將實現 generate函數來完成代碼生成的任務。
function compile(template) { //模板 AST const ast = parse(template) //將模板 AST轉換為JavaScript AST transform(ast) // 代碼生成 const code = generate(ast.jsNode) return code }
在這裡,代碼生成也需要上下文對象。
function generate(node){ const context = { // 存儲最終生成的渲染函數代碼 code: '', // 在生成代碼時,通過調用 push 函數完成代碼的拼接 push(code){ context.code += code } } // 該方法完成代碼生成的工作 genNode(node,context) // 返回渲染函數代碼 return context.code }
另外,為瞭讓最終生成的代碼具有較強的可讀性,要考慮生成代碼的格式,這就需要擴展context對象,為其增加用來完成換行和縮進的工具函數,如下面代碼所示:
function generate(node){ const context = { code: '', push(code){ context.code += code }, // 當前縮進的級別,初始值為 0,即沒有縮進 currentIndent: 0, // 該函數用來換行,即在代碼字符串的後面追加 \n 字符 // 另外,換行時應該保留縮進,所以還要追加 currentIndent * 2 個空格字符 newline(){ context.code += '\n'+` `.repeat(context.currentIndent) }, // 用來縮進,即讓 currentIndent 自增後,調用換行函數 indent(){ context.currentIndent++ context.newline() }, // 取消縮進 deIndent(){ context.currentIndent-- context.newline() }, } genNode(node, context) return context.code }
有瞭這些基礎能力之後,就可以開始編寫 genNode 函數來完成代碼生成的工作瞭。代碼生成的原理其實很簡單,隻需要匹配各種類型的 JavaScript AST節點,並調用對應的生成函數即可,如下面的代碼所示:
function genNode(node,context){ switch(node.type){ case 'FunctionDecl': genFunctionDecl(node,context) break case 'ReturnStatement': genReturnStatement(node,context) break case 'CallExpression': genCallExpression(node,context) break case 'StringLiteral': genStringLiteral(node,context) break case 'ArrayExpression': genArrayExpression(node,context) break } }
如果後續需要增加節點類型,隻需要再genNode函數中添加相應的處理分支即可
接下來逐步完善代碼生成工作,首先實現函數生命語句的代碼生成,即genFunctionDecl函數,如下面的代碼所示:
function genFunctionDecl(node,context){ // 從context 對象中取出工具函數 const {push, indent, deIndent} = context push(`function ${node.id.name}`) push(`(`) //調用 genNodeList 為函數的參數生成代碼 genNodeList(node.params, context) push(`)`) push(`{`) indent() //為函數體生成代碼,這裡遞歸地調用瞭 genNode 函數 node.body.forEach(n=>genNode(n,context)) deIndent() push(`}`) }
genFunctionDecl函數用來為函數聲明類型的節點生成對應的 JavaScript代碼。以聲明節點為例,它最終生成的代碼將會是:
function render(){ ...函數體 }
genNodeList函數的實現如下:
function genNodeList(nodes, context){ const {push} = context for(let i=0;i<nodes.length;i++){ const node = nodes[i] genNode(node,context) if(i<nodes.length - 1){ push(',') } } }
這裡要註意的一點是,每處理完一個節點,需要在生成的代碼後面拼接逗號。
實際上,genArrayExpression函數就利用瞭這個特點來實現對數組表達式的代碼生成,如下面的代碼所示:
function genArrayExpression(node, context){ const {push} = context // 追加方括號 push(`[`) genNodeList(node.elements, context) // 補全方括號 push(`]`) }
對於 genFunctionDecl 函數,另外需要註意的是,由於函數體本身也是一個節點數組,所以需要遍歷它並遞歸地調用 genNode 函數生成代碼。
對於ReturnStatement和StringLiteral類型的節點來說,為它們生成代碼很簡單,如下所示:
function genReturnStatement(node, context){ const {push} = context push(`return `) genNode(node.return, context) } function genStringLiteral(node, context){ const {push} = context // 對於字符串字面量,隻需要追加與 node.value 對應的字符串即可 genNode(`'${node.value}'`) }
最後,隻剩下genCallExpression 函數瞭,實現如下:
function genCallExpression(node, context){ const {push} = context // 取得被調用函數名稱和參數列表 const {callee, arguments: args} = node // 生成函數調用代碼 push(`${callee.name}`) genNodeList(args,context) push(`)`) }
配合上述生成器函數的實現,就能得到符合語氣的渲染函數代碼,用下面的代碼測試
const ast = parse(`<div><p>Vue</p><p>Template</p></div>`) transform(ast) const code = generate(ast.jsNode)
最終得到的代碼字符串如下:
function render (){ return h('div',[h('p','Vue'), h('p','Template')]) }
到此這篇關於Vue編譯器實現代碼生成方法介紹的文章就介紹到這瞭,更多相關Vue代碼生成內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Javascript結合Vue實現對任意迷宮圖片的自動尋路
- vue實現表單驗證功能
- 搞懂什麼是Node.js原來這麼簡單
- 一篇文章帶你瞭解JavaScript-語句
- node.js文件系統模塊和兩個重要模塊