Skip to content

代码编译 - Babel Compiler #71

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
pfan123 opened this issue Jun 21, 2020 · 0 comments
Open

代码编译 - Babel Compiler #71

pfan123 opened this issue Jun 21, 2020 · 0 comments

Comments

@pfan123
Copy link
Owner

pfan123 commented Jun 21, 2020

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

@pfan123 pfan123 changed the title 代码转换本质 代码编译 - Babel Compiler May 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant