Open buddywang opened 3 years ago
parse:解析代码生成 ast
transform:遍历 ast(访问者模式),对 ast 做增删改
代码转换功能以插件的形式出现,插件是小型的 JavaScript 程序,用于指导 Babel 如何对代码进行转换。
const MyVisitor = { Identifier: { enter() { console.log("Entered!"); }, exit() { console.log("Exited!"); } } }; // 每当在树中遇见一个 Identifier 的时候会调用 Identifier() 方法
generate:打印 ast(生成最终代码)并生成source maps,每种 node 会对应一个打印方法
// 插件长这样 // export default function(babel) { export default function({types: t}) { return { visitor: { Identifier(path, state) {}, ASTNodeTypeHere: { enter(path, state) {}, exit() {} } } }; };
ast 由 node 组成
Path 是表示两个节点之间连接的对象,在某种意义上,Path 是一个节点在树中的位置以及关于该节点各种信息的响应式 Reactive 表示。
{ "parent": {...}, "node": {...}, "contexts": [], "data": {}, "removed": false, "state": null, "parentPath": null, "context": null, "container": null, "parentKey": null, "key": null, "scope": null, "type": null, "typeAnnotation": null, ... }
通常通过操作 path 来更改 node
const MyVisitor = { Identifier(path) { console.log("Visiting: " + path.node.name); } };
{ path: path, block: path.node, parentBlock: path.parent, parent: parentScope, bindings: [...], ... }
所有引用属于特定的作用域,引用和作用域的这种关系被称作:绑定(binding)。单个绑定看起来像这样︰
{ identifier: node, scope: scope, path: path, kind: 'var', referenced: true, references: 3, referencePaths: [path, path, path], constant: false, constantViolations: [path], ... }
有了这些信息你就可以查找一个绑定的所有引用,并且知道这是什么类型的绑定(参数,定义等等),查找它所属的作用域,或者拷贝它的标识符。你甚至可以知道它是不是常量,如果不是,那么是哪个路径修改了它。
下面的插件例子功能是把代码里 console.xxx 输出日志的语句删除,第一位参数是 "do not move" 的保留:
const simpleDeleteLogPlugin = ({ types, template }, options, dirname) => { return { visitor: { CallExpression(path, state) { const calleeName = path.get('callee').toString(); const arg = path.node.arguments if (calleeName.indexOf('console') !== -1) { if (arg.length > 0 && types.isStringLiteral(arg[0], { value: "do not move" })) { // do not move } else { path.remove() } } } } } } module.exports = simpleDeleteLogPlugin;
使用结果:
const { transformFromAstSync } = require('@babel/core'); const parser = require('@babel/parser'); const simpleDeleteLogPlugin = require('./plugin/demo-plugin'); const sourceCode = ` console.log(console.log(123)) function a() { const obj = { b: 2 } console.log(obj); return obj } const c = new Promise() const b = a() console.log('do not move', b)` const ast = parser.parse(sourceCode, { sourceType: 'unambiguous' }); const { code } = transformFromAstSync(ast, sourceCode, { plugins: [ simpleDeleteLogPlugin ] }); console.log(code); // 输出结果 // require("core-js/modules/es.object.to-string.js"); // require("core-js/modules/es.promise.js"); // function a() { // var obj = { // b: 2 // }; // return obj; // } // var c = new Promise(); // var b = a(); // console.log('do not move', b);
问题
babel 流程(转译器流程)
parse:解析代码生成 ast
transform:遍历 ast(访问者模式),对 ast 做增删改
generate:打印 ast(生成最终代码)并生成source maps,每种 node 会对应一个打印方法
转译插件开发思路
数据结构
node
ast 由 node 组成
Paths(路径)
Path 是表示两个节点之间连接的对象,在某种意义上,Path 是一个节点在树中的位置以及关于该节点各种信息的响应式 Reactive 表示。
通常通过操作 path 来更改 node
Scopes(作用域)
Bindings(绑定)
所有引用属于特定的作用域,引用和作用域的这种关系被称作:绑定(binding)。单个绑定看起来像这样︰
有了这些信息你就可以查找一个绑定的所有引用,并且知道这是什么类型的绑定(参数,定义等等),查找它所属的作用域,或者拷贝它的标识符。你甚至可以知道它是不是常量,如果不是,那么是哪个路径修改了它。
babel提供的工具包
例子
下面的插件例子功能是把代码里 console.xxx 输出日志的语句删除,第一位参数是 "do not move" 的保留:
使用结果:
参考