pfan123 / Articles

经验文章

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

代码编译 - Babel Compiler

pfan123 opened this issue · comments

Babel 是一个 JavaScript 编译器

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

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块),Babel 7.0 后 core-js ( 推荐使用 V3 版本 ) 已经内置了 Polyfill 通过 useBuiltIns 开启
  • 源码转换 (codemods)

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

  • Babel 可以解析还没有进入 ECMAScript 规范的语法。例如装饰器这样的提案,虽然现在没有进入标准,但是已经广泛使用有一段时间了
  • Babel 提供插件机制解析 TypeScriptFlowJSX这样的 JavaScript 超集,不必单独处理这些语言
  • Babel 拥有庞大的生态,有非常多的文档和样例代码可供参考
  • 除去 parser 本身,Babel 还提供各种方便的工具库可以优化、生成、调试代码

代码的本质

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

  • 都是由字符串构成的文本
  • 都要遵循自己的语言规范

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

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

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

Babel

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

  • Babel 可以解析还没有进入 ECMAScript 规范的语法。例如装饰器这样的提案,虽然现在没有进入标准,但是已经广泛使用有一段时间了
  • Babel 提供插件机制解析 TypeScriptFlowJSX这样的 JavaScript 超集,不必单独处理这些语言
  • Babel 拥有庞大的生态,有非常多的文档和样例代码可供参考
  • 除去 parser 本身,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