24 Months with TypeScript

Posted on

It has been 24 months since I started working with TypeScript with our project at Harland Clarke. We were a very early adopter but even then 0.8 showed great signs of improving our team workflow and enabling us to build/maintain a large JS codebase. At the time, we figured that the quality of the JS output was clean enough that if TypeScript was abandoned we could continue development from the raw JS. Since then we have migrated the codebase across every release of TS and have a team of 5 actively maintaining and advancing the application(s). Overall it has been a great experience and has taught me a great deal about Javascript and team development.

Why use TypeScript?

Compilers are very smart, very fast, and very exact; Humans however vary wildly in all these areas. Humans make mistakes, I make mistakes, I make mistakes everyday. In a team environment my mistakes impact others, cause delays, and generally make a larger mess. When we started work at Harland Clarke we wanted to minimize our mistakes and be able to quickly correct before the team was impacted. We chose TypeScript to minimize human mistakes but over time we saw lots of additional value.

1) Compile-time errors > Runtime errors.

TypeScript will rather quickly show you complex errors at compile-time. This prevented non-valid builds from entering version control and resulted in a higher quality codebase. Better, these errors are very useful in both refactoring and seeing deeper into a codebase during development. With TypeScript we compile & edit more and test only when the build is valid.

2) You must be this tall to ride.

The compiler forced our team to a quality level before code moved into the team environment. Regardless of how senior or junior, everyone was accountable to the compiler. If your code did not validate, you learned why and fixed it. I know several developers improved the quality of code they wrote over time as a result. Some also found ways around the compiler but as we typed more of the codebase, more errors would surface for us to remove.

The type system in TypeScript is 100% optional. This is a great feature as you can type-down important areas of the codebase and all dependent code must conform yet leave other areas alone. We did this within our model classes and within our graphics api use ( CreateJS & Canvas ) and in these areas we were much stricter than others in terms of extension or use. Some of our more recent work was building a text/font engine extending CreateJS/EaselJS in TypeScript and we integrated this as an external library complete with its own type definition file, *.d.ts.

3) Validate all code at compile-time.

TSC takes all the code you feed it and validates it at compile-time. It makes sure that access or use of other developers code is valid. Even down to the type you pass in arguments to the type of data/object returned, to member variable. Over time this added lots of value in that every compilation is a full validation of the codebase. We know the app is valid across the whole codebase before we test.

4) Refactoring bliss

Refactoring in TypeScript is one of my favorite things. I tend to make all my changes at once and then run the compiler to see how the changes impact the codebase. This turns the compiler error output into a handy todo list with line/row numbers. Often times you will discover dependencies you never knew you had. Change a method name, compile, see everywhere it is used by line number. Change the data type passed, see all the incompatible calls. Delete a variable and see the impact. Upgrade the TypeScript compiler from 0.83 to 0.97 and see all the errors TS8.3-9.7p1 TS8.3-9.7p2 .

5) External Libraries

External JS libraries can be integrated seamlessly into a TypeScript project even though they are written in JS. The key is support for definition files. A TypeScript definition files describes the interface of an external JS library and adds type support to restrict your use of the api via the compiler. Rather than only be limited to TS libs, you can add definitions to structurally type your use of external libs like JQuery, VueJS, EaselJS and many many others. If you need a definition file, head on over to DefinitelyTyped for the latest from over 2,216 external libraries. If you cannot find one that fits, writing a definition file is fairly simple and easy to learn.

Is TypeScript appropriate for my project?

The real advantage to TypeScript comes in the form of type & interface validation. Most projects will benefit from this, some will not. My rule of thumb is 2+ devs and 5+ classes, anything past that and you will benefit tremendously from using TypeScript. Compiling between changes is not for everyone and for those coming from Javascript it can seem like a downgrade initially. Compared to pressing Refresh in the browser, running a compiler feels antiquated. In general, the benefits of team dev need to outweigh the individual compiler burden and often this is a tough sell on small projects. Many of the devs on our team (4 of 5) suggested we just use JS at the start, yet after we had lots of code (5 of 5) were thankful we used TypeScript. It will slow down your development workflow but it will improve code quality and reduce the testing cycle (no invalid builds are tested). This is a trade off that you will have to make but after 2 years, I feel strongly that slow and steady wins the race.

Integrating TypeScript

There are 100 different ways to structure a TypeScript project but after doing this 100 times, my ways are rather set in stone. I prefer a single build.ts file in the root of a project with internal references (/// <reference path=../tools/requirejs/require.d.ts/>) to the other project files in dependency order. By compiling one file, all files are processed and output like so:

// compile the project
tsc build.ts

//or with AMD module support
tsc -m ‘amd’ build.ts

I find this structure is easy to walk into and get working. It is simple and adding additional workflow above this is easy with grunt or gulp. Within our latest project we used gulp for compilation, example.

My issues with TypeScript

Having used anything for 2 years you run into a few things you want to see changes. Here is my list.

[NOTE: ADDED as Union Types ] Type cascade Rather than only providing one type, I would love to hand the compiler a set of appropriate types like so:
var foo:number|string = 123;

var foo:Solid|Liquid = new Liquid();

TypeScript Configuration Metadata It is hard to denote how you want referenced files handled exactly within a single compiler pass. While there is a notation for referencing files (adding external file into compiler within a .ts file ), there is no output notation other than use of import/export module syntax. Ideally one could add output values to the references to control compiler output. I prefer to have a single built.ts file in the root of the project and compile through it, thus the need for options for packaging, module syntax, concatenation, consolidating the __extends calls (subclass wiring), and optional file output at all (build.ts always results in an empty .js file). [NOTE: Turns out any file with a *.d.ts will not be output by the compiler but any references within will. This makes for an ideal entry point into the compiler with a single build.d.ts file. ]
Better ES6 support
Destructing assignments
Macros I have found that you can extend the TypeScript language with Sweet.js. Being able to define new language level extensions to the compiler would be a great hard feature.

I feel lucky to have started using TypeScript when it first came out and have learned a ton about building larger scale Javascript projects. My consulting clients at Harland Clarke have benefitted greatly and hold a maintainable high quality codebase as a result. During our first 12 months of development, we saved at least 3 months by not testing invalid builds. Saving 9 dev-months (3 devs initially) of time is a non-trivial benefit yet it says nothing about the maintainability or quality benefits TypeScript provided. Our later projects have benefitted the same, yet we are rarely building from scratch and typed library reuse becomes even more beneficial. At first TypeScript holds you back by adding a compilation step but it accelerates team development by keeping each developer build valid and improves code quality.

TypeScript is easily one of the best technologies to emerge from Microsoft in a long time. Having seen its impact on the quality of our team output, I would strongly recommend using it. Everyone makes mistakes, but having a quality compiler highlighting those errors before they impact your project is a big win. TypeScript will continue to be my co-pilot for large Javascript applications.