Typescript ambient module declarations

With ES2015 JavaScript got the concept of modules using the export and import keywords. Typescript supports this and and it all works well as long as you are writing your modules in TypeScript. But to use external libraries or code that is not written in TypeScript you need to have a type declaration. For all major libraries these already exists in the definitelytyped repository which can be queried via TypeSearch. But sometimes you’ll have to write one yourself.

I had some problems understanding how these declaration should be consumed and found the documentation a little bit tricky to understand which made me to write this.

Ambient modules

From TypeScript documentation:

We call declarations that don’t define an implementation “ambient”. Typically, these are defined in .d.ts files. If you’re familiar with C/C++, you can think of these as .h files

So to write type declarations for code that is already written in JavaScript we have to write an ambient module. As stated above these are almost always defined in a file ending with a .d.ts extension.

How can we define an ambient module ? There are two ways.

Global declarations

When using declare module to create an ambient module the naming of the .d.ts file doesn’t matter what’s important is that the file is included in the compilation.

declare module "my-module" {
    export const a: number;
    export function b(paramA: number): void;
}

When the file above is included in the compilation TypeScript will register that there’s a module named my-module which then can be imported

import { a, b } from "my-module"

There are different ways to include declaration files in the compilation

  1. Specify path in the typeRoots compilerOptions in tsconfig.All global declarations files under typeRoots will be automatically included. This can be controlled with the types property compilerOption where you can explicitly control which definitions should be automatically included
  2. Specify the files property in tsconfig so that the declaration file is included
  3. Use the tripple slash directive /// <reference path="..." />
  4. With the help of paths compilerOptions in tsconfig

From TypeScript documentation

Keep in mind that automatic inclusion is only important if you’re using files with global declarations (as opposed to files declared as modules). If you use an import “foo” statement, for instance, TypeScript may still look through node_modules & node_modules/@types folders to find the foo package.

Files declared as modules

Using top-level export and import module declarations the .d.ts does not need to be included in the compilation. The important thing here is that the file is named index.d.ts and resides in a folder named after the module which in this case is my-module

// index.d.ts
export const a: number;
export function b(paramA: number): void;

// In a file importing the library
import { a, b } from "my-module"

What TypeScript will do is by default to try and lookup my-module. It will try with a number of different steps looking for both code(ts files) and declarations(.d.ts files). One of the steps is to look for declaration files in node_modules/@types. It will look for a folder named like the imported module with an index.d.ts file looking like the one above.

Sometimes you don’t wan’t to publish declaration files to definitelytyped and have folder with custom type declarations and therefore inform TypeScript to look for declarations in other folder than node_modules/@types. This can be done with the help of compilerOption paths.

{
"baseUrl": ".",
"paths": {
"*": [
"custom-typings/*"
]
}
}

With this configuration in tsconfig.json TypeScript will look for code and declaration files in the custom-typings folder.

To verify where TypeScript is trying to resolve things you can run the compiler with the traceresolution flag

tsc --traceresolution

Getting started with TypeScript and Visual Studio Code

What’s TypeScript

Typescript is a typed superset of JavaScript that compiles down to plain JavaScript that works in any browser, host or operating system. TypeScript is open source and maintained by Microsoft.

TypeScript introduces features like:

  • Static typing
  • Interfaces
  • Generics

But also uses features from ES6:

  • Classes
  • Modules
  • The arrow syntax
  • Optional and default parameters

Larger JavaScript projects tends to get a bit messy without following design patterns. TypeScript tries to solve this problem by making it easier to write modular code and by introducing types in JavaScript.

Setting up the environment

I assume that you have Visual Studio Code and npm already installed. We start by installing the TypeScript compiler(tsc).

npm install -g typescript

Now lets create a file named app.ts. TypeScript files have the .ts extension and its’s in these files we will write our TypeScript code.

// app.ts
function sayHelloWorld(name) {
    console.log("Hello world " + name);
}

sayHelloWorld("Jonathan")

As you can see this is just plain old JavaScript but it proves that you can mix TypeScript and JavaScript code. Now lets create tsconfig.json file. The tsconfig file is placed in the root directory of the TypeScript project. It specifies the files and the compiler options to compile the project. It is not mandatory to have this file but it’s simpler than specifying a whole bunch of command line arguments to tsc.

// tsconfig.js
{
    "compilerOptions": {
        "target": "es5",
        "removeComments": true,
        "sourceMap": true
    },
    "exclude": [
        "node_modules",
        "typings"
    ]
}

To create a task in Visual Studio Code that will compile our TypeScript file press CTRL+SHIFT+P to access the command palette. Type “configure task runner” and press enter. You will now have a task.json file created under the folder .vscode in your root folder. It probably looks like this:

{
	// tasks.json
	"version": "0.1.0",
	"command": "tsc",
	"isShellCommand": true,
	"args": ["-w", "-p", "."],
	"showOutput": "silent",
	"isWatching": true,
	"problemMatcher": "$tsc-watch"
}

If we look at the args array we have the -w parameter which tells the compiler to look for change files in our project so we don’t have to run our task manually. The -p parameter specifies which folder is the root of our project. The dot specifies the current directory.

Now press CTRL+SHIFT+B to start the task. It should have created a app.js file in the same directory. Create this simple html document and verify in the JavaScript console that it works.

<!doctype html>
<html>

<head>
    <script src="app.js"></script>

<body>
</body>

</html>

Types

Lets look at types:

let any1: any = "asd";
let any2: any = 123;

let str1: string = "asd";
let str2 = "asd"; // Type inference

let number1: number = 123;

let bool1: Boolean = true;

let myFunction1: (message: string) => string = function (message: string) {
    return "hej";
}

let myFunction2 = function (message: string) { // Type inference
    return "hej";
}

let obj = { // Type inference
    property1: 123,
};

I’m not going in to detail about every row but there’s two notable things. First we have the type any which can be any type of value. Second we have the object literal at the bottom. When an object is declared you cannot add properties later.

interface ISayHelloCallback {
    (message: string): void;
}

interface ISayHelloOptions {
    names: string[];
    alert?: boolean;
    callback: ISayHelloCallback;
}

function sayHello(options: ISayHelloOptions) {
    var msg = "Hello " + options.names.join(", ") + "!";
    
    if(options.alert) {
        alert(msg);
    } else {
        console.log(msg);
    }
    
    options.callback("Said hello!");
}

sayHello({
   names: [
       "Kalle",
       "Nisse"
   ],
   callback: (message: string) => {
       console.log(message);
   }
   
});

You can set an interface as type parameter to a function so when you calling that function you need to specify an object with those properties. You can also set optional properties. Above i specified the alert property as an optional parameter. Interfaces can also be used for specifying function signatures.

Type assertions

window.onload = () => {
    let input = <HTMLInputElement>document.getElementById("input1");
    input.value = "Tjena";
}

Type assertions works like type casting in other languages. Sometimes you know more about the underlying type than TypeScript does. The return type of document.getElementById is HTMLElement. So we cast it to HTMLInputElement to get access to all the properties of an input element.

Classes and inheritance

class BaseClass {
    public publicBaseProperty1: number;
    publicBaseProperty2: number; // default public
    protected protectedBaseProperty: number;
    private baseProperty3: number;

    constructor(someNumber: number) {
        this.baseProperty3 = someNumber
    }
    
    baseMethod() {
        console.log("hej");
    }
}

class ChildClass extends BaseClass {
    static childProperty1: number = 123;
    private privateChildProperty: number;
    
    constructor() {
        super(1);
        this.publicBaseProperty1 = 123;
        this.publicBaseProperty2 = 1234;
    }
    
    childMethod() {
        this.protectedBaseProperty = 555;
        super.baseMethod()
    }
    
    get childProperty1(): number {
        return this.privateChildProperty;
    }
    
    set childProperty1(value: number) {
        this.privateChildProperty = value;
    }
}

Classes can inherit other classes and implement interfaces. Above we can se that TypeScript supports the common functionality of other object oriented languages.

Loading external libraries

To get intellisense for external libraries like jQuery you need to get a TypeScript definition file. Go to http://definitelytyped.org/  and download the .d.ts file for jQuery. And then reference it in the top of your .ts file

/// <reference path="typings/browser.d.ts" />

$(document).ready(function () {
    alert(1);
});

Don’t forget to load the jQuery library either with a module loader or just include it on the web page.

Moving on

I hope you’ve got something out of this and if you wan’t to learn more about TypeScript there’s a lot more documentation at the website https://www.typescriptlang.org/.