以前一直在掘金上看到一些关于面试写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
参考地址: