我已经使用 TypeScript 两年多时间,是时候写一两篇文章来总结一下了。
谷歌在很早之前就张开双臂拥抱 Web 应用程序,Gmail 已经发布 14 年了。当时,JavaScript 的世界是疯狂的。Gmail 工程师不得不为 IE 糟糕的垃圾回收算法捏一把汗,他们需要手动将字符串文字从 for 循环中提取出来,以避免 GC 停顿。最近,我找到了那个时代一个设计文档,是关于如何“minify” JavaScript 文件的,只不过一些工具仅用于 Windows 平台。这些事情在今天看来是难以想象的。
多年来,谷歌构建了大量的基础设施,用于开发大型的 JavaScript 应用程序。例如,他们开发了一个模块系统,可以让源文件自描述它们之间的相互依赖关系,还有一个捆绑器,可以将源文件合并在一起,并缩小为可与浏览器兼容的工件。另外还有一个工具,它通过动态加载入口点来分析应用程序的依赖关系图,并抽取服务的公共子模块。还有非常常见的服务器端渲染。所有这些概念对于今天的 Web 开发人员来说都是很熟悉的,但谷歌的技术栈总是很超前,它们并行演进,虽然在概念上很相似,但实际上却完全不同——不同的流程、工具,甚至名字都不一样。
另一个有关并行演进的例子:谷歌、Facebook 和微软各自构建了相似但不兼容的编译器,这些编译器向 JavaScript 中加入了静态检查。谷歌的编译器通俗地称为 Closure(不要与 Clojure 这门编程语言混淆,另外请注意,ClojureScript 使用的是 Closure 编译器)。
谷歌的 JavaScript 技术栈非常棒,其中一些部分已经超越了当今最好的技术。例如,Closure 编译器可能仍然是最复杂的 JavaScript 优化器,可以使用类型信息来优化代码、跨热加载块边界进行函数内联,以及将无用的代码剥离成单个符号。
当然,谷歌的 JavaScript 技术栈也存在一些问题。Closure 是 JavaScript 的一种带有静态类型的变体,通过注释来引入语言新特性。Closure 具有不可预测的语义,它运行速度慢,容易出现 bug,除非你很小心,否则它很容易就让你的代码变得一团糟。它是开源的,但除了一些能够招到谷歌前员工的公司之外,行业中很少有公司会使用它。在谷歌内部,我认为 JavaScript 的声望并不高,部分原因在于我们的工具,它将静态语言的冗长性与动态语言的不可预测性结合在了一起。
而在谷歌之外,JavaScript 在不断演进,变得越来越流行。为了解决 IE 垃圾回收器的问题,我们开发了 Chrome,然后 v8 出现了,继而 nodejs 诞生,所以今天的大多数 Web 工具都是用 JavaScript 编写的。模块系统(UMD、AMD、CommonJS)大肆膨胀,ES6 也发明了自己的模块系统,但由于某种原因,与其他模块系统不兼容,非常可惜。npm 统一了工具和库的共享方式。Webpack 可以在你开发代码的同时将模块动态加载到正在运行的应用程序中。
但谷歌没有使用这些东西。我们有一个类似 SASS 的 CSS 预处理语言,只是它不叫 SASS,而且没有人喜欢用它。花哨的块分割器并不支持第三方 JavaScript 库,部分原因是这些工具的出现早于 JavaScript 的库生态系统。
这些都已经成为历史。你可以说我们不应该忘了我们是如何到达这里的,但不管怎样都无法改变我们已经达到这里的事实。或许我们更应该关心接下来要去哪里。我们有几个选择。
第一个选择是放弃这个已经破败不堪的星球,去到一个没有 JavaScript 的新星球。如果我们在 GWT(一个将 Java 编译为 JavaScript 的谷歌项目)或 Dart(一个将其他语言编译为 JavaScript 的谷歌项目)或 WASM 或(Clojure?Haxe?Elm?)上做更多的投入,根本就不需要担心 JavaScript!
作为编程语言爱好者,我觉得这个主意不错。不过长话短说,这里有一些问题:首先,采用不同的语言对于我们现有的数百万行代码来说并没有任何意义——“使用新语言重写”在某些情况下可能是正确的选择,但很难做到充分利用 Gmail 工程师的时间。其次,采用不同的语言对于我们想要聘请的前端程序员来说一点帮助都没有,等于说他们之前的经验都用不上了。
改变一切的对立面是不做出任何改变。JavaScript 世界充斥着业余代码和 leftpad 灾难。一个优秀的工程师总能适应特殊的前端开发方式,我们总能改进或构建更多属于自己的工具。我们构建的应用类型——谷歌搜索主页每天的点击量超过数十亿次——与其他人构建的网络应用程序不同,我们的工具不仅优秀,而且都是必需的。我认同这种观点。我认为存在某种权衡,一方面,构建我们自己的工具是有意义的,另一方面,我们已经远离了主流,以致于我们的工具变成了一种负担。关键在于,我们现在处于这个权衡的哪个位置上,我相信我们离后者更近。我们从对 LLVM/Clang 的贡献中获益,因为我们依赖于 C++,但是我们不会从构建自己的 LLVM 中获得更多额外的价值。
我的团队一直在追求中间那条路:在必要的时候逐步采用一些外部工具。这项任务并不那么有趣——我们不可能只是简单地把遗留的那套东西直接丢掉——但我喜欢谦虚一些,向外看,而不是向内看。
在谷歌 JavaScript 孤岛和大陆之间架起桥梁的首先是一个静态检查器:
(1)它不是我们内部开发的
(2)已经很流行,与我们现有的代码相似
(3)旨在为 JavaScript 做桥接
(4)支持大规模开发
这个工具就是 TypeScript。Closure 编译器的优势在于它的优化输出,而 TypeScript 具有出色的用户接口,但不提供优化。这两个工具是互补的,并且在处理某些任务时可以进行分层式协作。
使用 TypeScript 给我们带来了很多好处——从 IDE 风格的代码完成到从 StackOverflow 上获取相关问题的答案。我们所要做的工作主要是集成:将我们的应用程序逐步迁移到 TypeScript,而不是从头开始重写。在与谷歌范围内的构建系统集成时,我们非常谨慎,我们进行增量编译,这对大型应用程序来说至关重要。一个模块发生变更,只要不影响公开的 API,就不应该导致下游模块进行重新编译。与 Closure 类型 / 模块系统的集成意味着 ES6 TypeScript 模块可以导入谷歌模块系统模块,反之亦然,而且可以保留大部分类型信息。
在谷歌,现在到处都可以看到 TypeScript 的身影。如果你在使用谷歌的产品,很可能是在与 TypeScript 代码打交道。TypeScript 本身就是一系列有趣的方案的折衷,它在静态类型的编程语言与自由转换的 JavaScript 生态系统之间做出了权衡。这就是我们的工程师要做的事情:做出有趣的权衡,尝试在各种问题之间找到平衡点。我希望后面能够写更多文章分享这些年发现的一些有趣的事情,我认为 TypeScript 在这个领域内达到了很好的平衡。
英文原文: http://neugierig.org/software/blog/2018/09/typescript-at-google.html
感谢覃云对本文的审校。
评论