Header image by Franz Harvin Aceituna on Unsplash.
TypeScript (TS) is a language which has seen quite a meteoric rise lately. It's gone some favourable results on the 2018 State of JavaScript (JS) survey. It has even come to the point where big names like Kent C. Dodds started migrating into it.
To learn more about how the TypeScript type system works, and how it can help you, watch this talk by Anders Hejlsberg, the creator of TypeScript.
For many of us already using TypeScript, we could never imagine writing JS without it anymore. And with newly-added support for Babel compilation, it gets much easier to integrate with the rest of the JS ecosystem. But for many people looking to migrate their apps into it, it could feel a little too overwhelming. This gets further out of control when you're looking at a medium/large-sized app, all already written in JavaScript.
A lot of TypeScript learning materials out there never seem to dive deep on migrating a well-matured app to TypeScript. Worse still, TypeScript does have their own, official migration guide - but it's horribly outdated.
So in this series of posts, I try to outline my personal steps on how to migrate an existing codebase to TypeScript. The first part will go through the steps on preparing your project for the Big Rewrite. This includes setting up the TS compiler, and the basic essentials of the TypeScript compiler.
Table of contents
- Part 1: Introduction and getting started (you are here)
- Part 2: Trust the compiler!
So what is TypeScript, exactly?
TypeScript is a superset of JavaScript that compiles to plain JavaScript code. It enables great tooling and developer experience through the power of static typing. Some of the improved JS experience being unlocked by static typing includes better refactoring tools, statement completion, and more.
TypeScript was authored by Anders Hejlsberg, known for being the lead architect of C# and creator of Turbo Pascal. TypeScript 2.0 was released on September 2016, with much-improved Node.js modules support and stricter null
checking. Since then, the language is continuously improved with features like object rest/spread, --strict
mode, conditional types, and more. TypeScript 3.0, released in July 2018, even has support for monorepos through project references.
To read more about TypeScript, I recommend the TypeScript Deep Dive book by Basarat.
Getting started with TypeScript
So to start off, we will need to set up our environment for TypeScript. There are two ways to set this up:
- You use Babel 7 + TypeScript preset to compile, and have the TypeScript compiler only do the type-checking.
- You use the TypeScript compiler to both type-check and compile your code.
Since we’re migrating from JavaScript, we can assume that we’re already using Babel in our development toolchain, so we can go with the first option. You can also run the second option and chain with Babel. But the first option is still better if we want to have finer control over the Babel presets/plugins we use as well.
Initialising the compiler
This guide will make use of TypeScript 3.2. It should work as well on any versions starting from 3.0+.
To get started with TypeScript, install the TypeScript compiler CLI by running:
bash
$ npm install -g typescript
Then run tsc --init
to initialise a tsconfig.json
file with the default options. It lists out all the options available as well as an explanation, with the non-essential options commented out. The number of options may overwhelm you, but let's break the config down to just the essentials.
tsconfig.json
json
{"compilerOptions": {"allowJs": true,"checkJs": false,"esModuleInterop": true,"downlevelIteration": true,"lib": ["esnext", "dom"],"module": "commonjs","noUnusedLocals": true,"outDir": "dist","skipLibCheck": true,"strict": true,"target": "esnext"},"include": ["src"]}
This setup will take everything from the src
and compile it into the dist
folder. There are some other essential compiler options here, but we'll go through them in the next section. To compile, run the tsc
command.
Note: If you use webpack to compile things, you don't need the outDir
option!
Setting up build tasks
Now that the TypeScript compiler works, we can include it as a script in our package.json
file!
json
{"scripts": {"build": "tsc"}}
This way, you can simply run yarn build
(or npm run build
if you're running npm) to build your project.
Wiring up the TypeScript Babel preset (Optional)
If you already use Babel to compile your ES6+ JS code, you can use the TS preset for Babel. Note that you need Babel 7 and above to use this.
json
{"presets": ["@babel/preset-env", "@babel/preset-typescript"],"plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-object-rest-spread"]}
The TypeScript compiler supports all modern ES2015 features, as well as next-generaton ES features. Though one common pitfall is that you can't use next-generation syntax newer than stage-3, since TS doesn't support it. This means that using proposed syntax like the pipeline will give you type errors. The proposal plugins should include the stage-3 features required for TypeScript transpilation.
Note that the Babel compiler only removes the types from your code. It does not do any extra type-checking! Make sure to run type-checking separately with tsc --noEmit
. Or better yet, add it as a compiler option into your tsconfig.json
:
json
{"compilerOptions": {"noEmit": true}}
This option will run the TypeScript compiler without outputting any code, so it only runs type-checking. You can then add the tsc
command to your package.json
scripts, which will help if you use a CI system as well.
json
{"scripts": {"type-check": "tsc"}}
Note: If you use Flowtype, you can't use the Flowtype Babel preset together with the TypeScript preset. You have to choose one or the other!
tsconfig.json
essentials
The above tsconfig.json
file already contains the essential compiler options when working with TypeScript. Let's go through the essentials one by one.
TS/JS interoperability
The TypeScript compiler can also be set up to type-check and compile JS files alongside TS files. allowJs
allows regular JavaScript files to be compiled. If you want to also enable type-checking in JavaScript files, you can also enable checkJs
. If you're just getting started, it's recommended to disable checkJs
and manually enable per-file type checking. To do that, add a // @ts-check
comment on the top of the JS file you'd like to type-check.
Another compiler option to take note of is esModuleInterop
. This allows you to do default imports with CommonJS modules (e.g. import React from 'react';
). For TS veterans, this option is similar to allowSyntheticDefaultImports
. The only difference is that it added some helpers during compile time for improved Babel interoperability.
Libraries and compile targets
There are three options that define how your TS code is interpreted by the compiler.
lib
outlines the TS library files used for compilation. Some libraries that are commonly used are:
esnext
- Modern ESnext features (up to stage-3 recommendations)es201x
- Yearly ES specifications. Note than including one year will include all of the yearly specs before it (e.g.es2018
will also includees2017
,es2016
andes2015
).dom
- DOM-specific APIs.webworker
- APIs for Web workers.
target
defines the target version of ES.
module
defines the module type the TS compiler will generate. If you set target
to es5
or below, it will default to commonjs
(standard CommonJS modules for Node.js compatibility). Otherwise, it will default to esnext
(ES Modules).
And that's it for this part. In part 2, we'll go through how to make your TypeScript migration painless by adding types gradually. We'll also go through the quirks of TypeScript's type system, as well as changing your way of thinking to write TypeScript apps.
Once again, I really recommend the TypeScript Deep Dive book by Basarat. His book on TypeScript has helped me a lot on learning this language. Should you ever get stuck, the #typescript
channel on the Reactiflux Discord server has a bunch of lovely people who know TypeScript inside and out. Feel free to hop in and ask questions!