Why TypeScript?
阅读原文时间:2021年09月08日阅读:1

本文经作者授权,翻译总结自 TypeScript Team 的成员 orta 的个人博客 《Understanding TypeScript's Popularity》。

原作者: orta

原文链接:https://orta.io/notes/js/why-typescript

翻译:ycaptain

TypeScript 是一种非常受欢迎的 JavaScript 语言扩展。它在现有的 JavaScript 语法之上加入了一层类型层,而这一层即使被删除,也丝毫不会影响运行时的原有表现。许多人认为 TypeScript "只是一个编译器",但更好的理解其实是把 TypeScript 看作两个独立的系统:编译器(即处理语法的部分)和语言工具(即处理与编辑器集成的部分)。通过独立看待这两个系统,就可以得到能够解释我们之前所做决策的两个重要视角。

npm 上,TypeScript 的下载量每年都在翻倍。如今(博客发布于 2021 年 4 月 14 日),它的每周下载量约为 2000 万次。而在去年 4 月,这一数字约为 1000 万次。它仍保持着高速增长的趋势,没有任何放缓的迹象。

1. 我们是怎么令它变得如此受欢迎的?

从 2.0 版本开始,TypeScript 每两月定期发布一个 release。但是现在我们放缓了发布的节奏,改为每三个月发布一次。其中我们会花一个月编写新 features 并发布 beta 版本,剩下两个月对 beta 版进行测试和 bug 修复,这使得后续的发布更加稳定。

在我看来,下面这些都是让 TypeScript 不断突破流行极限的大事件:

  • 2014 - 重写 TypeScript,TS v1.1 - TypeScript 上线后,我们重新 review 了 TypeScript 的代码库,想明白了它到底是什么,因此我们抛弃了原有的代码,并以函数式风格重新编写(区别于原来具有可变特性的类) - 这套架构一直沿用至今,它能够在极少发生变更的进程中稳定高效地长时间运行。有人曾提到 TypeScript 的前身是 C++ 编写的,这点我不能确定其真实性。

  • 2015 - Angular 采用 TypeScript,TS v1.5 - 当时 Google 正在考虑为 Angular 构建他们自己的语言,而不是选择使用 TypeScript。为了让 TypeScript 能够被纳入考虑,TypeScript 打破了它的其中一条基本规则:不要过早实现 TC39。就这样,TypeScript 支持了 experimentalDecorators。即使这一特性六年后还没有被添加到 JavaScript 中,对于所有参与开发的人来说,这个结果也是完全值得的他们承受这项技术债务的。

  • 2015 - 在 TypeScript 中支持 JSX, TS v1.6 - 这时 React 也成长为了一个非常受欢迎的 UI 库。而 React 使用 JSX: 一种可以在 JavaScript 中高效地编写 HTML 的 JS 语言扩展。TypeScript 对 JSX 的支持使得其他人可以进行 TypeScript 对 React 的支持(由 @types/react 维护,而不是内置于 TypeScript 中)

  • 2016 - 未定义类型,基于控制流进行静态类型分析 - TS v2.0 - 在 1.4 版本发布的功能 union types 的基础上,TypeScript 支持了 undefined 和 null 类型。这使得类型系统可以真正对大部分 JavaScript 代码建模。与此同时,代码控制流分析使得 if 语句及其它的用户代码都能影响到变量在不同位置的类型分析结果。

  • 2016 - 拥抱 DefinitedTyped, TS v2.0 - DefinitelyTyped 是一个由志愿者们编写的业余项目。当时有一些其它类似 DefinitelyTyped 的系统,TypeScript 团队采用了 DefinitelyTyped 并将 @types/x 的概念融入他们的编译器当中。在采用和维护 DefinitelyTyped 的过程中,TypeScript 团队进行了认真的测试并改进了工作流,这帮助它成为了 GitHub 上最活跃的 Repo 之一。关于 DT 的故事值得一读。

  • 2016 - 支持 JavaScript, TS v2.3 - 尽管当时已经有一些语言工具链支持 JavaScript 项目,对 JSDoc 的支持使得 JavaScript 项目即使不使用 TypeScript 也能获得它的一些好处。这不仅开启了 JavaScript 向 TypeScript 迁移之路,还为已存在的 JavaScript 项目提供了工具支持。

  • 2018 - 在 Babel 中增加 TypeScript 支持, Babel 7 - 这是 TypeScript 成为代码库归宿的起点。在 Babel 中增加 TypeScript 支持给 TypeScript 增加了一些约束(译者注:isolated modules),但这是值得的。从此 TypeScript 不再需要从 eslint 迁移到 tslint 的复杂的迁移过程,而是像勾选一个复选框一样轻松。

  • 2018 - Composite Projects, TS v3.0 - 有许多方式可以处理大规模源码库,TypeScript 的处理方式就是 Composite Projects。通过使用 .d.ts 文件作为项目的边界,你可以在一个单体代码库内维护很多 TypeScript 子项目。这节省了时间和内存,最重要的是这使得你可以将其扩展成非常大的代码库。

  • 2019 - Optional Chaining, TS v3.7 - 虽然这个列表中还遗漏了一些更大的语言特性,但是与 TC39 结合的完美特性 Optional Chaining 使得人们对 TypeScript 支持非常兴奋。作为 JS 生态系统和工具的优秀参与者,将可选链特性加入 JavaScript 的过程是一个 TypeScript 对于自身预期定位的完美例子。TypeScript 应该多做这类项目。

  • 2020 - esbuild / swc / sucrase - 这些新的编译器和 JavaScript 运行时从第一个版本就支持 TypeScript 语法,任何基于这些工具的项目都可以直接使用 TypeScript。TypeScript 语法在 .ts 文件中直接启用进一步使得它作为 JavaScript 的内置扩展合法化。

  • 2020 - 重写用户文档 - 重写文档是我的工作,所以不要完全相信这部分描述,但是这些年 TypeScript 的文档一直很薄弱。我与很多编译器团队里的老人一起重新编写面向用户的文档,并为他们提供一个帮助理解 TypeScript 的在线编辑器。这是对于回答语言问题的第一步,优秀的文档可以让编译器团队将注意力集中在编译器上。

我的看法:TypeScript 之所以流行,是因为它不断通过工具(DT / -- isolatedModules / JSDoc)降低入门门槛,与其它工具和组织合作(Babel / TC39),并通过渐进式的方式展示工具和类型安全在 JavaScript 中的价值。如今,那些写 JSDoc 的人实际上就是 TypeScript 用户,就像使用 --strict 的人一样。

TypeScript 的目标是为人们提供编写大型 JavaScript 项目并对后期维护有信心的工具。JavaScript 本身没有的语法支持表示每个标识符的类型,除非运行 JavaScript 并在运行时进行检测。为了解决这个问题,TypeScript 添加了额外的语法。

所以,如果说我们的目标是作为工具提供支持,那么在这个领域有少数几个竞争者是 TypeScript 无法与之竞争的:

  • ESLint 和 TSLint:与 TypeScript 的定位相同,它们都是用来突出代码中可能出现的错误,只是没有为检查过程添加新的语法。两者都不打算作为 IDE 集成的工具运行,而且 TS 和 TS/ESLint 经常会说那些对项目没有意义的特性“是对方的领域”。在现代代码中,TS/ESLint 的存在使得 TypeScript 可以做更少的检查,这些检查并不适用于所有代码库。虽然有一些功能重叠了,但可以把它们作为很好的补充工具。

  • CoffeeScript:嘿,TypeScript 是 2012 年发布的!CoffeeScript 和 TypeScript 的区别在于 CoffeeScript 想要改进 JavaScript 语言,比如给 JavaScript 添加一些特性。这意味着要了解 CoffeeScript 与其输出的 JavaScript 的区别。随着时间推移,CoffeeScript 的最佳理念反而将其变成了另一个 JavaScript,人们为几乎成为了 JavaScript 的 CoffeeScript 感到困扰。

  • Flow:这是 Facebook 的 JavaScript 类型检查工具和 IDE 工具语言。就像 TypeScript 一样,Flow 为 JavaScript 添加了一些额外的语法支持,让你拥有了一个更加丰富的类型系统,然后在编译时再将其删除。当我刚开始写 JavaScript 时,Flow 是我最先使用的工具,因为它更接近标准的 JavaScript。Flow 是一个很棒的类型系统,它与 TypeScript 有着不同的目标。任何看不见的类型层系统都必须不断做出“正确”或者“感觉足够正确”的决定,Flow 的目标是“正确”(译者注: Flow 偏向于 soundness,在类型判断中更加悲观),而 TypeScript 的目标是“感觉上大部分情况都是正确的”(译者注:而 TS 官方声称 TS 不是类型完备的,允许 unsound 行为,偏向于 completeness,在类型判断中更加乐观)。鱼和熊掌不可兼得,完备的类型推导、良好的开发体验和完美的 JS 协同(Perfect JavaScript Interop)只能取其二。

那么,为什么大多数开源 Flow 代码库最终都迁移到了 TypeScript 呢?在我看来,很大程度上是由两个团队不同的侧重点决定。Flow 是为了维护 Facebook 的代码库而建立的,而 TypeScript 是作为一种独立的语言建立的。这里有两个证据可以证明:

  1. Facebook 的代码库是一个不能被分割的巨大的 monorepo,而 Flow 团队为了使类型运行在这样的大代码库下做了大量令人难以置信的工作。另一方面,TypeScript 可以说是“为构建小代码库服务(use projects to make sets of smaller codebases)”,因为这符合人们在开源社区中编写 JavaScript 模块的方式。我认为这么说很合理,TypeScript 不能像 Flow 一样运行在 Facebook 的代码库上,它要么需要大量重写 Facebook 的代码来构建项目,要么需要对 TypeScript 进行大量修改,这可能会影响到 TypeScript 整体开发者的体验。

  2. 对比 DefinitelyTyped 和 Flow 对类型的做法,TypeScript 团队会轮值一名编译器工程师为 DefinitelyTyped 支持我们的构建工具,并帮助管理社区。而 Flow,它几乎完全由社区维护。DT 现在规模更大了,因为它们一直致力于非 Facebook 代码的开发,这将很难获得 Flow 团队的资金支持。

微软给 TypeScript 在内部创造的独立环境让它可以自由专注于工具开发和整个生态系统的维护,而不是只专注于解决某个特别困难的问题。这让 TypeScript 团队能够与许多人合作,不断发布社区想要的功能。随着时间的推移,我猜想因为外部的需求增长放缓,Flow 团队越来越难为社区工作分配时间。这就形成了一个恶性循环。这使得 Flow 今天不再是 TypeScript 的直接“竞争者”,而是一个关于如何从不同的角度,使用不同的约束去解决类似的问题的有趣视角。

2. 未来

目前阻碍人们使用 TypeScript 的最大障碍是它需要构建工具。我认为类型语法不太可能被加入 JavaScript 中,但是在 JavaScript 中“用注释的方式定义类型”的可能性非常大。

这个想法是为 TypeScript 这样的类型系统创建一套语法,但是不定义 JS 运行时会发生什么。

const a: string = "1234"

// 将会变成这样
const a/_: string _/ = "1234"

// 传入 JS 引擎

在这个例子中,JS 引擎会知道 : string 是一个类型注释,在 = 处结尾。这实际的工作方式是复杂的,需要时间来弄清楚。然而,让 TypeScript 能在 JavaScript 中“原生地”运行将降低它被使用的障碍。它会像 Babel 添加 TypeScript 支持时一样对 TypeScript 施加一些约束。但我觉得这是值得的。

Deno 是一个消除所有 TS 障碍的关键例子,它通过运行一个 Rust 编写的工具,能够非常快速地将 TS 编译到 JS,模拟了当前 JavaScript 引擎对原生 TypeScript 的支持。

  • JetBrins WebStorm - 这是一个有高级 JavaScript 工具支持的 IDE。他们有自己的引擎用于重构、代码流分析并对 JavaScript 语法进行检查。这很好,JetBrains 在他们所有的 IDE 上都做了扎实的工作。我过去经常使用 AppCode 处理 iOS 的工作。当你有一个 TypeScript 项目时,WebStorm 会将 TypeScript 的语言工具和自己的工具混合在一起,这对你来说是双赢的。

  • 编译到 JS 的语言 - 目前的例子有 Elm,ReScript,KotlinScript,这些语言的核心目标是与 JavaScript 交互。对于 TypeScript 来说,这些都是很有趣的语言,它们有一个干净的环境来实现类型系统 —— 也就是,没有 JS 包袱。作为竞争对手,它们倾向于更细分的市场,因为它们的核心不是 JavaScript ,并且社区也被从 CoffeeScript 迁移所困扰过。

  • WASM - 我听到 WASM 作为 TypeScript 竞争者的观点是,WASM 可以作为语言取代 JS 控制浏览器 DOM。反对这一观点的人普遍认为,WASM 没有 DOM 绑定,而且可能永远不会有。TypeScript 包含了 JavaScript 的缺点,如果你在 JavaScript 运行时中加入过 WASM 的话,你几乎总是会更加喜欢它。也就是说,AssemblyScript 在这方面做了一些很好的工作。也许把 WASM 想成 JSON 会更好,它是另一个组成项目的工具,不太可能成为 JavaScript 的竞争者,除非 WASM 和 DOM 的交互方式有所改变。

  • 编译到 WASM 的语言 - 比如 Rust,Go,Swift,等其它可以编译到 WASM 的语言。这些语言都可能占据 TypeScript 目前作为工具和 web 核心构建模块的位置,不过世事难料,谁知道会怎么样呢?这些语言能够提供各种不同的基本类型,并且基于不同的目标从头构建。如果 WASM 和 WASI 最终获得成功,那么我认为将会与平台相关(想想 apps 等功能实现),看看它们的发展方向会很有趣。说心里话,它们不会是 TypeScript 的竞争者,而是 JavaScript 的。

TypeScript 希望在类型系统和编辑器工具领域进行创新。我们拥有在主流编程语言中表达能力最强的类型系统之一。

TypeScript 最初被创建时,对 JavaScript 进行修改的流程和现在非常不同,所以 TypeScript 中有一些特性实际上是 TC39 的领域,但仍然需要向后兼容。这些特性可能在 JavaScript 中存在很多年,并且经过了多次迭代,这意味着 TypeScript 必须维护一个特定语言特性的两种版本。

所以我们的目标是成为 TC39 JavaScript 语言委员会的优秀成员,就编辑器支持的语言特性进行反馈,支持 TypeScript 用户想要看到的特性。通过这种协作方式,TC39 控制了 JavaScript,TypeScript 也支持他们。

TypeScript 的受众主要有:

  • JavaScript 用户(作为语言工具)
  • JS + JSDoc 用户(作为语言工具)
  • TypeScript 用户(作为编译器,语言工具)
  • TypeScript 严格模式(作为编译器,语言工具)

虽然项目使用 babel / swc / sucrase / esbuild 等工具构建时,tsc 是可选的,但是上面的几种受众仍然可以在每次或至少每两次 TS 版本发布中获得新特性(译者注:babel、esbuild 等会更新支持 TS 新特性。可能是 TS 团队直接去这些项目里做,也可能会在没有 tsc 的情况下为这些项目提供特性,比如通过 vscode。在 TS roadmap 中可以了解更多发布计划)。

团队从以下几个方式听取反馈:

  • GitHub issues 有持续不断的评论洪流
  • 微软内部团队要求提供特性,或者要求我们帮忙调试他们缓慢的代码库
  • 通过 Gitter 或者 TypeScript 社区的 Discord 与社区建立联系
  • 通过微团的内部工具对想法 / 设计进行用户测试
  • 与 VS Code 有着非常紧密的联系,许多语言工具的反馈都来自于他们
  • 我们会阅读每一条 @ TypeScript 团队的推特
  • 我们会跟踪迁移到 TypeScript 和从 TypeScript 迁走的博客文章
  • 我们会跟踪行业调查和编程语言概述