Migrating from typings to npm @types

Typescript definitions. What a confusing topic. Prior to Typescript 2.0 we were using tools like typings to manage and install them. After 2.0 we moved to npm@types, but what does that really mean? And how can we migrate an existing project that uses typings? What about custom type definitions?

In this post I will show you how to migrate to newest @types, answering all these questions.

Let’s work with a project that has typings from the typings repository and also custom ones. We need to preserve the project’s structure in code, the only thing we need is to get rid of typings.json and its definition files that are installed with it.

Before we do that though, let’s dig into some useful information on typings.

Typescript Definition Manager

Having a basic understanding of the Javascript programming language, one can spot its untyped nature. You can write code that passes around data and objects as you want, which don’t necessary means that this code, namely functions, objects or variables actually exist. You can perfectly write some code that calls an non-existent method and Javascript will not complaint, because there is no compiler in the first place the language is compiled by a JIT compiler when running on the browser and there you will get your runtime error
These issues, you will agree, are hard to discover when you are developing and can lead to unstable code.

Enter Typescript. Typescript is mainly about adding types to Javascript, transforming it from a loose-typed language to a hard-typed, requiring from the developer to accurately descript the format of the objects and data in the code. But Typescript does not run on the browser, Javascript does. That is why Typescript comes with its own compiler, which essentially compiles .ts files to pure Javascript. During the compilation process errors can be detected (of course compile time errors, not runtime/logic), essentially halting you from building that code. If you are owner of this code then its in your hands to make the syntax compatible with the compiler’s laws.

But what about third party libraries, like jQuery or Angular? These, you don’t have any authority over and Typescript cannot understand the libraries specific objects, like $ or angular. Compiler won’t be able to understand those objects and will throw an error if you reference them.
So, in order to use such libraries with Typescript, you need to get files that describe their public API. These are type definition files, with a file extension of .d.ts.

The Typescript Definition Manager is a solution to that problem, as it provides a single way to manage and install Typescript definitions, by using a typings.json file to resolve the library specific types from various repositories, like the typings registry, Github, NPM or even custom online or local files.
This project is heavily supported by the community, which has effectively gone and documented the nature of nearly 90% of the top Javascript projects in there.

Okay, and what is that @types thing?

npm @types

@types comes to solve the problem of using another third party tool for this job. Many people have plead for making Typescript support resolving these definition types internally.
This is a feature that was introduced in Typescript 2.0. All the type definition files that were part of the typings project are moved to NPM now, so one can download the directly from there with a simple command

npm install @types/angular --save

As you can see, installation is fairly simple. Of course, the compiler was modified accordingly to resolve these types automatically, supporting global and module type definitions.
Global definitions are by default any definitions that support global consumption and they are included automatically.
For module definitions, no special configuration is required after the installation, you just use it like a module

import * as angular from "angular";

There are ~3274 @types packages available at npm at the moment. Take a look here.

Now that we got some background on them, let’s move to the actual project.

Migration

First of all, make sure you have installed Typescript version >= 2.0. I have 2.3.3 version installed.
npm install typescript@2.3.3 -g
npm install typescript@2.3.3 --save-dev

I installed Typescript globally on my machine and also locally on my project, so my code can be compiled on the CI.

Next, remove type definitions from typings.json.
My typings file contains the following global dependencies

{
  "globalDependencies": {
    "angular": "registry:dt/angular#1.5.0+20160909133806",
    "angular-mocks": "registry:dt/angular-mocks#1.5.0+20160608104721",
    "angular-route": "registry:dt/angular-route#1.3.0+20160317120654",
    "angularlocalstorage": "registry:dt/angularlocalstorage#0.1.7+20160317120654",
    "clientConfig": "file:typings/custom/client.config/index.d.ts",
    "express": "registry:dt/express#4.0.0+20160708185218",
    "express-serve-static-core": "registry:dt/express-serve-static-core#4.0.0+20160914120416",
    "gulp": "registry:dt/gulp#3.8.0+20160907112908",
    "homeControllerScope": "file:typings/custom/home.controller.scope/index.d.ts",
    "jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
    "jquery": "registry:dt/jquery#1.10.0+20160908203239",
    "localstorage": "file:typings/custom/localstorage/index.d.ts",
    "menuControllerScope": "file:typings/custom/menu.controller.scope/index.d.ts",
    "mime": "registry:dt/mime#0.0.0+20160316155526",
    "node": "registry:dt/node#6.0.0+20160921192128",
    "orchestrator": "registry:dt/orchestrator#0.0.0+20160316155526",
    "q": "registry:dt/q#0.0.0+20160613154756",
    "roomControllerScope": "file:typings/custom/room.controller.scope/index.d.ts",
    "roomRoute": "file:typings/custom/room.route/index.d.ts",
    "serve-static": "registry:dt/serve-static#0.0.0+20160606155157",
    "serverConfig": "file:typings/custom/server.config/index.d.ts",
    "socket": "file:typings/custom/socket/index.d.ts",
    "socket.io": "registry:dt/socket.io#1.4.4+20160909205835",
    "socket.io-client": "registry:dt/socket.io-client#1.4.4+20160317120654",
    "tuple": "file:typings/custom/tuple/index.d.ts",
    "types": "file:typings/custom/types/index.d.ts",
    "user": "file:typings/custom/user/index.d.ts",
    "vinyl": "registry:dt/vinyl#1.2.0+20160924121322"
  }
}

As you can see, it contains definitions directly from the registry and also some custom ones (the file:typings/custom/*). We want to remove all these from the typings/globals/ directory.
Also, note that my custom definitions are under the typings/custom/ directory. I want to keep that directory, so I can register the custom type definitions with the tsconfig.json.
I use VS Code here, but you can do it with your command prompt from the location of the typings.json file. So, on VS Code, open the terminal (Ctrl + `) and start removing definitions using the following commands:

typings rm <name> -SG

The rm command is just an alias to the uninstall command. The -SG flag is removing from globals directory. So, for example, I want to remove the angular typed definition

typings rm angular -SG

I removed all the rest using the same command.

After you have removed all the definitions, go ahead and remove the typings.json file. Also, uninstall the typings from the npm modules.

npm remove typings --save-dev

My working directory looks like the following now:

Awesome! Next step is to configure tsconfig.json to register these definitions as globals and to install all @types for the rest of the libraries.
Let’s first install all the @types modules and then configure the tsconfig.json

npm install @types/angular @types/angular-mocks @types/angular-route @types/angularlocalstorage @types/express @types/express-serve-static-core @types/gulp @types/jasmine @types/jquery @types/mime @types/node @types/orchestrator @types/q @types/serve-static @types/socket.io @types/socket.io-client @types/vinyl --save-dev

And my tsconfig.json is:

{
    "compilerOptions": {
        "module": "commonjs",
        "moduleResolution": "node",
        "noImplicitAny": false,
        "sourceMap": true,
        "target": "es6",
        "removeComments": false,
        "allowSyntheticDefaultImports": true,
        "suppressExcessPropertyErrors": true,
        "typeRoots": [
            "node_modules/@types",
            "typings/custom"
        ]
    },
    "exclude": [
        "node_modules",
        "typings"
    ]
}

I will not explain all those properties, rather than stand on typerRoots and a bit on exclude.
As you know, exclude is used to exclude files from compilation process, so I excluded all typescript under node_modules and typings directories. Remember, I have custom types under the typings folder.

Now the typeRoots. As you know, @types are automatically included in the compilation. If you are casually saving your @types under node_modules/@types, then they will be automatically picked up by the compiler and will be globally available (just like having them as globals in typings). But what happens when you want to have other types, like custom ones available also? And they are in an entirely different folder from node_modules/@types? You need to explicitly tell the compiler to look not only on node_modules/@types but to other directories as well.
Enter the typeRoots compiler option. This option allows loading typing declarations from folders which have an NPM format, meaning they contain an index.d.ts file. The default for the typeRoots is:

{
   "typeRoots" : ["node_modules/@types"]
}

Now you understand why node_modules/@types is by default picked up.
Typescript goes to that folder and tries to load every sub-folder that is an npm package. Essentially, the typeRoots property is useful for loading global declarations.
The thing is that it loads as global every declaration under the folder structure(s) that it is instructed to look. If you wish to be explicit on what you want to be loaded as globals, then you should use the types property.
Looking on the tsconfig.json you can understand that declarations are loaded from node_modules/@types and from typings/custom directories.

Almost ready…

One more thing I need to do is to fix some of my Typescript definition files, as they use the /// <reference path="..." /> to reference other definitions inside those .d.ts files. These references need to be removed, so, I visited each of the typings/custom/*/index.d.ts files to do so.

Let’s try to compile now…

No problem at all! I successfully migrated my Typescript definitions!

Summary

In this post I presented some key points on typings definition manager and @types. I quickly and detailed demonstrated a seamless migration from old definitions to new ones, preserving the custom definitions in the same time. I discussed on how the compiler is tied to resolving global or module type definitions.

Hope you enjoyed that, see you next time!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s