Type Hinting in JavaScript

Most developers understand that JavaScript does not have a notion of datatypes: it’s often referred to as “dynamically typed”, “weakly typed,” or even “untyped.” The distinction is mostly academic, and the bottom line is when you declare a variable, you don’t specify its datatype. For example, if you see this:

var animals;

There’s no way to know whether animals is an array, a string, a function, or something else. So how do you make sure that people use variables as they were intended?

The first and most obvious solution is to document all your types. If your program fits in one or two files, you can just check the documentation to determine any given type. But when your application spans dozens or hundreds of files, or the number of developers working on it begins to climb, this solution can quickly lead to a huge mess. When you get to this point, it’s helpful to offload “checking that function signature” to your IDE or text editor.

If your IDE or editor doesn’t know that animals will eventually be an array, there’s no way for it to helpfully tell you that animals has the property length and the method map, among others. There’s no way for the IDE to know it’s an array… unless you tell it! That’s where code hinting comes in.

On projects of any size, code hinting reduces typos, makes coding easier, and reduces the need to check documentation. Programmers who use strongly-typed languages such as Java and IDEs such as Eclipse take automated code-assistance for granted. But what about JavaScript programmers?

This post will examine a couple ways to clue-in your IDE to the types of the variables, function parameters, and return values in your program so the IDE can clue you in on how to use them. We’ll go over two ways to “tell” your IDE (and other developers) about types, and how to load type information for third-party libraries as well.

Before writing type annotations, however, you need a tool that can read them.

Setting up The Environment

The first thing you need is a code editor that recognizes and supports the concept of types in JavaScript. You can either use a JavaScript-oriented IDE such as Webstorm or VisualStudio Code, or if you have a favorite text editor, see if it has a type-hinting plugin that supports JavaScript. For example, both Sublime and Atom have type-hinting plugins.

Built-in Types

To begin an example of, use npm init to start a new JavaScript project. At this point, you already get quite a bit from VS Code, which has both JavaScript language APIs (Math, String, etc.) and browser APIs (DOM, Console, XMLHttpRequest, etc.) built in.

Here’s an illustration of some of what you get out of the box:

Nice! But we’re more interested in Node.js annotations and sadly, VS Code does not ship with those. Type declarations for Node.js core APIs do exist, however, in the form of Typescript declaration files. You just need a way to add them to your workspace so VS Code can find them: Enter Typings.

Typings

Typings is a “Typescript Definition Manager” that informs an IDE about the Typescript definitions (or “declarations”) for the JavaScript APIs you’re using. Later we’ll look at the format of Typescript declarations later; for now you need to inform the IDE about Node.js core APIs.

Install Typings:

$npminstall--globaltypings

With Typings installed on your system, you can add the Node.js core API type definitions to your project. From the project root, enter this command:

$typingsinstalldt~node--global--save

Let’s break that command down:

Using the typings install command…

Install the node package from dt~, the DefinitelyTyped repository, a huge collection of TypeScript definitions.

Add the --global option to access to definitions for process and modules from throughout our project.

Finally, the --save option causes typings save this type definition as a project dependency in typings.json, which you can check into your repo so others can install these same types. (typings.json is to typings install what package.json is to npm install.)

Now you have a new typings/ directory containing the newly-downloaded definitions, as well as your typings.json file.

One More Step…

You now have these type definitions in your project, and VS Code loads all type definitions in your project automatically. However, it identifies the root of a JavaScript project by the presence of a jsconfig.json file, and we don’t have one yet. VS Code can usually guess if your project is JavaScript based, and when it does it will display a little green lightbulb in the status bar, prompting you to create just such a jsconfig.json file. Click that button, save the file, start writing some Node, and…

It works! You now get “Intellisense” code hints for all Node.js core APIs. Your project won’t just be using Node core APIs though, you’ll be pulling in some utility libraries, starting with lodash. typings search lodash reveals that there’s a lodash definition from the npm source as well as global and dt. You want the npm version since you’ll be consuming lodash as module included with require('lodash') and it will not be globally available.

So far you’ve seen how to install and consume types for Node and third party libraries, but you’re going to want these annotations for your own code as well. You can achieve this by using JSDoc comments, writing your own Typescript Declaration files, or a combination of both.

JSDoc Annotations

JSDoc is a tool that allows you to describe the parameters and return types of functions in JavaScript, as well as variables and constants. The main advantages of using JSDoc comments are:

They’re lightweight and easy to use (just add comments to your code).

The comments are human-readable, so they can be useful even if you’re reading the code on github or in a simple text editor.

JSDoc supports many annotations, but you can get a long way just by learning a few, namely @param and @return. Let’s annotate this simple function, which checks whether one string contains another string:

Suppose that while writing this, you realize this function actually works with regular expressions as the search parameter as well as strings. Update that line to make clear that both types are supported:

JSDoc works great and you’ve only scratched the surface of what it can do, but for more complex tasks or cases where you’re documenting a data structure that exists for example in a configuration file, TypeScript declaration files are often a better choice.

Typescript Declarations

A TypeScript declaration file uses the extension .d.ts and describes the shape of an API, but does not contain the actual API implementation. In this way, they are very similar to the Java or PHP concept of an interface. If you were writing Typescript, you would declare the types of function parameters and so on right in the code, but JavaScript’s lack of types makes this impossible. The solution: declare the types in an JavaScript library in a Typescript (definition) file that can be installed alongside the JavaScript library. This is the reason you installed the Lodash type definitions separately from Lodash.

This post won’t cover setting up external type definitions for an API you wnat to publish and registering them on the typings repository, but you can read about it in the Typings documentation. For now, let’s consider the case of a complex configuration file.

Imagine you have an application that creates a map and allows users to add features to the map. You’ll be deploying these editable maps to different client sites, so you want be able to configure the types of features users can add and the coordinates to on which to center the map for each site.

The config.json looks like this:

{"siteName":"Strongloop","introText":{"title":"<h1> Yo </h1>","body":"<strong>Welcome to StrongLoop!</strong>"},"mapbox":{"styleUrl":"mapbox://styles/test/ciolxdklf80000atmd1raqh0rs","accessToken":"pk.10Ijoic2slkdklKLSDKJ083246ImEiOi9823426In0.pWHSxiy24bkSm1V2z-SAkA"},"coords":[73.153,142.621],"types":[{"name":"walk","type":"path","lineColor":"#F900FC","icon":"test-icon-32.png"},{"name":"live","type":"point","question":"Where do you live?","icon":"placeLive.png"}...

You don’t want to have to read this complex JSON file each time you want to find the name of a key or remember the type of a property. Furthermore, it’s not possible to document this structure in the file itself because JSON does not allow comments.* Let’s create a Typescript declaration called config.d.ts to describe this configuration object, and put it in a directory in the project called types/.

Declares the Demo namespace, so you don’t collide with some other MapConfig interface.

Declares two interfaces, essentially schemas describing the structure and purpose of the JSON.

Defines the types property of the first interface as an array whose members are MapConfigFeatures.

Exports MapConfig so you can reference it from outside the file.

VS Code will load the file automatically because it’s in the project, and you’ll use the @type annotation to mark the conf object as a MapConfig when it’s loaded from disk:

/** @type {Demo.MapConfig} */constconf=require('./config.js');

Now you can access properties of the configuration object and get the same code-completion, type information, and documentation hints! Note how in the following GIF, VS Code identifies not only that conf.types is an array, but when you call an .filter on it, knows that each element in the array is a MapConfigFeature type object:

I’ve been enjoying the benefits of JSDoc, Typescript Declarations, and the typings repository in my work. Hopefully this article will help you get started up and running with type hinting in JavaScript. If you have any questions or corrections, or if this post was useful to you, please let me know!