特别说明
这是一个由 simviso 团队对 JSConf.Hawaii 中关于大规模应用Typescript相关话题进行翻译的文档,内容并非直译,其中有一些是译者自身的思考。分享者是Brie Bunge,Airbnb高级前端工程师。
视频地址:大规模应用TypeScript「2019 JSConf -Brie Bunge」
视频翻译文字版权归 simviso 所有,微信公众号:Simviso,未经授权严禁转载
本次参与翻译人员
前言
大家好,我的名字是 Bree,我在 Airbnb 工作。
在大公司中进行大的改革很难。这需要去说服很多人,同时又需要涉及大量的代码迁移。我想要与大家分享我们是如何将 TypeScript 应用到 Airbnb这个公司的日常开发中的。我感谢你们能在这里,我知道你们完全可以披着时髦的毛巾在海边娱乐。
我设想这里的每个人都会有这样一些问题, 你要为你的公司进行重大的变革, 同时这可能会被作为一个案例进行研究, 你现在是否在公司内部积极的推动将项目开发迁移到 TypeScript,为此我将提供一些技术和工具上的帮助。
或许,你已经听过一些 TypeScript 的内容,并且想了解更多。
首先,我们会介绍 TypeScript 是什么?规模化又意味着什么? 对于将Typescript规模化的过程又有什么建议? 基于这些问题和疑问,我会给出相应的解答, 我们该通过什么样的迁移策略将 JavaScript 逐步迁移到 TypeScript。
请大家快速举手示意一下以方便我知道大概有多少人之前使用过 TypeScript, cool,有这么多人。 还有一部分人没有举手, 那么,让我们快速介绍一下, 这样每个人都在同一起跑线上了。
类型检查
假如我们有这么一个 greeter 方法,
它接收一个name参数,然后返回 "hello" + name。
So,如果我们传入的是"JSConf",它会和气的说"hello JSConf"。
但是在这种情况下,如果我们传递的参数类型不是字符串,而是一个字符串数组,那么 TypeScript, 就会给我们一个错误。即这是一个字符串数组,函数接收的参数类型是 string,不支持该分配。
我们不需要再通过点击刷新页面这一流程来从我们的控制台中查看错误并确定该错误所发生的位置。 可以看到,在我们输入后,立即就从编辑器中得到了这个错误。
person对象。同时,你可以定义更复杂的构造类型。
Typescript通常带有一个编译器,
当出现问题时就可以立马告诉你。
它还有一个可以与编辑器挂钩的语言服务器,可以帮我们进行自动编译,提供重构相关提示等等
神奇吧!我没必要来回浏览文档页面,我在编辑器中就可以得到这些所有。 通过在我们的代码中使用类型,我们可以做更多的事情。
我们只是稍微接触了下TypeScript的类型限制,但是可以通过这个来帮你捕获这种类型的错误,以及支持它的工具。
关于TypeScript的类型检查内容就到此,那关于规模化应用这一部分呢?
如果你一时疏忽,输入了一个错误的变量,那你将会得到这样的一个错误提示,so,在 TypeScript 中真的会发生这样的事。 所以,让我们来修复下。
规模化应用
但当团队规模达到数百位工程师,同时代码量也越来越多,那么交流方式就要发生改变。 我们需要进行一些改变,即当我们想要在我们的主仓库中使用TypeScript的时候(这里指Powers | airbnb.com的主仓库), 同时让TypeScript成为前端主要开发语言。 这个改变影响的人越多,那么必须迁移的代码也就越多,那我们用数字来说明规模化意味着什么。
这真的有很多代码,我甚至能看到一些Backbone的代码。可以想一下JavaScript走过了多少年,它在Airbnb这里也走过了十多年。所以我们有大量的开发人员在维护这些代码,
目前公司有超过1300名开发人员,其中有200个是前端, 这些前端工程师大多数都参与了主仓库的贡献。
这些数字给我们展现了当时我们提议要使用TypeScript时所面临的大环境。 那么在这种规模下,我们当时是一个什么样子呢?
大家会权衡这些提议的利弊, 我们会站在团队的角度去决定向前迈出这步是否有意义。 这确保了我们可以作为一个集思广益的团队,对所做的事情做出深思熟虑的决定,这样可以避免在没有正当技术理由的情况下就“上车”。
从2016年起,Airbnb 已经在一些小规模团队中探索使用 TypeScript。在2017年的前端调查中,静态类型系统呼声最高。 基于这个信号,Joe(第二排那个Joe)和我起草了一个关于TypeScript的提案,并将它交给前端工作组。 这项提案详细说明了为什么在Airbnb使用TypeScript是有意义的。
让我来讲讲主要的原因。 airbnb的使命是要建立一个让每一个人都感受到世界处处都是家(airbnb是一家旅游住宿的公司), 用户对我们的产品提出的每一个建议都能会让我们向着这个目标更进一步, 对于你正在开发的产品也是如此。
使用TypeScript,工程师可以更安全快速的迁移代码。 在Airbnb我们引入了GraphQL 和Apollo,它可以使我们通过自定义的GraphQL模板来生成TypeScript类型
这意味着我们可以得到端到端之间的类型安全, 因为前端和后端所使用的数据类型共享了同一个事实上的定义源, 后端工程师能够在不影响客户端的情况下对API进行修改,而前端工程师则可以确信哪些数据将从服务器返回。
类型不匹配一直是我们的主要bug所在。 所以,这种端到端的类型安全性是一个主要的卖点。
似乎需要先将包转换为 TypeScript。 这看起来像是需要首先将这个包转换为TypeScript 但这里有个问题,因为维护人员不允许我们对它做TS转换操作,可能他们也不情愿这么做。 因为在我们提案的早期,我们并不确定是否要一直按照这个提案走下去。
类型声明文件
但是从另一个层面来讲,我们使用TypeScript是为了可以让开发人员可以有更好的体验。 我们需要TypeScript提供的类型安全性, 那么我们该如何解决看似鸡和蛋的问题呢?
TypeScript有一个叫做声明文件的功能, 即一个以.d.ts为扩展名的文件,通过它我们可以为JavaScript文件定义类型。
.d.ts文件。
方法里没有实现细节,
它只描述了类型。
TypeScript将它们组合到一起,这样,在编译是使用这个声明文件,在运行时使用这个原生的JS文件。
所以,让我们回到最初的场景,看看声明文件是如何提供帮助的。
那么我们回到我们刚才提的问题(要不要一开始就转换),看看声明文件是如何提供帮助的。
当然,如果那个项目已转换为 TypeScript,我们就没有必要再生成一个.d.ts文件来作为TypeScript构建时的一部分(因为在使用TS编程的时候,要通过它对生成的JS进行调用)。
但我们认为这不止一种选择。 相反,我们可以将声明文件放在我们的 TypeScript 项目中。 另一个选择则是我们可以创建一个单独的NPM包,并将所有声明文件放入其中 。 这很棒,因为我们现在可以跨多个仓库共享这些声明文件。
DefinitelyTyped仓库
@types/react包中针对React的5000个常用包做了类型声明,
@types/react与其他5000个其他包都在DefinitelyTyped 这个仓库中,它由社区在维护
在我们的主仓库中,绝大多数的公共依赖都已经由DefinitelyTyped 做到了类型声明,
有活跃的社区氛围是TypeScript 的一个主要卖点。我们也回馈了一点力量,我相信在这个房间里也有人做出了贡献,谢谢。
这些公共的NPM包的类型声明已经有DefinitelyTyped在做了,但那些内部的包该怎么办?
@airbnb-types/*),这样,你只需要安装@airbnb-types即可
仓库的设置类似于类型的明确性。
这个仓库的设置与DefinitelyTyped类似
所以我们可以在里面添加并发布这些内部类型。
我们开源了一个starter 工具包,如果你又兴趣的话,可以来参与下。
它里面没有类型定义,它只是在教你如何进行一些配置以便于进行测试或者发布自己的类型定义。
那么 TypeScript 究竟能帮忙避免多少 bugs 呢? 近期,一个叫做“该不该做类型定义”的研究表明,在选择了TypeScript 的 github 仓库中,有 15% 的 bugs 得到了避免。 在我们内部,有一个记录生产环境事故的流程。
这个流程的本意并不是为了责怪谁,而是要从错误中进行学习,这样我们之后就不会再犯类似的错误。 所以我坐下来读了六个月的总结报告。 阅读这些总结报告很有意思, 我最喜欢的就是未捕获的异常以及危险的参数计算。 好吧,也许这些错误的名字并没有那么令人激动。 无论如何,我将这些错误归类为与 JavaScript 相关或无关,以此确定哪些错误可以通过使用Typescript 来避免
实例讲解
让我们一起看个例子,使用TypeScript会带来哪些帮助 我们对所分享的这个 Input 组件进行修改,通过一些设置来还原bug。 用户无法提交表单是因为它不再能通过验证。
onBlur prop。
这就导致在好几个不同仓库中都出现了这同一个问题
这里 Input 组件作为Redux Form的一部分进行使用,期望得到一个事件或值,以便验证正常工作。
其他特性
实际效果
38%!
我们发现有38%的事故导致了生产阶段的bug。 这些对我们用户产生实际影响的bug,可以使用TypeScript来阻止。 这对我们来说是个巨大的发现。 它有助于实现影响。 它有助于将这种(积极)效果转换到现实中。 我们复制了一些BUG事件,并向大家展示了TypeScript所给出的Error提示,然后对bug进行修复(也就是我们看到的bug提示灯泡灭掉了)
的确,我们也可以通过写测试代码来捕捉这些,但是通过静态类型检查可以额外增加一层保护层。 因此,如果你所在公司有类似的历史,那么你可能就有必要和懂Typescript小伙伴一起来看一下这些问题在你们的代码里所占的比例。
团队构建
我们在主仓库启用超过了500条eslint规则,也使用typescript eslint解析器,我们很高兴地发现它们中的大多数都可以工作。 如果我们在将来要弃用Typescript的话,我们可以剥离类型,并最终得到大致相同的JavaScript。
所以我们逐一记录、思考、跟进,并且针对提出的问题和担忧找到解决办法。 与批评者合作并听取他们的担忧对我们来说非常重要。 最后这些批评者中的大部分转而会支持我们, 我们的提案也从他们的反馈中变得更为健壮。
渐进迁移
我们已经解决了早期的矛盾,并改进了工具和文档,所以之后团队成员会更容易入门。
我们一直与 typescript 团队保持着持续联系, 并帮忙解决一些问题,比如,更好的默认属性优先级处理。
在这个阶段,我们自己内部的 typescript 社区也得到了发展。 但是大部分 Airbnb 的员工还不知道 typescript, 这也意味着更多的人可以去帮助和回答他们的问题。 接下来我们将会进入测试状态,团队可以选择使用它。 为了帮助团队,我们创建了内部文档和风格指南,并举办了一些学习课程。
我们建立了一个聊天组,一个内部的类Stack Overflow,谷歌Email主题,github组,来供组内成员交流。 我们想确保人们能得到他们需要的帮助, 最后一步是将 typescript 完全普及化。 此时就意味着它是稳定状态,每个人都应该开始使用它, 我们目前正在努力地去接近这个目标。 剩下的步骤就是巩固风格指南、文档、加强内部培训和迁移更多代码。 到目前为止,我们大约有50%的团队使用 typescript,在主仓库中,有10%的文件已经被转换成 typescript 通过这种渐进的方法,使团队迁移至 typescript 的过程更加顺畅。 如果从第一天开始就要求每个人应该使用TypeScript, 那么接下来的每个人都会遇到同样的问题。 相反,我们先在小范围内使用 typescript ,然后总结一些经验技巧,当我们准备把它大规模推广时,这些经验技巧也会用得上。
迁移策略
我们已经探索出了几种将代码迁移至 typescript 的方式。
迁移技巧
在关于迁移的话题上,我想花些时间和大家分享一些我们认为有用的技巧。
$TSFixMe,
我们通过TypeScript的any类型添加了一个全局类型别名,这意味着它可以为任何类型。
我们将它称之为$TSFixedMe,表明在代码向TypeScript迁移完成后,再来将类型修正
平时最佳实践是避免使用any,因为它会造成类型安全丢失,但它在迁移过程中会很有帮助。
@ts-ignore注解可以做到忽略下一行错误。
正确地输入一个文件可能涉及一些深层依赖链解析(类似于复杂对象)。
我们可以尝试通过首先转换子文件来避免这种情况,但有时这是不可避免的。
因此,$TSFixedMe和@ts-ignore注解能够帮助拆分这些内容,同时则会增加这些检查工作。
这些都是暂时的,我们计划添加类型覆盖工具,并在后面我们改进类型时提供帮助
为了避免重复两次类型声明,那就需要与这些类型保持同步。
我们创建了一个Props类型,通过它将给定的propTypes和defaultProps来派生出一个TypeScript类型。
propTypes和defaultProps组合并得到这个最终类型。
如果你好奇它是如何工作的,你可以查看我在gist上分享的
代码片段。
为了通过编译,有些文件需要进行一些TS Fixed, ts就可以推断出剩余部分。
大型项目迁移策略
在这个网页下,它有一个可以通过源代码输出对应的AST树的功能,以及在你对代码的改变同时反映到AST树上。 我也在DefinitelyTyped 这个库提交了关于Jscodeshift的pr,这样的话可以来降低大家在使用TypeScript与Codemod的交互门槛,
迁移模式
在将JavaScript代码迁移到typescript时,有这几种模式。
随着时间的推移,你仍然需要慢慢找到类型,但它为你提供了一个工作前提。 我们将此工具应用于我们的内部分享的React组件库,现在在我们的网站上已经频繁地使用。 我们有内部的类型定义库(DefinitelyTyped),但是因为react分享组件库的快速发展,所以做到与时俱进地更新太难了, 所以,我们想直接从源码类型出发。所以这是我们迁移TS的第一个目标。
心得感想
在大型组织中实施变革可能是一项挑战,但强有力的事实依据和相关问题和担忧的解决,可以使我们信服。 采用逐步变化的方式有助于减少摩擦并证明其价值。 一条明确的迁移路线能帮助团队更好的转向新的模式,同时好的工具也能促进这个过渡的过程, 我之所以开始这个工作,是因为之前有个产品组对我的工具感到失望。 当我得知公司内部其他人也有这种改变的想法的时候,我便与他们合作并将之进行下去。
结语
感谢大家的倾听,同时感谢AirBnb为这个项目作出贡献的每一个人,尤其是台下的Joe和Mohsen。还有对其他一些优秀的Airbnb工程师表示感谢。 我手上也有些TypeScript主题的小便签和一些钥匙链,先到先得,只限前30人哦!
感谢大家的倾听。