babel核心成员;AST解析;babel分层理念;babel-loader;自定义eslint;
摘录&心得
1 Babel是什么
- Babel 不仅仅是一个工具,更是一个工具链(toolchain),是前端基建中绝对重要的一环。
- Babel is a JavaScript compiler.
- 编译是 Babel 的核心目标
- 自身的实现基于编译原理,深入 AST(抽象语法树)来生成目标代码;
- Babel 需要工程化协作,需要和各种工具(如 Webpack)相互配合。
- Babel 是一个使用 Lerna 构建的 Monorepo 风格的仓库
核心理念
是 Babel 实现转换的核心,提供了基础的编译能力
@babel/core 的能力由更底层的 @babel/parser、@babel/code-frame、@babel/generator、@babel/traverse、@babel/types等包提供。
在终端中通过命令行方式运行
- @babel/cli 负责获取配置内容,并最终依赖了 @babel/core 完成编译
@babel/core是@babel/cli的peerDependencies
2.3 @babel/preset-env
是直接暴露给开发者在业务中运用的包能力。
- @babel/preset-env 允许我们配置需要支持的目标环境,利用 babel-polyfill 完成补丁的接入。
- @babel/polyfill 其实就是 core-js 和 regenerator-runtime 两个包的结合,详见上一节
- @babel/polyfill 目前已经计划废弃,新的 Babel 生态(@babel/preset-env V7.4.0 版本)鼓励开发者直接在代码中引入 core-js 和 regenerator-runtime。
如何根据目标适配环境,按需引入业务中所需要的 polyfills 呢?
- @babel/preset-env 通过 targets 参数,按照 browserslist 规范,结 合core-js-compat,筛选出适配环境所需的 polyfills(或 plugins)
- 结合6 core-js及垫片polyfill思考。
2.4 helpers 函数相关包
@babel/plugin-transform-runtime结合@babel/runtime可以重复使用 Babel 注入的 helpers 函数,达到节省代码大小的目的:
在启用 @babel/plugin-transform-runtime 插件后,上述代码的编译结果可以变为:var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var Person = function Person() {
(0, _classCallCheck2.default)(this, Person);
};
@babel/plugin-transform-runtime
- 需要和 @babel/runtime 配合使用
- 除了可以对产出代码瘦身以外,还能避免污染全局作用域。
- 用于编译时,作为 devDependencies 使用;
- 将业务代码编译,引用 @babel/runtime 提供的 helpers,达到缩减编译产出体积的目的;
- 需要和 @babel/runtime 配合使用
@babel/runtime
@babel/standalone
- 可以在非 Node.js 环境自动编译含有 text/babel 或 text/jsx 的 type 值的 script 标签
@babel/standalone
@babel/plugin是 Babel 插件集合。
- @babel/plugin-syntax-* 是 Babel 的语法插件。
- 扩展 @babel/parser 的一些能力,提供给工程使用。
- @babel/plugin-proposal-* 用于编译转换在提议阶段的语言特性。
@babel/plugin-transform-* 是 Babel 的转换插件。
@babel/template
- 封装了基于 AST 的模板能力,可以将字符串代码转换为 AST。
- 在生成一些辅助代码(helper)时会用到这个库。
- @babel/node
- 类似 Node.js Cli,@babel/node 提供在命令行执行高级语法的环境
- 运行时进行编译转换,因此运行时性能上会有影响
@babel/register
基础层:提供了基础的编译能力,完成分词、解析 AST、生成产出代码的工作。
- 辅助层:可以理解为各种utils,将一些抽象能力下沉为辅助层,这些抽象能力被基础层使用。
- 胶水层:与corejs、polyfill协作的层,用来“把代码解释得大家都懂”
- 最好结合6 core-js及垫片polyfill一起研究
- (原文:完成了代码编译降级所需补丁的构建、运行时逻辑的模块化抽象等工作。)
应用层:终端命令行、Webpack loader、浏览器端编译等应用级别的能力。
3.1 特殊的babel-loader
通过 Babel 对代码的编译过程,可以从微观上缩小为前端基建的一个环节,这个环节融入整个工程中,也需要和其他环节相互配合—— babel-loader 就是 Babel 结合 Webpack,融入整个基建环节的例子。
3.2 自定义eslint
ESLint 的解析工具只支持最终进入 ECMAScript 语言标准的特性
- 如果想对试验性特性或者 Flow/TypeScript 进行代码检查,ESLint 提供了更换 parser 的能力。
- @babel/eslint-parser 就是配合 ESLint 检验合法 Babel 代码的解析器。
- 实现原理:ESLint 支持 custom-parser,它允许我们使用自定义的第三方编译器
使用了 espree 作为一个 custom-parser 的场景:
{
"parser": "./path/to/awesome-custom-parser.js"
}
// awesome-custom-parser.js
var espree = require("espree");
exports.parseForESLint = function(code, options) {
return {
ast: espree.parse(code, options),
services: {
foo: function() {
console.log("foo");
}
},
scopeManager: null,
visitorKeys: null
};
};
@babel/eslint-parser源码的实现:
export function parseForESLint(code, options = {}) {
const normalizedOptions = normalizeESLintConfig(options);
const ast = baseParse(code, normalizedOptions);
const scopeManager = analyzeScope(ast, normalizedOptions);
return { ast, scopeManager, visitorKeys };
}
- 返回值
- ast 是 estree 兼容的格式,可以被 ESLint 理解。
- visitorKeys 定义了自定义的编译 AST 能力
- ScopeManager 定义了新(试验)特性自定义的作用域。
3.3 总结
由此可见,Babel 生态和前端工程中的各个环节都是打通开放的。它可以以 babel-loader 的形式和 Webpack 协作,也可以以 @babel/eslint-parser 的方式和 ESLint 合作。现代化的前端工程是一环扣一环的,作为工程链上的任意一环,插件化能力、协作能力将是设计的重点和关键。
这句话很好就原样摘抄了,babel因此具备很强的可拔插、可调试、灵活配置特性,我始终觉得这也是普通软件开发中应该参考的设计模式。