In Neufund we care a lot about the quality of the software we develop. That’s why we constantly research new ways for improving our technology stack. One of our recent enhances is starting to use Typescript.

Microsoft created Typescript in 2012, and since then it has gained a lot of popularity. Its main advantages are the opt in/opt out type system and its similarity to JavaScript.

Google Trends on Typescript https://trends.google.com/trends/explore?date=today%205-y&q=typescript

In this article, I want to present highlights of migrating one of our ES2016, Webpack, React, Redux application to Typescript. You can review the whole process directly on our github. It took about one full day of work to finish the migration.

Working with non ES2015 modules

I think this is one of the first frustrations that developers encounter while trying out Typescript. It happens that, by default, TS is very strict about using external modules not written using ES2015 modules.

Most of the time you want to import the whole library as default import which won’t work: import React from ‘react’ . That’s because there is no es2015 default export used. The syntax that you need to use instead is import * as React from ‘react’ . It turns out that this is a pretty popular issue and there is a compiler option to make this setting less strict: allowSyntheticDefaultImports .

Declaration merging

You need to provide typings for any dependency that you want to use. Sometimes type information comes directly with a library (react), at times they are accessible on @types on npm, and other times they are completely missing or they are broken / not up to date. TS comes with a unique feature — declaration merging — that becomes handy in such cases (and in many more!).

Imagine a situation where you rely on module A in version 1.1.0, but unfortunately, you could find typings only for version 1.0.0. Version 1.1.0 added few extra configuration options. Declaration merging makes fixing typings trivial — you just need to redeclare the given module again and fill out missing fields in the interface definition:

declare module “A” {

interface Options {

brandNewOption1?: string;

brandNewOption2?: string;

}

}

This technique is critical when using dynamic feature of JavaScript like prototype modification, etc. I used declaration merging a lot while migrating our small project. You can read more about declaration merging in documentation.

Working with React ecosystem

As I mentioned before, React comes with a high-quality typings boiled directly into the package. One of the benefits of using React with TS is that you don’t need to use prop types (at least as long as you don’t create a library with components) — you provide type information by using type system.

The standard react component is a generic type React.Component<IProps, IState> , which allows you not only to specify types for props but also for state definition. For stateless functional components, you can use React.SFC type.

Generic components

Generic components, i.e. components that take a type as a parameter, come handy when you want to pass props from one component to another. redux-form is one of the libraries that use this approach.

export class Field<P = GenericFieldHTMLAttributes> extends Component<BaseFieldProps<P> & P> implements GenericField<P> {…}

Unfortunately, you can’t specify generic type directly in TSX, so you need to create the concrete type before:

const TextField = Field as { new (): Field<IExtraTextFieldProps> };

Redux

Redux also comes with baked-in typings. It contains a few helpers like reducer type, etc.:

export type Reducer<S> = (state: S, action: AnyAction) => S;

It gets more interesting when working with redux-thunk , a popular middleware for creating async actions. It comes with ThunkAction generic type, which takes three arguments: return type of action, state type and extra arguments, which allow you to infer types for action creators easily:

export const commitETH = (amount: number, userAddress: string): ThunkAction<{}, AppState, {}> => async (

dispatch,

getState

) => { … }

Webpack custom loaders

Webpack allows you to load almost anything in your code. This great feature doesn’t play nicely with Typescript because of its inability to access typings for loaded objects.

Imagine loading a stylesheet. With css-modules in reality (besides making css file part of the bundle) you’re loading object containing mapping between original names and the mangled ones. Providing accurate type information for this use case is hard.

One of the solutions is to use a project like typed-css-modules to generate accurate typings on the fly. This also means that you will get type suggestions in your IDE. The small disadvantage of this approach is that many d.ts files containing type definition are being generated.

Currently there is a github issue regarding creation of real plugin system for tsc allowing loading custom files. This would mean that one could provide typings without generating lots of d.ts files.

Note: At the moment, Typescript language service supports plugins which allows for improved IDE experience while working with custom files. Unfortunately, it doesn't have any effect on a type system.

Tooling: Formatting and linting

There is a small war raging on this field. Some tools were created only for TS and other try to work both with TS and JS. For example, Prettier, an excellent formatting tool, supports Typescript and Javascript seamlessly.

Eslint, on the other hand, didn’t support TS for a very long time and Tslint was created to fill that void. Currently, there is a experimental plugin for Eslint to parse TS; also with babel supporting parsing of TS we will for sure see a unification of different tools in future, but in the current state we decided to use Tslint as linter.

Conclusion

I found Typescript super easy to work with. Typings provided by the community were most of the times good enough, and even when they lacked something I could easily improve them. Migrating an existing code base can be time consuming, but you can always do it one file at the time and benefits of using type system are obvious.

To recap, if you plan to start a new big or medium size project choosing TS over plain JS is a no brainer and this is what we agreed on doing at Neufund.