ts-migrate:Airbnb进行大规模TypeScript迁移的神器
共 3512字,需浏览 8分钟
·
2020-08-27 11:20
这是奶爸码农第71篇原创文章,点击上方蓝字关注
引言
业界对于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所需要的主要步骤。
首先是创建tsconfig.json文件。ts-migrate会提供一个默认基础配置文件,下面是个例子:
{
"extends": "../typescript/tsconfig.base.json",
"include": [".", "../typescript/types"]
}
下一步是将代码后缀从.js/.jsx转换成.ts/.tsx,自动化实现这步其实非常简单。
第三步是执行codemod,ts-migrate通过plugins方式来组织代码转换的能力。通过AST解析工具,我们将原有的JS代码,进行解析,分析类型,添加声明,然后生成相应的TS代码。
codemod可以理解成一种基于AST对源代码进行修改的方式,如下图所示,一个函数表达可以通过AST解析成一个语法树。
通过如下转换器,可以实现将所有变量申明进行翻转:
会输出如右图所示的代码:
ts-migrate概览
ts-migrate有三个部分组成,并且开源在https://github.com/airbnb/ts-migrate
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... */}
some text
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 -
推荐阅读:
Airbnb:大规模代码迁移至Apollo+GraphQL的实践
CSR、SSR、NSR、ESR傻傻分不清楚,一文帮你理清前端渲染方案!
『奶爸码农』从事互联网研发工作10+年,经历IBM、SAP、陆金所、携程等国内外IT公司,目前在美团负责餐饮相关大前端技术团队,定期分享关于大前端技术、投资理财、个人成长的思考与总结。