以前一直在掘金上看到一些關於面試寫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
參考地址: