An Introduction to TypeScript: Static Typing for the Web
TypeScript is one of many attempts at creating a better experience with JavaScript.
TypeScript is a strongly-typed superset of JavaScript, which means it adds some syntactical benefits to the language while still letting you write normal JavaScript if you want to. It encourages a more declarative style of programming through things like interfaces and static typing (more on these later), offers modules and classes, and most importantly, integrates relatively well with popular JavaScript libraries and code. You could think of it as a strongly static layer over current JavaScript that has a few features to make life (and debugging especially) a bit more bearable.
TypeScript gained particular attention a few years ago because it was selected for full support by Angular 2 and following (which is also written in TypeScript itself). It’s also developed by Microsoft, which means it has the backing of two major tech companies (not a bad place for any language). Since this time, it’s gained more of a following and mainstream status.
Needless to say, TypeScript is definitely worth looking into.
How Does it Work?
TypeScript actually looks much like modern JavaScript. At the most basic level, it introduces a static typing paradigm to JavaScript, so instead of the following:
var name = “Susan”,
age = 25,
hasCode = true;
We could write the following:
let name: string = "Susan",
age: number = 25,
hasCode: boolean = true;
As you can see, there’s not a whole lot of difference here. All we’re doing is explicitly telling the system what type each variable is; we’re telling it from the get-go that name
is a string and age
is a number. But that just seems like we have to write more code. Why bother telling the system such specific information? Because it gives the system more information about our program, which in turn means it can catch errors that we might make further down the road.
Imagine, for instance, you have something like this in your code:
var age = 25;
age = "twenty-five";
Mutating a variable like this and changing its type will likely end up breaking stuff somewhere else, especially in a really big program, so it’s great if the compiler can catch this before we load this up in our browser and have to sit for half an hour looking for the issue ourselves. Basically, it makes our program safer and more secure from bugs.
There’s more, though. Here’s an example from the TypeScript website intro tutorial (which you can find here):
interface Person {
firstname: string;
lastname: string;
}
function greeter(person : Person):string {
return "Hello, " + person.firstname + " " + person.lastname;
}
let user = {firstname: "Jane", lastname: "User"};
document.body.innerHTML = greeter(user);
Now there are a few more unusual things here than we had before. We’ve got a run-of-the-mill object, called user
, containing a first and last name, and that’s being passed to greeter()
and the output inserted into the body of the document. But there is some bizarre-looking stuff in the arguments of thegreeter
function, as well as something called an interface
.
Let’s start with the greeter
function:
function greeter(person: Person):string {
return "Hello, " + person.firstname + " " + person.lastname;
}
We can see that greeter
takes a person
parameter and we expect it to be of type Person
. In this way, we can be sure that when we ask for that person’s first name, it will definitely be there and we won’t induce headaches upon ourselves if it fails. The :string
after the function parameters tells us what type we expect this function to return when we call it.
The body of the function is nothing complicated but, of course, by now you’re probably wondering what on earth a Person
type actually is. This is where the interface
feature comes in:
interface Person {
firstname: string;
lastname: string;
}
Interfaces are used in TypeScript to define the structure of objects (and only objects). In this example, we’re saying that any variable of type Person
must be an object containing a firstname
and a lastname
property, both of the string type. We’re basically creating a custom type for our object.
This is useful because it tells the compiler, as well as yourself and any developer who will work on this in the future, exactly what type of data to expect. We’re basically modelling the object properties, creating something we can reference if we need to debug later. This is often why you’ll see interfaces at the top of TypeScript files, as they give us a good idea of the data the program is working with in the rest of the file.
In our example, if we use this Person
interface with a variable at any point in the program and it doesn’t contain either a firstname
or lastname
, both of type string
(our user
object thankfully does), then the compiler will moan at us and we will be forced to mend our ways.
Not only that, but having static typing means that an IDE or editor with support for TypeScript will be able to provide us with very good, very specific hinting and auto-completion so that we can develop code that is both faster and safer.
There are many more features that TypeScript allows us to use, such as generics and namespaces, so at least a quick read of their documentation is highly recommended.
How Do I Set it Up?
Because TypeScript is a superset of JavaScript, we’ll need to transpile it into JavaScript if we want to use it in the browser. Thankfully, it integrates well with a number of task runners and bundlers already.
If you’re just looking to play around with it locally first in particular, you can install TypeScript globally via npm and use it from the command line with the tsc
command, like so:
tsc your-typescript-file.ts
This will output a JavaScript file, in this case called your-typescript-file.js
, which you can then use in the browser as per usual. Setting it up in a project, though, will almost certainly entail setting up a proper tsconfig.json
.
This file denotes that the project is a TypeScript project, and allows us to set a number of configuration options. Here’s a truncated example from the docs:
{
"compilerOptions": {
"module": "commonjs",
"outFile": "./build/local/tsc.js",
"sourceMap": true
},
"exclude": [
"node_modules"
]
}
Here we’re configuring the compiler in a number of ways. We’re specifying a module system to compile to, where to put the compiled file when it’s finished and to include a source map. We’re also giving it an exclude
option, which basically tells the compiler to compile any TypeScript files — those ending in .ts
— it finds as long as they’re not in the node_modules
folder.
From here, we can integrate things into our favorite task runner or bundler. Both Grunt and Gulp have plugins for TypeScript which will expose the compiler options for your task runners. Webpack has an awesome TypeScript loader, and there’s good support for some other setups as well. Basically, you can get TypeScript integrated into pretty much any workflow you currently have going on without too much effort.
External Typings
If you’re using external libraries in your project (let’s be honest, who isn’t?) you’ll likely also need some type definitions. These definitions — denoted by a .d.ts
extension — give us access to interfaces that other people have written for a number of JavaScript libraries. By and large, these definitions are available in a gigantic repo called DefinitelyTyped, which is where we install them from.
To use them you’ll need to install Typings, which is kind of like npm but for TypeScript type definitions. It has its own config file, called typings.json
, where you can configure your bundles and paths for type definition installation.
We won’t go into too much detail here, but if we wanted to use AngularJS 1.x types, for example, we could simply go typings install angularjs --save
and have them downloaded into a path defined in typings.json
. After that, you could use Angular’s type definitions anywhere in your project simply by including this line:
/// <reference path="angularjs/angular.d.ts" />
Now we can use Angular type definitions like the following:
var http: ng.IHttpService;
Any developers who happen upon our code at a later stage (or ourselves, three months after we’ve written it) will be able to make more sense of what we’ve written by looking at them.
Okay, What About the Community?
The TypeScript community is continuing to grow, as is the language’s adoption. Perhaps most importantly, it’s what Angular 2+ is written in and the framework provides full support for it straight from the beginning. There is also fantastic support for its syntax baked into Microsoft Visual Studio IDE and Visual Studio Code, with packages and plugins for editors like Atom, Sublime Text and Emacs readily available as well.
What this means is that there’s plenty of activity going on around TypeScript, so this is something you’ll want to keep your eye on.
Further Reading
- Official TypeScript site
- DefinitelyTyped — 3rd-party TypeScript Definitions
Conclusion
TypeScript is an interesting push toward improving on JavaScript’s shortcomings by introducing a static typing system, complete with interfaces and type unions. This helps us write safer, more legible and declarative code.
It integrates well with virtually every mainstream build setup out there at the moment and even gives us the ability to create and use custom types as well. There are also a myriad IDEs and text editors that have great support for its syntax and compile process, so you can use it in your coding environment of choice with little pain or process.
Perhaps most importantly, TypeScript is a big part of Angular 2+, which means we’ll continue to see it well into the future. The more we know about it and how it works, the better equipped we’ll be to deal with it when it arrives as a fully-fledged mainstream alternative to JavaScript.
Do you feel inspired to use TypeScript in your next project? Is strong typing the future of JavaScript, or is it just a fad? Let me know what you think below!