TypeScript at Google

September 01, 2018

I've been working on TypeScript for over two years now(!) so I thought I'd write a post or two to reflect. I should open with the standard disclaimer: I am just a random engineer at a company with tens of thousands of them, and that others surely disagree with the opinions expressed here.

Google embraced web applications early. Can you believe it's been 14 years since Gmail was released? JavaScript back then was pure madness. Gmail engineers had to worry so much about Internet Explorer's poor garbage collection algorithm that they'd need to manually hoist string literals out of for loops to avoid GC pauses. I recently found a design doc from that era where they considered doing what today we'd call "minifying" the JavaScript but noted that some candidate tools were Windows-only. Today that feels unimaginable.

Over those years Google built lots of infrastructure for making big JavaScript apps. For example, there's a module system to let source files express their interdependencies. There's a bundler that combines and minifies source files into browser-compatible artifacts. Another piece analyzes an app's dependency graph by dynamically-loadable entry points and factors out common subset chunks for serving. Server-side rendering is common. All of these concepts are familiar to a web developer today but Google's stack predates today's, evolved in parallel, and is consequently conceptually similar but concretely totally different, with different processes, tools, and even names for these ideas.

In another example of parallel evolution, each of Google, Facebook, and Microsoft built similar but incompatible compilers that add static checks to JavaScript. Google's compiler is colloquially called Closure. (Not to be confused with the Clojure language; for extra confusion note that ClojureScript uses the Closure compiler.)

Google's JavaScript stack is unevenly great. It has enabled Google to write and maintain web apps that have changed the face of the internet. (Remember how amazing Maps was when it came out? Today it seems boringly obvious to make a draggy map widget.) There are pieces of it that surpass the best of today's technologies. For example, the Closure compiler is perhaps still the most sophisticated JavaScript optimizer, able to do things like optimize code using type information, inline functions across hot-loading chunk boundaries, and strip unused code down to individual symbols.

Google's JavaScript stack also has problems. The incremental evolution from a linter means Closure is a statically typed variant of JavaScript where language features are introduced in comments. Closure has unpredictable semantics, it's slow, it's buggy, and it tends to mangle your code unless you hold it just right. Though it is open source, perhaps because of these reasons it isn't widely used in the industry except at companies that hire Googlers who are familiar with it. Within Google I think JavaScript has a low reputation in part because of our finicky tools, which combine the verbosity of a static language with the unpredictability of a dynamic one.

Meanwhile, outside of Google, JavaScript also continued to evolve and surprisingly even became popular. In part to work around those IE garbage collection bugs we built Chrome which led to v8 which led to nodejs which means most web tools today are themselves written in JavaScript, unlike the Java used to build this sort of tool at Google. Module systems (UMD, AMD, CommonJS) proliferated. (ES6 also came along and invented its own module system that is incompatible with all the others for some reason, sigh.) npm unified the way tools and libraries are shared. Webpack can dynamically swap modules into a running application while you develop it.

Google uses none of this. Experienced webdevs show up at Google and it's like visiting an alternative timeline. There's a CSS preprocessing language like SASS but it's not SASS and nobody likes it. The fancy chunk-splitter doesn't really support third-party JavaScript libraries in part because the tools predate the existence of a JavaScript library ecosystem.

That's all just history. You can argue we shouldn't have gotten to this place but it doesn't change that we're here now. Instead the interesting question is: where do we go now? There are a few options. My perspective is surely biased by the one I prefer.

The first tempting option is to just abandon this ruined planet and settle a new one that doesn't even involve JavaScript. If only we invested more in GWT (a Google project that compiles Java to JavaScript) or Dart (a Google project that compiles a new language to JavaScript) or WASM or [insert your favorite language here — Clojure? Haxe? Elm?] we wouldn't need to worry about JavaScript at all!

As a PL enthusiast I'm pretty fond of this idea. I'd like to give it the careful analysis it deserves, but this post is long enough and I think that discussion would work as its own post. Instead of a full refutation, here are some mundane concerns: adopting a different language (1) does nothing for the literally millions of lines of existing code we have — "rewrite from scratch in a new language" is the correct choice for some situations, but it's a hard argument to make as the best use of gmail engineers' time — and (2) it does little for the aforementioned experienced frontend programmers that we would like to hire.

The opposite of rewriting everything is to change nothing. The public JavaScript world, you could correctly point out, is full of amateur code and leftpad disasters. A good engineer can always adapt to our idiosyncratic way of making frontends and we can always improve or build more of own tools. I am sympathetic to this view. I think there's a point in the space of tradeoffs where it makes sense to build our own tools, and another point at which we've diverged so far from the mainstream that our tools are a liability. The argument is then about where we are in that space, and I believe we're drifted too far towards the latter. We benefit from contributing to LLVM/Clang because we depend on C++, but we wouldn't get much additional value from building our own LLVM.

Which leads me to the middle path, which my little team has been pursuing: incrementally adopt some external tooling where it makes sense, by figuring out how to make it interoperate with our existing code base. This task isn't as fun — we don't get to just throw away our legacy mess and "do it right this time" — but I like to think rather more humble, looking outward rather than inward.

The first part of our bridge from the Google JavaScript Galapagos back to the mainland was adopting a well-supported static checker that is (1) not home-grown; (2) popular already, while similar to our existing code; (3) designed to bridge into JavaScript; (4) designed to support the large-scale development that motivated our custom tools in the first place. And that tool is TypeScript. The strength of the Closure compiler is its optimized output, while TypeScript has a great user interface and no optimization at all. The two tools are complementary and (with some work) can be layered together.

Because TypeScript already mostly works — that's part of the reason to adopt it, after all — we get many of the benefits of adopting an established language, from IDE-style code completion to being able to check StackOverflow for answers. The work that's left for us has primarily been integration: allowing our apps to incrementally move into TypeScript without rewriting from scratch. Our integration into the Google-wide build system is careful to compile incrementally, which is critical for large apps; changes in one module that don't affect its exported API don't cause downstream modules to recompile. Our integration into the Closure type/module system means that ES6 TypeScript modules can import Google-module-system modules and the reverse, with (much of) the type information preserved. One company successfully used the tools we published to automatically translate their entire code base while preserving their minified output.

Within Google TypeScript is now found in varying quantities everywhere; it's likely if you use Google products you've interacted with some TypeScript code. TypeScript itself is a bunch of interesting compromises, balancing a statically typed programming language against the free-wheeling JavaScript ecosystem. But that's what we engineers do: we make interesting compromises that attempt to balance different concerns. I hope to write more in the future about some of interesting corners we've discovered over the years. As I wrote when we first started down this path, I think TypeScript makes good tradeoffs within the design space.