TypeScript: The JavaScript that should have been

TypeScript is a JavaScript superset, which is an important fact for two reasons: it means all JavaScript is TypeScript and it also means all TypeScript can be converted -- technically transpiled is the appropriate technical term -- into JavaScript. Having such a close relationship, TypeScript is actually very similar to JavaScript, but it has a much more thought-out design.

Due to TypeScript's better design, it has become almost the de facto language to create JavaScript-bound applications, meaning the source code for many JavaScript applications, is in fact TypeScript that gets transpiled into JavaScript to be executed in JavaScript engines.

TypeScript origins and its reason for being

So why was TypeScript invented if it's so similar to JavaScript ? The simple answer has to do with software development stability and scalability when you're one of the largest creators of software in the world. Microsoft introduced TypeScript in October 2012, which was when ECMAScript 5.1 was barely one year old, ECMAScript 6 was still years away from being formalized and JavaScript use in applications outside of a browser was well underway.

For a company the size of Microsoft, TypeScript was the pragmatic answer to dealing with JavaScript development. On the one hand, JavaScript's dominance on the browser and inroads into server-side applications, made ignoring JavaScript a losing proposition. On the other hand, JavaScript's shortcomings -- as you've already learned in previous chapters (e.g. scoping, namespaces, modules) -- also made working with JavaScript on a large scale an equally losing proposition.

TypeScript's purpose from the outset has been to provide a solid programming language foundation -- similar to other enterprise grade languages like C# and Java -- while at the same time making it work seamlessly with JavaScript engines. In this sense, TypeScript is a means to an end, applications are developed in TypeScript but are eventually transpiled into JavaScript, in order to run on regular JavaScript engines, just as if the applications were developed in JavaScript from the outset.

TypeScript versions and what makes TypeScript a JavaScript superset

As of 2022, TypeScript[1] has been released in four major versions and hundreds of minor versions. However, you don't need to worry too much about all these versions, since there's a perfectly reasonable explanation why you can focus on a single TypeScript version, most of which is related to how both JavaScript and TypeScript development work.

As new JavaScript features and ECMAScript versions are released, TypeScript incorporates these new functionalities into minor TypeScript versions, this means TypeScript continually adds support for new ECMAScript features as they're formalized -- similar to how browsers support fragmented ECMAScript features -- leading to dozens of minor TypeScript versions that add minor JavaScript feature changes. This also means newer TypeScript versions are capable of interpreting newer JavaScript features, whereas older TypeScript versions can only work with older JavaScript features.

Another reason for the copious amounts of TypeScript versions has to do with TypeScript having its own features and development cycle. If the TypeScript steering committee decides to incorporate new features, it can do so without requiring any type of approval from anyone in the JavaScript community, but more importantly, it can do so without fear of breaking JavaScript functionalities -- remember TypeScript always ends up being converted to a compatible ECMAScript version. In this sense, newer TypeScript versions give you access to more powerful features to make development easier, whereas older TypeScript versions only support better known TypeScript features (e.g. static typing).

All of which takes us to the major TypeScript versions: V.1, V.2, V.3 & V.4. Yes, you should focus on the latest TypeScript V.4 since it offers the greatest amount of features, however, this doesn't mean you won't learn functionalities previously available in V.3, V.2 and even V.1. Although there are breaking changes between TypeScript major versions[2], for the most part being well versed in TypeScript V.4 should be sufficient to produce any TypeScript version, similar to how building against the latest ECMAScript version should be sufficient to produce backward compatible ECMAScript -- you should rely on tools (e.g. IDEs, transpilers) to help you distinguish syntax supported by major releases.

Finally, before exploring TypeScript specifics, I would add you shouldn't feel intimidated by the amount of TypeScript versions. A TypeScript project can easily be started by importing a plain JavaScript project. Once you have plain JavaScript, you can rely on a TypeScript environment (e.g. Integrated Development Environment (IDE) or Deno) and start tweaking said JavaScript to make it TypeScript compatible. A TypeScript environment will immediately start giving notifications/warnings/errors about changes required to make a project TypeScript compatible, which shouldn't be much if it's already valid JavaScript. Afterwards, once you have a TypeScript project you can transpile it back into plain JavaScript to run on any JavaScript engine or use a native TypeScript run-time like Deno.

How to run TypeScript ?

There are various ways to run TypeScript with varying degrees of complexity to set up.

Can browsers run TypeScript ? Yes, but with a lot of work

For the most part, browsers are only equipped with JavaScript engines, which means that in order to execute any other type of programming language they require some type of plug-in. At the time of this writing, there are no known TypeScript engines known to run on browsers, although there are transpilers capable of converting TypeScript into JavaScript directly on browsers.

Unfortunately, the act of transpiling and adding a TypeScript transpiler to an application delivered to a browser, while possible, is a heavyweight operation that should be avoided.

TypeScript: Static type syntax

One of the unequivocal signs you're dealing with TypeScript is its use -- albeit optional -- of static data type syntax. TypeScript was conceived as a statically typed langauge, unlike JavaScript which is dynamically typed language. This means TypeScript can define data types on all its references, preventing difficult to detect bugs on account of values changing from one data type to another. JavaScript references on the other hand, can change dynamically as illustrated in listing 4-1 and can't define an explicit data type (e.g. string, number, array) as it was explained in greater detail in the chapter on JavaScript data types.

The TypeScript syntax to specify static data types is declared after references with the syntax : <data_type>, as illustrated in listing T-1.

Listing T-1. TypeScript: Static type syntax
var letter: string = 'a';
let digit: number = 1;
const vowels: string[] = ['a', 'e', 'i', 'o', 'u'];
let letters: Array<string> = ['c', 'z', ...vowels, 'b'];
let mixed: any[] = ['a', 1, 'b', 2, 'c', 3];
let plainArray = ['a', 1, 'b', 2, 'c', 3];

function worldMessage(message: string) { 
  // Template literal
  console.log(`${message} World!`)
}

let arrowEchoer = (message: number) => { console.log(message); return message };

worldMessage("Hello");
arrowEchoer(1);

// TypeScript detects incorrect assignments/calls
//digit = "a"; // Type 'string' is not assignable to type 'number'
//worldMessage(1);// Argument of type 'number' is not assignable to parameter of type 'string'
//arrowEchoer("Hello");// Argument of type 'string' is not assignable to parameter of type 'number'


Listing T-1 shows a series of examples from prior JavaScript listings, however, notice the bolded out statements in the form : <data_type>, where <data_type> is string, number, Array<string> or another data type. This syntax enforces that references are only given values of a given data type. As you can see toward the end of the listing T-1, attempting to reassign a static type reference or calling a function with a static type parameter with a different value than its static type, causes TypeScript to generate an error.

You can also see in listing T-1 that TypeScript data types can be standalone string or number types, as well as arrays of these data types, supported through either the Array<data_type> or <data_type>[] syntax. In addition, TypeScript also supports the any data type -- to allow any value -- as well as other data types.

Finally, in listing T-1 you can see the plain JavaScript statement let plainArray = ['a', 1, 'b', 2, 'c', 3]; that lacks a static type. This last statement is perfectly valid TypeScript, illustrating that static type definitions -- like other TypeScript features -- are optional. The thing you loose by not using TypeScript static type definitions is the safeguard that guarantees data type and value compatibility (e.g. that integers be assigned to integers references, that strings be used to call functions with string parameters). At the end of the day, TypeScript static types -- and all other TypeScript features -- are stripped out when you transpile an application into an ECMAScript version and run it on a JavaScript engine, the TypeScript syntax is there just to make JavaScript development more stable and scalable.

TypeScript: Same old, same old JavaScript syntax

If you look back at the TypeScript statements in listing T-1, you can see all of them are based on JavaScript syntax! There's a var statement, a let statement, a const statements, a standard function, an arrow => function, as well as spread/rest operator ....

So besides TypeScript's static type syntax, there's not a lot more you need to learn to create basic TypeScript applications. If you're worried about developing TypeScript applications with outdated JavaScript syntax, TypeScript environments offer a solution through special options/flags, where by you can allow more flexible features in TypeScript applications (e.g. ECMAScript 5) or enforce stricter rules in TypeScript applications (e.g. ECMAScript 11 [ES2020] syntax or TypeScript specific rules).

TypeScript: Files, modules, scripts, module, namespace, export = , import = require() and declare

TypeScript being a JavaScript superset is not exempt from having to deal with the fragmented approach to working with modules. In fact, because TypeScript was introduced prior to the ES6 module standard and some pre-ES6 module standards, TypeScript has its own module syntax! And although the TypeScript module syntax has been mostly replaced in favor of ECMAScript standard syntax, you're still likely to find it under certain circumstances.

TypeScript logic is contained in files with the .ts extension. And following a similar convention to JavaScript .js files, TypeScript files can be either modules or scripts depending on their contents. If a TypeScript file contains statements that attempt to access external constructs or has statements permitting other files to access its own constructs, the file is deemed a module. On the other hand, if a TypeScript file doesn't import or export constructs from other files, it's considered a script.

TypeScript was conceived with the semantically confusing concepts of external modules and internal modules. TypeScript external modules relied on the module keyword, used at the beginning of a TypeScript file to make all if its constructs available to other TypeScript modules or scripts. Whereas TypeScript internal modules relied on the namespace keyword, to enclose a section of TypeScript logic and make its constructs available under a given name -- to avoid name clashes -- inside the same TypeScript module or script.

The design decision of incorporating external modules/module and internal modules/namespace is rooted in the timing of TypeScript's first release -- one year after the formalization of ES5. As you learned in the previous JavaScript ES5: Understanding scope and functions in JavaScript section, ES5 only support for namespaces and modules was through ad-hoc syntax using IIFE (ES5: Namespaces with IIFE prevent naming conflicts; listing 1-7) or object notation (ES5: Namespaces with object assignment and object notation; listing-1-8). Thus the module keyword allowed the constructs of a TypeScript file to become available to other TypeScript files using a much simpler syntax, whereas the namespace keyword allowed the definition of a block scope or namespace, which was severely missing due to the non-block scoping nature of var statements (listing 1-9).

With the release of TypeScript 1.5 in July 2015, TypeScript introduced support for ES6 modules and simplified its original concept of modules. TypeScript dropped the concept of external and internal modules for simply modules, with the ES6 module syntax (e.g. export, import, default ) taking precedence for TypeScript module definition. With TypeScript 1.5, TypeScript internal modules simply became known by their obvious namespace keyword as namespaces and the use of the module keyword for TypeScript external modules was simply phased out -- although you may still find it in legacy TypeScript logic.

However, even with TypeScript streamlined to use ES6 module syntax, it still left TypeScript with one loose end in the area of JavaScript module support: Pre-ES6 modules. In order to streamline support for CommonJS and AMD module syntax, TypeScript introduced the syntax variation export = and the corresponding import = require() to replace the use of the exports keyword to define a single module object to export/import.

Finally, to allow a TypeScript module to leverage references from other locations (e.g. another TypeScript module, a JavaScript script), TypeScript introduced the declare keyword. The declare keyword is used to support ambient declarations, which is a fancy name for placeholder references. If a reference originates in another location other than the TypeScript where it's used, it must still be declared to avoid undefined error references, in such cases, the declare keyword is used to indicate the original reference hails from another location and is simply a placeholder reference to avoid undefined error references.

TypeScript: for loops

TypeScript loop syntax doesn't suffer from the same tortuous evolution as ECMAScript, mainly because TypeScript came to light when ES6 was close to completion. This means that from a feature standpoint, TypeScript loop syntax parts from the same foundations as ES6 (e.g. it supports iterators and generators out-of-the-box). Inclusively, even if a TypeScript loop is transpiled into an older ES6 JavaScript flavor (i.e. ES3, ES5), TypeScript generates synthetic array iterators to emulate ES6-like behavior[TO FOOTNOTE]. In this sense, TypeScript loop syntax is truly portable across any ECMAScript version.

Asynchronous TypeScript

  1. https://www.typescriptlang.org/    

  2. https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes    

  3. https://www.typescriptlang.org/play    

  4. https://replit.com/languages/typescript    

  5. https://www.npmjs.com/package/typescript    

  6. https://www.npmjs.com/package/ts-node    

  7. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#new---downleveliteration