The Definitive TypeScript Guide

This article describes the features and functionality of TypeScript 2.5.

One of the newer and most interesting languages for large-scale RIA development is Microsoft’s TypeScript. TypeScript’s claim to fame is that it adds optional static typing, interfaces, and modern ECMAScript 6+ (ES6+) features to JavaScript. In comparison to similar languages like CoffeeScript and Dart, TypeScript is unique because it is written as a superset of JavaScript—in other words, all JavaScript is already TypeScript. The language and compiler are open source and written in JavaScript, and the TypeScript team have taken great care to align the language’s extra features as closely as possible with what’s available in ES6 and later, so it also provides developers with an easy path to start using many of these newer language features.

While TypeScript’s documentation has improved significantly since this guide was first posted, this Definitive Guide still provides one of the best overviews of the key features of TypeScript, assuming you already have a reasonable knowledge of JavaScript and a basic understanding of how class-based inheritance works (as in Java, PHP, C#, etc.). The guide is regularly updated to provide new information about the latest versions of TypeScript.

Very narrow sections of TypeScript which are necessary to support legacy approaches to writing JavaScript but which are inappropriate when writing modern, portable code are excluded from this guide (declaration merging and namespaces).

Installation and usage

Installing TypeScript is as simple as running npm install typescript. Once installed, the TypeScript compiler is available by running tsc or running a local task to automatically compile after each file is saved. If you want to try out TypeScript in your browser, the TypeScript Playground lets you experience TypeScript with a full code editor, with the limitation that modules cannot be used. Most of the examples in this guide can be pasted directly into the playground to quickly see how TypeScript is compiled into easy-to-read JavaScript.

Using let and const

ES6 introduces two new ways to declare variables using let and const. Declaring variable using let or const is almost always preferable to using var because var declarations have some unusual scoping rules compared to other languages. Unlike variables declared with var, those declared with let are “block-scoped” variables which are not visible outside of their nearest containing block or for loop. This helps avoid unintended collisions from reusing variable names. Variables declared using const are scoped similarly to those using let — the big difference is that it is a compile-time error if you to try to reassign the value of a const. You can however still change the properties within an object held by a const variable. Using const when possible helps a programmer signal their intent about how such variables will behave — which makes code easier to understand.

TypeScript 2.0 added the readonly keyword which disallows reassignment and implies a non-writable property or a property with only a get accessor. It does not mean non-primitives are immutable.

Imports and exports

In order to start talking about how to write TypeScript, we first need to understand how to create and load TypeScript files. TypeScript files use the .ts file extension, and like both AMD and CommonJS, each TypeScript file nominally represents a single module. Importing modules in TypeScript follows the ES Modules (ESM) API:

import myModule from './myModule';

By default, module path resolution for relative module IDs is the same in TypeScript as it is in AMD and CommonJS, where the module ID is resolved relative to the directory containing the referencing module. Absolute module IDs work slightly differently; first, if there is a matching ambient module declaration, its value is used as-is. Otherwise, the compiler walks up the filesystem, starting from the directory containing the referencing module, looking for <moduleId>.ts, then <moduleId>.d.ts, in each parent directory, until it finds a match.

Prior to TypeScript 2.0, support existed for two ways of resolving module names: classic (a module name always resolves to a file, modules are searched using a folder walk) and node (uses rules similar to the Node.js module loader). Unfortunately neither approach solves the approach of defining modules relative to a baseUrl, which is what AMD systems such as Dojo and RequireJS, and SystemJS use.

Instead of introducing a third type of module resolution for TypeScript 2.0, the TypeScript team added configuration settings to solve this within the existing systems: baseUrl, paths, and rootDirs.

paths may only be used if baseUrl is set. If at least one of these properties is defined then the TypeScript compiler will try to use it to resolve module names and if it fails, it will fallback to a default strategy.

Importing the entire module using star (*) will cause the module’s exports to be available locally with the same names: foo, bar, and MyClass. To use these values from another module, simply import the module and access its exported properties:

To attach a module’s exports to a named property, like when you assign properties to the AMD or CommonJS exports object, provide an alias to the star import:

import * as foo from './myModule';
foo.foo();
foo.bar;
new foo.MyClass();

Note that when using mixins with TypeScript classes (2.2+), there are some subtle details which are described under the section about classes.

Language sugar

Before diving into the static typing features of TypeScript, it’s essential to review some of the general enhancements made to functions in TypeScript, since some of these changes make features of the type system easier to understand.

Optional parameters can now be defined by suffixing a parameter identifier with a question mark:

function getRange(max, min, exclusive?) {
// ...
}

Here, exclusive is an optional parameter. This is meaningless when talking about JavaScript, since all parameters are always optional, but in TypeScript, the compiler prohibits omitting typed arguments unless they are specified as optional or have a default value.

An optional parameter is essentially just a shorthand for specifying undefined as the default value for a parameter. Making a parameter optional with a different value as the default is as simple as assigning it within the parameters list instead of using the question mark shorthand:

function getRange(max, min = 0, exclusive = false) {
// ...
}

In this case, min is optional and will default to 0, and exclusive is optional and will default to false.

TypeScript also adds support for a final variadic ...rest parameter, which collects any extra arguments passed to the function into a single named array:

function publish(topic, ...args):void {
// ...
}

In this case, calling publish('/foo', 'a', 'b', 'c') would cause topic to be a string '/foo' and args to be an array [ 'a', 'b', 'c' ]. Note that using this feature adds an extra loop to your function that runs on each call to collect arguments into the rest parameter, so performance-critical code should continue to operate directly against the arguments object instead.

TypeScript 2.1 added support for ES8 rest parameters within objects.

TypeScript also includes support for the arrow function from ES6. This new function type provides a new shorthand syntax, and also changes the way the this keyword works, so its value is taken from the nearest lexical scope rather than the caller context object like regular JavaScript functions:

Types

Without adding any type hinting, variables in TypeScript are of the any type, which means they are allowed to contain any type of data, just like JavaScript. The basic syntax for adding type constraints to code in TypeScript looks like this:

In the above code, the toNumber function accepts one parameter that must be a string, and it returns a number. The variable num is explicitly typed to contain a number (though TypeScript is smart enough to know that the standard parseFloat function returns a number and therefore that num is a number since it is assigned at the time it is declared). The primitive types that TypeScript provides match the primitive types of JavaScript itself: any, number, string, boolean,void (i.e. null or undefined), and as of TS 2.0, never. In most cases, never is inferred in functions where the code flow analysis detects unreachable code and as a developer you don’t have to worry about it. For example, if a function only throws, it will get a never type.

When writing an expression (function call, arithmetic operation, etc.), you can also explicitly assert the resulting type of the expression, which is necessary if you are calling a function where TypeScript cannot figure out the return type automatically. For example:

In this example, the return value of numberStringSwap is ambiguous (the any type) because the function might return more than one type. In order to remove the ambiguity, the type of the expression being assigned to num on the last line is being explicitly asserted by prefixing the call expression with <number>. This can be done anywhere, so long as the type being asserted is compatible with the type of the expression; in other words, if TypeScript knew that numberStringSwap returned a number on line 10, attempting to assert <string> would result in a compiler error (“Cannot convert number to string”) since the two types are known to be incompatible.

When writing code in TypeScript, it is a good idea to explicitly add types to most of your variables and functions, even if the compiler can resolve the implicit types (e.g. let foo = 'a';), since doing so makes it easier for humans reading your code to know at a glance exactly what is intended. When compiling, setting "noImplicitAny": true in the tsconfig.json compilerOptions section will also prevent any accidental implicit any types from sneaking into your code (i.e. areas where the compiler is not smart enough to figure out the correct type).

TypeScript 1.8 also added support for string literal types. These are useful when you know that the value of a parameter can match one of a list of strings, e.g. easing: "ease-in" | "ease-out" | "ease-in-out";.

Local class, interface, enum, and type alias declarations may also appear inside function declarations (TS 1.6+). Scoping for local types is blocked, similar to variables declared with let and const.

Object types

In addition to the five primitive types, TypeScript allows complex types (like objects and functions) to be easily defined and used as type constraints. Just as object literals are at the root of most object definitions in JavaScript, the object type literal is at the root of most object type definitions in TypeScript. In its most basic form, it looks very similar to a normal JavaScript object literal:

let point: {
x: number;
y: number;
};

In this example, the point variable is defined as accepting any object with x and y properties. Note that, unlike a JavaScript object literal, the object type literal separates fields using semicolons, not commas.

When TypeScript compares two different object types to decide whether or not they match, it does so structurally. This means that rather than compare types by checking whether or not they both inherit the same base constraint object (like instanceof), the properties of each object are compared. As long as a given object has all of the properties that are required by the constraint on the variable being assigned to, they are considered compatible (although object literal assignments are treated more strictly as a special case in TS 1.6+):

This mechanism helps to reduce the amount of code we need to reference the same type, but there is another even more powerful abstraction in TypeScript for reusing object types: interfaces. An interface is, in essence, a named object type literal. Changing the previous example to use an interface would look like this:

This change allows the Point type to be used in multiple places within the code without having to redefine the type’s details over and over again. Interfaces can also extend other interfaces or classes using the extends keyword in order to compose more complex types out of simple types:

interface Point3d extends Point {
z: number;
}

In this example, the resulting Point3d type would consist of the x and y properties of the Point interface, plus the new z property.

Methods and properties on objects can also be specified as optional, in the same way that function parameters can be made optional:

interface Point {
x: number;
y: number;
z?: number;
}

Here, instead of specifying a separate interface for a three-dimensional point, we simply make the z property of the interface optional; the resulting type checking would look like this:

So far, we’ve looked at object types with properties, but haven’t specified how to add a method to an object. Because functions are first-class objects in JavaScript, it’s possible to use the property syntax, but TypeScript also provides a shorthand syntax for specifying methods, which becomes very convenient later when we start working with classes:

interface Point {
x: number;
y: number;
z?: number;
toGeo(): Point;
}

In this example, we’ve added a toGeo method to the Point interface, which accepts no arguments and returns another Point object. Like properties, methods can also be made optional by putting a question mark after the method name:

interface Point {
// ...
toGeo?(): Point;
}

Objects that are intended to be used as hash maps or ordered lists can be given an index signature, which enables arbitrary keys to be defined on an object:

interface HashMapOfPoints {
[key: string]: Point;
}

In this example, we’ve defined a type where any key can be set, so long as the assigned value is of type Point. As in JavaScript, it is only possible to use string or number as the type of the index signature.

For object types without an index signature, TypeScript will only allow properties to be set that are explicitly defined on the type. If you try to assign to a property that doesn’t exist on the type, you will get a compiler error. Occasionally, though, you do want to add dynamic properties to an object without an index signature. To do so, you can simply use array notation to set the property on the object instead: a['foo'] = 'foo';. Note, however, that using this workaround defeats the type system for these properties, so only do this as a last resort.

Note that as of TypeScript 2.2, property (dotted) access syntax for types with string index signatures is now allowed. This change normalizes type checking behavior between foo.bar and foo['bar']. Previously there were scenarios that did not work as expected with dotted access, which should clean up the need for some unnecessary things like:

contentStyles['transform'] = `...`;

could now be written as:

contentStyles.transform = `...`;

The potential downside with this change that we need to investigate further is that anything that has an index type and declared properties will not receive a compilation error if you typo a known property.

object type (TS 2.2+)

TypeScript 2.2 adds the the object type which fixes a previous limitation in defining a type definition where something can be an Object or a non-primitive type. This scenario was not possible to handle with previous versions of TypeScript because number, string, and boolean could all be assigned to Object. The new object type (note the lowercase) implies a type that is assignable to Object, except for primitives.

Tuple types

While JavaScript itself doesn’t have tuples, TypeScript makes it possible to emulate typed tuples using Arrays. If you wanted to store a point as an (x, y, z) tuple instead of as an object, this can be done by specifying a tuple type on a variable:

let point: [ number, number, number ] = [ 0, 0, 0 ];

Tuple types cannot be written as interfaces and they cannot have optional values, which limits their usefulness. Still, if you are working with code that emulates tuples using Arrays in JavaScript, now you can successfully type these objects in TypeScript.

Function types

Because functions in JavaScript are first-class objects, the object type literal syntax can also be used to specify that an object is supposed to be a function. To do this, the same method syntax as shown above for toGeo is used, but with the method name left blank:

let printPoint: {
(point: Point): string;
};

Here, printPoint is defined as accepting a function that takes a point object and returns a string.

Because functions are so common in JavaScript, there is a specific function type shorthand syntax in TypeScript that can be used to define functions with a single call signature:

let printPoint: (point: Point) => string;

Note the use of the arrow (=>), which comes from the ES6 arrow function, to define the return type of the function instead of a colon. Colons (:) are used when defining the return type of a method defined within an object type literal, interface, or class, whereas arrows are used by the function type shorthand shown here. This is a little confusing at first, but as you work with TypeScript, you will find it is easy to know when one or the other should be used. For instance, in the above example, using a colon would look wrong because it would result in two colons directly within the constraint: let printPoint:(point:Point):string.

Now that we know the function type syntax, going back to our Point definition, defining toGeo as a property instead of a method looks like this:

In this example, any function assigned to Point or ShorthandEquivalent would need to be a constructor that creates Point objects.

Because the object literal syntax allows us to define objects as functions, it’s also possible to define function types with static properties or methods (like the JavaScript String function, which also has a static method String.fromCharCode):

Here, we’ve defined Point as accepting a constructor that also needs to have static Point.fromLinear and Point.fromGeo methods. The only way to actually do this is to define a class that implements Point and has static fromLinear and fromGeo methods; we’ll look at how to do this later when we discuss classes in depth.

Overloaded functions

Earlier, we created an example numberStringSwap function that converts between numbers and strings:

In this example, ambiguity exists in the call signature because the return type is any. However, we know that this function returns a string when it is passed a number, and a number when it is passed a string. To help the TypeScript compiler know what we know, we can use function overloads to eliminate the call signature ambiguity and restore type checking on the return value.

One way to write the above function, in which typing is correctly handled, is:

With the extra call signatures, TypeScript now knows that when the function is passed a string, it returns a number, and vice-versa. In TypeScript 1.4+, you can also use union types in some cases instead of function overloads, which will be discussed later in this guide.

It is extremely important to keep in mind that the concrete function implementation must have an interface that matches the lowest common denominator of all of the overload signatures. This means that if a parameter accepts multiple types, as value does here, the concrete implementation must specify a type that encompasses all the possible options. In the case of numberStringSwap, because string and number have no common base, the type for valuemust be any.

Similarly, if different overloads accept different numbers of arguments, the concrete implementation must be written such that any arguments that do not exist in all overload signatures are optional. For numberStringSwap, this means that we have to make the radix argument optional in the concrete implementation. This is done by specifying a default value.

Not following these rules will result in a generic “Overload signature is not compatible with function definition” error.

Note that even though our fully defined function uses the any type for value, attempting to pass another type (like a boolean) for this parameter will cause TypeScript to throw an error because only the overloaded signatures are used for type checking. In a case where more than one signature would match a given call, the first overload listed in the source code will win:

Here, even though the second overload signature is more specific, the first will be used. This means that you always need to make sure your source code is properly ordered so that your preferred overloads are listed first.

Function overloads also work within object type literals, interfaces, and classes:

This means, in TypeScript, when you call e.g. document.createElement('video'), TypeScript knows the return value is an HTMLVideoElement and will be able to ensure you are interacting correctly with the DOM Video API without any need to type assert.

Generic types

TypeScript includes the concept of a generic type, which can be roughly thought of as a type that must include or reference another type in order to be complete. Two very common generic types that you will run into are Array and Promise.

The syntax of a generic type is GenericType<SpecificType>. For example, an “array of strings” type would be Array<string>, and a “promise that resolves to a number” type would be Promise<number>. Generic types may require more than one specific type, like Converter<TInput, TOutput>, but this is extremely uncommon. The placeholder types inside the angle brackets are called type parameters. Unlike non-generic object types, generic types can only be created as interfaces or classes.

Since arrays are the most common type of generic type, it is easiest to explain how to create your own generic types using an array-like interface as an example:

In this example, Arrayish is defined as a generic type with a single map method, which corresponds to the Array#map method from ECMAScript 5. The map method has a type parameter of its own, U, which is used to indicate that the return type of the callback function needs to be the same as the return type of the map call.

Here, arrayOfStrings is defined as being an Arrayish containing strings, and arrayOfCharCodes is defined as being an Arrayish containing numbers. We call map on the array of strings, passing a callback function that returns numbers. If the callback were changed to return a string instead of a number, the compiler would raise an error that the types were not compatible, because arrayOfCharCodes is explicitly typed and the use of a type parameter for the return value of the callback ensures that the compiler can determine compatibility.

Because arrays are an exceptionally common generic type, TypeScript provides a shorthand just for arrays: SpecificType[]. Note, however, ambiguity can occasionally arise when using this shorthand. For example, is the type () => boolean[] an array of functions that return booleans, or is it a single function that returns an array of booleans? The answer is the latter; to represent the former, you would need to write Array<() => boolean> or { (): boolean; }[].

TypeScript also allows type parameters to be constrained to a specific type by using the extends keyword within the type parameter, like interface PointPromise<T extends Point>. In this case, only a type that structurally matched Point could be used with this generic type; trying to use something else, like string, would cause a type error.

In version 2.3, Typescript added the ability to declare a default type for generic types. This is very useful for functions where the return type is dependent upon a parameter, but you want the parameter to be optional. For example, if we wanted a function that created an Arrayish based on the arguments passed but defaulted to string when no arguments are passed, before 2.3 we would have had to write:

Union types

The union type is used to indicate that a parameter or variable can contain more than one type. For example, if you wanted to have a convenience function like document.getElementById that also allowed you to pass an element, like Dojo’s byId function, you could do this using a union type:

With Mapped types, we can simplify the syntax to express this, by iterating over the original type using keyof, as a way to quickly create the new partial type. Mapped types are also useful for transforming types. For example, turning a group of synchronous properties into Promise instances.

readonly properties: Readonly<Partial<P>>;

Classes

One major feature of TypeScript we have yet to discuss is the class-based inheritance syntax. The class system in TypeScript uses a single-inheritance model that should be familiar to any programmer that has ever worked with any class-based language. A basic class definition looks like this:

The special constructor method represents the JavaScript function used as the constructor when compiled back into JavaScript. This function can return a value to use as the instance if desired, just like JavaScript, but unlike all other methods of a class, constructorcannot have a defined return type; the return type of the constructor method is always the class itself.

Subclassing works like other class-based inheritance systems, using the extends keyword to create a subtype and the super identifier to refer to the superclass:

Because property privacy is a compile-time constraint and not a runtime constraint, it’s a good idea to continue to follow JavaScript conventions for private properties and prefix with an underscore if your compiled TypeScript code might ever be consumed by someone writing pure JavaScript.

Property default values can also be specified within a class definition. The default value of a property can be any assignment expression, not just a static value, and will be executed every time a new instance is created:

However, there are some caveats that come with defining default properties in this manner. Most notably, if you have defined a constructor function on a subclass, you must call super() before anything else within the constructor, which means you can’t perform operations before the superclass’s constructor runs, and your subclass’s default properties will not be set until after the superclass’s constructor runs. The alternative for this is to simply set the defaults in the constructor yourself:

Default properties are always set by TypeScript in the same manner as above, which means these two class definitions are equivalent from the perspective of how the default properties are set. As a result, you do not have to worry about objects or arrays being shared across instances as you would if they were specified on the prototype, which alleviates a common point of confusion for people using JavaScript “class-like” inheritance libraries that specify properties on the prototype.

Mixins and multiple inheritance

In TypeScript, interfaces can also extend classes, which can be useful when composing complex types, especially if you are used to writing mixins and using multiple inheritance:

In this example, two classes (Dog, Lion) and an interface (Monsterish) have been combined into a new Chimera interface, and then a MyChimera class implements that interface, delegating back to the correct functions of the original classes. Note that the bark and roar methods are actually defined as properties rather than methods; this allows the interface to be “fully implemented” by the class despite the concrete implementation not actually existing within the class definition. This is one of the most advanced use cases for classes in TypeScript, but enables extremely robust and efficient code reuse when used properly.

TypeScript 2.2 made a number of changes to make mixins and compositional classes easier to work with. Rather than adding a new grammar to classes that might later conflict with the next version of ES, the TypeScript team achieved this result by removing some of the restrictions on classes. For example, it’s now possible to extend from a value that constructs an intersection type. The way signatures on intersection types get combined has also changed.

Enumerables

TypeScript adds a basic enum type that allows for efficient representation of sets of explicit values. For example, from the TypeScript specification, an enumeration of possible styles to apply to text might look like this:

When enumerator values are integers, you can use, as shown above, the bitwise OR operator to create bitmask values and use the bitwise AND operator to check if a value is set in the bitmask. As above, you can also explicitly define the value of a member of an enum using assignment. Enums that use bitwise operators should be specified to explicitly use 2n values for each item; enums are normally simple 0-indexed values.

Prior to version 2.4, enumerator values were restricted by the compiler to numbers. In version 2.4 and beyond, enumerator values can be strings:

enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}

Numeric enumerable types in TypeScript are two-way maps, so you can determine the name of an enumerated value by looking it up in the enum object. Using the Style above example, Style[1] would evaluate to 'BOLD'. String-initialized enums cannot be reverse mapped.

The const enum type is the same as a regular enumerable, except that the compiler replaces all references to enumerable values with literal values instead of generating code representing the enumerable structures at runtime.

Aliases

More robust type aliases can be used, which use the type keyword to provide the same sort of aliasing, but can also support aliasing of other primitive types:

Ambient declarations

In order to use existing JavaScript code with TypeScript, the compiler needs to be able to know what modules and variables come from outside TypeScript. To do this, TypeScript introduces the concept of an ambient declaration—a special declaration that provides type information about APIs that exist “ambiently” within the application’s execution environment.

Ambient declarations are created by prefixing any normal TypeScript module, var, let, const, function, class, or enum statement with the declare keyword, which indicates to the compiler that the statement is intended for ambient type hinting only. Since ambient declarations exist entirely for the benefit of the type system, they never include any implementation code, and they do not generate code on compilation.

For example, if you were to want to write code in TypeScript that used jQuery, the global jQuery function would need to be defined using an ambient declaration. In fact, many ambient declarations for various JavaScript libraries, including jQuery, can be found in the DefinitelyTyped project. The jquery.d.ts from DefinitelyTyped looks like this:

Because ambient declarations don’t generate code, they are normally placed in files with an extension of .d.ts. Any file that ends in .d.ts instead of .ts will never generate a corresponding compiled module, so this file extension can also be useful for normal TypeScript modules that contain only interface definitions.

As touched on briefly when we discussed imports and exports, modules can also be defined as ambient declarations, which makes it possible to consume JavaScript code that is already properly modularized, like the Dojo Toolkit:

When you’re writing TypeScript code that needs access to ambient declarations, a special reference comment must be added to the top of the module that needs it:

/// <reference path="jquery" />

The path given in the reference comment can be either a standard module ID or a path to a file. If you use a filesystem path and get an error about TypeScript not being able to resolve the path, make sure that you have not accidentally typoed .ts as .js.

When writing modular code, reference comments should only ever be used to import ambient declarations; all other dependencies should be loaded using the import keyword. Happily, dependencies loaded using import that are never used, or that are only used for compile-time checks, will be intelligently excluded from the compiler output.

Loader plugins

If you’re an AMD user, you’ll probably be used to working with loader plugins (text! and the like). TypeScript doesn’t support importing these kinds of dynamic modules automatically, but it does have a mechanism for enabling their use.

To use an AMD loader plugin, you’ll need to use the <amd-dependency> directive. In TypeScript 1.4 and earlier, hypothetical usage of a text! plugin looks like this:

TypeScript 2.0+ greatly simplified this through the addition of wildcard modules. To support module loader plugins within AMD or SystemJS, it’s necessary to be able to type the module, with the understanding that the name of the module is variable through the parameter that is passed to the module loader plugin. For example, this makes it possible to support the loading of HTML files, JSON resources, and other resources with more flexibility.

React and JSX support (TS 1.6+)

Numerous improvements have been made over the years to TypeScript to improve support for the JSX syntax that is popularized by React. More information may be found in the JSX Support documentation.

Control flow analysis (TS 1.8+)

Control flow analysis helps catch and prevent common errors. Examples of analysis features added include unreachable code, unused labels, implicit returns, case clause fall-throughs, narrowing and widening of types inline with the logic of your code, strict null checking, and String and number literal narrowing on strict equality, and better inference for literal types. Many of these changes can be overriden with compiler flags such as --allowUnreachableCode, --allowUnusedLabels, --noImplicitReturns, --noImplicitAny--noFallthroughCasesInSwitch, --strictNullChecks, etc.

Configuration

tsconfig.json is where most applications store information about various compiler flags and settings, as well as module path resolution information. An example tsconfig.json from the Dojo 2 project:

Many projects also include a tslint.json to specify linter settings, a typings.json file to point to any type definition files from non-TypeScript dependencies, and a package.json file that is standard with most JavaScript packages.

Glob support was added for TypeScript 2.0, making it easy to include or exclude a group of files following patterns that leverage *, ? and **/.

Note that as of TypeScript 2.1, tsconfig.json can now inherit from other configuration files, reducing duplication across complex applications and libraries. This is done via the extends key which has a path as a value.

In conclusion

Our Advanced TypeScript post goes into more depth exploring how to use TypeScript’s class system, and explores some of TypeScript’s advanced features, such as symbols and decorators.

As TypeScript continues to evolve, it brings with it not just static typing, but also new features from the current and future ECMAScript specifications. This means you can safely start using TypeScript today without worrying that your code will need to be overhauled in a few months, or that you’ll need to switch to a new compiler to take advantage of the latest and greatest language features. Any breaking changes are documented in the TypeScript wiki.

Learning more

We believe it’s more important than ever to learn the fundamentals of ES6+ and TypeScript. With the first substantial changes to the language in nearly 20 years, now is the time to learn how to efficiently leverage these changes to our primary language for creating web applications. We created an efficient workshop where we’ve distilled all of the valuable lessons we’ve learned over the past few years into a fast-paced ES6 & TypeScript for the Enterprise Developer workshop. This workshop is offered online or for your team at your location.

There is no incompatibility between TypeScript and Dojo classes; they both just become JavaScript constructor functions. schungx’s repository provides the class definitions that are necessary to use and extend existing Dojo APIs from TypeScript, and extending the existing Dojo classes can be done using the normal extends mechanism within TypeScript.

Combining multiple classes together with mixins is not as easy within TypeScript as it is with dojo/_base/declare but it is still quite possible; simply follow the instructions in the part of the guide on mixins and multiple inheritance.

Providing more native support within TypeScript for mixins or traits is something that we are interested in seeing in the future and it is a feature that the TS team are looking at adding for the next major version.

Thanks for writing,

dojoVader

Does that mean we should invest in TypeScript towards Dojo 2.0, in my opinion TypeScript doesn’t look that bad, but am assumning this is a going to be a major re-write of dojo/_base/declare.

Or lobby Microsoft’s TypeScript developers to make class definitions more configurable such that the generated JavaScript can use the specific third-party-library’s own class declaration syntax. Microsoft may not care enough though, as they seem to be die-hards on jQuery and node.js. As long as jQuery and node.js works, it seems to be fine for them.

I’ve been talking with the TypeScript team and they are interested in ensuring that these sorts of extensions are possible in the future; you can see some of the progress at Mixins in TypeScript documentation and a discussion on a more robust solution for class combination. Perfect is the enemy of good, and I’d rather see a 1.0 release that makes these interactions possible even if it is a little messy, instead of never actually getting to 1.0. Of course, the ultimate goal should probably be to extend the built-in class system so that it can be made as powerful as any other solution.

Corey Alix

I have been enjoying typescript and dojo for several months now. First using my own definitions and then schungx. I’m finding the transition from 0.9.1 to 0.9.5 painful in large part because only one developer has taken the initiative to define dojo.d.ts and related. If a team took on this burden we’d have a much easier time at it. I’m trying to resolve issues as I come across them but just don’t have the ability to get very far. Is this something sitepen is looking to take on in the near future?

At the moment, we’re probably more focused on exploring whether or not TypeScript is a good solution for future work such as Dojo 2. That said, if there’s demand, it’s certainly something we would consider working on, or at least finding a way to support a community effort to maintain TS interfaces for Dojo 1.9.x.

John Reilly

Really nice run through guys – great job!

Corey Alix

How about a definitive-guide-to-dojo-amd-typescript? I’ve struggled with making use of “class” to extend a sub-class of ContentPane but no luck. I can’t expose the interface. It’s possible?https://gist.github.com/ca0v/8441296

“Happily, dependencies loaded using import that are never used, or that are only used for compile-time checks, will be intelligently excluded from the compiler output.”

This is not always true! When I build the project (VS2013) it injects the false dependency. Dependency is injected with a node build as well. Interestingly saving the file in VS does not generate the dependency!
:
Imagine how confusing this is…app doesn’t work; re-save a file without any changes; app works.

I’ve never seen any behaviour like what you are describing. I don’t use Visual Studio, only the command-line tsc, and always compile to AMD modules. In every case, an import statement whose value is referenced only by the type system is not emitted as a dependency in the output.

The last section on loader plugins doesn’t appear to work exactly as advertised in Dojo 1.10 for dojo/text and dojo/i18n. I had to pre-load those modules (with no plugin argument) and set their “dynamic” field to false. Otherwise, the loader assumes the content will be dynamically loaded and, therefore, doesn’t try to load from the cache.

The bit about loader plugins seems incorrect. The name attribute doesn’t seem to do anything, at least for TypeScript 1.4. The code below works however; it’s an example with a regular import and two different loader plugins being used.

I’ve updated the article to re-include some of the original information about loader plugins for TypeScript 1.4. (Limitation of the blog system prevent listing all the collaborators on this article, the update removing that content was not me :))

In general, as long as a given object has all of the properties that are required by the constraint on the variable being assigned to, they are considered compatible in TypeScript. So, the assignment in this case is accepted by TypeScript because an Array instance satisfies the minimum requirement of the Arrayish interface of supplying the minimum structure of a single method named “map”.

One exception (which does not apply here) is a special case for object literal assignments that need to be more exact.

Bitchiko

Thanks, Accepted answer.
Addition, in case of object literal assignment, extra properties will not have a reference to them, if if were allowed.

Thx for the great overview. One comment: Use of the fat arrow syntax to provide the type for a function (or method, etc) in Typescript is slightly misleading. As you know, arrow and non-arrow functions in ES6 are subtly but significantly different, e.g. in the use of ‘this’. Yet, if I understand correctly, when I characterize a function using ‘let fn: () => any;’ Typescript allows me to use either ‘fn = () => {…};’ or ‘fn = function() {…};’. I think this is acceptable enough, but it might be helpful to point out this discrepancy in this Typescript overview. Specifically, readers should know that the Typescript arrow syntax for typing functions is a more general abbreviation for functions than is true in ES6 in that it allows implementation using either arrow or non-arrow functions. Otherwise people new to Typescript might, e.g., think that the Typescript arrow typing allows only true arrow functions which is false.

Dave

Does anybody know how to use ‘require’ syntax in the TypeScript context? i.e. Something like:

The ubiquitous use of semicolons reveals you need some education yourself about learning how to efficiently leverage changes in this now-core language. Completely useless semicolons are second only to Hungarian notation on the idiotic scale.