前置知识:实现一个最小的 JS 编译器
因为从 0 开始实现过于复杂,所以本文使用 bebel7 内置方法对输入代码进行修改。
babel7 内置包有:
@babel/parser
:将源码解析成 AST。对应 parse 阶段。
@babel/traverse
:遍历 AST 节点,并调用 visitor。对应 transform 阶段。
@babel/generate
:打印 AST,生成目标代码和 sourcemap。对应 generate 阶段。
@babel/types
:创建、判断 AST。
@babel/template
:根据模块批量创建 AST。
@babel/core
:核心引擎,解析配置。
@babel/helpers
:转换 esNext 所需的 AST。
@babel/helper-*
:操作 AST 的公共函数。
@babel/runtime
:
helper:@bebel/helper-*
的 runtime 版本。
corejs:esNext API 的实现。
regenerator:async、await 实现。
如何编写 babel 插件
去查找我们想修改的 Token 是属于什么类型的。可以在其他网站 进行查看,或者使用 babel/parse。
编写我们的 plugin 文件。在 visitor 里面写对应的 Token 的 visite 方法。
在bebel.config.json
中使用我们编写的插件,babel/core 会自动去导入执行。
查看输出的代码。
代码实现: 1 2 3 4 5 6 7 8 9 10 11 12 # 项目结构 babel-demo │ ├─babel.config.json ├─package.json ├─pnpm-lock.yaml ├─dist ├─plugins │ └babel-plugin-test-1.js └─src └index.js
依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { "name" : "babel-demo" , "version" : "1.0.0" , "scripts" : { "build" : "babel src -d dist" } , "keywords" : [ ] , "author" : "Edward Wang<wang.huiyang@outlook.com>" , "license" : "ISC" , "devDependencies" : { "@babel/cli" : "^7.14.8" , "@babel/core" : "^7.15.0" , "prettier" : "^2.3.2" } }
babel.config.json:
1 2 3 4 5 6 7 8 9 10 { "plugins" : [ [ "./plugins/babel-plugin-test-1" , { "ignore" : [ "info" ] } ] ] }
plugins/babel-plugin-test-1.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 module .exports = ({ types } ) => { return { visitor : { VariableDeclaration (path) { const node = path.node ; if (node.kind === "const" ) node.kind = "var" ; delete node.leadingComments ; delete node.trailingComments ; }, BinaryExpression (path) { const node = path.node ; const equalMap = { "==" : "===" , "!=" : "!==" , }; node.operator = equalMap[node.operator ] || node.operator ; }, Identifier (path) { const node = path.node ; if (node.name === "foo" ) node.name = "bar" ; }, ArrowFunctionExpression (path) { const node = path.node ; if (node.type === "ArrowFunctionExpression" ) { const body = path.get ("body" ).node ; if (body.type !== "BlockStatement" ) { const statement = []; statement.push (types.returnStatement (body)); node.body = types.blockStatement (statement); } node.type = "FunctionExpression" ; } }, ExpressionStatement (path, { opts : options }) { const node = path.node ; const { object, property } = node.expression .callee ; console .log (options); if (object.name === "console" ) { (i ) => i === property.name ); if (!ignore) { path.remove (); } } delete node.leadingComments ; delete node.trailingComments ; }, }, }; };
src/index.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const foo = ( ) => 1 const b = ( ) => { console .log ("a" ) const c = foo () console .info ("test foo console" ) } const obj = {}if (obj.a == obj.c ) { console .info ("equal" ) } if (obj.a != obj.c ) { console .err ("not equal" ) }
dist/index.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var bar = function ( ) { return 1 } var b = function ( ) { var c = bar () console .info ("test foo console" ) } var obj = {}if (obj.a === obj.c ) { console .info ("equal" ) } if (obj.a !== obj.c ) {}