以前一直在掘金上看到一些關於面試寫babel插件的文章,最近也在學,如下就是學習後的總結。javascript
關鍵詞:AST編譯解析, babeljava
AST[維基百科]:在計算機科學中,抽象語法樹(Abstract Syntax Tree,AST),或簡稱語法樹(Syntax tree),是源代碼語法結構的一種抽象表示。它以樹狀的形式表現編程語言的語法結構,樹上的每一個節點都表示源代碼中的一種結構。之因此說語法是「抽象」的,是由於這裏的語法並不會表示出真實語法中出現的每一個細節。好比,嵌套括號被隱含在樹的結構中,並無以節點的形式呈現;而相似於 if-condition-then
這樣的條件跳轉語句,可使用帶有兩個分支的節點來表示。node
和抽象語法樹相對的是具體語法樹(一般稱做分析樹)。通常的,在源代碼的翻譯和編譯過程當中,語法分析器建立出分析樹。一旦AST被建立出來,在後續的處理過程當中,好比語義分析階段,會添加一些信息。react
如何利用AST解析function ast(){},更改後從新恢復
webpack
分三步走:git
const esprima = require('esprima');//解析js的語法的包 const estraverse = require('estraverse');//遍歷樹的包 const escodegen = require('escodegen');//生成新的樹的包 let code = `function ast(){}`; //解析js的語法 let tree = esprima.parseScript(code); //遍歷樹 estraverse.traverse(tree, { enter(node) { console.log('enter: '+node.type); }, leave(node){ console.log('leave: '+node.type); } }); //生成新的樹 let r = escodegen.generate(tree); console.log(r);
更改樹的內容後github
const esprima = require('esprima'); const estraverse = require('estraverse'); const escodegen = require('escodegen'); let code = `function ast(){}`; let tree = esprima.parseScript(code); estraverse.traverse(tree, { enter(node) { if (node.type === 'Identifier') { node.name = 'Jomsou'; } // console.log('enter: '+node.type); // }, leave(node){ // console.log('leave: '+node.type); } }); let r = escodegen.generate(tree); console.log(r);
//結果 function Jomsou() { }
一、ES6箭頭函數`let sum = (a, b)=>{return a+b};轉化爲ES5普通函數web
const babel = require('babel-core');//babel核心解析庫 const t = require('babel-types');//babel類型轉化庫 let code = `let sum = (a, b)=>{return a+b}`; let ArrowPlugins = { //訪問者模式 visitor: { //捕獲匹配的API ArrowFunctionExpression(path){ let {node} = path; let body = node.body; let params = node.params; let r = t.functionExpression(null, params, body, false, false); path.replaceWith(r); } } } let d = babel.transform(code, { plugins: [ ArrowPlugins ] }) console.log(d.code);
箭頭函數這樣寫let sum = (a, b)=>a+b;的轉化面試
let babel = require('babel-core'); let t = require('babel-types'); let code = `let sum = (a, b)=>a+b`; //.babelrc let AllowPlugins = { visitor: { ArrowFunctionExpression(path){ let node = path.node; let params = node.params; let body = node.body; if(!t.isBlockStatement(body)){ let returnStatement = t.returnStatement(body); body = t.blockStatement([returnStatement]); } let funcs = t.functionExpression(null, params, body, false, false); path.replaceWith(funcs); } } } let r = babel.transform(code, { plugins:[ AllowPlugins ] }) console.log(r.code);
二、classnpm
let code = ` class Jomsou{ constructor(name){ this.name = name; } getName(){ return this.name; } } `
a) 實現constructor的轉化
const babel = require('babel-core');//babel核心解析庫 const t = require('babel-types');//babel類型轉化庫 /** * function Jomsou(name){ * this.name = name; * } * Jomsou.prototype.getName = function(){ * return this.name; * } */ let code = ` class Jomsou{ constructor(name){ this.name = name; } getName(){ return this.name; } } ` let ClassPlugin = { visitor: { ClassDeclaration(path){ let {node} = path; let className = node.id.name; className = t.identifier(className); //console.log(className); let funs = t.functionDeclaration(className, [], t.blockStatement([]), false, false); path.replaceWith(funs); } } } let d = babel.transform(code, { plugins: [ ClassPlugin ] }) console.log(d.code);
b) 實現class的方法函數轉化爲原型方法
const babel = require('babel-core');//babel核心解析庫 const t = require('babel-types');//babel類型轉化庫 /** * function Jomsou(name){ * * } */ let code = ` class Jomsou{ constructor(name){ this.name = name; } getName(){ return this.name; } } ` let ClassPlugin = { visitor: { ClassDeclaration(path){ let {node} = path; let className = node.id.name; className = t.identifier(className); let classList = node.body.body; //console.log(className); let funs = t.functionDeclaration(className, [], t.blockStatement([]), false, false); let es5func = []; classList.forEach((item, index)=>{ let body = classList[index].body; if(item.kind==='constructor') { let params = item.params.length?item.params.map(item=>item.name):[]; params = t.identifier(params); funs = t.functionDeclaration(className, [params], body, false, false); path.replaceWith(funs); }else { let protoObj = t.memberExpression(className, t.identifier('prototype')); let left = t.memberExpression(protoObj, t.identifier(item.key.name)); let right = t.functionExpression(null, [], body, false, false); let assign = t.assignmentExpression('=', left, right); es5func.push(assign); } }) if(es5func.length==0) { path.replaceWith(funs); } else { es5func.push(funs); path.replaceWithMultiple(es5func); } } } } let d = babel.transform(code, { plugins: [ ClassPlugin ] }) console.log(d.code);
三、實現模塊的按需加載
eg:
//babel-plugin-固定的前綴,放在node_module裏 //babel-plugin-czq-import const babel = require('babel-core');//babel核心解析庫 const t = require('babel-types');//babel類型轉化庫 let code = `import {Button, ALert} from 'antd'`; let importPlugin = { visitor: { ImportDeclaration(path){ let {node} = path; //console.log(node); let source = node.source.value; let specifiers = node.specifiers; if(!t.isImportDefaultSpecifier(specifiers[0])){ specifiers = specifiers.map(specifier=>{ return t.importDeclaration( [t.importDefaultSpecifier(specifier.local)], t.stringLiteral(`${source}/lib/${specifier.local.name.toLowerCase()}`) ) }); path.replaceWithMultiple(specifiers); } } } } let r = babel.transform(code, { plugins: [ importPlugin ] }) module.exports = importPlugin;
最後的測試
安裝依賴:
npm antd babel-preset-env babel-preset-react react react-dom webpack webpack-cli --save-dev
測試代碼:
//test.js import React from 'react'; import ReactDOM from 'react-dom'; import {Button} from 'antd';
測試:
npx webpack
用babel-plugin-czq-import先後的效果對比:
前:
後:
原文:從AST編譯解析談到寫babel插件,歡迎star,歡迎交流。
項目地址babelPlugin
參考地址: