Dieser Artikel ist auch auf Deutsch verfügbar

Ten years after the release of TypeScript 0.8, it is difficult to imagine software development without this programming language developed by Microsoft. Above all, developers value the fact that it supports popular JavaScript libraries such as AngularJS and jQuery. The integration of various build management tools such as Gradle or Grunt is also easily achieved with the aid of plugins.

TypeScript is now at version 4.6, and many additional advantages have been added to the ones already mentioned. Nonetheless, according to the Popularity of Programming Languages Index, only 1.91% of all search queries for tutorials relate to TypeScript. Pole position is occupied by Python with 29.66%, while JavaScript accounts for 8.81%. The popularity of TypeScript may be increasing, but these figures highlight that the language is still paid little attention by most developers. Time to look back at its beginnings and consider its main advantages.

The original technology duo HTML and HTTP were not only continually developed over the years but also complemented by countless other languages, protocols, and interfaces. JavaScript played a particularly important role here. Over time, it developed into a ubiquitous language, not only because it forms the basis of dynamic applications but also because Node.js offers an equivalent on the server side. With the help of sophisticated tools, it is now gradually taking over HTML and CSS, so that developers can perform all tasks centrally with this one language.

At the same time, JavaScript is controversial because the language design has grown historically and has many inconsistencies. Add an object and an array? No problem for JavaScript! The result of this addition, which you probably wouldn’t find in this form in any math textbook, is a string. The prototype-based object orientation also seems somewhat antiquated in view of the otherwise dominant class-based languages. But the JavaScript TC39 design committee (or more accurately, ECMAScript) is working steadfastly to clean up the messy structures. Popular innovations include promises for simplified asynchronous programming or const and let, with which the scope of variables can be handled more simply.

However, all changes must be monotonic. They are therefore always new features because TC39 wants to avoid breaking web pages that were working before through incompatible changes. As a consequence, this means that even in ten years’ time, nonsensical arithmetic will still be allowed. However flippant this may sound, this causes tangible problems for software quality. But correctly functioning JavaScript code is essential, especially on such a popular platform as the World Wide Web. After all, people do almost everything on it these days – banking transactions, doctor’s appointments, mail, etc. etc.

JavaScript as Intermediate Language

In recent times, a trend has therefore emerged: Developers write their code in another language and then use a compiler to convert it into JavaScript. Such compilers are often called transpilers. At the end of the day, it makes no difference to the browser whether the JavaScript code it runs was written by hand or generated by machine. Unfortunately, many of these incarnations of “language X” leave unanswered the question of what they think about interoperability. However, since there are now millions of ready-made JavaScript libraries, integration with existing JavaScript code is unavoidable. Otherwise, developers would have to implement thousands of lines of code over and over again. As a 2013 survey by the two computer scientists Leo A. Meyerovic and Ariel Rabkin shows, the availability of open source libraries is one of the most important considerations for developers when choosing a programming language.

This is where TypeScript comes in, a language first presented by Microsoft in 2012 and which has now reached version 4.2. At first glance, it is exactly such a “language X” that a compiler converts into JavaScript. But then you notice that the syntax of TypeScript follows that of JavaScript. In fact, any valid JavaScript program is also a valid TypeScript program. Existing code can thus be reused without any problem. So why should developers use TypeScript if it’s almost JavaScript anyway? The answer is found in its name: TypeScript knows (optional) type annotations with which you can define functions, variables, and classes. Although the compiler removes them again, it thoroughly checks them against the written code first. It also checks every operation, every method call, and every import. For example, it acknowledges our addition of object and array with the following message:

error TS2365: Operator '+' cannot be applied to types '{}' and 'any[]'

In practice, TypeScript seems like a natural extension of JavaScript. Developers used to languages like Java feel right at home. This can be illustrated with a hypothetical drawing program in the browser. First, developers would create an interface for geometric shapes. TypeScript offers the well-known keyword interface for this, which is lacking in JavaScript. Somewhat unusually, type specifications are separated by a colon after parameters and methods.

interface Shape {
  scale(factor: number): void;
  move(x: number, y: number): void;
  render(dom: HTMLCanvasElement): void;
  size(): [number, number];
}

The interface defined here provides methods for manipulating the shape (scale and move), rendering on a <canvas> element (render), and calculating the object size (size). The latter returns a pair of two numbers, which can be used as follows:

const [width, height] = shape.size();

Although the interface declaration looks exactly the same as in many other object-oriented languages, TypeScript takes a unique approach: When type checking, it doesn’t matter which interface a method comes from – TypeScript only checks the method signature. Here is an example of this: The interface above is seen as compatible with this one, though it doesn’t extend it.

interface Renderable {
  render(canvas: HTMLCanvasElement): void;
}

This “structural typing” is the equivalent of duck typing in JavaScript and simplifies development enormously. Programmers can more flexibly integrate different libraries. And they are less reliant on “glue code.” Implementation of the interface is as expected:

class Rectangle implements Shape {
  private x: number;
  private y: number;
  private height: number;
  private width: number;

  constructor() {
    this.x = this.y = 0;
    this.height = this.width = 1;
  }

  scale(factor: number) {
    this.height *= factor;
    this.width *= factor;
  }

  // ...
}

Incidentally, the compiler would complain about this (incomplete) definition because some of the methods are not implemented. Just like in Java, multiple inheritance of interfaces is allowed. However, there may only be a maximum of one superclass.

Definitely Typed

What happens if I want to mix TypeScript code with “raw” JavaScript code within a project? At a practical level, this occurs almost every time a developer downloads ready-made packages via npm, because these almost always come as bundles of JavaScript files. Although the TypeScript compiler can analyze untyped code and infer types from it, this only works to a limited extent: When the compiler gets stuck, it automatically uses the infamous any type, which effectively eliminates further checking. Practically, developers are back to square one and have gained nothing. In order to solve this problem, the development community brought to life the “DefinitelyTyped” repository. At the time of writing, 7,000 packages with type definition were available there. Using them couldn’t be easier. For React, for example, the following call is sufficient:

npm install react @types/react

This allows developers to download both the React library and its corresponding types. The TypeScript compiler then automatically finds them and includes them in the checking process. These packages are files in .d.ts format, i.e., TypeScript source code without implementation of methods, consisting only of pure type declarations. A whole slew of other libraries deliver these .d.ts files directly, without the detour via DefinitelyTyped. For most packages, the type definitions are available in one of these two ways. In practice, developers can always rely on the type checks by the compiler.

Incidentally, TypeScript implements new features of JavaScript that are still being finalized by the TC39 committee. For example, asynchronous programming officially entered the language with ES2017 with the keywords async and await. TypeScript had supported it since 2015 already. Used carefully, developers can take advantage of this head start to develop new features in their own code faster and more reliably. This gives them a real competitive advantage.

Also in current compiler versions developers can configure the so-called target and in this way select an ES version as far back as ES3 (from the year 1999).

In addition to type checking, TypeScript can also take over the tasks of transpilers such as Babel or Sucrase, thus ensuring that modern code can also be executed on older browsers. It should be noted, however, that not all newer JavaScript features – such as symbols – can be mapped one-to-one on older versions. The risk increases with the distance between source code and target version.

Development with TypeScript

TypeScript can already show off its advantages in the simple development of applications with Vanilla JS – i.e., when developers build a website via server-side rendering and without frontend frameworks. Modern browsers come with numerous powerful APIs that cover almost all needs. Paired with an editor like VS Code, TypeScript can provide good tooling support for this, for example with code completion.

An almost life-saving feature is that TypeScript also recognizes certain “magic strings” and “magic constants.” For example, if developers want to query a resource at runtime via fetch, they can pass numerous configuration options via string. Thanks to the type checker, they no longer need to remember whether it is mode: “same-origin” or mode: “sameorigin”. (The former is correct, the latter is underlined in red in the editor.)

TypeScript can also handle JSX – the HTML dialect of React – and checks if the HTML attributes are written correctly. A lot could be written about the type system and other features of TypeScript. At this point, it suffices to say that it covers almost all common programming patterns in JavaScript, be they conditional types, overloading, proxy objects, or anything else.

Conclusion

TypeScript has been around for a decade now and has been continually growing in importance over this time. Many frameworks like React or Svelte not only support a wide range of libraries, but also integrate TypeScript. Some, such as Angular, even use it as their primary language.

The compiler supports programmers throughout the development process without them needing to get used to a completely new environment. However, this help comes at the cost of somewhat more complex tooling: TypeScript comes with a whole string of other packages, for example DefinitelyTyped. But the effort is worth it because every bug that can be avoided saves money. By the way, TypeScript can be tried out without requiring installation at Playground.