【CSDN 编者按】哪种类型系统将主宰JavaScript的世界?不同时代有不同的答案。本文作者在选择Web生态系统用于构建的产品Factorial时,对比当时Facebook的Flow和微软的TypeScript,综合考虑选择了略胜一筹的Flow。但随着时间的推移,TypeScript开始在该领域展现主导地位。于是决定换框架,来看看他们是怎么做的吧。
原文链接:https://labs.factorialhr.com/posts/how-we-migrated-400k-lines-of-code-from-flow-to-typescript
本文由 CSDN翻译,转载请注明来源出处。
在构建我们的产品Factorial之际,Web生态系统正在经历一场激战:哪种类型系统将主宰JavaScript的世界。两大主要竞争者分别是来自Facebook的Flow和来自微软的TypeScript。就功能而言,这两大框架似乎并没有太大区别,只不过Flow支持async/await和装饰器等不错的功能,所以略胜一筹。更不用说它与React都出自Facebook之手,因此在当时,它与React配合使用的效果更佳。
在Web(无论是前端还是后端)不支持类型检查的时代,将类型系统(无论是哪一种)添加到最小可行产品成为了代码质量和维护方面的及格线。最后,我们还是选择了Flow。
随着时间的推移,TypeScript开始在该领域展现出主导地位。TypeScript的发展更快、社区更大、拥有大量的类型库、拥有更好的插件和IDE支持,以及许多其他优势,例如在npm生态系统中的广泛采用,因此许多拥有类型的库都得以公开。
另一方面,我们深受Flow检查器无休止的分析和内存泄漏方面的折磨,尽管TypeScript检查器在性能方面的表现也差强人意,但它在配置方面的粒度与各种标志确实非常吸引人。于是,我们决定换框架。
迁移的注意事项
在开始迁移代码之前,我们首先研究了其他团队,他们也面临着相同的挑战:将庞大的代码库从Flow(或JavaScript)迁移到TypeScript。我们做了很多研究,并在动手迁移代码之前总结了一些经验。
在代码迁移的策略方面,我们有两个选择:
方法A:将大部分代码库都迁移过去,以尽可能减少思维模式混乱的问题。这就需要渐进式迁移,所以我们选择从没有依赖关系的文件夹开始下手。
方法B:创建一个“一次性”脚本来迁移整个代码库。通过这种方法正确迁移代码的难度非常大,因为我们需要将Flow的每一行代码都自动地转换成TypeScript。
最后,我们决定混合使用这两种方法(吸取两家之长)。我们发现,代码库中有一大块代码与其他文件之间是解耦的,很适合作为迁移的首个目标,并验证在生产中使用Typescript的想法。令人惊讶的是,我们率先迁移了这部分代码,并成功交付了。我们证明了Flow和TypeScript可以并存。因此,我们对一次性交付其余源代码的信心大增。
为了遵守上面提到的首要约束(避免代码冻结),我们想方设法将尽可能多地将安全代码合并到主分支中。所谓“安全代码”指的是将TypeScript的配置文件、输入定义等推送到代码库。
另一方面,我们在一个分支中写了一个bash脚本,其中包含了迁移的所有步骤。以下是该脚本中包含的迁移步骤:
运行一组自定义的JsCodeShift转换;
在Git上,将所有js和jsx文件重命名为ts和tsx;
针对上述所有代码运行flow-to-ts转换;
运行lint和格式化工具,检查格式;
运行tsc验证并查看不符合规范的地方。
考虑到上述步骤,我们决定不断迭代,以逐步减少tsc检测出的规范违反的代码。对于所有重复出现的规范违反,我们尝试编写了一些JSCodeShift转换。我们大量使用ASTExplorer作为实验场所,来编写我们的自定义转换。由于我们的代码库是Flow,所以我们决定利用这个先天优势,自动调整与类型相关的代码,以简化flow-to-ts的转换。
在这一步中,我们编写了十多个自定义转换,然后同时执行所有转换。
module.exports = function (file, api, options) {const fixes = [
transformation1,
transformation2,
transformation3,
...
]
return fixes.reduce((src, fix) => {
if (typeof src === 'undefined') {
return
}
return fix({ ...file, source: src }, api, options)
}, file.source)
}
根据bash脚本,下一步是文件的重命名。我们使用了git mv,尽可能保持Git历史记录:
find $@ -iname "*.js" | while read line; do git mv -- $line ${line%.js}.ts; done;
find $@ -iname "*.jsx" | while read line; do git mv -- $line ${line%.jsx}.tsx; done;
这一步的工作很简单,很容易被忽略,但从长远来看却非常重要。
在这一步中,脚本需要使用flow-to-ts包执行主要的转换。flow-to-ts是Flow项目的一个核心工具,它理解许多Flow的常见模式,这些模式可以确定地映射到Typescript。它的内部是一个巨大的 JSCodeShift 转换,但有一些预设的配置可以让该工具的使用更容易。
尽管flow-to-ts的底层使用了recast ,而且会尽可能地保留代码原来的格式,但在转换到过程中会新建一些代码。这些代码需要格式化,以匹配我们的内部规则。
这一步有点慢,但不可略过。尽可能保持代码原来的格式对于保留Git历史记录至关重要。
有了这个脚本,完成迁移到最后一步就是解决其他非重复性的规则违反。老实说,这是最困难的一部分工作,因为我们必须逐一排查并解决。在这个过程中,由于代码库是Flow,所以我们可以不断地将类型修复推送到master分支,同时还不会影响到应用程序的行为。
我们成功了!我们一次性将40万行代码从Flow迁移到了TypeScript!在这个过程中,我们积累了很多宝贵的经验。
保持透明,持续沟通,以获得团队的支持。最糟糕的情况是,在没有取得每一位团队成员支持的情况下,开展大量的工作。我们建议,分享、参与和记录迁移的每一个步骤、里程碑和关键日期。
在开始动手迁移代码之前,做好准备。这是一个漫长的过程,需要深入了解代码库。你可能会发现,为了完成最后一步的迁移,前面的有些步骤需要返工。举个例子,我们发现装饰器的使用会影响flow-to-ts的执行。
仔细思考迁移后代码的情况,并提出正确的问题。迁移后的所有工具都准备好了吗?开放的拉取请求会影响迁移吗?迁移后是否会出现lint和格式的问题?我们在合并代码之前,就已经解决了这些问题。
最好将所有与迁移相关的提交压缩为一个提交。这可以简化部署,如果出现意外情况,也很方便回滚。
我们的TypeScript之旅并没有就此结束。事实上,这只是一个开始!在采用该语言几天后,我们决定启用编译器中的strict标志。对我们来说幸运的是,我们可以逐步完成这项工作,因为这些标志可以一个个添加。
此外,我们也在考虑其他工具和机会。例如,目前我们使用Webpack来构建应用程序,但我们已经开始考虑esbuild和swc,因为这两个编译器对 TypeScript的支持更好。
几个月前,我们也惴惴不安,不知道能否在前端代码库中使用TypeScript。我们分析了利弊,并为此次大规模的代码迁移制定了明确的计划。两周后的今天,我们团队实现了更快的交付,而且还大幅提高了代码质量。这不仅可以减少生产中的错误,而且还可以让客户更满意。
如果你也面临相同的困境,希望这篇文章对你有所帮助。
《新程序员003》正式上市,50余位技术专家共同创作,云原生和数字化的开发者们的一本技术精选图书。内容既有发展趋势及方法论结构,华为、阿里、字节跳动、网易、快手、微软、亚马逊、英特尔、西门子、施耐德等30多家知名公司云原生和数字化一手实战经验!
☞ ☞React、Angular、Vue霸榜,薪酬以50k-100k为主,JavaScript 2021年度报告正式发布!