从AST编译解析谈到写babel插件
之前一直在掘金上看到一些关于面试写babel插件的文章,最近也在学,以下就是学习后的总结。
关键词:AST编译解析, babel
AST编译解析
AST[维基百科]:在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于 if-condition-then 这样的条件跳转语句,可以使用带有两个分支的节点来表示。
和抽象语法树相对的是具体语法树(通常称作分析树)。一般的,在源代码的翻译和编译过程中,语法分析器创建出分析树。一旦AST被创建出来,在后续的处理过程中,比如语义分析阶段,会添加一些信息。
如何利用AST解析function ast(){},更改后重新恢复
分三步走:
- 解析js的语法=>语法树
- 遍历树(先序深度优先)=> 更改树的内容
- 生成新的树
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);
更改树的内容后
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() {
}
babel插件
1、ES6箭头函数`let sum = (a, b)=>{return a+b};转化为ES5普通函数
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);
2、class
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);
3、实现模块的按需加载
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
参考地址:
从AST编译解析谈到写babel插件的更多相关文章
- 开发一个简单的babel插件
前言 对于前端开发而言,babel肯定是再熟悉不过了,工作中肯定会用到.除了用作转换es6和jsx的工具之外,个人感觉babel基于抽象语法树的插件机制,给我们提供了更多的可能.关于babel相关概念 ...
- 【babel+小程序】记“编写babel插件”与“通过语法解析替换小程序路由表”的经历
话不多说先上图,简要说明一下干了些什么事.图可能太模糊,可以点svg看看 背景 最近公司开展了小程序的业务,派我去负责这一块的业务,其中需要处理的一个问题是接入我们web开发的传统架构--模块化开发. ...
- Atitit main函数的ast分析 数组参数调用的ast astview解析
Atitit main函数的ast分析 数组参数调用的ast astview解析 1.1. Xxcls.main(new String[]{"","bb"}) ...
- 张小龙谈如何写E-mail软件
编者语:鼎鼎大名的Foxmail软件制作者,你一定不会陌生吧!本刊第三期特刊还刊登过此君的生活照一张,可谓威风八面.小编此次突发奇想,“死缠烂打”,费了九牛二虎之力,终于约他写了一篇有关写E-mail ...
- TableML-GUI篇(Excel编译/解析工具)
项目情况 本文接上篇TableML Excel编译/解析工具,本文主要介绍GUI工具的使用,及配置项,如果你想了解此工具更加详细的说明,请阅读上篇文章. 项目地址:https://github.com ...
- TableML-GUI篇(C# 编译/解析 Excel/CSV工具)
项目情况 本文接上篇TableML Excel编译/解析工具,本文主要介绍GUI工具的使用,及配置项,如果你想了解此工具更加详细的说明,请阅读上篇文章. 项目地址:https://github.com ...
- 【前端知识体系-JS相关】你真的了解JavaScript编译解析的流程吗?
1. JS编译解析的流程 1.1 JS运行分三步 语法分析(通篇扫描是否有语法错误),预编译(发生在函数执行的前一刻),解释执行(一行行执行). 1.2 预编译执行分五步 创建AO对象(Activat ...
- 快速写一个babel插件
es6/7/8的出现,给我们带来了很多方便,但是浏览器并不怎么支持,目前chrome应该是支持率最高的,所以为了兼容我们只能把代码转成es5,这应该算是我们最初使用babel的一个缘由,随着业务的开发 ...
- (6)webpack使用babel插件的使用
为什么要使用babel插件? 首先要了解babel插件是干嘛的,随着js的语法规范发展,出现了越来越多的高级语法,但是使用webpack打包的时候,webpack并不能全部理解这些高级语法,需要我们使 ...
随机推荐
- JDK 自带的观察者模式源码分析以及和自定义实现的取舍
前言 总的结论就是:不推荐使用JDK自带的观察者API,而是自定义实现,但是可以借鉴其好的思想. java.util.Observer 接口源码分析 该接口十分简单,是各个观察者需要实现的接口 pac ...
- Dedecms判断当前栏目下是否有子栏目
使用dedecms建网站,有时为了某种功能的需要,需要通过代码判断当前栏目下是否有子栏目,如果有,显示一种样式,如果没有,显示另一种样式. dedecms判断当前栏目下是否有子栏目可使用以下的代码进行 ...
- Vue学习路线
前言:学习Vue已经两个月了,目前前端的框架用得比较多的就是Bootstrap和Vue,而Bootstrap是开发人员用得较多,因为较为简单,上手也快.Vue是目前很火的数据驱动框,17年的时候就开始 ...
- C# 解决“请求被中止: 未能创建 SSL/TLS 安全通道”的问题
最近在开发项目的时候,使用爬虫抓取网络数据的时候,当请求Web数据时,碰到了“请求被中止: 未能创建 SSL/TLS 安全通道”的问题,尝试过很多网上的方法,例如添加证书等都没有用.最后在GitHub ...
- CF 1131C Birthday
C. Birthday time limit per test 1 second memory limit per test 256 megabytes input standard input ou ...
- 【CF461E】Appleman and a Game 倍增floyd
[CF461E]Appleman and a Game 题意:你有一个字符串t(由A,B,C,D组成),你还需要构造一个长度为n的字符串s.你的对手需要用t的子串来拼出s,具体来说就是每次找一个t的子 ...
- 11.11luffycity(5)
2018-11-11 17:52:46 昨天和今天把两个接口写完啦! 今天双十一,感觉没啥感觉!!!买了个耳机,原价24, -5-7. 最后12买了 由于这个今天没源码,所以贴上笔记, 越努力,越幸 ...
- [ERROR] - Error reading string. Unexpected token: StartObject. Path 'formData', line 1, position 13.
公司流程框架: businessData 为 string 所有要使用JSON.stringify();
- ajaxFileUpload只能上传一次,和上传同名图片不能上传等bug问题
createUploadForm: function (id, fileElementId) { //create form var formId = 'jUploadForm' + id; var ...
- Luogu 1093 - 奖学金 - [排序水题]
题目链接:https://www.luogu.org/problemnew/show/P1093 题目描述某小学最近得到了一笔赞助,打算拿出其中一部分为学习成绩优秀的前5名学生发奖学金.期末,每个学生 ...