ts-migrate:Airbnb进行大规模TypeScript迁移的神器

3,036 阅读6分钟

引言

业界对于TypeScript已经是政治正确的选择了,越来越多的前端库/框架均采用TS,例如Ant Design 4.0、Vue 3.0等。同时,TS也提供了更健全的语法能力、静态的代码类型检查、更友好的代码辅助提示等等。

早在2019年,Airbnb就已经在JSConf上分享了其要对现有前端项目进行大规模TypeScript迁移转换的计划。但是对于现有项目进行TypeScript转换并不是一件轻松,Airbnb总共有超过200万行代码,超过100个内部npm包。

迁移策略

大规模代码迁移往往是一项非常复杂的工作,Airbnb探索过几个迁移策略:

  • 混合迁移策略:一个个文件进行迁移,修复类型错误。allowJS配置可以允许JS和TS文件在一个项目中并存。使用混合迁移策略可以不用暂停当前开发,逐步的一个个文件进行迁移。但是,对于研发人员需要更长的适应和迁移时间。

  • 完全迁移策略:一次性实现文件完全迁移。我们会使用any或者@ts-ignore注释帮助项目编译,随后会补充更加具体的类型申明。

采用完全迁移策略有很多的好处:

  • 项目的一致性:在完全迁移策略下,项目使用使用TypeScript来编写,因此,开发者并不需要在JS和TS之间进行切换。

  • 修复类型比修复文件容易:修复一个完整文件非常复杂,因为它可能有很多外部依赖,因此,采用混合迁移的方式,很难保证迁移的进度和状态。

因此,Airbnb采用了完全迁移策略。然而,一次性迁移完整项目难度非常大。Airbnb研发了一个转换工具:ts-migrate,在初试转换过程中,尽可能实现类型的自动转换。

当然,这个工具并不能保证实现完全没有错误的转换,但是在实际使用过程中,对于一个超过50000行代码、1000个文件的项目,从JavaScript转换到TypeScript使用这个工具往往只需要1天!

在Airbnb,React是主要前端框架,所以主要的codemods转换都是基于React概念。ts-migrate也可能可以适用于其他框架或者三方库。

迁移流程的步骤

让我们看下一个JavaScript项目转换到TypeScript所需要的主要步骤。

  1. 首先是创建tsconfig.json文件。ts-migrate会提供一个默认基础配置文件,下面是个例子:
{
  "extends": "../typescript/tsconfig.base.json",
  "include": [".", "../typescript/types"]
}
  1. 下一步是将代码后缀从.js/.jsx转换成.ts/.tsx,自动化实现这步其实非常简单。

  2. 第三步是执行codemod,ts-migrate通过plugins方式来组织代码转换的能力。通过AST解析工具,我们将原有的JS代码,进行解析,分析类型,添加声明,然后生成相应的TS代码。

ts-migrate概览

ts-migrate有三个部分组成,并且开源在github.com/airbnb/ts-m…

  • ts-migrate
  • ts-migrate-server
  • ts-migrate-plugins

转换工具并不能将一个项目完全迁移完成,但是,它会通过ignore的注释让编译器忽略错误,从而可以让项目尽可能运行起来,随后你可以再逐步修复错误问题。

迁移的整体过程如下:

  • 解析tsconfig.json
  • 创建.ts源代码文件
  • 把每一个文件都放到TS Server进行诊断,包括三种类型的诊断:semanticDiagnostics(语义诊断),syntacticDiagnostics(句法诊断)和suggestionDiagnostics(建议诊断)。通过这些诊断,工具会找到需要修改的代码内容,并且进行标记。
  • 对每一个文件执行plugin。plugin会对源代码进行修改,并且通知TS Server。

通用性Plugin

plugin都会放在ts-migrate-plugins目录下面,我们可以先看下两个插件:explicitAnyPlugin和declareMissingClassPropertiesPlugin。

exlicitAnyPlugin主要会对所有文件中的语义诊断错误进行处理。对于无法推导类型的变量添加any,可以帮助解决编译问题。

迁移前:

const fn2 = function(p3, p4) {}
const var1 = [];

迁移后:

const fn2 = function(p3: any, p4: any) {}
const var1: any = [];

declareMissingClassPropertiesPlugin同样也会找到类申明中缺失的类型,并且添加any修饰。

React相关的插件

reactPropsPlugin可以将PropTypes的类型信息转换成TypeScript的props类型申明。这个插件会在.tsx文件中执行,reactPropsPlugin会寻找所有PropTypes的定义,通过AST进行解析,并且将其转换成一个新的props申明:type Props = {...}。

React中的state和生命周期非常常见,我们通过两个插件来处理。

如果一个组件是有状态的,reactClassStatePlugin会生成一个新的语句: type State = any; reactClassLifecycleMethodsPlugin会给生命周期方法提供相应的类型申明。

整个工具依旧有很大的改进空间,但是,可以作为TypeScript的初始工具,很好的帮你开始整个迁移过程。这个工具并不支持hooks,因为Airbnb原始项目使用的是老版本的React。

确保项目编译成功

我们的目标是希望将项目进行基本类型转换,同时不改变任何代码行为。

通过工具转换可能会导致代码lint检查失败,因此,可以使用Prettier进行代码自动格式化,通过ESLint确保代码符合规范。

迁移过程的最后一步是保证所有TypeScript的编译错误可以发现并且修复。我们会在这些地方插入@ts-ignore并且提供备注。

// @ts-ignore ts-migrate(7053) FIXME: No index signature with a parameter of type 'string...
const { field1, field2, field3 } = DATA[prop];
// @ts-ignore ts-migrate(2532) FIXME: Object is possibly 'undefined'.
const field2 = object.some_property;

工具也会支持JSX语法

{*
// @ts-ignore ts-migrate(2339) FIXME: Property 'NORMAL' does not exist on type 'typeof W... */}
<Text weight={WEIGHT.NORMAL}>
  some text
</Text>
<input
  id="input"
  // @ts-ignore ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'.
  name={getName()}
/>

有了这些注释和提示,可以很方便研发人员进行修改。这些注释,也能够帮助我们了解代码的质量,并且发现潜在的代码问题。

总结

Airbnb的TypeScript代码转换还在进行中,我们依旧还有一些老旧代码项目使用JavaScript,我们也有不少$TSFixMe和@ts-ignore注释需要修复。

使用ts-migrate工具极大的提升了我们的迁移效率。研发人员更加关注于类型的修复。目前,我们已经转换了~86%的代码,到年底我们预期会达到95%。

-End-

关注**『奶爸码农』**微信公众号,从事互联网研发工作10+年,经历IBM、SAP、陆金所、携程等国内外IT公司,目前在美团负责餐饮相关大前端技术团队,定期分享关于大前端技术、投资理财、个人成长的思考与总结。