Rob Palmer, JavaScript infrastructure and tooling lead at Bloomberg, recently shared several learning points and insights from the adoption of TypeScript at scale at Bloomberg. While some learning points are specific to Bloomberg’s custom runtime, others may be valuable across any large codebase switching to TypeScript.
Palmer explained that Bloomberg Engineering followed a thoughtful process to migrate to TypeScript due to the scale of its existing JavaScript codebase. The Bloomberg terminal contains more than 10,000 applications. Bloomberg’s JavaScript codebase has over 50 million lines of code. 2,000 software engineers are writing JavaScript.
Bloomberg has its own JavaScript runtime environment and platform, built on the V8 engine and Chromium. Bloomberg’s custom runtime directly supports TypeScript. It strives to provide a standardized, streamlined experience to Bloomberg developers. Palmer elaborated:
Our platform supports an internal ecosystem of packages that uses a common tooling and publishing system. This allows us to encourage and enforce best practices, such as defaulting to TypeScript’s “strict mode”, as well as ensuring global invariants. For example, we guarantee that all published types are modular rather than global. It also means that engineers can focus on writing code rather than needing to figure out how to make TypeScript play nicely with a bundler or test framework. DevTools and error stacks use sourcemaps correctly. Tests can be written in TypeScript and code coverage is accurately expressed in terms of the original TypeScript code. It just works.
The migration process sought to stick to standards (such as ECMAScript), keep development productivity high even as the TypeScript codebase grows, and ensure that packages efficiently work together. Palmer shared 10 learning points that accumulated over time. Some of those may apply to any codebase migrating to TypeScript. Others relate to Bloomberg’s specific TypeScript adoption strategy.
One generally applicable learning point is to use the parts of the TypeScript specifications that do not contradict future JavaScript features and focus on adding typing information. The alignment with standard ECMAScript syntax and runtime semantics means that TypeScript language features that are not a type-level extension of JavaScript — enum
, namespace
, parameter properties, or TypeScript decorators, should be avoided. The TypeScript decorator semantics are for instance incompatible with the JavaScript class fields proposal (Stage 3, i.e. nearing adoption in the JavaScript language standard). The MobX state management library recently released a new version that instead aligns with the new JavaScript decorators proposal (Stage 2).
As a second point, Palmer recommended to regularly upgrade to the latest version of the TypeScript compiler. Ideally, a single TypeScript version should be used across the entire codebase. The rationale here is to benefit from the typing, stability, and speed improvements that generally coincide with new TypeScript releases. New TypeScript versions may however break the build process by surfacing previously undetected type errors. Compatibility issues may also occur with declaration files, some of which may contain new syntax that is not understood by outdated versions of TypeScript. Bloomberg’s CI integration, common tooling, and centralized registry of TypeScript projects ensure that beta and release candidate (RC) releases of TypeScript can be tested on the codebase ahead of time. Issues can thus be fixed ahead of the compiler upgrade.
A third general learning point is to use consistent tsconfig
settings across the codebase. The goal here, once again, is to ensure ecosystem coherence by setting up a single configuration that applies to all projects. The absence of inter-package conflicts compensates for the lesser customization flexibility.
Another generally applicable learning point mentioned by Palmer involves ensuring that only one version of types exists at any given time. Palmer said:
We wanted to provide […] “exactly-one” guarantee for types to ensure that, for a given compilation of a project, the type-check would only ever consider one single version of a package’s dependencies. In addition to compile-time efficiency, […] we specifically wanted to avoid staleness issues and “nominality hell,” in which two incompatible versions of nominal types are imported via a “diamond pattern”. This is a hazard that will likely grow as ecosystem adoption of nominal types increases.
For similar coherence reasons, implicit type dependencies should be avoided. Such type dependencies may be caused by the reliance on global types. The TypeScript documentation explains that using global-modifying modules is a somewhat dangerous pattern due to the possibility of runtime conflicts.
Palmer mentioned additional learning points that largely originate from Bloomberg’s specific decision to auto-generate .d.ts
type files from .ts
source files — instead of writing them manually. The strategy keeps the source file as a single source of truth for the types, thereby avoiding the synchronization issues that characterize a manual type file writing process. On the other hand, the strategy creates coherence and scalability issues that Bloomberg has learned to address over time.
Palmer’s blog post relates thoroughly, with an abundance of examples and technical details, the experience of Bloomberg. Palmer describes the issues encountered and how they have been mitigated or resolved. Developers interested in additional details may review the post online.
Palmer revealed that the migration effort has been well received by Bloomberg developers:
Engineers were self-starting conversions and championing the process! When we launched the beta version of our TypeScript platform support, more than 200 projects opted into TypeScript in the first year alone. Zero projects went back.
As TypeScript adoption continues to grow, tech leads may learn from the migration strategies, processes, and tooling adopted by large companies. The Airbnb engineering team recently released ts-migrate, a tool to help migrate JavaScript code to TypeScript. The team presented last year at JSConf Hawaii their TypeScript adoption process.