pfan123 / Articles

经验文章
169 stars 25 forks source link

代码编译 - Babel Compiler #71

Open pfan123 opened 4 years ago

pfan123 commented 4 years ago

Babel 是一个 JavaScript 编译器

Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。 Babel 能帮我们做的的事情:

JavaScript 社区其实有非常多 parser 实现,比如 AcornEsprimaRecastTraceurCherow 等等。但我们还是选择使用 Babel,主要有以下几个原因:

代码的本质

不管是任意语言的代码,其实它们都有两个共同点

第一点很好理解,既然代码是字符串构成的,我们要修改/编译代码的最简单的方法就是使用字符串的各种正则表达式。例如我们要将 JSON 中一个键名 foo 改为 bar,只要写一个简单的正则表达式就能做到:

jsonStr.replace(/(?<=")foo(?="\s*:)/i, 'bar')

编译就是把一段字符串改成另外一段字符串。

Babel

JavaScript 社区其实有非常多 parser 实现,比如 AcornEsprimaRecastTraceurCherow 等等。但我们还是选择使用 Babel,主要有以下几个原因:

Babel-core(@babel/core)

@babel/core 功能可以 js 代码转换为 低版本代码 / ast / parse ,方便各个插件分析语法进行相应的处理。有些新语法在低版本 js 中是不存在的,如箭头函数,rest 参数,函数默认值等,这种语言层面的不兼容只能通过将代码转为 ast,分析其语法后再转为低版本 js。

 提供 API 方法
transform
transformSync
transformAsync
transformFile
transformFileSync
transformFromAst
transformFromAstSync
transformFromAstAsync
parse
parseSync
parseAsync

plugin-transform-runtime(@babel/plugin-transform-runtime)

@babel/plugin-transform-runtime 使用上依赖 @babel/runtime(可以在 @babel/plugin-transform-runtime 包中package.json中查看),babel 编译 es6 到 es5 的过程中,plugin-transform-runtime 为新特性的 API 添加私有 Helper 方法实现非侵入式不污染全局 API, 私有 Helper 方法实现存在 @babel/runtime 中 。

提到 plugin-transform-runtime 通常会与 babel-polyfill 对比,babel-polyfill 实现是改 API Prototype 进行实现会造成全局污染,因此开发过程中通常针对第三方模块或者组件库时使用 plugin-transform-runtime,平常的项目使用 babel-polyfill 即可

Babel-parser(@babel/parser)

@babel/parser 就是 Babelparser。它可以把一段符合规范的 JavaScript 代码输出成一个符合 Esprima 规范的 AST。 大部分 parser 生成的 AST 数据结构都遵循 Esprima 规范,包括 ESLint 的 parser ESTree。这就意味着我们熟悉了 Esprima 规范的 AST 数据结构还能去写 ESLint 插件。

我们可以尝试解析 n * n 这句简单的表达式:

import * as parser from "@babel/parser";

const code = `n * n`

parser.parse(code)

最终 @babel/parser 会解析成这样的数据结构:

结构:

img

可以使用 ASTExploroer 快速地查看代码的 AST

Babel-traverse (@babel/traverse)

babel-traverse 可以遍历由 Babylon 生成的抽象语法树,并把抽象语法树的各个节点从拓扑数据结构转化成一颗路径(Path)树,Path 表示两个节点之间连接的响应式(Reactive)对象,它拥有添加、删除、替换节点等方法。当你调用这些修改树的方法之后,路径信息也会被更新。除此之外,Path 还提供了一些操作作用域(Scope) 和标识符绑定(Identifier Binding) 的方法可以去做处理一些更精细复杂的需求。可以说 babel-traverse 是使用 Babel 作为编译器最核心的模块。

尝试一下把一段代码中的 n * n 变为 x * x

import * as parser from "@babel/parser";
import traverse from "@babel/traverse";

const code = `function square(n) {
  return n * n;
}`;

const ast = parser.parse(code);

traverse(ast, {
  enter(path) {
    if (path.isIdentifier({ name: "n" })) {
      path.node.name = "x";
    }
  }
})

Babel-types(@babel/types)

babel-types 是一个用于 AST 节点的 Lodash 式工具库,它包含了构造、验证以及变换 AST节点的方法。 该工具库包含考虑周到的工具方法,对编写处理 AST 逻辑非常有用。例如我们之前在 babel-traverse中改变标识符 n 的代码可以简写为:

发现使用 babel-types能提高我们转换代码的可读性,在配合 TypeScript 这样的静态类型语言后,babel-types 的方法还能提供类型校验的功能,能有效地提高我们转换代码的健壮性和可靠性

Other Resources

babel

Babel 中文网

Babel 用户手册

astexplorer