代码编译 - 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
实现,比如 Acorn
、Esprima
、Recast
、Traceur
、Cherow
等等。但我们还是选择使用 Babel
,主要有以下几个原因:
Babel
可以解析还没有进入 ECMAScript 规范的语法。例如装饰器这样的提案,虽然现在没有进入标准,但是已经广泛使用有一段时间了Babel
提供插件机制解析TypeScript
、Flow
、JSX
这样的JavaScript
超集,不必单独处理这些语言Babel
拥有庞大的生态,有非常多的文档和样例代码可供参考- 除去
parser
本身,Babel
还提供各种方便的工具库可以优化、生成、调试代码
代码的本质
不管是任意语言的代码,其实它们都有两个共同点
- 都是由字符串构成的文本
- 都要遵循自己的语言规范
第一点很好理解,既然代码是字符串构成的,我们要修改/编译代码的最简单的方法就是使用字符串的各种正则表达式。例如我们要将 JSON
中一个键名 foo
改为 bar
,只要写一个简单的正则表达式就能做到:
jsonStr.replace(/(?<=")foo(?="\s*:)/i, 'bar')
编译就是把一段字符串改成另外一段字符串。
Babel
JavaScript
社区其实有非常多 parser
实现,比如 Acorn
、Esprima
、Recast
、Traceur
、Cherow
等等。但我们还是选择使用 Babel
,主要有以下几个原因:
Babel
可以解析还没有进入 ECMAScript 规范的语法。例如装饰器这样的提案,虽然现在没有进入标准,但是已经广泛使用有一段时间了Babel
提供插件机制解析TypeScript
、Flow
、JSX
这样的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
就是 Babel
的 parser
。它可以把一段符合规范的 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
会解析成这样的数据结构:
结构:
可以使用 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
的方法还能提供类型校验的功能,能有效地提高我们转换代码的健壮性和可靠性