参与 Babel 贡献:三个值得牢记的经验教训
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
熟悉一个新的代码库总会带来挑战,Babel 也不例外。
作为 Google Summer of Code 2017 项目的一部分,我一直在参与 Babel 的工作,致力于更新 Babel 的转换器和 Babylon 解析器,以适应规范的变化并实现新功能。
以下是我到目前为止从这段经历中学到的几点心得。
1. 没错,沟通很重要
为了更深入地了解代码库,我仔细翻阅了 Babel 的开放问题列表,并找到了一个相对容易处理的问题(issue #5728)。
为了确认我的理解无误,我在该讨论串中快速提了一个问题:
得到澄清后,我开始着手修改插件,使其在转译期间不抛出“运行时”错误,而只在代码实际运行时抛出。一段引人注目的代码引起了我的注意:
for (const violation of (binding.constantViolations: Array)) {
throw violation.buildCodeFrameError(messages.get("readOnly", name));
}
现在需要做的是在生成的代码中实际插入一个 throw 语句,这并不算太难。然而,仍然存在一些情况,运行时错误会在其他地方抛出,而这些错误与当前文件并无直接关系。
由于我想去探索 Babel 代码库的其他部分,便将这个问题暂时搁置,留待后续处理。
没过多久,我就收到了一个关于该问题的、呃、有趣的更新……等等,什么情况?
我其实从未明确表示我正在着手修复该问题,但以为发帖就暗示了我打算处理它。
哎呀。
2. 快照测试的不足之处
在开始另一次问题搜寻时,我偶然发现了 issue #5656:
当在嵌套函数中被遮蔽时,参数被取消优化
这是一个功能请求(我认为)。如果内部函数通过参数(或在我的情况下是 rest 参数)遮蔽了该名称,则参数不会被优化。
输入代码
JavaScriptconst log = (...args) => console.log(...args);
function test_opt(...args) {
log(...args);
}
function test_deopt(...args) {
const fn = (...args) => log(...args);
fn(...args);
}...
预期行为与当前行为
我期望代码能够被优化,从而在整个过程中使用 .apply( thisArg, arguments )。 然而,在 test_deopt 中,外层的 ...args 被复制,只是为了传递给内部的 fn。 我可以验证,如果重命名 test_deopt 的 ...args 或 fn 箭头函数的 ...args,问题就会消失。
这是怎么回事?
现在的情况是,这段代码会生成如下内容:
var log = function log() {
var _console;
return (_console = console).log.apply(_console, arguments);
};
function test_opt() {
log.apply(undefined, arguments);
}
function test_deopt() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { // unnecessary loop
args[_key] = arguments[_key];
}
var fn = function fn() {
return log.apply(undefined, arguments);
};
fn.apply(undefined, args);
}
看到那里的 for 循环了吗?通常这是必需的,因为 arguments 对象并非真正的数组——例如,如果你尝试运行 arguments.slice(),它会失败得很惨。
然而,在这个例子中,它只是被传递给 Function.prototype.apply。令人惊讶的是,Babel 已经费心优化了这种特定情况,就像上面 test_opt 示例中那样。
尝试修复
那么我做了什么呢?我将问题文件添加为一个新的测试用例,试图让输出符合我的期望。
“测试怎么又失败了?稍微改改应该就能解决吧。”
尽管反复执行 make test-only 并修改代码中的标识符转换逻辑,每次改动都只是导致另一批测试失败。
Chromium 调试器的"乐趣"
沮丧又困惑的我终于启动 Node.js 调试器,想一步步看看到底发生了什么。
喝完水回到电脑前,迎接我的是疯狂闪烁的硬盘指示灯和几乎卡死的系统。
Holding my computer together with judicious applications of Alt + SysRq + F, I managed to work through the flow of things¹ and figure out how exactly the code worked.
即便如此,我还是没明白为什么调试器认定这段"必要"代码(我最初认为)应该被移除——而这正是我原始修复方案删除的部分。
真正的问题?
看到上面标绿的代码了吗?这段代码本不该存在,尽管测试"预期"它出现。
简而言之:测试用例本身有缺陷。真是棒极了。 :/
真正的修复方案 是创建 referencesRest 函数,确保展开运算符作用于原始参数,而不是被其他作用域的变量遮蔽。
注¹:后来发现向 DevTools 工作区添加大型文件夹会导致内存泄漏直至 OOM(我提交的相关 bug 报告)。
那我们为什么还要用快照测试?!
首先,创建测试极其便捷——只需让 Babel 运行测试用例生成预期文件即可。这种方式时间成本低,同时能防范大部分潜在错误。
特别是对于 Babel 这类程序,其他测试方式会更复杂。例如检查 AST 特定节点不仅编写耗时,当转换逻辑变更时也容易隐式失效。
综上,我总结了几个经验:
-
确保测试用例本身正确——永远不要想当然!
-
调试器确实能帮你看清执行过程
-
有些问题需要时间沉淀——毫无头绪时不妨暂停或切换任务
3. 团队会议!
虽然这不算严格意义的"issue",但还是值得说说 :)
在多人协作项目中,定期交流讨论工作重点总是有益的。
那么具体怎么操作呢?!
唉,又是开会。
当成员遍布全球时,协调沟通本就不易,但我们仍尽力找到了解决方案。
时区难题
在跨时区的开源项目中,选择合适会议时间很快变成了繁琐的协调工作。
尽管时差跨度很大,我们最终还是勉强敲定了方案。
可惜好景不长。为照顾其他成员,我们不得不采取双周轮换制(UTC 13:00 和 16:00),这意味着我每两周才能参加一次会议。
尽管如此,我们在协调 Babel 关键变更的各个部分修复方面取得了显著进展,包括对 TypeScript 的支持、转换插件执行顺序的调整,以及紧跟 TC39 的最新规范变更。
后续计划
我们正在持续完善 Babel 7 以供广泛使用,届时将推出多项新功能。
我正与其他贡献者合作,将更新后的类字段(Class Fields)规范提案支持集成到 Babel 中,方便开发者测试并提供反馈。
借此机会,我要衷心感谢所有 Babel 导师和贡献者,从初次接触到今天,他们始终在代码评审和提案指导方面给予我极大帮助。
想了解更多关于 Babel 的信息?请访问我们的贡献指南页面并加入 Slack 社区!
关于 Karl 的更多信息
Karl Cheng 是来自澳大利亚悉尼的GSoC 2017学生。欢迎在 GitHub (Qantas94Heavy) 和 Twitter (@Qantas94Heavy) 关注他!
更多详情请参阅我们关于Summer of Code的首篇博文!